Diff below implements file/line number decoding in ddb for ELF; e.g.,

ddb{2}> trace
Debugger() at Debugger+0x9 [../../../../arch/amd64/amd64/db_interface.c:406]
ddb_sysctl() at ddb_sysctl+0x1b4 [../../../../ddb/db_usrreq.c:104]
sys___sysctl() at sys___sysctl+0x214 [../../../../kern/kern_sysctl.c:229]
syscall() at syscall+0x297 [../../../../sys/syscall_mi.h:84]
--- syscall (number 202) ---
end of kernel
end trace frame: 0x7f7ffffe1def, count: -4
acpi_pdirpa+0x41195a:
ddb{2}> 

It works by modifying boot(8) to copy the .debug_line DWARF section
into memory when present (same as it does currently for symbol and
string table sections), and then ddb's ELF symbol format handler code
makes use of it to translate program counters into file/line pairs
just like addr2line would.

For line decoding to work, the kernel must have been compiled with -g.
Normal and/or stripped kernels continue to work as they do now, just
won't produce file/line information.

Only lightly tested on amd64 so far, but might work on other platforms
too.  If you're brave, you can try it out thusly:

  1. Apply patch.
  2. Build boot(8), copy to /boot, and run installboot(8).
  3. Configure kernel with 'makeoptions DEBUG="-g"'.
  4. Build kernel and install "bsd.gdb" (not "bsd"!).
  5. Reboot.

I did this mostly for fun / personal enlightenment, so I haven't put
too much effort into polishing it yet.


Index: ddb/db_elf.c
===================================================================
RCS file: /home/matthew/anoncvs/cvs/src/sys/ddb/db_elf.c,v
retrieving revision 1.10
diff -u -p -r1.10 db_elf.c
--- ddb/db_elf.c        16 Mar 2014 20:31:46 -0000      1.10
+++ ddb/db_elf.c        23 Jun 2014 12:27:26 -0000
@@ -46,6 +46,7 @@
 #include <sys/exec_elf.h>
 
 static char *db_elf_find_strtab(db_symtab_t *);
+static char *db_elf_find_linetab(db_symtab_t *, size_t *);
 
 #define        STAB_TO_SYMSTART(stab)  ((Elf_Sym *)((stab)->start))
 #define        STAB_TO_SYMEND(stab)    ((Elf_Sym *)((stab)->end))
