mirror of
https://github.com/libunwind/libunwind.git
synced 2026-01-12 00:04:03 +08:00
Fix lose caller frame of kernel call on QNX aarch64
QNX aarch64 kernel call without frame pointer and cfi directives, this will fallback to use frame pointer unwinder, but the frame pointer is caller's frame pointer, causing caller frame be skipped. Try to sniff kernel call pattern, get procedure's ip range by symtab and do the matchup from the beginning of procedure. If matched, then fallback to use link register unwinder.
This commit is contained in:
committed by
Stephen M. Webb
parent
c1c88dacbb
commit
a35ff787bf
@@ -208,6 +208,13 @@ typedef struct unw_accessors
|
||||
int (*get_proc_name) (unw_addr_space_t, unw_word_t, char *, size_t,
|
||||
unw_word_t *, void *);
|
||||
|
||||
/* Optional call back to obtain the start and end ip of a procedure.
|
||||
* procedure ip range is [start, end), the range is without end.
|
||||
* This callback is optional and may be set to NULL.
|
||||
*/
|
||||
int (*get_proc_ip_range) (unw_addr_space_t, unw_word_t, unw_word_t *,
|
||||
unw_word_t *, void *);
|
||||
|
||||
/* Optional call back to return a mask to be used with pointer
|
||||
* authentication on arm64.
|
||||
*
|
||||
|
||||
@@ -53,6 +53,7 @@ extern int unw_nto_access_mem(unw_addr_space_t, unw_word_t, unw_word_t *, int, v
|
||||
extern int unw_nto_access_reg(unw_addr_space_t, unw_regnum_t, unw_word_t *, int, void *);
|
||||
extern int unw_nto_access_fpreg(unw_addr_space_t, unw_regnum_t, unw_fpreg_t *, int, void *);
|
||||
extern int unw_nto_get_proc_name(unw_addr_space_t, unw_word_t, char *, size_t, unw_word_t *, void *);
|
||||
extern int unw_nto_get_proc_ip_range (unw_addr_space_t as, unw_word_t ip, unw_word_t *start, unw_word_t *end, void *);
|
||||
extern int unw_nto_resume(unw_addr_space_t, unw_cursor_t *, void *);
|
||||
|
||||
/**
|
||||
|
||||
@@ -148,6 +148,96 @@ is_plt_entry (struct dwarf_cursor *c)
|
||||
}
|
||||
}
|
||||
|
||||
#if defined __QNX__
|
||||
/*
|
||||
* QNX kernel call have neither CFI nor save frame pointer,
|
||||
* 00000000000549e8 <MsgSendsv_r>:
|
||||
* 549e8: cb0203e2 neg x2, x2
|
||||
* 549ec: 52800168 mov w8, #0xb // #11
|
||||
* 549f0: d4000a21 svc #0x51
|
||||
* 549f4: d65f03c0 ret
|
||||
* 549f8: cb0003e0 neg x0, x0
|
||||
* 549fc: d65f03c0 ret
|
||||
*
|
||||
* From disassemble, QNX kernel call have mov followed by svc, and may with some
|
||||
* neg instructions at beginning.
|
||||
* 1. find procedure's ip start,end start
|
||||
* 2. search mov,svc from begin, skip any neg instructions
|
||||
*/
|
||||
static bool is_neg_instr(uint32_t instr)
|
||||
{
|
||||
/* 64bit register Xd */
|
||||
return ((instr & 0xffe003e0) == 0xcb0003e0);
|
||||
}
|
||||
|
||||
/* QNX use w8 to pass kernel call number */
|
||||
static bool is_mov_w8_instr(uint32_t instr)
|
||||
{
|
||||
/* movz 32bit register Wd */
|
||||
return ((instr & 0xffe00008) == 0x52800008);
|
||||
}
|
||||
|
||||
static bool is_svc_instr(uint32_t instr)
|
||||
{
|
||||
return instr == 0xd4000a21;
|
||||
}
|
||||
|
||||
static bool
|
||||
is_qnx_kercall(struct dwarf_cursor *c)
|
||||
{
|
||||
unw_word_t w0;
|
||||
unw_accessors_t *a;
|
||||
int ret;
|
||||
unw_word_t proc_start_ip;
|
||||
unw_word_t proc_end_ip;
|
||||
|
||||
a = unw_get_accessors_int (c->as);
|
||||
if (c->as->big_endian || !a->get_proc_ip_range)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = (*a->get_proc_ip_range) (c->as, c->ip, &proc_start_ip, &proc_end_ip, c->as_arg);
|
||||
if (ret < 0)
|
||||
{
|
||||
Debug (2, "ip=0x%lx get proc ip range fail, ret = %d\n", c->ip, ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
unw_word_t ip = proc_start_ip;
|
||||
while ((ip < proc_end_ip) && (ip + 8 < proc_end_ip))
|
||||
{
|
||||
if ((*a->access_mem) (c->as, ip, &w0, 0, c->as_arg) < 0)
|
||||
{
|
||||
Debug (14, "access_mem ip=0x%lx fail\n", ip);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t low32 = w0 & 0xffffffff;
|
||||
uint32_t high32 = w0 >> 32;
|
||||
|
||||
if (is_mov_w8_instr(low32) && is_svc_instr(high32))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (is_neg_instr(low32) && is_neg_instr(high32))
|
||||
{
|
||||
ip += 8;
|
||||
}
|
||||
else if (is_neg_instr(low32) && is_mov_w8_instr(high32))
|
||||
{
|
||||
ip += 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Save the location of VL (vector length) from the signal frame to the VG (vector
|
||||
* granule) register if it exists, otherwise do nothing. If there is an error,
|
||||
@@ -361,6 +451,13 @@ unw_step (unw_cursor_t *cursor)
|
||||
Debug (2, "found plt entry\n");
|
||||
c->frame_info.frame_type = UNW_AARCH64_FRAME_STANDARD;
|
||||
}
|
||||
#if defined __QNX__
|
||||
else if (is_qnx_kercall(&c->dwarf))
|
||||
{
|
||||
Debug (2, "found qnx kernel call, fallback to use link register\n");
|
||||
c->frame_info.frame_type = UNW_AARCH64_FRAME_GUESSED;
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
/* Try use frame pointer (X29). */
|
||||
|
||||
204
src/elfxx.c
204
src/elfxx.c
@@ -34,6 +34,34 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
#include <lzma.h>
|
||||
#endif /* HAVE_LZMA */
|
||||
|
||||
struct symbol_info
|
||||
{
|
||||
const char *strtab;
|
||||
const Elf_W (Sym) *sym;
|
||||
Elf_W (Addr) start_ip;
|
||||
};
|
||||
|
||||
struct symbol_lookup_context
|
||||
{
|
||||
unw_addr_space_t as;
|
||||
unw_word_t ip;
|
||||
struct elf_image *ei;
|
||||
Elf_W (Addr) load_offset;
|
||||
Elf_W (Addr) *min_dist;
|
||||
};
|
||||
|
||||
struct symbol_callback_data
|
||||
{
|
||||
char *buf;
|
||||
size_t buf_len;
|
||||
};
|
||||
|
||||
struct ip_range_callback_data
|
||||
{
|
||||
Elf_W (Addr) *start_ip;
|
||||
Elf_W (Addr) *end_ip;
|
||||
};
|
||||
|
||||
static Elf_W (Shdr)*
|
||||
elf_w (section_table) (const struct elf_image *ei)
|
||||
{
|
||||
@@ -85,11 +113,13 @@ elf_w (string_table) (const struct elf_image *ei, int section)
|
||||
}
|
||||
|
||||
static int
|
||||
elf_w (lookup_symbol) (unw_addr_space_t as UNUSED,
|
||||
unw_word_t ip, struct elf_image *ei,
|
||||
Elf_W (Addr) load_offset,
|
||||
char *buf, size_t buf_len, Elf_W (Addr) *min_dist)
|
||||
elf_w (lookup_symbol_closeness) (const struct symbol_lookup_context *context,
|
||||
int (*callback)(const struct symbol_lookup_context *context,
|
||||
const struct symbol_info *syminfo, void *data),
|
||||
void *data)
|
||||
{
|
||||
struct elf_image *ei = context->ei;
|
||||
Elf_W (Addr) load_offset = context->load_offset;
|
||||
size_t syment_size;
|
||||
Elf_W (Ehdr) *ehdr = ei->image;
|
||||
Elf_W (Sym) *sym, *symtab, *symtab_end;
|
||||
@@ -137,13 +167,17 @@ elf_w (lookup_symbol) (unw_addr_space_t as UNUSED,
|
||||
Debug (16, "0x%016lx info=0x%02x %s\n",
|
||||
(long) val, sym->st_info, strtab + sym->st_name);
|
||||
|
||||
if ((Elf_W (Addr)) (ip - val) < *min_dist)
|
||||
/* as long as found one, the return will be success*/
|
||||
struct symbol_info syminfo =
|
||||
{
|
||||
*min_dist = (Elf_W (Addr)) (ip - val);
|
||||
strncpy (buf, strtab + sym->st_name, buf_len);
|
||||
buf[buf_len - 1] = '\0';
|
||||
ret = (strlen (strtab + sym->st_name) >= buf_len
|
||||
? -UNW_ENOMEM : 0);
|
||||
.strtab = strtab,
|
||||
.sym = sym,
|
||||
.start_ip = val
|
||||
};
|
||||
if ((*callback) (context, &syminfo, data) == UNW_ESUCCESS)
|
||||
{
|
||||
if (ret != UNW_ESUCCESS)
|
||||
ret = UNW_ESUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,9 +188,97 @@ elf_w (lookup_symbol) (unw_addr_space_t as UNUSED,
|
||||
}
|
||||
shdr = (Elf_W (Shdr) *) (((char *) shdr) + ehdr->e_shentsize);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
elf_w (lookup_symbol_callback)(const struct symbol_lookup_context *context,
|
||||
const struct symbol_info *syminfo, void *data)
|
||||
{
|
||||
int ret = -UNW_ENOINFO;
|
||||
struct symbol_callback_data *d = data;
|
||||
|
||||
if ((Elf_W (Addr)) (context->ip - syminfo->start_ip) < *(context->min_dist))
|
||||
{
|
||||
*(context->min_dist) = (Elf_W (Addr)) (context->ip - syminfo->start_ip);
|
||||
Debug (1, "candidate sym: %s\n", syminfo->strtab + syminfo->sym->st_name);
|
||||
strncpy (d->buf, syminfo->strtab + syminfo->sym->st_name, d->buf_len);
|
||||
d->buf[d->buf_len - 1] = '\0';
|
||||
ret = (strlen (syminfo->strtab + syminfo->sym->st_name) >= d->buf_len
|
||||
? -UNW_ENOMEM : UNW_ESUCCESS);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
elf_w (lookup_symbol) (unw_addr_space_t as UNUSED,
|
||||
unw_word_t ip, struct elf_image *ei,
|
||||
Elf_W (Addr) load_offset,
|
||||
char *buf, size_t buf_len, Elf_W (Addr) *min_dist)
|
||||
{
|
||||
struct symbol_lookup_context context =
|
||||
{
|
||||
.as = as,
|
||||
.ip = ip,
|
||||
.ei = ei,
|
||||
.load_offset = load_offset,
|
||||
.min_dist = min_dist,
|
||||
};
|
||||
struct symbol_callback_data data =
|
||||
{
|
||||
.buf = buf,
|
||||
.buf_len = buf_len,
|
||||
};
|
||||
return elf_w (lookup_symbol_closeness) (&context,
|
||||
elf_w (lookup_symbol_callback),
|
||||
&data);
|
||||
}
|
||||
|
||||
static int
|
||||
elf_w (lookup_ip_range_callback)(const struct symbol_lookup_context *context,
|
||||
const struct symbol_info *syminfo, void *data)
|
||||
{
|
||||
int ret = -UNW_ENOINFO;
|
||||
struct ip_range_callback_data *d = data;
|
||||
|
||||
if ((Elf_W (Addr)) (context->ip - syminfo->start_ip) < *(context->min_dist))
|
||||
{
|
||||
*(context->min_dist) = (Elf_W (Addr)) (context->ip - syminfo->start_ip);
|
||||
*(d->start_ip) = syminfo->start_ip;
|
||||
*(d->end_ip) = syminfo->start_ip + syminfo->sym->st_size;
|
||||
|
||||
ret = UNW_ESUCCESS;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
elf_w (lookup_ip_range)(unw_addr_space_t as UNUSED,
|
||||
unw_word_t ip, struct elf_image *ei,
|
||||
Elf_W (Addr) load_offset, Elf_W (Addr) *start_ip,
|
||||
Elf_W (Addr) *end_ip, Elf_W (Addr) *min_dist)
|
||||
{
|
||||
struct symbol_lookup_context context =
|
||||
{
|
||||
.as = as,
|
||||
.ip = ip,
|
||||
.ei = ei,
|
||||
.load_offset = load_offset,
|
||||
.min_dist = min_dist
|
||||
};
|
||||
struct ip_range_callback_data data =
|
||||
{
|
||||
.start_ip = start_ip,
|
||||
.end_ip = end_ip
|
||||
};
|
||||
return elf_w (lookup_symbol_closeness) (&context,
|
||||
elf_w (lookup_ip_range_callback),
|
||||
&data);
|
||||
}
|
||||
|
||||
static Elf_W (Addr)
|
||||
elf_w (get_load_offset) (struct elf_image *ei, unsigned long segbase)
|
||||
{
|
||||
@@ -401,6 +523,66 @@ elf_w (get_proc_name) (unw_addr_space_t as, pid_t pid, unw_word_t ip,
|
||||
return ret;
|
||||
}
|
||||
|
||||
HIDDEN int
|
||||
elf_w (get_proc_ip_range_in_image) (unw_addr_space_t as, struct elf_image *ei,
|
||||
unsigned long segbase,
|
||||
unw_word_t ip,
|
||||
unw_word_t *start, unw_word_t *end)
|
||||
{
|
||||
Elf_W (Addr) load_offset;
|
||||
Elf_W (Addr) min_dist = ~(Elf_W (Addr))0;
|
||||
int ret;
|
||||
|
||||
load_offset = elf_w (get_load_offset) (ei, segbase);
|
||||
ret = elf_w (lookup_ip_range) (as, ip, ei, load_offset, start, end, &min_dist);
|
||||
|
||||
/* If the ELF image has MiniDebugInfo embedded in it, look up the symbol in
|
||||
there as well and replace the previously found if it is closer. */
|
||||
struct elf_image mdi;
|
||||
if (elf_w (extract_minidebuginfo) (ei, &mdi))
|
||||
{
|
||||
int ret_mdi = elf_w (lookup_ip_range) (as, ip, &mdi, load_offset, start,
|
||||
end, &min_dist);
|
||||
|
||||
/* Closer symbol was found (possibly truncated). */
|
||||
if (ret_mdi == 0 || ret_mdi == -UNW_ENOMEM)
|
||||
{
|
||||
ret = ret_mdi;
|
||||
}
|
||||
|
||||
mi_munmap (mdi.image, mdi.size);
|
||||
}
|
||||
|
||||
if (min_dist >= ei->size)
|
||||
return -UNW_ENOINFO; /* not found */
|
||||
return ret;
|
||||
}
|
||||
|
||||
HIDDEN int
|
||||
elf_w (get_proc_ip_range) (unw_addr_space_t as, pid_t pid, unw_word_t ip,
|
||||
unw_word_t *start, unw_word_t *end)
|
||||
{
|
||||
unsigned long segbase, mapoff;
|
||||
struct elf_image ei;
|
||||
int ret;
|
||||
char file[PATH_MAX];
|
||||
|
||||
ret = tdep_get_elf_image (&ei, pid, ip, &segbase, &mapoff, file, PATH_MAX);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = elf_w (load_debuginfo) (file, &ei, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = elf_w (get_proc_ip_range_in_image) (as, &ei, segbase, ip, start, end);
|
||||
|
||||
mi_munmap (ei.image, ei.size);
|
||||
ei.image = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
HIDDEN Elf_W (Shdr)*
|
||||
elf_w (find_section) (const struct elf_image *ei, const char* secname)
|
||||
{
|
||||
@@ -554,7 +736,7 @@ elf_w (load_debuginfo) (const char* file, struct elf_image *ei, int is_local)
|
||||
{
|
||||
ret = elf_map_image(ei, file);
|
||||
if (ret)
|
||||
return ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
prev_image = ei->image;
|
||||
|
||||
@@ -53,6 +53,14 @@ extern int elf_w (get_proc_name_in_image) (unw_addr_space_t as,
|
||||
unw_word_t ip,
|
||||
char *buf, size_t buf_len, unw_word_t *offp);
|
||||
|
||||
extern int elf_w (get_proc_ip_range) (unw_addr_space_t as,
|
||||
pid_t pid, unw_word_t ip,
|
||||
unw_word_t *start, unw_word_t *end);
|
||||
|
||||
extern int elf_w (get_proc_ip_range_in_image) (unw_addr_space_t as, struct elf_image *ei,
|
||||
unsigned long segbase, unw_word_t ip,
|
||||
unw_word_t *start, unw_word_t *end);
|
||||
|
||||
extern Elf_W (Shdr)* elf_w (find_section) (const struct elf_image *ei, const char* secname);
|
||||
extern int elf_w (load_debuginfo) (const char* file, struct elf_image *ei, int is_local);
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ unw_accessors_t unw_nto_accessors =
|
||||
.access_reg = unw_nto_access_reg,
|
||||
.access_fpreg = unw_nto_access_fpreg,
|
||||
.resume = unw_nto_resume,
|
||||
.get_proc_name = unw_nto_get_proc_name
|
||||
.get_proc_name = unw_nto_get_proc_name,
|
||||
.get_proc_ip_range = unw_nto_get_proc_ip_range,
|
||||
};
|
||||
|
||||
|
||||
@@ -67,3 +67,22 @@ int unw_nto_get_proc_name (unw_addr_space_t as,
|
||||
return ret;
|
||||
}
|
||||
|
||||
int unw_nto_get_proc_ip_range (unw_addr_space_t as,
|
||||
unw_word_t ip,
|
||||
unw_word_t *start,
|
||||
unw_word_t *end,
|
||||
void *arg)
|
||||
{
|
||||
unw_nto_internal_t *uni = (unw_nto_internal_t *)arg;
|
||||
int ret = -UNW_ENOINFO;
|
||||
|
||||
#if UNW_ELF_CLASS == UNW_ELFCLASS64
|
||||
ret = _Uelf64_get_proc_ip_range (as, uni->pid, ip, start, end);
|
||||
#elif UNW_ELF_CLASS == UNW_ELFCLASS32
|
||||
ret = _Uelf32_get_proc_ip_range (as, uni->pid, ip, start, end);
|
||||
#else
|
||||
# error no valid ELF class defined
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
Reference in New Issue
Block a user