backtrace.c revision 254689
1254661Semaste/*	$NetBSD: backtrace.c,v 1.2 2012/07/09 03:11:59 christos Exp $	*/
2254661Semaste
3254661Semaste/*-
4254661Semaste * Copyright (c) 2012 The NetBSD Foundation, Inc.
5254661Semaste * All rights reserved.
6254661Semaste *
7254661Semaste * This code is derived from software contributed to The NetBSD Foundation
8254661Semaste * by Christos Zoulas.
9254661Semaste *
10254661Semaste * Redistribution and use in source and binary forms, with or without
11254661Semaste * modification, are permitted provided that the following conditions
12254661Semaste * are met:
13254661Semaste * 1. Redistributions of source code must retain the above copyright
14254661Semaste *    notice, this list of conditions and the following disclaimer.
15254661Semaste * 2. Redistributions in binary form must reproduce the above copyright
16254661Semaste *    notice, this list of conditions and the following disclaimer in the
17254661Semaste *    documentation and/or other materials provided with the distribution.
18254661Semaste *
19254661Semaste * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20254661Semaste * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21254661Semaste * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22254661Semaste * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23254661Semaste * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24254661Semaste * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25254661Semaste * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26254661Semaste * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27254661Semaste * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28254661Semaste * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29254661Semaste * POSSIBILITY OF SUCH DAMAGE.
30254661Semaste */
31254661Semaste#include <sys/cdefs.h>
32254661Semaste__RCSID("$NetBSD: backtrace.c,v 1.2 2012/07/09 03:11:59 christos Exp $");
33254661Semaste
34254661Semaste#include <sys/param.h>
35254661Semaste#include <assert.h>
36254689Semaste#define _WITH_DPRINTF
37254661Semaste#include <stdio.h>
38254661Semaste#include <string.h>
39254661Semaste#include <stdlib.h>
40254661Semaste#include <stdarg.h>
41254661Semaste#include <stdint.h>
42254661Semaste#include <stddef.h>
43254661Semaste#include <unistd.h>
44254661Semaste#include <fcntl.h>
45254661Semaste#include <dlfcn.h>
46254661Semaste#include <elf.h>
47254661Semaste
48254661Semaste#include "execinfo.h"
49254661Semaste#include "symtab.h"
50254661Semaste
51254661Semaste#ifdef __linux__
52254661Semaste#define SELF	"/proc/self/exe"
53254661Semaste#else
54254661Semaste#define SELF	"/proc/curproc/file"
55254661Semaste#endif
56254661Semaste
57254661Semastestatic int __printflike(4, 5)
58254661Semasterasprintf(char **buf, size_t *bufsiz, size_t offs, const char *fmt, ...)
59254661Semaste{
60254661Semaste	for (;;) {
61254661Semaste		size_t nbufsiz;
62254661Semaste		char *nbuf;
63254661Semaste
64254661Semaste		if (*buf && offs < *bufsiz) {
65254661Semaste			va_list ap;
66254661Semaste			int len;
67254661Semaste
68254661Semaste			va_start(ap, fmt);
69254661Semaste			len = vsnprintf(*buf + offs, *bufsiz - offs, fmt, ap);
70254661Semaste			va_end(ap);
71254661Semaste
72254661Semaste			if (len < 0 || (size_t)len < *bufsiz - offs)
73254661Semaste				return len;
74254661Semaste			nbufsiz = MAX(*bufsiz + 512, (size_t)len + 1);
75254661Semaste		} else
76254661Semaste			nbufsiz = MAX(offs, *bufsiz) + 512;
77254661Semaste
78254661Semaste		nbuf = realloc(*buf, nbufsiz);
79254661Semaste		if (nbuf == NULL)
80254661Semaste			return -1;
81254661Semaste		*buf = nbuf;
82254661Semaste		*bufsiz = nbufsiz;
83254661Semaste	}
84254661Semaste}
85254661Semaste
86254661Semaste/*
87254661Semaste * format specifiers:
88254661Semaste *	%a	= address
89254661Semaste *	%n	= symbol_name
90254661Semaste *	%d	= symbol_address - address
91254661Semaste *	%D	= if symbol_address == address "" else +%d
92254661Semaste *	%f	= filename
93254661Semaste */
94254661Semastestatic ssize_t
95254661Semasteformat_string(char **buf, size_t *bufsiz, size_t offs, const char *fmt,
96254661Semaste    Dl_info *dli, const void *addr)
97254661Semaste{
98254661Semaste	ptrdiff_t diff = (const char *)addr - (const char *)dli->dli_saddr;
99254661Semaste	size_t o = offs;
100254661Semaste	int len;
101254661Semaste
102254661Semaste	for (; *fmt; fmt++) {
103254661Semaste		if (*fmt != '%')
104254661Semaste			goto printone;
105254661Semaste		switch (*++fmt) {
106254661Semaste		case 'a':
107254661Semaste			len = rasprintf(buf, bufsiz, o, "%p", addr);
108254661Semaste			break;
109254661Semaste		case 'n':
110254661Semaste			len = rasprintf(buf, bufsiz, o, "%s", dli->dli_sname);
111254661Semaste			break;
112254661Semaste		case 'D':
113254661Semaste			if (diff)
114254661Semaste				len = rasprintf(buf, bufsiz, o, "+0x%tx", diff);
115254661Semaste			else
116254661Semaste				len = 0;
117254661Semaste			break;
118254661Semaste		case 'd':
119254661Semaste			len = rasprintf(buf, bufsiz, o, "0x%tx", diff);
120254661Semaste			break;
121254661Semaste		case 'f':
122254661Semaste			len = rasprintf(buf, bufsiz, o, "%s", dli->dli_fname);
123254661Semaste			break;
124254661Semaste		default:
125254661Semaste		printone:
126254661Semaste			len = rasprintf(buf, bufsiz, o, "%c", *fmt);
127254661Semaste			break;
128254661Semaste		}
129254661Semaste		if (len == -1)
130254661Semaste			return -1;
131254661Semaste		o += len;
132254661Semaste	}
133254661Semaste	return o - offs;
134254661Semaste}
135254661Semaste
136254661Semastestatic ssize_t
137254661Semasteformat_address(symtab_t *st, char **buf, size_t *bufsiz, size_t offs,
138254661Semaste    const char *fmt, const void *addr)
139254661Semaste{
140254661Semaste	Dl_info dli;
141254661Semaste
142254661Semaste	memset(&dli, 0, sizeof(dli));
143254661Semaste	(void)dladdr(addr, &dli);
144254661Semaste	if (st)
145254661Semaste		symtab_find(st, addr, &dli);
146254661Semaste
147254661Semaste	if (dli.dli_sname == NULL)
148254661Semaste		dli.dli_sname = "???";
149254661Semaste	if (dli.dli_fname == NULL)
150254661Semaste		dli.dli_fname = "???";
151254661Semaste	if (dli.dli_saddr == NULL)
152254661Semaste		dli.dli_saddr = (void *)(intptr_t)addr;
153254661Semaste
154254661Semaste	return format_string(buf, bufsiz, offs, fmt, &dli, addr);
155254661Semaste}
156254661Semaste
157254661Semastechar **
158254661Semastebacktrace_symbols_fmt(void *const *trace, size_t len, const char *fmt)
159254661Semaste{
160254661Semaste
161254661Semaste	static const size_t slen = sizeof(char *) + 64;	/* estimate */
162254661Semaste	char *ptr;
163254661Semaste	symtab_t *st;
164254661Semaste	int fd;
165254661Semaste
166254661Semaste	if ((fd = open(SELF, O_RDONLY)) != -1)
167254661Semaste		st = symtab_create(fd, -1, STT_FUNC);
168254661Semaste	else
169254661Semaste		st = NULL;
170254661Semaste
171254661Semaste	if ((ptr = calloc(len, slen)) == NULL)
172254661Semaste		goto out;
173254661Semaste
174254661Semaste	size_t psize = len * slen;
175254661Semaste	size_t offs = len * sizeof(char *);
176254661Semaste
177254661Semaste	/* We store only offsets in the first pass because of realloc */
178254661Semaste	for (size_t i = 0; i < len; i++) {
179254661Semaste		ssize_t x;
180254661Semaste		((char **)(void *)ptr)[i] = (void *)offs;
181254661Semaste		x = format_address(st, &ptr, &psize, offs, fmt, trace[i]);
182254661Semaste		if (x == -1) {
183254661Semaste			free(ptr);
184254661Semaste			ptr = NULL;
185254661Semaste			goto out;
186254661Semaste		}
187254661Semaste		offs += x;
188254661Semaste		ptr[offs++] = '\0';
189254661Semaste		assert(offs < psize);
190254661Semaste	}
191254661Semaste
192254661Semaste	/* Change offsets to pointers */
193254661Semaste	for (size_t j = 0; j < len; j++)
194254661Semaste		((char **)(void *)ptr)[j] += (intptr_t)ptr;
195254661Semaste
196254661Semasteout:
197254661Semaste	symtab_destroy(st);
198254661Semaste	if (fd != -1)
199254661Semaste		(void)close(fd);
200254661Semaste
201254661Semaste	return (void *)ptr;
202254661Semaste}
203254661Semaste
204254661Semasteint
205254661Semastebacktrace_symbols_fd_fmt(void *const *trace, size_t len, int fd,
206254661Semaste    const char *fmt)
207254661Semaste{
208254661Semaste	char **s = backtrace_symbols_fmt(trace, len, fmt);
209254661Semaste	if (s == NULL)
210254661Semaste		return -1;
211254661Semaste	for (size_t i = 0; i < len; i++)
212254661Semaste		if (dprintf(fd, "%s\n", s[i]) < 0)
213254661Semaste			break;
214254661Semaste	free(s);
215254661Semaste	return 0;
216254661Semaste}
217254661Semaste
218254661Semastestatic const char fmt[] = "%a <%n%D> at %f";
219254661Semaste
220254661Semastechar **
221254661Semastebacktrace_symbols(void *const *trace, size_t len)
222254661Semaste{
223254661Semaste	return backtrace_symbols_fmt(trace, len, fmt);
224254661Semaste}
225254661Semaste
226254661Semasteint
227254661Semastebacktrace_symbols_fd(void *const *trace, size_t len, int fd)
228254661Semaste{
229254661Semaste	return backtrace_symbols_fd_fmt(trace, len, fd, fmt);
230254661Semaste}
231