xref: /openbsd/sys/ddb/db_dwarf.c (revision a6445c1d)
1 /*	$OpenBSD: db_dwarf.c,v 1.1 2014/07/11 03:17:20 matthew Exp $	 */
2 /*
3  * Copyright (c) 2014 Matthew Dempsky <matthew@dempsky.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #ifdef _KERNEL
19 #include <sys/param.h>
20 #include <sys/stdint.h>
21 #include <sys/systm.h>
22 #include <sys/types.h>
23 #include <ddb/db_dwarf.h>
24 #ifdef DIAGNOSTIC
25 #define DWARN(fmt, ...) printf("ddb: " fmt "\n", __VA_ARGS__)
26 #else
27 #define DWARN(fmt, ...) ((void)0)
28 #endif
29 #else /* _KERNEL */
30 #include <err.h>
31 #include <stdbool.h>
32 #include <stdint.h>
33 #include <string.h>
34 #define DWARN warnx
35 #endif /* _KERNEL */
36 
37 enum {
38 	DW_LNS_copy			= 1,
39 	DW_LNS_advance_pc		= 2,
40 	DW_LNS_advance_line		= 3,
41 	DW_LNS_set_file			= 4,
42 	DW_LNS_set_column		= 5,
43 	DW_LNS_negate_stmt		= 6,
44 	DW_LNS_set_basic_block		= 7,
45 	DW_LNS_const_add_pc		= 8,
46 	DW_LNS_fixed_advance_pc		= 9,
47 	DW_LNS_set_prologue_end		= 10,
48 	DW_LNS_set_epilogue_begin	= 11,
49 };
50 
51 enum {
52 	DW_LNE_end_sequence		= 1,
53 	DW_LNE_set_address		= 2,
54 	DW_LNE_define_file		= 3,
55 };
56 
57 struct dwbuf {
58 	const char *buf;
59 	size_t len;
60 };
61 
62 static inline bool
63 read_bytes(struct dwbuf *d, void *v, size_t n)
64 {
65 	if (d->len < n)
66 		return (false);
67 	memcpy(v, d->buf, n);
68 	d->buf += n;
69 	d->len -= n;
70 	return (true);
71 }
72 
73 static bool
74 read_s8(struct dwbuf *d, int8_t *v)
75 {
76 	return (read_bytes(d, v, sizeof(*v)));
77 }
78 
79 static bool
80 read_u8(struct dwbuf *d, uint8_t *v)
81 {
82 	return (read_bytes(d, v, sizeof(*v)));
83 }
84 
85 static bool
86 read_u16(struct dwbuf *d, uint16_t *v)
87 {
88 	return (read_bytes(d, v, sizeof(*v)));
89 }
90 
91 static bool
92 read_u32(struct dwbuf *d, uint32_t *v)
93 {
94 	return (read_bytes(d, v, sizeof(*v)));
95 }
96 
97 static bool
98 read_u64(struct dwbuf *d, uint64_t *v)
99 {
100 	return (read_bytes(d, v, sizeof(*v)));
101 }
102 
103 /* Read a DWARF LEB128 (little-endian base-128) value. */
104 static bool
105 read_leb128(struct dwbuf *d, uint64_t *v, bool signextend)
106 {
107 	unsigned int shift = 0;
108 	uint64_t res = 0;
109 	uint8_t x;
110 	while (shift < 64 && read_u8(d, &x)) {
111 		res |= (uint64_t)(x & 0x7f) << shift;
112 		shift += 7;
113 		if ((x & 0x80) == 0) {
114 			if (signextend && shift < 64 && (x & 0x40) != 0)
115 				res |= ~(uint64_t)0 << shift;
116 			*v = res;
117 			return (true);
118 		}
119 	}
120 	return (false);
121 }
122 
123 static bool
124 read_sleb128(struct dwbuf *d, int64_t *v)
125 {
126 	return (read_leb128(d, (uint64_t *)v, true));
127 }
128 
129 static bool
130 read_uleb128(struct dwbuf *d, uint64_t *v)
131 {
132 	return (read_leb128(d, v, false));
133 }
134 
135 /* Read a NUL terminated string. */
136 static bool
137 read_string(struct dwbuf *d, const char **s)
138 {
139 	const char *end = memchr(d->buf, '\0', d->len);
140 	if (end == NULL)
141 		return (false);
142 	size_t n = end - d->buf + 1;
143 	*s = d->buf;
144 	d->buf += n;
145 	d->len -= n;
146 	return (true);
147 }
148 
149 static bool
150 read_buf(struct dwbuf *d, struct dwbuf *v, size_t n)
151 {
152 	if (d->len < n)
153 		return (false);
154 	v->buf = d->buf;
155 	v->len = n;
156 	d->buf += n;
157 	d->len -= n;
158 	return (true);
159 }
160 
161 static bool
162 skip_bytes(struct dwbuf *d, size_t n)
163 {
164 	if (d->len < n)
165 		return (false);
166 	d->buf += n;
167 	d->len -= n;
168 	return (true);
169 }
170 
171 static bool
172 read_filename(struct dwbuf *names, const char **outdirname,
173     const char **outbasename, uint8_t opcode_base, uint64_t file)
174 {
175 	if (file == 0)
176 		return (false);
177 
178 	/* Skip over opcode table. */
179 	size_t i;
180 	for (i = 1; i < opcode_base; i++) {
181 		uint64_t dummy;
182 		if (!read_uleb128(names, &dummy))
183 			return (false);
184 	}
185 
186 	/* Skip over directory name table for now. */
187 	struct dwbuf dirnames = *names;
188 	for (;;) {
189 		const char *name;
190 		if (!read_string(names, &name))
191 			return (false);
192 		if (*name == '\0')
193 			break;
194 	}
195 
196 	/* Locate file entry. */
197 	const char *basename = NULL;
198 	uint64_t dir = 0;
199 	for (i = 0; i < file; i++) {
200 		uint64_t mtime, size;
201 		if (!read_string(names, &basename) || *basename == '\0' ||
202 		    !read_uleb128(names, &dir) ||
203 		    !read_uleb128(names, &mtime) ||
204 		    !read_uleb128(names, &size))
205 			return (false);
206 	}
207 
208 	const char *dirname = NULL;
209 	for (i = 0; i < dir; i++) {
210 		if (!read_string(&dirnames, &dirname) || *dirname == '\0')
211 			return (false);
212 	}
213 
214 	*outdirname = dirname;
215 	*outbasename = basename;
216 	return (true);
217 }
218 
219 bool
220 db_dwarf_line_at_pc(const char *linetab, size_t linetabsize, uintptr_t pc,
221     const char **outdirname, const char **outbasename, int *outline)
222 {
223 	struct dwbuf table = { .buf = linetab, .len = linetabsize };
224 
225 	/*
226 	 * For simplicity, we simply brute force search through the entire
227 	 * line table each time.
228 	 */
229 	uint32_t unitsize;
230 	struct dwbuf unit;
231 next:
232 	/* Line tables are a sequence of compilation unit entries. */
233 	if (!read_u32(&table, &unitsize) || unitsize >= 0xfffffff0 ||
234 	    !read_buf(&table, &unit, unitsize))
235 		return (false);
236 
237 	uint16_t version;
238 	uint32_t header_size;
239 	if (!read_u16(&unit, &version) || version > 2 ||
240 	    !read_u32(&unit, &header_size))
241 		goto next;
242 
243 	struct dwbuf headerstart = unit;
244 	uint8_t min_insn_length, default_is_stmt, line_range, opcode_base;
245 	int8_t line_base;
246 	if (!read_u8(&unit, &min_insn_length) ||
247 	    !read_u8(&unit, &default_is_stmt) ||
248 	    !read_s8(&unit, &line_base) ||
249 	    !read_u8(&unit, &line_range) ||
250 	    !read_u8(&unit, &opcode_base))
251 		goto next;
252 
253 	/*
254 	 * Directory and file names are next in the header, but for now we
255 	 * skip directly to the line number program.
256 	 */
257 	struct dwbuf names = unit;
258 	unit = headerstart;
259 	if (!skip_bytes(&unit, header_size))
260 		return (false);
261 
262 	/* VM registers. */
263 	uint64_t address = 0, file = 1, line = 1, column = 0;
264 	uint8_t is_stmt = default_is_stmt;
265 	bool basic_block = false, end_sequence = false;
266 	bool prologue_end = false, epilogue_begin = false;
267 
268 	/* Last line table entry emitted, if any. */
269 	bool have_last = false;
270 	uint64_t last_line = 0, last_file = 0;
271 
272 	/* Time to run the line program. */
273 	uint8_t opcode;
274 	while (read_u8(&unit, &opcode)) {
275 		bool emit = false, reset_basic_block = false;
276 
277 		if (opcode >= opcode_base) {
278 			/* "Special" opcodes. */
279 			uint8_t diff = opcode - opcode_base;
280 			address += diff / line_range;
281 			line += line_base + diff % line_range;
282 			emit = true;
283 		} else if (opcode == 0) {
284 			/* "Extended" opcodes. */
285 			uint64_t extsize;
286 			struct dwbuf extra;
287 			if (!read_uleb128(&unit, &extsize) ||
288 			    !read_buf(&unit, &extra, extsize) ||
289 			    !read_u8(&extra, &opcode))
290 				goto next;
291 			switch (opcode) {
292 			case DW_LNE_end_sequence:
293 				emit = true;
294 				end_sequence = true;
295 				break;
296 			case DW_LNE_set_address:
297 				switch (extra.len) {
298 				case 4: {
299 					uint32_t address32;
300 					if (!read_u32(&extra, &address32))
301 						goto next;
302 					address = address32;
303 					break;
304 				}
305 				case 8:
306 					if (!read_u64(&extra, &address))
307 						goto next;
308 					break;
309 				default:
310 					DWARN("unexpected address length: %zu",
311 					    extra.len);
312 					goto next;
313 				}
314 				break;
315 			case DW_LNE_define_file:
316 				/* XXX: hope this isn't needed */
317 			default:
318 				DWARN("unknown extended opcode: %d", opcode);
319 				goto next;
320 			}
321 		} else {
322 			/* "Standard" opcodes. */
323 			switch (opcode) {
324 			case DW_LNS_copy:
325 				emit = true;
326 				reset_basic_block = true;
327 				break;
328 			case DW_LNS_advance_pc: {
329 				uint64_t delta;
330 				if (!read_uleb128(&unit, &delta))
331 					goto next;
332 				address += delta * min_insn_length;
333 				break;
334 			}
335 			case DW_LNS_advance_line: {
336 				int64_t delta;
337 				if (!read_sleb128(&unit, &delta))
338 					goto next;
339 				line += delta;
340 				break;
341 			}
342 			case DW_LNS_set_file:
343 				if (!read_uleb128(&unit, &file))
344 					goto next;
345 				break;
346 			case DW_LNS_set_column:
347 				if (!read_uleb128(&unit, &column))
348 					goto next;
349 				break;
350 			case DW_LNS_negate_stmt:
351 				is_stmt = !is_stmt;
352 				break;
353 			case DW_LNS_set_basic_block:
354 				basic_block = true;
355 				break;
356 			case DW_LNS_const_add_pc:
357 				address += (255 - opcode_base) / line_range;
358 				break;
359 			case DW_LNS_set_prologue_end:
360 				prologue_end = true;
361 				break;
362 			case DW_LNS_set_epilogue_begin:
363 				epilogue_begin = true;
364 				break;
365 			default:
366 				DWARN("unknown standard opcode: %d", opcode);
367 				goto next;
368 			}
369 		}
370 
371 		if (emit) {
372 			if (address > pc) {
373 				/* Found an entry after our target PC. */
374 				if (!have_last) {
375 					/* Give up on this program. */
376 					break;
377 				}
378 				/* Return the last entry. */
379 				*outline = last_line;
380 				return (read_filename(&names, outdirname,
381 				    outbasename, opcode_base, file));
382 			}
383 
384 			last_file = file;
385 			last_line = line;
386 			have_last = true;
387 		}
388 
389 		if (reset_basic_block)
390 			basic_block = false;
391 	}
392 
393 	goto next;
394 }
395 
396 #ifndef _KERNEL
397 #include <sys/endian.h>
398 #include <sys/mman.h>
399 #include <sys/stat.h>
400 #include <elf_abi.h>
401 #include <fcntl.h>
402 #include <stdio.h>
403 #include <stdlib.h>
404 #include <unistd.h>
405 
406 #ifndef ELFDATA
407 #if BYTE_ORDER == LITTLE_ENDIAN
408 #define ELFDATA ELFDATA2LSB
409 #elif BYTE_ORDER == BIG_ENDIAN
410 #define ELFDATA ELFDATA2MSB
411 #else
412 #error Unsupported byte order
413 #endif
414 #endif /* !ELFDATA */
415 
416 static void
417 usage()
418 {
419 	extern const char *__progname;
420 	errx(1, "usage: %s [-s] [-e filename] [addr addr ...]", __progname);
421 }
422 
423 /*
424  * Basic addr2line clone for stand-alone testing.
425  */
426 int
427 main(int argc, char *argv[])
428 {
429 	const char *filename = "a.out";
430 
431 	int ch;
432 	bool showdir = true;
433 	while ((ch = getopt(argc, argv, "e:s")) != EOF) {
434 		switch (ch) {
435 		case 'e':
436 			filename = optarg;
437 			break;
438 		case 's':
439 			showdir = false;
440 			break;
441 		default:
442 			usage();
443 		}
444 	}
445 
446 	argc -= optind;
447 	argv += optind;
448 
449 	/* Start by mapping the full file into memory. */
450 	int fd = open(filename, O_RDONLY);
451 	if (fd == -1)
452 		err(1, "open");
453 
454 	struct stat st;
455 	if (fstat(fd, &st) == -1)
456 		err(1, "fstat");
457 	if (st.st_size < (off_t)sizeof(Elf_Ehdr))
458 		errx(1, "file too small to be ELF");
459 	if ((uintmax_t)st.st_size > SIZE_MAX)
460 		errx(1, "file too big to fit memory");
461 	size_t filesize = st.st_size;
462 
463 	const char *p = mmap(NULL, filesize, PROT_READ, MAP_SHARED, fd, 0);
464 	if (p == MAP_FAILED)
465 		err(1, "mmap");
466 
467 	close(fd);
468 
469 	/* Read and validate ELF header. */
470 	Elf_Ehdr ehdr;
471 	memcpy(&ehdr, p, sizeof(ehdr));
472 	if (!IS_ELF(ehdr))
473 		errx(1, "file is not ELF");
474 	if (ehdr.e_ident[EI_CLASS] != ELFCLASS)
475 		errx(1, "unexpected word size");
476 	if (ehdr.e_ident[EI_DATA] != ELFDATA)
477 		errx(1, "unexpected data format");
478 	if (ehdr.e_shoff > filesize)
479 		errx(1, "bogus section table offset");
480 	if (ehdr.e_shentsize < sizeof(Elf_Shdr))
481 		errx(1, "unexpected section header size");
482 	if (ehdr.e_shnum > (filesize - ehdr.e_shoff) / ehdr.e_shentsize)
483 		errx(1, "bogus section header count");
484 	if (ehdr.e_shstrndx >= ehdr.e_shnum)
485 		errx(1, "bogus string table index");
486 
487 	/* Find section header string table location and size. */
488 	Elf_Shdr shdr;
489 	memcpy(&shdr, p + ehdr.e_shoff + ehdr.e_shstrndx * ehdr.e_shentsize,
490 	    sizeof(shdr));
491 	if (shdr.sh_type != SHT_STRTAB)
492 		errx(1, "unexpected string table type");
493 	if (shdr.sh_offset > filesize)
494 		errx(1, "bogus string table offset");
495 	if (shdr.sh_size > filesize - shdr.sh_offset)
496 		errx(1, "bogus string table size");
497 	const char *shstrtab = p + shdr.sh_offset;
498 	size_t shstrtabsize = shdr.sh_size;
499 
500 	/* Search through section table for .debug_line section. */
501 	size_t i;
502 	for (i = 0; i < ehdr.e_shnum; i++) {
503 		memcpy(&shdr, p + ehdr.e_shoff + i * ehdr.e_shentsize,
504 		    sizeof(shdr));
505 		if (0 == strncmp(".debug_line", shstrtab + shdr.sh_name,
506 		    shstrtabsize - shdr.sh_name))
507 			break;
508 	}
509 	if (i == ehdr.e_shnum)
510 		errx(1, "no DWARF line number table found");
511 	if (shdr.sh_offset > filesize)
512 		errx(1, "bogus line table offset");
513 	if (shdr.sh_size > filesize - shdr.sh_offset)
514 		errx(1, "bogus line table size");
515 	const char *linetab = p + shdr.sh_offset;
516 	size_t linetabsize = shdr.sh_size;
517 
518 	const char *addrstr;
519 	while ((addrstr = *argv++) != NULL) {
520 		unsigned long addr = strtoul(addrstr, NULL, 16);
521 
522 		const char *dir, *file;
523 		int line;
524 		if (!db_dwarf_line_at_pc(linetab, linetabsize, addr,
525 		    &dir, &file, &line)) {
526 			dir = NULL;
527 			file = "??";
528 			line = 0;
529 		}
530 		if (showdir && dir != NULL)
531 			printf("%s/", dir);
532 		printf("%s:%d\n", file, line);
533 	}
534 
535 	return (0);
536 }
537 #endif /* !_KERNEL */
538