1 1.9 christos /* $NetBSD: backtrace.c,v 1.9 2025/01/23 12:08:12 christos Exp $ */ 2 1.1 christos 3 1.1 christos /*- 4 1.1 christos * Copyright (c) 2012 The NetBSD Foundation, Inc. 5 1.1 christos * All rights reserved. 6 1.1 christos * 7 1.1 christos * This code is derived from software contributed to The NetBSD Foundation 8 1.1 christos * by Christos Zoulas. 9 1.1 christos * 10 1.1 christos * Redistribution and use in source and binary forms, with or without 11 1.1 christos * modification, are permitted provided that the following conditions 12 1.1 christos * are met: 13 1.1 christos * 1. Redistributions of source code must retain the above copyright 14 1.1 christos * notice, this list of conditions and the following disclaimer. 15 1.1 christos * 2. Redistributions in binary form must reproduce the above copyright 16 1.1 christos * notice, this list of conditions and the following disclaimer in the 17 1.1 christos * documentation and/or other materials provided with the distribution. 18 1.1 christos * 19 1.1 christos * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 1.1 christos * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 1.1 christos * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 1.1 christos * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 1.1 christos * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 1.1 christos * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 1.1 christos * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 1.1 christos * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 1.1 christos * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 1.1 christos * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 1.1 christos * POSSIBILITY OF SUCH DAMAGE. 30 1.1 christos */ 31 1.1 christos #include <sys/cdefs.h> 32 1.9 christos __RCSID("$NetBSD: backtrace.c,v 1.9 2025/01/23 12:08:12 christos Exp $"); 33 1.1 christos 34 1.1 christos #include <sys/param.h> 35 1.1 christos #include <assert.h> 36 1.1 christos #include <stdio.h> 37 1.1 christos #include <string.h> 38 1.1 christos #include <stdlib.h> 39 1.1 christos #include <stdarg.h> 40 1.1 christos #include <stdint.h> 41 1.1 christos #include <stddef.h> 42 1.1 christos #include <unistd.h> 43 1.1 christos #include <fcntl.h> 44 1.1 christos #include <dlfcn.h> 45 1.1 christos #include <elf.h> 46 1.1 christos 47 1.1 christos #include "execinfo.h" 48 1.8 skrll #include "symbol.h" 49 1.1 christos #include "symtab.h" 50 1.1 christos 51 1.1 christos #ifdef __linux__ 52 1.1 christos #define SELF "/proc/self/exe" 53 1.1 christos #else 54 1.3 christos #include <sys/sysctl.h> 55 1.1 christos #define SELF "/proc/curproc/file" 56 1.1 christos #endif 57 1.1 christos 58 1.9 christos static int self_fd = -1; 59 1.9 christos 60 1.3 christos static int 61 1.3 christos open_self(int flags) 62 1.3 christos { 63 1.3 christos const char *pathname = SELF; 64 1.3 christos #ifdef KERN_PROC_PATHNAME 65 1.3 christos static const int name[] = { 66 1.6 christos CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME, 67 1.3 christos }; 68 1.3 christos char path[MAXPATHLEN]; 69 1.3 christos size_t len; 70 1.3 christos 71 1.3 christos len = sizeof(path); 72 1.3 christos if (sysctl(name, __arraycount(name), path, &len, NULL, 0) != -1) 73 1.3 christos pathname = path; 74 1.3 christos #endif 75 1.3 christos return open(pathname, flags); 76 1.3 christos } 77 1.3 christos 78 1.9 christos int 79 1.9 christos backtrace_sandbox_init(void) 80 1.9 christos { 81 1.9 christos 82 1.9 christos if (self_fd == -1) 83 1.9 christos self_fd = open_self(O_RDONLY); 84 1.9 christos return self_fd >= 0 ? 0 : -1; 85 1.9 christos } 86 1.9 christos 87 1.9 christos void 88 1.9 christos backtrace_sandbox_fini(void) 89 1.9 christos { 90 1.9 christos assert(self_fd >= 0); 91 1.9 christos 92 1.9 christos close(self_fd); 93 1.9 christos self_fd = -1; 94 1.9 christos } 95 1.3 christos 96 1.1 christos static int __printflike(4, 5) 97 1.1 christos rasprintf(char **buf, size_t *bufsiz, size_t offs, const char *fmt, ...) 98 1.1 christos { 99 1.1 christos for (;;) { 100 1.1 christos size_t nbufsiz; 101 1.1 christos char *nbuf; 102 1.1 christos 103 1.1 christos if (*buf && offs < *bufsiz) { 104 1.1 christos va_list ap; 105 1.1 christos int len; 106 1.1 christos 107 1.1 christos va_start(ap, fmt); 108 1.1 christos len = vsnprintf(*buf + offs, *bufsiz - offs, fmt, ap); 109 1.1 christos va_end(ap); 110 1.1 christos 111 1.4 christos if (len < 0 || (size_t)len + 1 < *bufsiz - offs) 112 1.1 christos return len; 113 1.1 christos nbufsiz = MAX(*bufsiz + 512, (size_t)len + 1); 114 1.1 christos } else 115 1.1 christos nbufsiz = MAX(offs, *bufsiz) + 512; 116 1.7 skrll 117 1.1 christos nbuf = realloc(*buf, nbufsiz); 118 1.1 christos if (nbuf == NULL) 119 1.1 christos return -1; 120 1.1 christos *buf = nbuf; 121 1.1 christos *bufsiz = nbufsiz; 122 1.1 christos } 123 1.1 christos } 124 1.1 christos 125 1.1 christos /* 126 1.1 christos * format specifiers: 127 1.1 christos * %a = address 128 1.1 christos * %n = symbol_name 129 1.1 christos * %d = symbol_address - address 130 1.1 christos * %D = if symbol_address == address "" else +%d 131 1.1 christos * %f = filename 132 1.1 christos */ 133 1.1 christos static ssize_t 134 1.1 christos format_string(char **buf, size_t *bufsiz, size_t offs, const char *fmt, 135 1.1 christos Dl_info *dli, const void *addr) 136 1.1 christos { 137 1.8 skrll const uintptr_t symaddr = SYMBOL_CANONICALIZE(dli->dli_saddr); 138 1.8 skrll ptrdiff_t diff = (const char *)addr - (const char *)symaddr; 139 1.1 christos size_t o = offs; 140 1.1 christos int len; 141 1.1 christos 142 1.1 christos for (; *fmt; fmt++) { 143 1.1 christos if (*fmt != '%') 144 1.1 christos goto printone; 145 1.1 christos switch (*++fmt) { 146 1.1 christos case 'a': 147 1.1 christos len = rasprintf(buf, bufsiz, o, "%p", addr); 148 1.1 christos break; 149 1.1 christos case 'n': 150 1.1 christos len = rasprintf(buf, bufsiz, o, "%s", dli->dli_sname); 151 1.1 christos break; 152 1.1 christos case 'D': 153 1.1 christos if (diff) 154 1.1 christos len = rasprintf(buf, bufsiz, o, "+0x%tx", diff); 155 1.1 christos else 156 1.1 christos len = 0; 157 1.1 christos break; 158 1.1 christos case 'd': 159 1.1 christos len = rasprintf(buf, bufsiz, o, "0x%tx", diff); 160 1.1 christos break; 161 1.1 christos case 'f': 162 1.1 christos len = rasprintf(buf, bufsiz, o, "%s", dli->dli_fname); 163 1.1 christos break; 164 1.1 christos default: 165 1.1 christos printone: 166 1.1 christos len = rasprintf(buf, bufsiz, o, "%c", *fmt); 167 1.1 christos break; 168 1.1 christos } 169 1.1 christos if (len == -1) 170 1.1 christos return -1; 171 1.1 christos o += len; 172 1.1 christos } 173 1.1 christos return o - offs; 174 1.1 christos } 175 1.1 christos 176 1.1 christos static ssize_t 177 1.1 christos format_address(symtab_t *st, char **buf, size_t *bufsiz, size_t offs, 178 1.1 christos const char *fmt, const void *addr) 179 1.1 christos { 180 1.1 christos Dl_info dli; 181 1.1 christos 182 1.1 christos memset(&dli, 0, sizeof(dli)); 183 1.1 christos (void)dladdr(addr, &dli); 184 1.1 christos if (st) 185 1.1 christos symtab_find(st, addr, &dli); 186 1.1 christos 187 1.1 christos if (dli.dli_sname == NULL) 188 1.1 christos dli.dli_sname = "???"; 189 1.1 christos if (dli.dli_fname == NULL) 190 1.1 christos dli.dli_fname = "???"; 191 1.1 christos if (dli.dli_saddr == NULL) 192 1.1 christos dli.dli_saddr = (void *)(intptr_t)addr; 193 1.1 christos 194 1.1 christos return format_string(buf, bufsiz, offs, fmt, &dli, addr); 195 1.1 christos } 196 1.1 christos 197 1.1 christos char ** 198 1.1 christos backtrace_symbols_fmt(void *const *trace, size_t len, const char *fmt) 199 1.1 christos { 200 1.1 christos 201 1.1 christos static const size_t slen = sizeof(char *) + 64; /* estimate */ 202 1.1 christos char *ptr; 203 1.1 christos symtab_t *st; 204 1.9 christos int fd = self_fd; 205 1.1 christos 206 1.9 christos if (fd != -1 || (fd = open_self(O_RDONLY)) != -1) 207 1.1 christos st = symtab_create(fd, -1, STT_FUNC); 208 1.1 christos else 209 1.1 christos st = NULL; 210 1.1 christos 211 1.1 christos if ((ptr = calloc(len, slen)) == NULL) 212 1.2 christos goto out; 213 1.1 christos 214 1.1 christos size_t psize = len * slen; 215 1.1 christos size_t offs = len * sizeof(char *); 216 1.1 christos 217 1.1 christos /* We store only offsets in the first pass because of realloc */ 218 1.1 christos for (size_t i = 0; i < len; i++) { 219 1.1 christos ssize_t x; 220 1.1 christos ((char **)(void *)ptr)[i] = (void *)offs; 221 1.1 christos x = format_address(st, &ptr, &psize, offs, fmt, trace[i]); 222 1.1 christos if (x == -1) { 223 1.1 christos free(ptr); 224 1.2 christos ptr = NULL; 225 1.2 christos goto out; 226 1.1 christos } 227 1.1 christos offs += x; 228 1.1 christos ptr[offs++] = '\0'; 229 1.1 christos assert(offs < psize); 230 1.1 christos } 231 1.1 christos 232 1.1 christos /* Change offsets to pointers */ 233 1.1 christos for (size_t j = 0; j < len; j++) 234 1.1 christos ((char **)(void *)ptr)[j] += (intptr_t)ptr; 235 1.1 christos 236 1.2 christos out: 237 1.1 christos symtab_destroy(st); 238 1.9 christos if (fd != -1 && fd != self_fd) 239 1.1 christos (void)close(fd); 240 1.1 christos 241 1.1 christos return (void *)ptr; 242 1.1 christos } 243 1.1 christos 244 1.1 christos int 245 1.1 christos backtrace_symbols_fd_fmt(void *const *trace, size_t len, int fd, 246 1.1 christos const char *fmt) 247 1.1 christos { 248 1.1 christos char **s = backtrace_symbols_fmt(trace, len, fmt); 249 1.1 christos if (s == NULL) 250 1.1 christos return -1; 251 1.1 christos for (size_t i = 0; i < len; i++) 252 1.1 christos if (dprintf(fd, "%s\n", s[i]) < 0) 253 1.1 christos break; 254 1.1 christos free(s); 255 1.1 christos return 0; 256 1.1 christos } 257 1.1 christos 258 1.1 christos static const char fmt[] = "%a <%n%D> at %f"; 259 1.1 christos 260 1.1 christos char ** 261 1.1 christos backtrace_symbols(void *const *trace, size_t len) 262 1.1 christos { 263 1.1 christos return backtrace_symbols_fmt(trace, len, fmt); 264 1.1 christos } 265 1.1 christos 266 1.1 christos int 267 1.1 christos backtrace_symbols_fd(void *const *trace, size_t len, int fd) 268 1.1 christos { 269 1.1 christos return backtrace_symbols_fd_fmt(trace, len, fd, fmt); 270 1.1 christos } 271