xref: /netbsd-src/lib/libexecinfo/backtrace.c (revision 1c512be1f7a3bda2d36a9790dcaa63d0161b1ed7)
1*1c512be1Schristos /*	$NetBSD: backtrace.c,v 1.9 2025/01/23 12:08:12 christos Exp $	*/
283d64dcfSchristos 
383d64dcfSchristos /*-
483d64dcfSchristos  * Copyright (c) 2012 The NetBSD Foundation, Inc.
583d64dcfSchristos  * All rights reserved.
683d64dcfSchristos  *
783d64dcfSchristos  * This code is derived from software contributed to The NetBSD Foundation
883d64dcfSchristos  * by Christos Zoulas.
983d64dcfSchristos  *
1083d64dcfSchristos  * Redistribution and use in source and binary forms, with or without
1183d64dcfSchristos  * modification, are permitted provided that the following conditions
1283d64dcfSchristos  * are met:
1383d64dcfSchristos  * 1. Redistributions of source code must retain the above copyright
1483d64dcfSchristos  *    notice, this list of conditions and the following disclaimer.
1583d64dcfSchristos  * 2. Redistributions in binary form must reproduce the above copyright
1683d64dcfSchristos  *    notice, this list of conditions and the following disclaimer in the
1783d64dcfSchristos  *    documentation and/or other materials provided with the distribution.
1883d64dcfSchristos  *
1983d64dcfSchristos  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
2083d64dcfSchristos  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
2183d64dcfSchristos  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
2283d64dcfSchristos  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
2383d64dcfSchristos  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
2483d64dcfSchristos  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
2583d64dcfSchristos  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
2683d64dcfSchristos  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
2783d64dcfSchristos  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2883d64dcfSchristos  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
2983d64dcfSchristos  * POSSIBILITY OF SUCH DAMAGE.
3083d64dcfSchristos  */
3183d64dcfSchristos #include <sys/cdefs.h>
32*1c512be1Schristos __RCSID("$NetBSD: backtrace.c,v 1.9 2025/01/23 12:08:12 christos Exp $");
3383d64dcfSchristos 
3483d64dcfSchristos #include <sys/param.h>
3583d64dcfSchristos #include <assert.h>
3683d64dcfSchristos #include <stdio.h>
3783d64dcfSchristos #include <string.h>
3883d64dcfSchristos #include <stdlib.h>
3983d64dcfSchristos #include <stdarg.h>
4083d64dcfSchristos #include <stdint.h>
4183d64dcfSchristos #include <stddef.h>
4283d64dcfSchristos #include <unistd.h>
4383d64dcfSchristos #include <fcntl.h>
4483d64dcfSchristos #include <dlfcn.h>
4583d64dcfSchristos #include <elf.h>
4683d64dcfSchristos 
4783d64dcfSchristos #include "execinfo.h"
4844889fb9Sskrll #include "symbol.h"
4983d64dcfSchristos #include "symtab.h"
5083d64dcfSchristos 
5183d64dcfSchristos #ifdef __linux__
5283d64dcfSchristos #define SELF	"/proc/self/exe"
5383d64dcfSchristos #else
54e6ada316Schristos #include <sys/sysctl.h>
5583d64dcfSchristos #define SELF	"/proc/curproc/file"
5683d64dcfSchristos #endif
5783d64dcfSchristos 
58*1c512be1Schristos static int self_fd = -1;
59*1c512be1Schristos 
60e6ada316Schristos static int
61e6ada316Schristos open_self(int flags)
62e6ada316Schristos {
63e6ada316Schristos 	const char *pathname = SELF;
64e6ada316Schristos #ifdef KERN_PROC_PATHNAME
65e6ada316Schristos 	static const int name[] = {
66e09cabeeSchristos 		CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME,
67e6ada316Schristos 	};
68e6ada316Schristos 	char path[MAXPATHLEN];
69e6ada316Schristos 	size_t len;
70e6ada316Schristos 
71e6ada316Schristos 	len = sizeof(path);
72e6ada316Schristos 	if (sysctl(name, __arraycount(name), path, &len, NULL, 0) != -1)
73e6ada316Schristos 		pathname = path;
74e6ada316Schristos #endif
75e6ada316Schristos 	return open(pathname, flags);
76e6ada316Schristos }
77e6ada316Schristos 
78*1c512be1Schristos int
79*1c512be1Schristos backtrace_sandbox_init(void)
80*1c512be1Schristos {
81*1c512be1Schristos 
82*1c512be1Schristos 	if (self_fd == -1)
83*1c512be1Schristos 		self_fd = open_self(O_RDONLY);
84*1c512be1Schristos 	return self_fd >= 0 ? 0 : -1;
85*1c512be1Schristos }
86*1c512be1Schristos 
87*1c512be1Schristos void
88*1c512be1Schristos backtrace_sandbox_fini(void)
89*1c512be1Schristos {
90*1c512be1Schristos 	assert(self_fd >= 0);
91*1c512be1Schristos 
92*1c512be1Schristos 	close(self_fd);
93*1c512be1Schristos 	self_fd = -1;
94*1c512be1Schristos }
95e6ada316Schristos 
9683d64dcfSchristos static int __printflike(4, 5)
9783d64dcfSchristos rasprintf(char **buf, size_t *bufsiz, size_t offs, const char *fmt, ...)
9883d64dcfSchristos {
9983d64dcfSchristos 	for (;;) {
10083d64dcfSchristos 		size_t nbufsiz;
10183d64dcfSchristos 		char *nbuf;
10283d64dcfSchristos 
10383d64dcfSchristos 		if (*buf && offs < *bufsiz) {
10483d64dcfSchristos 			va_list ap;
10583d64dcfSchristos 			int len;
10683d64dcfSchristos 
10783d64dcfSchristos 			va_start(ap, fmt);
10883d64dcfSchristos 			len = vsnprintf(*buf + offs, *bufsiz - offs, fmt, ap);
10983d64dcfSchristos 			va_end(ap);
11083d64dcfSchristos 
1119e14bd8cSchristos 			if (len < 0 || (size_t)len + 1 < *bufsiz - offs)
11283d64dcfSchristos 				return len;
11383d64dcfSchristos 			nbufsiz = MAX(*bufsiz + 512, (size_t)len + 1);
11483d64dcfSchristos 		} else
11583d64dcfSchristos 			nbufsiz = MAX(offs, *bufsiz) + 512;
11683d64dcfSchristos 
11783d64dcfSchristos 		nbuf = realloc(*buf, nbufsiz);
11883d64dcfSchristos 		if (nbuf == NULL)
11983d64dcfSchristos 			return -1;
12083d64dcfSchristos 		*buf = nbuf;
12183d64dcfSchristos 		*bufsiz = nbufsiz;
12283d64dcfSchristos 	}
12383d64dcfSchristos }
12483d64dcfSchristos 
12583d64dcfSchristos /*
12683d64dcfSchristos  * format specifiers:
12783d64dcfSchristos  *	%a	= address
12883d64dcfSchristos  *	%n	= symbol_name
12983d64dcfSchristos  *	%d	= symbol_address - address
13083d64dcfSchristos  *	%D	= if symbol_address == address "" else +%d
13183d64dcfSchristos  *	%f	= filename
13283d64dcfSchristos  */
13383d64dcfSchristos static ssize_t
13483d64dcfSchristos format_string(char **buf, size_t *bufsiz, size_t offs, const char *fmt,
13583d64dcfSchristos     Dl_info *dli, const void *addr)
13683d64dcfSchristos {
13744889fb9Sskrll 	const uintptr_t symaddr = SYMBOL_CANONICALIZE(dli->dli_saddr);
13844889fb9Sskrll 	ptrdiff_t diff = (const char *)addr - (const char *)symaddr;
13983d64dcfSchristos 	size_t o = offs;
14083d64dcfSchristos 	int len;
14183d64dcfSchristos 
14283d64dcfSchristos 	for (; *fmt; fmt++) {
14383d64dcfSchristos 		if (*fmt != '%')
14483d64dcfSchristos 			goto printone;
14583d64dcfSchristos 		switch (*++fmt) {
14683d64dcfSchristos 		case 'a':
14783d64dcfSchristos 			len = rasprintf(buf, bufsiz, o, "%p", addr);
14883d64dcfSchristos 			break;
14983d64dcfSchristos 		case 'n':
15083d64dcfSchristos 			len = rasprintf(buf, bufsiz, o, "%s", dli->dli_sname);
15183d64dcfSchristos 			break;
15283d64dcfSchristos 		case 'D':
15383d64dcfSchristos 			if (diff)
15483d64dcfSchristos 				len = rasprintf(buf, bufsiz, o, "+0x%tx", diff);
15583d64dcfSchristos 			else
15683d64dcfSchristos 				len = 0;
15783d64dcfSchristos 			break;
15883d64dcfSchristos 		case 'd':
15983d64dcfSchristos 			len = rasprintf(buf, bufsiz, o, "0x%tx", diff);
16083d64dcfSchristos 			break;
16183d64dcfSchristos 		case 'f':
16283d64dcfSchristos 			len = rasprintf(buf, bufsiz, o, "%s", dli->dli_fname);
16383d64dcfSchristos 			break;
16483d64dcfSchristos 		default:
16583d64dcfSchristos 		printone:
16683d64dcfSchristos 			len = rasprintf(buf, bufsiz, o, "%c", *fmt);
16783d64dcfSchristos 			break;
16883d64dcfSchristos 		}
16983d64dcfSchristos 		if (len == -1)
17083d64dcfSchristos 			return -1;
17183d64dcfSchristos 		o += len;
17283d64dcfSchristos 	}
17383d64dcfSchristos 	return o - offs;
17483d64dcfSchristos }
17583d64dcfSchristos 
17683d64dcfSchristos static ssize_t
17783d64dcfSchristos format_address(symtab_t *st, char **buf, size_t *bufsiz, size_t offs,
17883d64dcfSchristos     const char *fmt, const void *addr)
17983d64dcfSchristos {
18083d64dcfSchristos 	Dl_info dli;
18183d64dcfSchristos 
18283d64dcfSchristos 	memset(&dli, 0, sizeof(dli));
18383d64dcfSchristos 	(void)dladdr(addr, &dli);
18483d64dcfSchristos 	if (st)
18583d64dcfSchristos 		symtab_find(st, addr, &dli);
18683d64dcfSchristos 
18783d64dcfSchristos 	if (dli.dli_sname == NULL)
18883d64dcfSchristos 		dli.dli_sname = "???";
18983d64dcfSchristos 	if (dli.dli_fname == NULL)
19083d64dcfSchristos 		dli.dli_fname = "???";
19183d64dcfSchristos 	if (dli.dli_saddr == NULL)
19283d64dcfSchristos 		dli.dli_saddr = (void *)(intptr_t)addr;
19383d64dcfSchristos 
19483d64dcfSchristos 	return format_string(buf, bufsiz, offs, fmt, &dli, addr);
19583d64dcfSchristos }
19683d64dcfSchristos 
19783d64dcfSchristos char **
19883d64dcfSchristos backtrace_symbols_fmt(void *const *trace, size_t len, const char *fmt)
19983d64dcfSchristos {
20083d64dcfSchristos 
20183d64dcfSchristos 	static const size_t slen = sizeof(char *) + 64;	/* estimate */
20283d64dcfSchristos 	char *ptr;
20383d64dcfSchristos 	symtab_t *st;
204*1c512be1Schristos 	int fd = self_fd;
20583d64dcfSchristos 
206*1c512be1Schristos 	if (fd != -1 || (fd = open_self(O_RDONLY)) != -1)
20783d64dcfSchristos 		st = symtab_create(fd, -1, STT_FUNC);
20883d64dcfSchristos 	else
20983d64dcfSchristos 		st = NULL;
21083d64dcfSchristos 
21183d64dcfSchristos 	if ((ptr = calloc(len, slen)) == NULL)
212ac9e9457Schristos 		goto out;
21383d64dcfSchristos 
21483d64dcfSchristos 	size_t psize = len * slen;
21583d64dcfSchristos 	size_t offs = len * sizeof(char *);
21683d64dcfSchristos 
21783d64dcfSchristos 	/* We store only offsets in the first pass because of realloc */
21883d64dcfSchristos 	for (size_t i = 0; i < len; i++) {
21983d64dcfSchristos 		ssize_t x;
22083d64dcfSchristos 		((char **)(void *)ptr)[i] = (void *)offs;
22183d64dcfSchristos 		x = format_address(st, &ptr, &psize, offs, fmt, trace[i]);
22283d64dcfSchristos 		if (x == -1) {
22383d64dcfSchristos 			free(ptr);
224ac9e9457Schristos 			ptr = NULL;
225ac9e9457Schristos 			goto out;
22683d64dcfSchristos 		}
22783d64dcfSchristos 		offs += x;
22883d64dcfSchristos 		ptr[offs++] = '\0';
22983d64dcfSchristos 		assert(offs < psize);
23083d64dcfSchristos 	}
23183d64dcfSchristos 
23283d64dcfSchristos 	/* Change offsets to pointers */
23383d64dcfSchristos 	for (size_t j = 0; j < len; j++)
23483d64dcfSchristos 		((char **)(void *)ptr)[j] += (intptr_t)ptr;
23583d64dcfSchristos 
236ac9e9457Schristos out:
23783d64dcfSchristos 	symtab_destroy(st);
238*1c512be1Schristos 	if (fd != -1 && fd != self_fd)
23983d64dcfSchristos 		(void)close(fd);
24083d64dcfSchristos 
24183d64dcfSchristos 	return (void *)ptr;
24283d64dcfSchristos }
24383d64dcfSchristos 
24483d64dcfSchristos int
24583d64dcfSchristos backtrace_symbols_fd_fmt(void *const *trace, size_t len, int fd,
24683d64dcfSchristos     const char *fmt)
24783d64dcfSchristos {
24883d64dcfSchristos 	char **s = backtrace_symbols_fmt(trace, len, fmt);
24983d64dcfSchristos 	if (s == NULL)
25083d64dcfSchristos 		return -1;
25183d64dcfSchristos 	for (size_t i = 0; i < len; i++)
25283d64dcfSchristos 		if (dprintf(fd, "%s\n", s[i]) < 0)
25383d64dcfSchristos 			break;
25483d64dcfSchristos 	free(s);
25583d64dcfSchristos 	return 0;
25683d64dcfSchristos }
25783d64dcfSchristos 
25883d64dcfSchristos static const char fmt[] = "%a <%n%D> at %f";
25983d64dcfSchristos 
26083d64dcfSchristos char **
26183d64dcfSchristos backtrace_symbols(void *const *trace, size_t len)
26283d64dcfSchristos {
26383d64dcfSchristos 	return backtrace_symbols_fmt(trace, len, fmt);
26483d64dcfSchristos }
26583d64dcfSchristos 
26683d64dcfSchristos int
26783d64dcfSchristos backtrace_symbols_fd(void *const *trace, size_t len, int fd)
26883d64dcfSchristos {
26983d64dcfSchristos 	return backtrace_symbols_fd_fmt(trace, len, fd, fmt);
27083d64dcfSchristos }
271