Edit

IABSD.fr/src/sys/ddb/db_dwarf.c

Branch :

  • Show log

    Commit

  • Author : mpi
    Date : 2017-10-27 08:40:15
    Hash : abcfa06c
    Message : Use <elf.h> in !_KERNEL code path.

  • sys/ddb/db_dwarf.c
  • /*	$OpenBSD: db_dwarf.c,v 1.7 2017/10/27 08:40:15 mpi Exp $	 */
    /*
     * Copyright (c) 2014 Matthew Dempsky <matthew@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.
     */
    
    #ifdef _KERNEL
    #include <sys/param.h>
    #include <sys/systm.h>
    #include <machine/db_machdep.h>
    #include <ddb/db_sym.h>
    #ifdef DIAGNOSTIC
    #define DWARN(fmt, ...) printf("ddb: " fmt "\n", __VA_ARGS__)
    #else
    #define DWARN(fmt, ...) ((void)0)
    #endif
    #else /* _KERNEL */
    #include <err.h>
    #include <stdbool.h>
    #include <stdint.h>
    #include <string.h>
    #define DWARN warnx
    #endif /* _KERNEL */
    
    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,
    };
    
    enum {
    	DW_LNE_end_sequence		= 1,
    	DW_LNE_set_address		= 2,
    	DW_LNE_define_file		= 3,
    };
    
    struct dwbuf {
    	const char *buf;
    	size_t len;
    };
    
    static inline bool
    read_bytes(struct dwbuf *d, void *v, size_t n)
    {
    	if (d->len < n)
    		return (false);
    	memcpy(v, d->buf, n);
    	d->buf += n;
    	d->len -= n;
    	return (true);
    }
    
    static bool
    read_s8(struct dwbuf *d, int8_t *v)
    {
    	return (read_bytes(d, v, sizeof(*v)));
    }
    
    static bool
    read_u8(struct dwbuf *d, uint8_t *v)
    {
    	return (read_bytes(d, v, sizeof(*v)));
    }
    
    static bool
    read_u16(struct dwbuf *d, uint16_t *v)
    {
    	return (read_bytes(d, v, sizeof(*v)));
    }
    
    static bool
    read_u32(struct dwbuf *d, uint32_t *v)
    {
    	return (read_bytes(d, v, sizeof(*v)));
    }
    
    static bool
    read_u64(struct dwbuf *d, uint64_t *v)
    {
    	return (read_bytes(d, v, sizeof(*v)));
    }
    
    /* Read a DWARF LEB128 (little-endian base-128) value. */
    static bool
    read_leb128(struct dwbuf *d, uint64_t *v, bool signextend)
    {
    	unsigned int shift = 0;
    	uint64_t res = 0;
    	uint8_t x;
    	while (shift < 64 && read_u8(d, &x)) {
    		res |= (uint64_t)(x & 0x7f) << shift;
    		shift += 7;
    		if ((x & 0x80) == 0) {
    			if (signextend && shift < 64 && (x & 0x40) != 0)
    				res |= ~(uint64_t)0 << shift;
    			*v = res;
    			return (true);
    		}
    	}
    	return (false);
    }
    
    static bool
    read_sleb128(struct dwbuf *d, int64_t *v)
    {
    	return (read_leb128(d, (uint64_t *)v, true));
    }
    
    static bool
    read_uleb128(struct dwbuf *d, uint64_t *v)
    {
    	return (read_leb128(d, v, false));
    }
    
    /* Read a NUL terminated string. */
    static bool
    read_string(struct dwbuf *d, const char **s)
    {
    	const char *end = memchr(d->buf, '\0', d->len);
    	if (end == NULL)
    		return (false);
    	size_t n = end - d->buf + 1;
    	*s = d->buf;
    	d->buf += n;
    	d->len -= n;
    	return (true);
    }
    
    static bool
    read_buf(struct dwbuf *d, struct dwbuf *v, size_t n)
    {
    	if (d->len < n)
    		return (false);
    	v->buf = d->buf;
    	v->len = n;
    	d->buf += n;
    	d->len -= n;
    	return (true);
    }
    
    static bool
    skip_bytes(struct dwbuf *d, size_t n)
    {
    	if (d->len < n)
    		return (false);
    	d->buf += n;
    	d->len -= n;
    	return (true);
    }
    
    static bool
    read_filename(struct dwbuf *names, const char **outdirname,
        const char **outbasename, uint8_t opcode_base, uint64_t file)
    {
    	if (file == 0)
    		return (false);
    
    	/* Skip over opcode table. */
    	size_t i;
    	for (i = 1; i < opcode_base; i++) {
    		uint64_t dummy;
    		if (!read_uleb128(names, &dummy))
    			return (false);
    	}
    
    	/* Skip over directory name table for now. */
    	struct dwbuf dirnames = *names;
    	for (;;) {
    		const char *name;
    		if (!read_string(names, &name))
    			return (false);
    		if (*name == '\0')
    			break;
    	}
    
    	/* Locate file entry. */
    	const char *basename = NULL;
    	uint64_t dir = 0;
    	for (i = 0; i < file; i++) {
    		uint64_t mtime, size;
    		if (!read_string(names, &basename) || *basename == '\0' ||
    		    !read_uleb128(names, &dir) ||
    		    !read_uleb128(names, &mtime) ||
    		    !read_uleb128(names, &size))
    			return (false);
    	}
    
    	const char *dirname = NULL;
    	for (i = 0; i < dir; i++) {
    		if (!read_string(&dirnames, &dirname) || *dirname == '\0')
    			return (false);
    	}
    
    	*outdirname = dirname;
    	*outbasename = basename;
    	return (true);
    }
    
    bool
    db_dwarf_line_at_pc(const char *linetab, size_t linetabsize, uintptr_t pc,
        const char **outdirname, const char **outbasename, int *outline)
    {
    	struct dwbuf table = { .buf = linetab, .len = linetabsize };
    
    	/*
    	 * For simplicity, we simply brute force search through the entire
    	 * line table each time.
    	 */
    	uint32_t unitsize;
    	struct dwbuf unit;
    next:
    	/* Line tables are a sequence of compilation unit entries. */
    	if (!read_u32(&table, &unitsize) || unitsize >= 0xfffffff0 ||
    	    !read_buf(&table, &unit, unitsize))
    		return (false);
    
    	uint16_t version;
    	uint32_t header_size;
    	if (!read_u16(&unit, &version) || version > 2 ||
    	    !read_u32(&unit, &header_size))
    		goto next;
    
    	struct dwbuf headerstart = unit;
    	uint8_t min_insn_length, default_is_stmt, line_range, opcode_base;
    	int8_t line_base;
    	if (!read_u8(&unit, &min_insn_length) ||
    	    !read_u8(&unit, &default_is_stmt) ||
    	    !read_s8(&unit, &line_base) ||
    	    !read_u8(&unit, &line_range) ||
    	    !read_u8(&unit, &opcode_base))
    		goto next;
    
    	/*
    	 * Directory and file names are next in the header, but for now we
    	 * skip directly to the line number program.
    	 */
    	struct dwbuf names = unit;
    	unit = headerstart;
    	if (!skip_bytes(&unit, header_size))
    		return (false);
    
    	/* VM registers. */
    	uint64_t address = 0, file = 1, line = 1, column = 0;
    	uint8_t is_stmt = default_is_stmt;
    	bool basic_block = false, end_sequence = false;
    	bool prologue_end = false, epilogue_begin = false;
    
    	/* Last line table entry emitted, if any. */
    	bool have_last = false;
    	uint64_t last_line = 0, last_file = 0;
    
    	/* Time to run the line program. */
    	uint8_t opcode;
    	while (read_u8(&unit, &opcode)) {
    		bool emit = false, reset_basic_block = false;
    
    		if (opcode >= opcode_base) {
    			/* "Special" opcodes. */
    			uint8_t diff = opcode - opcode_base;
    			address += diff / line_range;
    			line += line_base + diff % line_range;
    			emit = true;
    		} else if (opcode == 0) {
    			/* "Extended" opcodes. */
    			uint64_t extsize;
    			struct dwbuf extra;
    			if (!read_uleb128(&unit, &extsize) ||
    			    !read_buf(&unit, &extra, extsize) ||
    			    !read_u8(&extra, &opcode))
    				goto next;
    			switch (opcode) {
    			case DW_LNE_end_sequence:
    				emit = true;
    				end_sequence = true;
    				break;
    			case DW_LNE_set_address:
    				switch (extra.len) {
    				case 4: {
    					uint32_t address32;
    					if (!read_u32(&extra, &address32))
    						goto next;
    					address = address32;
    					break;
    				}
    				case 8:
    					if (!read_u64(&extra, &address))
    						goto next;
    					break;
    				default:
    					DWARN("unexpected address length: %zu",
    					    extra.len);
    					goto next;
    				}
    				break;
    			case DW_LNE_define_file:
    				/* XXX: hope this isn't needed */
    			default:
    				DWARN("unknown extended opcode: %d", opcode);
    				goto next;
    			}
    		} else {
    			/* "Standard" opcodes. */
    			switch (opcode) {
    			case DW_LNS_copy:
    				emit = true;
    				reset_basic_block = true;
    				break;
    			case DW_LNS_advance_pc: {
    				uint64_t delta;
    				if (!read_uleb128(&unit, &delta))
    					goto next;
    				address += delta * min_insn_length;
    				break;
    			}
    			case DW_LNS_advance_line: {
    				int64_t delta;
    				if (!read_sleb128(&unit, &delta))
    					goto next;
    				line += delta;
    				break;
    			}
    			case DW_LNS_set_file:
    				if (!read_uleb128(&unit, &file))
    					goto next;
    				break;
    			case DW_LNS_set_column:
    				if (!read_uleb128(&unit, &column))
    					goto next;
    				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:
    				DWARN("unknown standard opcode: %d", opcode);
    				goto next;
    			}
    		}
    
    		if (emit) {
    			if (address > pc) {
    				/* Found an entry after our target PC. */
    				if (!have_last) {
    					/* Give up on this program. */
    					break;
    				}
    				/* Return the last entry. */
    				*outline = last_line;
    				return (read_filename(&names, outdirname,
    				    outbasename, opcode_base, last_file));
    			}
    
    			last_file = file;
    			last_line = line;
    			have_last = true;
    		}
    
    		if (reset_basic_block)
    			basic_block = false;
    	}
    
    	goto next;
    }
    
    #ifndef _KERNEL
    #include <sys/endian.h>
    #include <sys/mman.h>
    #include <sys/stat.h>
    #include <elf.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    #ifndef ELFDATA
    #if BYTE_ORDER == LITTLE_ENDIAN
    #define ELFDATA ELFDATA2LSB
    #elif BYTE_ORDER == BIG_ENDIAN
    #define ELFDATA ELFDATA2MSB
    #else
    #error Unsupported byte order
    #endif
    #endif /* !ELFDATA */
    
    static void
    usage(void)
    {
    	extern const char *__progname;
    	errx(1, "usage: %s [-s] [-e filename] [addr addr ...]", __progname);
    }
    
    /*
     * Basic addr2line clone for stand-alone testing.
     */
    int
    main(int argc, char *argv[])
    {
    	const char *filename = "a.out";
    
    	int ch;
    	bool showdir = true;
    	while ((ch = getopt(argc, argv, "e:s")) != EOF) {
    		switch (ch) {
    		case 'e':
    			filename = optarg;
    			break;
    		case 's':
    			showdir = false;
    			break;
    		default:
    			usage();
    		}
    	}
    
    	argc -= optind;
    	argv += optind;
    
    	/* Start by mapping the full file into memory. */
    	int fd = open(filename, O_RDONLY);
    	if (fd == -1)
    		err(1, "open");
    
    	struct stat st;
    	if (fstat(fd, &st) == -1)
    		err(1, "fstat");
    	if (st.st_size < (off_t)sizeof(Elf_Ehdr))
    		errx(1, "file too small to be ELF");
    	if ((uintmax_t)st.st_size > SIZE_MAX)
    		errx(1, "file too big to fit memory");
    	size_t filesize = st.st_size;
    
    	const char *p = mmap(NULL, filesize, PROT_READ, MAP_SHARED, fd, 0);
    	if (p == MAP_FAILED)
    		err(1, "mmap");
    
    	close(fd);
    
    	/* Read and validate ELF header. */
    	Elf_Ehdr ehdr;
    	memcpy(&ehdr, p, sizeof(ehdr));
    	if (!IS_ELF(ehdr))
    		errx(1, "file is not ELF");
    	if (ehdr.e_ident[EI_CLASS] != ELFCLASS)
    		errx(1, "unexpected word size");
    	if (ehdr.e_ident[EI_DATA] != ELFDATA)
    		errx(1, "unexpected data format");
    	if (ehdr.e_shoff > filesize)
    		errx(1, "bogus section table offset");
    	if (ehdr.e_shentsize < sizeof(Elf_Shdr))
    		errx(1, "unexpected section header size");
    	if (ehdr.e_shnum > (filesize - ehdr.e_shoff) / ehdr.e_shentsize)
    		errx(1, "bogus section header count");
    	if (ehdr.e_shstrndx >= ehdr.e_shnum)
    		errx(1, "bogus string table index");
    
    	/* Find section header string table location and size. */
    	Elf_Shdr shdr;
    	memcpy(&shdr, p + ehdr.e_shoff + ehdr.e_shstrndx * ehdr.e_shentsize,
    	    sizeof(shdr));
    	if (shdr.sh_type != SHT_STRTAB)
    		errx(1, "unexpected string table type");
    	if (shdr.sh_offset > filesize)
    		errx(1, "bogus string table offset");
    	if (shdr.sh_size > filesize - shdr.sh_offset)
    		errx(1, "bogus string table size");
    	const char *shstrtab = p + shdr.sh_offset;
    	size_t shstrtabsize = shdr.sh_size;
    
    	/* Search through section table for .debug_line section. */
    	size_t i;
    	for (i = 0; i < ehdr.e_shnum; i++) {
    		memcpy(&shdr, p + ehdr.e_shoff + i * ehdr.e_shentsize,
    		    sizeof(shdr));
    		if (0 == strncmp(".debug_line", shstrtab + shdr.sh_name,
    		    shstrtabsize - shdr.sh_name))
    			break;
    	}
    	if (i == ehdr.e_shnum)
    		errx(1, "no DWARF line number table found");
    	if (shdr.sh_offset > filesize)
    		errx(1, "bogus line table offset");
    	if (shdr.sh_size > filesize - shdr.sh_offset)
    		errx(1, "bogus line table size");
    	const char *linetab = p + shdr.sh_offset;
    	size_t linetabsize = shdr.sh_size;
    
    	const char *addrstr;
    	while ((addrstr = *argv++) != NULL) {
    		unsigned long addr = strtoul(addrstr, NULL, 16);
    
    		const char *dir, *file;
    		int line;
    		if (!db_dwarf_line_at_pc(linetab, linetabsize, addr,
    		    &dir, &file, &line)) {
    			dir = NULL;
    			file = "??";
    			line = 0;
    		}
    		if (showdir && dir != NULL)
    			printf("%s/", dir);
    		printf("%s:%d\n", file, line);
    	}
    
    	return (0);
    }
    #endif /* !_KERNEL */