One need identified from implementing patches for downstream profiling tools to adopt libdwfl_stacktrace is that libebl per-architecture constants are not exposed to external projects.
This makes handling stack pointer especially tricky, as shown by the incorrect prior eu-stacktrace logic cf PR34498, since the profiler has to account for how perf_regs mask might alter the position. v2: This now incorporates a necessary fix for PR34498. * libdwfl_stacktrace/libdwfl_stacktrace.h (dwflst_arch_from_uname): New API, translates umachine str obtained from uname() -> struct utsname -> machine to ELF machine ID. (dwflst_arch_sp_dwarf_reg): New API, identifies index of stack ptr. (dwflst_arch_sp_perf_reg): New API, identifies index of stack ptr in perf_events regs[] array given a perf_regs_mask (cf PR34498). (dwflst_arch_expected_frame_nregs): New API, minimal number of regs for unwinding (useful for sanity-checking incoming stack samples). * libdwfl_stacktrace/dwflst_sample_frame.c: Format in two sections. (dwflst_arch_from_uname): Implementation. (dwflst_arch_expected_frame_nregs): Implementation. (dwflst_arch_sp_dwarf_reg): Implementation. (dwflst_arch_sp_perf_reg): Implementation. * libdw/libdw.map (ELFUTILS_0.196_EXPERIMENTAL): Add the new functions. Signed-off-by: Serhei Makarov <[email protected]> --- libdw/libdw.map | 8 ++ libdwfl_stacktrace/dwflst_sample_frame.c | 102 ++++++++++++++++++++++- libdwfl_stacktrace/libdwfl_stacktrace.h | 32 +++++++ 3 files changed, 141 insertions(+), 1 deletion(-) diff --git a/libdw/libdw.map b/libdw/libdw.map index b45647e6..37715d4e 100644 --- a/libdw/libdw.map +++ b/libdw/libdw.map @@ -411,3 +411,11 @@ ELFUTILS_0.194_EXPERIMENTAL { global: dwflst_sample_getframes; } ELFUTILS_0.193_EXPERIMENTAL; + +ELFUTILS_0.196_EXPERIMENTAL { + global: + dwflst_arch_from_uname; + dwflst_arch_expected_frame_nregs; + dwflst_arch_sp_dwarf_reg; + dwflst_arch_sp_perf_reg; +} ELFUTILS_0.194_EXPERIMENTAL; diff --git a/libdwfl_stacktrace/dwflst_sample_frame.c b/libdwfl_stacktrace/dwflst_sample_frame.c index cf33a439..95cfb572 100644 --- a/libdwfl_stacktrace/dwflst_sample_frame.c +++ b/libdwfl_stacktrace/dwflst_sample_frame.c @@ -32,10 +32,13 @@ #include "libdwfl_stacktraceP.h" +/* Various functions providing arch-specific info: */ + Ebl *default_ebl = NULL; GElf_Half default_ebl_machine = EM_NONE; -uint64_t dwflst_perf_sample_preferred_regs_mask (GElf_Half machine) +uint64_t +dwflst_perf_sample_preferred_regs_mask (GElf_Half machine) { /* XXX The most likely case is that this will only be called once, for the current architecture. So we keep one Ebl* around for @@ -56,6 +59,103 @@ uint64_t dwflst_perf_sample_preferred_regs_mask (GElf_Half machine) return 0; } +GElf_Half +dwflst_arch_from_uname (const char *umachine) +{ + if (strncmp(umachine, "x86_64", 6) == 0) + return EM_X86_64; + else if (strncmp(umachine, "i686", 4) == 0 + || strncmp(umachine, "i386", 4) == 0) + return EM_386; + else if (strncmp(umachine, "aarch64", 7) == 0) + return EM_AARCH64; + else if (strncmp(umachine, "armv7l", 6) == 0) + return EM_ARM; + /* XXX Other architectures not yet supported. */ + return EM_NONE; +} + +/* XXX The per-machine switches suggest the following implementations + could be folded into backends/, but we would need to create a Ebl + to handle the dispatch: */ + +uint32_t +dwflst_arch_expected_frame_nregs (GElf_Half machine) +{ + /* For aarch64, we actually use fewer than ebl->frame_nregs to unwind: */ + if (machine == EM_AARCH64) + return 14; + if (machine == EM_ARM) + return 16; + /* On x86, expect everything except FLAGS: */ + if (machine == EM_X86_64 || machine == EM_386) + /* XXX An external user of the library can't access the Ebl, hence + can't conveniently provide it to us it here. We provide the + constant directly rather than initializing a new Ebl. */ + return machine == EM_X86_64 ? 17 : 9; + /* return ebl_frame_nregs(ebl); */ + /* XXX Other architectures are not supported yet. + In general, it's fine to be on the permissive side here. */ + return 1; +} + +int +dwflst_arch_sp_dwarf_reg (GElf_Half machine, bool force_abi32) +{ + switch (machine) { + case EM_X86_64: + return force_abi32 ? 4 : 7; + case EM_386: + return 4; + case EM_ARM: + return 13; + case EM_AARCH64: + return force_abi32 ? 13 : 31; + default: + /* XXX Other architectures are not supported yet. */ + return -1; + } +} + +int +dwflst_arch_sp_perf_reg (GElf_Half machine, + uint64_t perf_regs_mask, bool is_abi32) +{ + int sp_perf_index; + if (machine == EM_X86_64 || machine == EM_386) + sp_perf_index = 7; /* uniform on 32/64-bit abi */ + /* compare backends/x86_initreg_sample.c; + for dwarf_index, would be (is_abi32 ? 4 : 7) */ + else if (machine == EM_ARM || machine == EM_AARCH64) + sp_perf_index = (is_abi32 ? 13 : 31); + /* basic linear mapping of perf_regs<->dwarf_regs */ + else + /* XXX Other architectures are not supported yet. */ + return -1; + + if (perf_regs_mask == 0) + /* Assume all registers present: */ + return sp_perf_index; + + /* Iterate perf_regs_mask to find + k == index of sp in perf_regs_mask + j == index of sp in regs[] */ + int j, k; uint64_t bit; + for (k = 0, j = -1, bit = 1; + k <= sp_perf_index; k++, bit <<= 1) + { + if ((bit & perf_regs_mask)) + j++; + else + continue; /* sp may not be present */ + if (k == sp_perf_index) + return j; + } + return -2; +} + +/* Stack sample handling: */ + struct sample_info { pid_t pid; pid_t tid; diff --git a/libdwfl_stacktrace/libdwfl_stacktrace.h b/libdwfl_stacktrace/libdwfl_stacktrace.h index 84cb69a3..2f4e9164 100644 --- a/libdwfl_stacktrace/libdwfl_stacktrace.h +++ b/libdwfl_stacktrace/libdwfl_stacktrace.h @@ -154,6 +154,38 @@ int dwflst_perf_sample_getframes (Dwfl *dwfl, Elf *elf, pid_t pid, pid_t tid, opened later. */ uint64_t dwflst_perf_sample_preferred_regs_mask (GElf_Half machine); +/* Returns the correct ELF machine identifier for the arch identifier + string UMACHINE from struct utsname, if libdwflst handles stack + samples for MACHINE. Returns EM_NONE if stack sample handling is + not supported. */ +GElf_Half dwflst_arch_from_uname (const char *umachine); + +/* Returns the minimum required number of registers for unwinding + for MACHINE, which should be at most the number of bits set in + dwflst_perf_sample_preferred_regs_mask(MACHINE). Returns 0 + if libdwfl does not handle stack samples for MACHINE. */ +uint32_t dwflst_arch_expected_frame_nregs (GElf_Half machine); + +/* Returns the index of the stack pointer register within the + dwarf_regs ordering for MACHINE. If FORCE_ABI32 is true, returns + the index within the dwarf_regs ordering for the 32-bit variant of + MACHINE. Returns -1 if libdwfl does not handle stack samples for + MACHINE. */ +int dwflst_arch_sp_dwarf_reg (GElf_Half machine, bool force_abi32); + +/* Returns the index of the stack pointer register within the + perf_regs ordering for MACHINE, assuming only the registers within + PERF_REGS_MASK are included. (If PERF_REGS_MASK is 0, assumes all + registers are included.) If IS_ABI32 is true, returns the index + within the perf_regs ordering for the 32-bit variant of MACHINE, + to allow handling of mixed-architecture perf_events data; if + IS_ABI32 is false, returns the index for the 64-bit variant in + accordance to perf_events conventions. Returns -1 if libdwfl does + not handle stack samples for MACHINE, -2 if sp is not present in + PERF_REGS_MASK. */ +int dwflst_arch_sp_perf_reg (GElf_Half machine, + uint64_t perf_regs_mask, bool is_abi32); + #ifdef __cplusplus } #endif -- 2.54.0
