/********************************************************************** addr2line.h - $Author$ Copyright (C) 2010 Shinichiro Hamaji **********************************************************************/ #include "ruby/config.h" #include "addr2line.h" #include #include #ifdef USE_ELF #ifdef __OpenBSD__ #include #else #include #endif #include #include #include #include #include #include #include #include #include #if defined(HAVE_ALLOCA_H) #include #endif #ifdef HAVE_DL_ITERATE_PHDR # ifndef _GNU_SOURCE # define _GNU_SOURCE # endif # include #endif #define DW_LNS_copy 0x01 #define DW_LNS_advance_pc 0x02 #define DW_LNS_advance_line 0x03 #define DW_LNS_set_file 0x04 #define DW_LNS_set_column 0x05 #define DW_LNS_negate_stmt 0x06 #define DW_LNS_set_basic_block 0x07 #define DW_LNS_const_add_pc 0x08 #define DW_LNS_fixed_advance_pc 0x09 #define DW_LNS_set_prologue_end 0x0a /* DWARF3 */ #define DW_LNS_set_epilogue_begin 0x0b /* DWARF3 */ #define DW_LNS_set_isa 0x0c /* DWARF3 */ /* Line number extended opcode name. */ #define DW_LNE_end_sequence 0x01 #define DW_LNE_set_address 0x02 #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 typedef struct { const char *dirname; const char *filename; int line; int fd; void *mapped; size_t mapped_size; unsigned long base_addr; } line_info_t; /* Avoid consuming stack as this module may be used from signal handler */ static char binary_filename[PATH_MAX]; static unsigned long uleb128(char **p) { unsigned long r = 0; int s = 0; for (;;) { unsigned char b = *(unsigned char *)(*p)++; if (b < 0x80) { r += (unsigned long)b << s; break; } r += (b & 0x7f) << s; s += 7; } return r; } static long sleb128(char **p) { long r = 0; int s = 0; for (;;) { unsigned char b = *(unsigned char *)(*p)++; if (b < 0x80) { if (b & 0x40) { r -= (0x80 - b) << s; } else { r += (b & 0x3f) << s; } break; } r += (b & 0x7f) << s; s += 7; } return r; } static const char * get_nth_dirname(unsigned long dir, char *p) { if (!dir--) { return ""; } while (dir--) { while (*p) p++; p++; if (!*p) { fprintf(stderr, "Unexpected directory number %lu in %s\n", dir, binary_filename); return ""; } } return p; } static void fill_filename(int file, char *include_directories, char *filenames, line_info_t *line) { int i; char *p = filenames; char *filename; unsigned long dir; for (i = 1; i <= file; i++) { filename = p; if (!*p) { /* Need to output binary file name? */ fprintf(stderr, "Unexpected file number %d in %s\n", file, binary_filename); return; } while (*p) p++; p++; dir = uleb128(&p); /* last modified. */ uleb128(&p); /* size of the file. */ uleb128(&p); if (i == file) { line->filename = filename; line->dirname = get_nth_dirname(dir, include_directories); } } } static int get_path_from_symbol(const char *symbol, const char **p, size_t *len) { if (symbol[0] == '0') { /* libexecinfo */ *p = strchr(symbol, '/'); if (*p == NULL) return 0; *len = strlen(*p); } else { /* glibc */ const char *q; *p = symbol; q = strchr(symbol, '('); if (q == NULL) return 0; *len = q - symbol; } return 1; } static void fill_line(int num_traces, void **traces, unsigned long addr, int file, int line, char *include_directories, char *filenames, line_info_t *lines) { int i; for (i = 0; i < num_traces; i++) { unsigned long a = (unsigned long)traces[i] - lines[i].base_addr; /* We assume one line code doesn't result >100 bytes of native code. We may want more reliable way eventually... */ if (addr < a && a < addr + 100) { fill_filename(file, include_directories, filenames, &lines[i]); lines[i].line = line; } } } static void parse_debug_line_cu(int num_traces, void **traces, char **debug_line, line_info_t *lines) { char *p, *cu_end, *cu_start, *include_directories, *filenames; unsigned long unit_length; int default_is_stmt, line_base; unsigned int header_length, minimum_instruction_length, line_range, opcode_base; unsigned char *standard_opcode_lengths; /* The registers. */ unsigned long addr = 0; unsigned int file = 1; unsigned int line = 1; unsigned int column = 0; int is_stmt; int basic_block = 0; int end_sequence = 0; int prologue_end = 0; int epilogue_begin = 0; unsigned int isa = 0; p = *debug_line; unit_length = *(unsigned int *)p; p += sizeof(unsigned int); if (unit_length == 0xffffffff) { unit_length = *(unsigned long *)p; p += sizeof(unsigned long); } cu_end = p + unit_length; /*dwarf_version = *(unsigned short *)p;*/ p += 2; header_length = *(unsigned int *)p; p += sizeof(unsigned int); cu_start = p + header_length; minimum_instruction_length = *(unsigned char *)p; p++; is_stmt = default_is_stmt = *(unsigned char *)p; p++; line_base = *(char *)p; p++; line_range = *(unsigned char *)p; p++; opcode_base = *(unsigned char *)p; p++; standard_opcode_lengths = (unsigned char *)p - 1; p += opcode_base - 1; include_directories = p; /* skip include directories */ while (*p) { while (*p) p++; p++; } p++; filenames = p; p = cu_start; #define FILL_LINE() \ do { \ fill_line(num_traces, traces, addr, file, line, \ include_directories, filenames, lines); \ basic_block = prologue_end = epilogue_begin = 0; \ } while (0) while (p < cu_end) { unsigned long a; unsigned char op = *p++; switch (op) { case DW_LNS_copy: FILL_LINE(); break; case DW_LNS_advance_pc: a = uleb128(&p); addr += a; break; case DW_LNS_advance_line: { long a = sleb128(&p); line += a; break; } case DW_LNS_set_file: file = (unsigned int)uleb128(&p); break; case DW_LNS_set_column: column = (unsigned int)uleb128(&p); break; case DW_LNS_negate_stmt: is_stmt = !is_stmt; break; case DW_LNS_set_basic_block: basic_block = 1; break; case DW_LNS_const_add_pc: a = ((255 - opcode_base) / line_range) * minimum_instruction_length; addr += a; break; case DW_LNS_fixed_advance_pc: a = *(unsigned char *)p++; addr += a; break; case DW_LNS_set_prologue_end: prologue_end = 1; break; case DW_LNS_set_epilogue_begin: epilogue_begin = 1; break; case DW_LNS_set_isa: isa = (unsigned int)uleb128(&p); break; case 0: a = *(unsigned char *)p++; op = *p++; switch (op) { case DW_LNE_end_sequence: end_sequence = 1; FILL_LINE(); addr = 0; file = 1; line = 1; column = 0; is_stmt = default_is_stmt; end_sequence = 0; isa = 0; break; case DW_LNE_set_address: addr = *(unsigned long *)p; p += sizeof(unsigned long); break; case DW_LNE_define_file: fprintf(stderr, "Unsupported operation in %s\n", binary_filename); break; case DW_LNE_set_discriminator: /* TODO:currently ignore */ uleb128(&p); break; default: fprintf(stderr, "Unknown extended opcode: %d in %s\n", op, binary_filename); } break; default: { unsigned long addr_incr; unsigned long line_incr; a = op - opcode_base; addr_incr = (a / line_range) * minimum_instruction_length; line_incr = line_base + (a % line_range); addr += (unsigned int)addr_incr; line += (unsigned int)line_incr; FILL_LINE(); } } } *debug_line = p; } static void parse_debug_line(int num_traces, void **traces, char *debug_line, unsigned long size, line_info_t *lines) { char *debug_line_end = debug_line + size; while (debug_line < debug_line_end) { parse_debug_line_cu(num_traces, traces, &debug_line, lines); } if (debug_line != debug_line_end) { fprintf(stderr, "Unexpected size of .debug_line in %s\n", binary_filename); } } /* read file and fill lines */ static void fill_lines(int num_traces, void **traces, char **syms, int check_debuglink, line_info_t *current_line, line_info_t *lines); static void follow_debuglink(char *debuglink, int num_traces, void **traces, char **syms, line_info_t *current_line, line_info_t *lines) { /* Ideally we should check 4 paths to follow gnu_debuglink, but we handle only one case for now as this format is used by some linux distributions. See GDB's info for detail. */ static const char global_debug_dir[] = "/usr/lib/debug"; char *p, *subdir; p = strrchr(binary_filename, '/'); if (!p) { return; } p[1] = '\0'; subdir = (char *)alloca(strlen(binary_filename) + 1); strcpy(subdir, binary_filename); strcpy(binary_filename, global_debug_dir); strncat(binary_filename, subdir, PATH_MAX - strlen(binary_filename) - 1); strncat(binary_filename, debuglink, PATH_MAX - strlen(binary_filename) - 1); munmap(current_line->mapped, current_line->mapped_size); close(current_line->fd); fill_lines(num_traces, traces, syms, 0, current_line, lines); } /* read file and fill lines */ static void fill_lines(int num_traces, void **traces, char **syms, int check_debuglink, line_info_t *current_line, line_info_t *lines) { int i; char *shstr; char *section_name; ElfW(Ehdr) *ehdr; ElfW(Shdr) *shdr, *shstr_shdr; ElfW(Shdr) *debug_line_shdr = NULL, *gnu_debuglink_shdr = NULL; int fd; off_t filesize; char *file; fd = open(binary_filename, O_RDONLY); if (fd < 0) { return; } filesize = lseek(fd, 0, SEEK_END); if (filesize < 0) { int e = errno; close(fd); fprintf(stderr, "lseek: %s\n", strerror(e)); return; } lseek(fd, 0, SEEK_SET); /* async-signal unsafe */ file = (char *)mmap(NULL, filesize, PROT_READ, MAP_SHARED, fd, 0); if (file == MAP_FAILED) { int e = errno; close(fd); fprintf(stderr, "mmap: %s\n", strerror(e)); return; } current_line->fd = fd; current_line->mapped = file; current_line->mapped_size = filesize; for (i = 0; i < num_traces; i++) { const char *path; size_t len; if (get_path_from_symbol(syms[i], &path, &len) && !strncmp(path, binary_filename, len)) { lines[i].line = -1; } } ehdr = (ElfW(Ehdr) *)file; shdr = (ElfW(Shdr) *)(file + ehdr->e_shoff); shstr_shdr = shdr + ehdr->e_shstrndx; shstr = file + shstr_shdr->sh_offset; for (i = 0; i < ehdr->e_shnum; i++) { section_name = shstr + shdr[i].sh_name; if (!strcmp(section_name, ".debug_line")) { debug_line_shdr = shdr + i; break; } else if (!strcmp(section_name, ".gnu_debuglink")) { gnu_debuglink_shdr = shdr + i; } } if (!debug_line_shdr) { /* This file doesn't have .debug_line section, let's check .gnu_debuglink section instead. */ if (gnu_debuglink_shdr && check_debuglink) { follow_debuglink(file + gnu_debuglink_shdr->sh_offset, num_traces, traces, syms, current_line, lines); } return; } parse_debug_line(num_traces, traces, file + debug_line_shdr->sh_offset, debug_line_shdr->sh_size, lines); } #ifdef HAVE_DL_ITERATE_PHDR typedef struct { int num_traces; char **syms; line_info_t *lines; } fill_base_addr_state_t; static int fill_base_addr(struct dl_phdr_info *info, size_t size, void *data) { int i; fill_base_addr_state_t *st = (fill_base_addr_state_t *)data; for (i = 0; i < st->num_traces; i++) { const char *path; size_t len; size_t name_len = strlen(info->dlpi_name); if (get_path_from_symbol(st->syms[i], &path, &len) && (len == name_len || (len > name_len && path[len-name_len-1] == '/')) && !strncmp(path+len-name_len, info->dlpi_name, name_len)) { st->lines[i].base_addr = info->dlpi_addr; } } return 0; } #endif /* HAVE_DL_ITERATE_PHDR */ void rb_dump_backtrace_with_lines(int num_traces, void **trace, char **syms) { int i; /* async-signal unsafe */ line_info_t *lines = (line_info_t *)calloc(num_traces, sizeof(line_info_t)); /* Note that line info of shared objects might not be shown if we don't have dl_iterate_phdr */ #ifdef HAVE_DL_ITERATE_PHDR fill_base_addr_state_t fill_base_addr_state; fill_base_addr_state.num_traces = num_traces; fill_base_addr_state.syms = syms; fill_base_addr_state.lines = lines; /* maybe async-signal unsafe */ dl_iterate_phdr(fill_base_addr, &fill_base_addr_state); #endif /* HAVE_DL_ITERATE_PHDR */ for (i = 0; i < num_traces; i++) { const char *path; size_t len; if (lines[i].line) { continue; } if (!get_path_from_symbol(syms[i], &path, &len)) { continue; } strncpy(binary_filename, path, len); binary_filename[len] = '\0'; fill_lines(num_traces, trace, syms, 1, &lines[i], lines); } /* fprintf may not be async-signal safe */ for (i = 0; i < num_traces; i++) { line_info_t *line = &lines[i]; if (line->line > 0) { fprintf(stderr, "%s ", syms[i]); if (line->filename) { if (line->dirname && line->dirname[0]) { fprintf(stderr, "%s/", line->dirname); } fprintf(stderr, "%s", line->filename); } else { fprintf(stderr, "???"); } fprintf(stderr, ":%d\n", line->line); } else { fprintf(stderr, "%s\n", syms[i]); } } for (i = 0; i < num_traces; i++) { line_info_t *line = &lines[i]; if (line->fd) { munmap(line->mapped, line->mapped_size); close(line->fd); } } free(lines); } #else /* defined(USE_ELF) */ #error not supported #endif