Support Mach-O on backtrace with DWARF

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@65202 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
naruse 2018-10-20 06:35:25 +00:00
Родитель fe735bd30f
Коммит 595056135f
4 изменённых файлов: 228 добавлений и 67 удалений

Просмотреть файл

@ -28,7 +28,7 @@
#include "missing/stdbool.h"
#endif
#ifdef USE_ELF
#if defined(USE_ELF) || defined(HAVE_MACH_O_LOADER_H)
#include <fcntl.h>
#include <limits.h>
@ -41,12 +41,6 @@
#include <sys/stat.h>
#include <unistd.h>
#ifdef __OpenBSD__
#include <elf_abi.h>
#else
#include <elf.h>
#endif
/* Make alloca work the best possible way. */
#ifdef __GNUC__
# ifndef alloca
@ -70,6 +64,47 @@ void *alloca();
# include <dlfcn.h>
#endif
#ifdef HAVE_MACH_O_LOADER_H
# include <mach-o/loader.h>
#endif
#ifdef USE_ELF
# ifdef __OpenBSD__
# include <elf_abi.h>
# else
# include <elf.h>
# endif
#ifndef ElfW
# if SIZEOF_VOIDP == 8
# define ElfW(x) Elf64##_##x
# else
# define ElfW(x) Elf32##_##x
# endif
#endif
#ifndef ELF_ST_TYPE
# if SIZEOF_VOIDP == 8
# define ELF_ST_TYPE ELF64_ST_TYPE
# else
# define ELF_ST_TYPE ELF32_ST_TYPE
# endif
#endif
#endif
#ifdef SHF_COMPRESSED
# if defined(ELFCOMPRESS_ZLIB) && defined(HAVE_LIBZ)
/* FreeBSD 11.0 lacks ELFCOMPRESS_ZLIB */
# include <zlib.h>
# define SUPPORT_COMPRESSED_DEBUG_LINE
# endif
#else /* compatibility with glibc < 2.22 */
# define SHF_COMPRESSED 0
#endif
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif
#define DW_LNS_copy 0x01
#define DW_LNS_advance_pc 0x02
#define DW_LNS_advance_line 0x03
@ -89,34 +124,6 @@ void *alloca();
#define DW_LNE_define_file 0x03
#define DW_LNE_set_discriminator 0x04 /* DWARF4 */
#ifndef ElfW
# if SIZEOF_VOIDP == 8
# define ElfW(x) Elf64##_##x
# else
# define ElfW(x) Elf32##_##x
# endif
#endif
#ifndef ELF_ST_TYPE
# if SIZEOF_VOIDP == 8
# define ELF_ST_TYPE ELF64_ST_TYPE
# else
# define ELF_ST_TYPE ELF32_ST_TYPE
# endif
#endif
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif
#ifdef SHF_COMPRESSED
# if defined(ELFCOMPRESS_ZLIB) && defined(HAVE_LIBZ)
/* FreeBSD 11.0 lacks ELFCOMPRESS_ZLIB */
# include <zlib.h>
# define SUPPORT_COMPRESSED_DEBUG_LINE
# endif
#else /* compatibility with glibc < 2.22 */
# define SHF_COMPRESSED 0
#endif
PRINTF_ARGS(static int kprintf(const char *fmt, ...), 1, 2);
typedef struct line_info {
@ -133,9 +140,9 @@ typedef struct line_info {
} line_info_t;
struct dwarf_section {
ElfW(Shdr) *shdr;
char *ptr;
size_t size;
uint64_t flags;
};
typedef struct obj_info {
@ -485,6 +492,7 @@ append_obj(obj_info_t **objp)
*objp = newobj;
}
#ifdef USE_ELF
static void
follow_debuglink(const char *debuglink, int num_traces, void **traces,
obj_info_t **objp, line_info_t *lines, int offset)
@ -518,6 +526,7 @@ follow_debuglink(const char *debuglink, int num_traces, void **traces,
o2->path = o1->path;
fill_lines(num_traces, traces, 0, objp, lines, offset);
}
#endif
enum
{
@ -1371,7 +1380,7 @@ ranges_include(DebugInfoReader *reader, ranges_t *ptr, uint64_t addr)
/* base address selection entry */
base = to;
}
else if (base + from <= addr && addr <= base + to) {
else if (base + from <= addr && addr < base + to) {
return from;
}
}
@ -1393,22 +1402,21 @@ ranges_inspect(DebugInfoReader *reader, ranges_t *ptr)
fprintf(stderr,"low_pc_set:%d high_pc_set:%d ranges_set:%d\n",ptr->low_pc_set,ptr->high_pc_set,ptr->ranges_set);
exit(1);
}
fprintf(stderr,"low_pc:%lx high_pc:%lx\n",ptr->low_pc,ptr->high_pc);
fprintf(stderr,"low_pc:%"PRIx64" high_pc:%"PRIx64"\n",ptr->low_pc,ptr->high_pc);
}
else if (ptr->ranges_set) {
char *p;
fprintf(stderr,"low_pc:%lx ranges:%lx ",ptr->low_pc,ptr->ranges);
p = reader->obj->debug_ranges.ptr + ptr->ranges;
char *p = reader->obj->debug_ranges.ptr + ptr->ranges;
fprintf(stderr,"low_pc:%"PRIx64" ranges:%"PRIx64" %lx ",ptr->low_pc,ptr->ranges, p-reader->obj->mapped);
for (;;) {
uintptr_t from = read_uintptr(&p);
uintptr_t to = read_uintptr(&p);
if (!from && !to) break;
fprintf(stderr,"%lx-%lx ",ptr->low_pc+from,ptr->low_pc+to);
fprintf(stderr,"%"PRIx64"-%"PRIx64" ",ptr->low_pc+from,ptr->low_pc+to);
}
fprintf(stderr,"\n");
}
else if (ptr->low_pc_set) {
fprintf(stderr,"low_pc:%lx\n",ptr->low_pc);
fprintf(stderr,"low_pc:%"PRIx64"\n",ptr->low_pc);
}
else {
fprintf(stderr,"empty\n");
@ -1565,12 +1573,13 @@ debug_info_read(DebugInfoReader *reader, int num_traces, void **traces,
lines[i].path = reader->obj->path;
lines[i].base_addr = line.base_addr;
lines[i].sname = line.sname;
lines[i].saddr = reader->obj->base_addr + saddr;
lines[i].saddr = saddr + reader->obj->base_addr - reader->obj->vmaddr;
}
}
}
}
#ifdef USE_ELF
static unsigned long
uncompress_debug_section(ElfW(Shdr) *shdr, char *file, char **ptr)
{
@ -1605,7 +1614,6 @@ fill_lines(int num_traces, void **traces, int check_debuglink,
{
int i, j;
char *shstr;
char *section_name;
ElfW(Ehdr) *ehdr;
ElfW(Shdr) *shdr, *shstr_shdr;
ElfW(Shdr) *gnu_debuglink_shdr = NULL;
@ -1663,7 +1671,7 @@ fill_lines(int num_traces, void **traces, int check_debuglink,
shstr = file + shstr_shdr->sh_offset;
for (i = 0; i < ehdr->e_shnum; i++) {
section_name = shstr + shdr[i].sh_name;
char *section_name = shstr + shdr[i].sh_name;
switch (shdr[i].sh_type) {
case SHT_STRTAB:
if (!strcmp(section_name, ".strtab")) {
@ -1693,11 +1701,21 @@ fill_lines(int num_traces, void **traces, int check_debuglink,
".debug_ranges",
".debug_str"
};
for (j=0; j < DWARF_SECTION_COUNT; j++) {
if (strcmp(section_name, debug_section_names[j]) == 0) {
obj_dwarf_section_at(obj, j)->shdr = &shdr[i];
break;
struct dwarf_section *s = obj_dwarf_section_at(obj, j);
if (strcmp(section_name, debug_section_names[j]) != 0)
continue;
s->ptr = file + shdr[i].sh_offset;
s->size = shdr[i].sh_size;
s->flags = shdr[i].sh_flags;
if (s->flags & SHF_COMPRESSED) {
s->size = uncompress_debug_section(&shdr[i], file, &s->ptr);
if (!s->size) goto fail;
}
break;
}
}
break;
@ -1720,6 +1738,7 @@ fill_lines(int num_traces, void **traces, int check_debuglink,
if (ELF_ST_TYPE(sym->st_info) != STT_FUNC || sym->st_size == 0) continue;
s = dlsym(handle, strtab + sym->st_name);
if (s && dladdr(s, &info)) {
obj->base_addr = dladdr_fbase;
dladdr_fbase = (uintptr_t)info.dli_fbase;
break;
}
@ -1736,21 +1755,6 @@ fill_lines(int num_traces, void **traces, int check_debuglink,
}
}
if (obj->debug_info.shdr) {
size_t j;
for (j=0; j < DWARF_SECTION_COUNT; j++) {
struct dwarf_section *s = obj_dwarf_section_at(obj, j);
ElfW(Shdr) *shdr = s->shdr;
if (!shdr) break;
s->ptr = file + shdr->sh_offset;
s->size = shdr->sh_size;
if (shdr->sh_flags & SHF_COMPRESSED) {
s->size = uncompress_debug_section(shdr, file, &s->ptr);
if (!s->size) goto fail;
}
}
}
if (obj->debug_info.ptr && obj->debug_abbrev.ptr) {
DebugInfoReader reader;
debug_info_reader_init(&reader, obj);
@ -1813,6 +1817,158 @@ finish:
fail:
return (uintptr_t)-1;
}
#else /* Mach-O */
/* read file and fill lines */
static uintptr_t
fill_lines(int num_traces, void **traces, int check_debuglink,
obj_info_t **objp, line_info_t *lines, int offset)
{
int fd;
off_t filesize;
char *file, *p;
obj_info_t *obj = *objp;
struct mach_header_64 *header;
uintptr_t dladdr_fbase = 0;
{
char *s = binary_filename;
char *base = strrchr(binary_filename, '/')+1;
size_t max = PATH_MAX;
size_t size = strlen(binary_filename);
size_t basesize = size - (base - binary_filename);
s += size;
max -= size;
size = strlcpy(s, ".dSYM/Contents/Resources/DWARF/", max);
if (size == 0) goto fail;
s += size;
max -= size;
if (max <= basesize) goto fail;
memcpy(s, base, basesize);
s[basesize] = 0;
}
fd = open(binary_filename, O_RDONLY);
if (fd < 0) {
goto fail;
}
filesize = lseek(fd, 0, SEEK_END);
if (filesize < 0) {
int e = errno;
close(fd);
kprintf("lseek: %s\n", strerror(e));
goto fail;
}
#if SIZEOF_OFF_T > SIZEOF_SIZE_T
if (filesize > (off_t)SIZE_MAX) {
close(fd);
kprintf("Too large file %s\n", binary_filename);
goto fail;
}
#endif
lseek(fd, 0, SEEK_SET);
/* async-signal unsafe */
file = (char *)mmap(NULL, (size_t)filesize, PROT_READ, MAP_SHARED, fd, 0);
if (file == MAP_FAILED) {
int e = errno;
close(fd);
kprintf("mmap: %s\n", strerror(e));
goto fail;
}
close(fd);
obj->mapped = file;
obj->mapped_size = (size_t)filesize;
header = (struct mach_header_64 *)file;
if (header->magic != MH_MAGIC_64) {
/* TODO: universal binaries */
kprintf("'%s' is not a 64-bit Mach-O file!\n",binary_filename);
close(fd);
goto fail;
}
p = file + sizeof(struct mach_header_64);
for (uint32_t i = 0; i < (uint32_t)header->ncmds; i++) {
struct load_command *lcmd = (struct load_command *)p;
switch (lcmd->cmd) {
case LC_SEGMENT_64:
{
static const char *debug_section_names[] = {
"__debug_abbrev",
"__debug_info",
"__debug_line",
"__debug_ranges",
"__debug_str"
};
struct segment_command_64 *scmd = (struct segment_command_64 *)lcmd;
if (strcmp(scmd->segname, "__TEXT") == 0) {
obj->vmaddr = scmd->vmaddr;
}
else if (strcmp(scmd->segname, "__DWARF") == 0) {
p += sizeof(struct segment_command_64);
for (uint64_t i = 0; i < scmd->nsects; i++) {
struct section_64 *sect = (struct section_64 *)p;
p += sizeof(struct section_64);
for (int j=0; j < DWARF_SECTION_COUNT; j++) {
struct dwarf_section *s = obj_dwarf_section_at(obj, j);
if (strcmp(sect->sectname, debug_section_names[j]) != 0)
continue;
s->ptr = file + sect->offset;
s->size = sect->size;
s->flags = sect->flags;
if (s->flags & SHF_COMPRESSED) {
goto fail;
}
break;
}
}
}
}
break;
#if 0
case LC_SYMTAB:
{
struct symtab_command *c = (struct symtab_command *)lcmd;
struct nlist_64 *nl = (struct nlist *)(file + c->symoff);
char *strtab = file + c->stroff;
uint32_t j;
kprintf("[%2d]: %x/symtab %lx\n", i, c->cmd, p);
for (j = 0; j < c->nsyms; j++) {
struct nlist_64 *e = &nl[j];
if (!(e->n_type & N_STAB)) continue;
/* if (e->n_type != N_FUN) continue; */
kprintf("[%2d][%4d]: %02x/%x/%x: %s %lx\n", i, j,
e->n_type,e->n_sect,e->n_desc,strtab+e->n_un.n_strx,e->n_value);
}
}
#endif
}
p += lcmd->cmdsize;
}
if (obj->debug_info.ptr && obj->debug_abbrev.ptr) {
DebugInfoReader reader;
debug_info_reader_init(&reader, obj);
while (reader.p < reader.pend) {
di_read_cu(&reader);
debug_info_read(&reader, num_traces, traces, lines, offset);
}
}
if (parse_debug_line(num_traces, traces,
obj->debug_line.ptr,
obj->debug_line.size,
obj, lines, offset) == -1)
goto fail;
return dladdr_fbase;
fail:
return (uintptr_t)-1;
}
#endif
#define HAVE_MAIN_EXE_PATH
#if defined(__FreeBSD__)
@ -1971,7 +2127,7 @@ next_line:
obj_info_t *o = obj;
for (i=0; i < DWARF_SECTION_COUNT; i++) {
struct dwarf_section *s = obj_dwarf_section_at(obj, i);
if (s->shdr && (s->shdr->sh_flags & SHF_COMPRESSED)) {
if (s->flags & SHF_COMPRESSED) {
free(s->ptr);
}
}

Просмотреть файл

@ -11,7 +11,7 @@
#ifndef RUBY_ADDR2LINE_H
#define RUBY_ADDR2LINE_H
#ifdef USE_ELF
#if (defined(USE_ELF) || defined(HAVE_MACH_O_LOADER_H))
void
rb_dump_backtrace_with_lines(int num_traces, void **traces);

Просмотреть файл

@ -2515,6 +2515,11 @@ AS_IF([test "$rb_cv_binary_elf" = yes], [
])
])
AC_CHECK_HEADERS([mach-o/loader.h])
AS_IF([test "$ac_cv_header_mach_o_loader_h" = yes], [
AC_LIBOBJ([addr2line])
])
AS_CASE(["$target_os"],
[linux* | gnu* | k*bsd*-gnu | bsdi* | kopensolaris*-gnu], [
AS_IF([test "$rb_cv_binary_elf" = no], [

Просмотреть файл

@ -713,7 +713,7 @@ rb_print_backtrace(void)
#define MAX_NATIVE_TRACE 1024
static void *trace[MAX_NATIVE_TRACE];
int n = (int)backtrace(trace, MAX_NATIVE_TRACE);
#if defined(USE_ELF) && defined(HAVE_DLADDR) && !defined(__sparc)
#if (defined(USE_ELF) || defined(HAVE_MACH_O_LOADER_H)) && defined(HAVE_DLADDR) && !defined(__sparc)
rb_dump_backtrace_with_lines(n, trace);
#else
char **syms = backtrace_symbols(trace, n);