@@ -111,10 +112,10 @@ db_elf_sym_init(int symsize, void *symta
         *      . . .
         *      . . .
         *      last section header
-        *      first symbol or string table section
+        *      first symbol, string, or line table section
         *      . . .
         *      . . .
-        *      last symbol or string table section
+        *      last symbol, string, or line table section
         */
 
        /*
@@ -233,6 +234,29 @@ db_elf_find_strtab(db_symtab_t *stab)
 }
 
 /*
+ * Internal helper function - return a pointer to the line table
+ * for the current symbol table.
+ */
+static char *
+db_elf_find_linetab(db_symtab_t *stab, size_t *size)
+{
+       Elf_Ehdr *elf = STAB_TO_EHDR(stab);
+       Elf_Shdr *shp = STAB_TO_SHDR(stab, elf);
+       char *shstrtab;
+       int i;
+
+       shstrtab = (char *)elf + shp[elf->e_shstrndx].sh_offset;
+       for (i = 0; i < elf->e_shnum; i++) {
+               if (strcmp(".debug_line", shstrtab+shp[i].sh_name) == 0) {
+                       *size = shp[i].sh_size;
+                       return ((char *)elf + shp[i].sh_offset);
+               }
+       }
+
+       return (NULL);
+}
+
+/*
  * Lookup the symbol with the given name.
  */
 db_sym_t
@@ -343,6 +367,8 @@ db_elf_symbol_values(db_symtab_t *symtab
                *valuep = symp->st_value;
 }
 
+#include "db_elf_dwarf.c"
+
 /*
  * Return the file and line number of the current program counter
  * if we can find the appropriate debugging symbol.
@@ -351,11 +377,15 @@ boolean_t
 db_elf_line_at_pc(db_symtab_t *symtab, db_sym_t cursym, char **filename,
     int *linenum, db_expr_t off)
 {
+       const char *linetab;
+       size_t linetab_size;
 
-       /*
-        * XXX We don't support this (yet).
-        */
-       return (FALSE);
+       linetab = db_elf_find_linetab(symtab, &linetab_size);
+       if (linetab == NULL)
+               return (FALSE);
+
+       return (db_elf_dwarf_line_at_pc(linetab, linetab_size, off,
+           filename, linenum));
 }
 
 /*
Index: ddb/db_elf_dwarf.c
===================================================================
RCS file: ddb/db_elf_dwarf.c
diff -N ddb/db_elf_dwarf.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ ddb/db_elf_dwarf.c  23 Jun 2014 13:37:34 -0000
@@ -0,0 +1,404 @@
+/*     $OpenBSD$        */
+/*
+ * Copyright (c) 2014 Matthew Dempsky <matt...@dempsky.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+boolean_t db_elf_dwarf_line_at_pc(const char *, size_t size, db_expr_t,
+    char **, int *);
+
+static int
+read_s8(int8_t *val, const void *buf, size_t *off, size_t len)
+{
+       if (len < sizeof(*val) || *off > len - sizeof(*val))
+               return (0);
+       memcpy(val, (const char *)buf + *off, sizeof(*val));
+       *off += sizeof(*val);
+       return (1);
+}
+
+static int
+read_u8(uint8_t *val, const void *buf, size_t *off, size_t len)
+{
+       if (len < sizeof(*val) || *off > len - sizeof(*val))
+               return (0);
+       memcpy(val, (const char *)buf + *off, sizeof(*val));
+       *off += sizeof(*val);
+       return (1);
+}
+
+static int
+read_u16(uint16_t *val, const void *buf, size_t *off, size_t len)
+{
+       if (len < sizeof(*val) || *off > len - sizeof(*val))
+               return (0);
+       memcpy(val, (const char *)buf + *off, sizeof(*val));
+       *off += sizeof(*val);
+       return (1);
+}
+
+static int
+read_u32(uint32_t *val, const void *buf, size_t *off, size_t len)
+{
+       if (len < sizeof(*val) || *off > len - sizeof(*val))
+               return (0);
+       memcpy(val, (const char *)buf + *off, sizeof(*val));
+       *off += sizeof(*val);
+       return (1);
+}
+
+static int
+read_u64(uint64_t *val, const void *buf, size_t *off, size_t len)
+{
+       if (len < sizeof(*val) || *off > len - sizeof(*val))
+               return (0);
+       memcpy(val, (const char *)buf + *off, sizeof(*val));
+       *off += sizeof(*val);
+       return (1);
+}
+
+static int
+read_uleb128(uint64_t *val, const void *buf, size_t *offp, size_t len)
+{
+       size_t off = *offp;
+       const unsigned char *p = buf;
+       unsigned int shift = 0;
+       uint64_t res = 0;
+       while (off < len) {
+               unsigned char x = p[off++];
+               res |= (uint64_t)(x & 0x7f) << shift;
+               shift += 7;
+               if ((x & 0x80) == 0) {
+                       *offp = off;
+                       *val = res;
+                       return (1);
+               }
+       }
+       return (0);
+}
+
+static int
+read_sleb128(int64_t *val, const void *buf, size_t *offp, size_t len)
+{
+       size_t off = *offp;
+       const unsigned char *p = buf;
+       unsigned int shift = 0;
+       int64_t res = 0;
+       while (off < len) {
+               unsigned char x = p[off++];
+               res |= (int64_t)(x & 0x7f) << shift;
+               shift += 7;
+               if ((x & 0x80) == 0) {
+                       if (x & 0x40)
+                               res |= ~(int64_t)0 << shift;
+                       *offp = off;
+                       *val = res;
+                       return (1);
+               }
+       }
+       return (0);
+}
+
+static int
+read_string(const char **s, const void *buf, size_t *offp, size_t len)
+{
+       if (*offp >= len)
+               return (0);
+       const char *p = buf;
+       const char *end = memchr(p + *offp, '\0', len - *offp);
+       if (end == NULL)
+               return (0);
+       *s = p + *offp;
+       *offp += end - (p + *offp) + 1;
+       return (1);
+}
+
+enum {
+       DW_LNS_copy = 1,
+       DW_LNS_advance_pc = 2,
+       DW_LNS_advance_line = 3,
+       DW_LNS_set_file = 4,
+       DW_LNS_set_column = 5,
+       DW_LNS_negate_stmt = 6,
+       DW_LNS_set_basic_block = 7,
+       DW_LNS_const_add_pc = 8,
+       DW_LNS_fixed_advance_pc = 9,
+       DW_LNS_set_prologue_end = 10,
+       DW_LNS_set_epilogue_begin = 11,
+
+       DW_LNE_end_sequence = 1,
+       DW_LNE_set_address = 2,
+       DW_LNE_define_file = 3,
+};
+
+static int
+read_filename(char **outfilename, uint64_t file, const char *unit,
+    size_t unitpos, size_t unitsize, uint8_t opcode_base)
+{
+       // Skip over opcode table.
+       size_t i;
+       for (i = 0; i < opcode_base - 1; i++) {
+               uint64_t dummy;
+               if (!read_uleb128(&dummy, unit, &unitpos, unitsize))
+                       return (0);
+       }
+
+       // Skip over directory names table for now.
+       size_t dirpos = unitpos;
+       for (;;) {
+               const char *name;
+               if (!read_string(&name, unit, &unitpos, unitsize))
+                       return (0);
+               if (*name == '\0')
+                       break;
+       }
+
+       // Locate file entry.
+       uint64_t dir;
+       const char *basename = NULL;
+       for (i = 0;;i++) {
+               const char *name;
+               if (!read_string(&name, unit, &unitpos, unitsize))
+                       return (0);
+               if (*name == '\0')
+                       break;
+               uint64_t mtime, size;
+               if (!read_uleb128(&dir, unit, &unitpos, unitsize) ||
+                   !read_uleb128(&mtime, unit, &unitpos, unitsize) ||
+                   !read_uleb128(&size, unit, &unitpos, unitsize))
+                       return (0);
+
+               if (i == file - 1) {
+                       basename = name;
+                       break;
+               }
+       }
+       if (basename == NULL)
+               return (0);
+
+       if (dir == 0) {
+               /* No directory; just return. */
+               *outfilename = (char *)basename;
+               return (1);
+       }
+
+       const char *dirname = NULL;
+       unitpos = dirpos;
+       for (i = 0;;i++) {
+               const char *name;
+               if (!read_string(&name, unit, &unitpos, unitsize))
+                       return (0);
+               if (*name == '\0')
+                       break;
+               if (i == dir - 1) {
+                       dirname = name;
+                       break;
+               }
+       }
+       if (dirname == NULL)
+               return (0);
+
+       static char pathbuf[MAXPATHLEN];
+       snprintf(pathbuf, sizeof(pathbuf), "%s/%s", dirname, basename);
+       *outfilename = pathbuf;
+       return (1);
+}
+
+boolean_t
+db_elf_dwarf_line_at_pc(const char *const tab, const size_t tabsize, db_expr_t 
pc,
+    char **outfilename, int *outline)
+{
+       size_t tabpos = 0;
+
+       /*
+        * For simplicity and memory conservation, we simply brute force search
+        * through the entire line table each time.
+        */
+again:
+       if (tabpos >= tabsize) {
+               return (FALSE);
+       }
+
+       uint32_t unitsize;
+       if (!read_u32(&unitsize, tab, &tabpos, tabsize)) {
+               return (FALSE);
+       }
+       KASSERT(unitsize < 0xfffffff0);
+       // Greater values are reserved; 0xffffffff indicates DWARF 64.
+
+       if (unitsize > tabsize - tabpos) {
+               return (FALSE);
+       }
+
+       const char *const unit = tab + tabpos;
+       tabpos += unitsize;
+       size_t unitpos = 0;
+
+       uint16_t version;
+       if (!read_u16(&version, unit, &unitpos, unitsize))
+               goto again;
+       if (version > 4)
+               goto again;
+       uint32_t header_size;
+       uint8_t min_insn_length, default_is_stmt, line_range, opcode_base;
+       uint8_t max_ops_per_insn = 1;
+       int8_t line_base;
+       if (!read_u32(&header_size, unit, &unitpos, unitsize))
+               goto again;
+       size_t headerpos = unitpos;
+       if (!read_u8(&min_insn_length, unit, &unitpos, unitsize) ||
+           (version >= 4 && !read_u8(&max_ops_per_insn, unit, &unitpos, 
unitsize)) ||
+           !read_u8(&default_is_stmt, unit, &unitpos, unitsize) ||
+           !read_s8(&line_base, unit, &unitpos, unitsize) ||
+           !read_u8(&line_range, unit, &unitpos, unitsize) ||
+           !read_u8(&opcode_base, unit, &unitpos, unitsize))
+               goto again;
+
+       size_t skippos = unitpos;
+       unitpos = headerpos + header_size;
+
+       // VM registers.
+       uint64_t address = 0, file = 1, line = 1, column = 0;
+       uint8_t is_stmt = default_is_stmt;
+       boolean_t basic_block = FALSE, end_sequence = FALSE;
+       boolean_t prologue_end = FALSE, epilogue_begin = FALSE;
+
+       boolean_t have_prev = FALSE;
+       uint64_t prev_line, prev_file;
+
+       // Time to run the line program.
+       uint8_t opcode;
+       while (read_u8(&opcode, unit, &unitpos, unitsize)) {
+               boolean_t emit = FALSE, reset_basic_block = FALSE;
+
+               switch (opcode) {
+               case 0: {
+                       /* "Extended" opcodes. */
+                       uint64_t extsize;
+                       if (!read_uleb128(&extsize, unit, &unitpos, unitsize))
+                               goto again;
+                       const char *extra = unit + unitpos;
+                       unitpos += extsize;
+                       size_t extpos = 0;
+                       if (!read_u8(&opcode, extra, &extpos, extsize))
+                               goto again;
+                       switch (opcode) {
+                       case DW_LNE_end_sequence:
+                               emit = TRUE;
+                               end_sequence = TRUE;
+                               break;
+                       case DW_LNE_set_address:
+                               switch (extsize - extpos) {
+                               case 4: {
+                                       uint32_t address32;
+                                       if (!read_u32(&address32, extra, 
&extpos, extsize))
+                                               goto again;
+                                       address = address32;
+                                       break;
+                               }
+                               case 8:
+                                       if (!read_u64(&address, extra, &extpos, 
extsize))
+                                               goto again;
+                                       break;
+                               default:
+                                       printf("unexpected address length: 
%llu\n", extsize - extpos);
+                                       goto again;
+                               }
+                               break;
+                       default:
+                               printf("unknown extended opcode: %d\n", opcode);
+                               goto again;
+                       }
+                       break;
+               }
+               case DW_LNS_copy:
+                       emit = TRUE;
+                       reset_basic_block = TRUE;
+                       break;
+               case DW_LNS_advance_pc: {
+                       uint64_t delta;
+                       if (!read_uleb128(&delta, unit, &unitpos, unitsize))
+                               goto again;
+                       address += delta * min_insn_length;
+                       break;
+               }
+               case DW_LNS_advance_line: {
+                       int64_t delta;
+                       if (!read_sleb128(&delta, unit, &unitpos, unitsize))
+                               goto again;
+                       line += delta;
+                       break;
+               }
+               case DW_LNS_set_file:
+                       if (!read_uleb128(&file, unit, &unitpos, unitsize))
+                               goto again;
+                       break;
+               case DW_LNS_set_column:
+                       if (!read_uleb128(&column, unit, &unitpos, unitsize))
+                               goto again;
+                       break;
+               case DW_LNS_negate_stmt:
+                       is_stmt = !is_stmt;
+                       break;
+               case DW_LNS_set_basic_block:
+                       basic_block = TRUE;
+                       break;
+               case DW_LNS_const_add_pc:
+                       address += (255 - opcode_base) / line_range;
+                       break;
+               case DW_LNS_set_prologue_end:
+                       prologue_end = TRUE;
+                       break;
+               case DW_LNS_set_epilogue_begin:
+                       epilogue_begin = TRUE;
+                       break;
+               default:
+                       if (opcode < opcode_base) {
+                               printf("unknown standard opcode: %d\n", opcode);
+                               goto again;
+                       }
+                       /* "Special" opcodes. */
+                       uint8_t diff = opcode - opcode_base;
+                       address += diff / line_range;
+                       line += line_base + diff % line_range;
+                       emit = TRUE;
+                       break;
+               }
+
+               if (emit) {
+                       if (address > pc) {
+                               /* Found an entry after our target PC. */
+                               if (!have_prev) {
+                                       /* Give up on this program. */
+                                       break;
+                               }
+                               /* Return the previous entry. */
+                               if (!read_filename(outfilename, file, unit,
+                                   skippos, unitsize, opcode_base))
+                                       return (FALSE);
+                               *outline = prev_line;
+                               return (TRUE);
+                       }
+
+                       prev_file = file;
+                       prev_line = line;
+                       have_prev = TRUE;
+               }
+
+               if (reset_basic_block)
+                       basic_block = FALSE;
+       }
+
+       goto again;
+}
Index: lib/libsa/loadfile_elf.c
===================================================================
RCS file: /home/matthew/anoncvs/cvs/src/sys/lib/libsa/loadfile_elf.c,v
retrieving revision 1.8
diff -u -p -r1.8 loadfile_elf.c
--- lib/libsa/loadfile_elf.c    25 Feb 2014 21:40:39 -0000      1.8
+++ lib/libsa/loadfile_elf.c    23 Jun 2014 09:38:46 -0000
@@ -76,7 +76,7 @@ ELFNAME(exec)(int fd, Elf_Ehdr *elf, u_l
        int i;
        size_t sz;
        int first;
-       int havesyms;
+       int havesyms, havelines;
        paddr_t minp = ~0, maxp = 0, pos = 0;
        paddr_t offset = marks[MARK_START], shpp, elfp;
 
@@ -199,6 +199,21 @@ ELFNAME(exec)(int fd, Elf_Ehdr *elf, u_l
                shpp = maxp;
                maxp += roundup(sz, sizeof(Elf_Addr));
 
+               size_t shstrsz = shp[elf->e_shstrndx].sh_size;
+               char *shstr = ALLOC(shstrsz);
+               if (lseek(fd, (off_t)shp[elf->e_shstrndx].sh_offset, SEEK_SET) 
== -1) {
+                       WARN(("lseek section header string table"));
+                       FREE(shstr, shstrsz);
+                       FREE(shp, sz);
+                       return 1;
+               }
+               if (READ(fd, shstr, shstrsz) != shstrsz) {
+                       WARN(("read section header string table"));
+                       FREE(shstr, shstrsz);
+                       FREE(shp, sz);
+                       return 1;
+               }
+
                /*
                 * Now load the symbol sections themselves. Make sure the
                 * sections are aligned. Don't bother with string tables if
@@ -206,25 +221,28 @@ ELFNAME(exec)(int fd, Elf_Ehdr *elf, u_l
                 */
                off = roundup((sizeof(Elf_Ehdr) + sz), sizeof(Elf_Addr));
 
-               for (havesyms = i = 0; i < elf->e_shnum; i++)
+               for (havesyms = havelines = i = 0; i < elf->e_shnum; i++)
                        if (shp[i].sh_type == SHT_SYMTAB)
                                havesyms = 1;
 
                for (first = 1, i = 0; i < elf->e_shnum; i++) {
                        if (shp[i].sh_type == SHT_SYMTAB ||
-                           shp[i].sh_type == SHT_STRTAB) {
+                           shp[i].sh_type == SHT_STRTAB ||
+                           !strcmp(shstr + shp[i].sh_name, ".debug_line")) {
                                if (havesyms && (flags & LOAD_SYM)) {
                                        PROGRESS(("%s%ld", first ? " [" : "+",
                                            (u_long)shp[i].sh_size));
                                        if (lseek(fd, (off_t)shp[i].sh_offset,
                                            SEEK_SET) == -1) {
                                                WARN(("lseek symbols"));
+                                               FREE(shstr, shstrsz);
                                                FREE(shp, sz);
                                                return 1;
                                        }
                                        if (READ(fd, maxp, shp[i].sh_size) !=
                                            shp[i].sh_size) {
                                                WARN(("read symbols"));
+                                               FREE(shstr, shstrsz);
                                                FREE(shp, sz);
                                                return 1;
                                        }
@@ -242,6 +260,7 @@ ELFNAME(exec)(int fd, Elf_Ehdr *elf, u_l
                        if (havesyms && first == 0)
                                PROGRESS(("]"));
                }
+               FREE(shstr, shstrsz);
                FREE(shp, sz);
        }
 

Reply via email to