1 /* $OpenBSD: ksyms.c,v 1.10 2024/04/01 22:49:04 jsg Exp $ */
2
3 /*
4 * Copyright (c) 2016 Martin Pieuchot <mpi@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #define _DYN_LOADER /* needed for AuxInfo */
20
21 #include <sys/types.h>
22
23 #include <err.h>
24 #include <fcntl.h>
25 #include <gelf.h>
26 #include <stdint.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31
32 #include "btrace.h"
33
34 struct sym {
35 char *sym_name;
36 unsigned long sym_value; /* from st_value */
37 unsigned long sym_size; /* from st_size */
38 };
39
40 struct syms {
41 struct sym *table;
42 size_t nsymb;
43 };
44
45 int sym_compare_search(const void *, const void *);
46 int sym_compare_sort(const void *, const void *);
47
48 struct syms *
kelf_open(const char * path)49 kelf_open(const char *path)
50 {
51 char *name;
52 Elf *elf;
53 Elf_Data *data = NULL;
54 Elf_Scn *scn = NULL, *symtab = NULL;
55 GElf_Sym sym;
56 GElf_Shdr shdr;
57 size_t i, shstrndx, strtabndx = SIZE_MAX, symtab_size;
58 unsigned long diff;
59 struct sym *tmp;
60 struct syms *syms = NULL;
61 int fd;
62
63 if (elf_version(EV_CURRENT) == EV_NONE)
64 errx(1, "elf_version: %s", elf_errmsg(-1));
65
66 fd = open(path, O_RDONLY);
67 if (fd == -1) {
68 warn("open: %s", path);
69 return NULL;
70 }
71
72 if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL) {
73 warnx("elf_begin: %s", elf_errmsg(-1));
74 goto bad;
75 }
76
77 if (elf_kind(elf) != ELF_K_ELF)
78 goto bad;
79
80 if (elf_getshdrstrndx(elf, &shstrndx) != 0) {
81 warnx("elf_getshdrstrndx: %s", elf_errmsg(-1));
82 goto bad;
83 }
84
85 while ((scn = elf_nextscn(elf, scn)) != NULL) {
86 if (gelf_getshdr(scn, &shdr) != &shdr) {
87 warnx("elf_getshdr: %s", elf_errmsg(-1));
88 goto bad;
89 }
90 if ((name = elf_strptr(elf, shstrndx, shdr.sh_name)) == NULL) {
91 warnx("elf_strptr: %s", elf_errmsg(-1));
92 goto bad;
93 }
94 if (strcmp(name, ELF_SYMTAB) == 0 &&
95 shdr.sh_type == SHT_SYMTAB && shdr.sh_entsize != 0) {
96 symtab = scn;
97 symtab_size = shdr.sh_size / shdr.sh_entsize;
98 }
99 if (strcmp(name, ELF_STRTAB) == 0 &&
100 shdr.sh_type == SHT_STRTAB) {
101 strtabndx = elf_ndxscn(scn);
102 }
103 }
104 if (symtab == NULL) {
105 warnx("%s: %s: section not found", path, ELF_SYMTAB);
106 goto bad;
107 }
108 if (strtabndx == SIZE_MAX) {
109 warnx("%s: %s: section not found", path, ELF_STRTAB);
110 goto bad;
111 }
112
113 data = elf_rawdata(symtab, data);
114 if (data == NULL)
115 goto bad;
116
117 if ((syms = calloc(1, sizeof(*syms))) == NULL)
118 err(1, NULL);
119 syms->table = calloc(symtab_size, sizeof *syms->table);
120 if (syms->table == NULL)
121 err(1, NULL);
122 for (i = 0; i < symtab_size; i++) {
123 if (gelf_getsym(data, i, &sym) == NULL)
124 continue;
125 if (GELF_ST_TYPE(sym.st_info) != STT_FUNC)
126 continue;
127 name = elf_strptr(elf, strtabndx, sym.st_name);
128 if (name == NULL)
129 continue;
130 syms->table[syms->nsymb].sym_name = strdup(name);
131 if (syms->table[syms->nsymb].sym_name == NULL)
132 err(1, NULL);
133 syms->table[syms->nsymb].sym_value = sym.st_value;
134 syms->table[syms->nsymb].sym_size = sym.st_size;
135 syms->nsymb++;
136 }
137 tmp = reallocarray(syms->table, syms->nsymb, sizeof *syms->table);
138 if (tmp == NULL)
139 err(1, NULL);
140 syms->table = tmp;
141
142 /* Sort symbols in ascending order by address. */
143 qsort(syms->table, syms->nsymb, sizeof *syms->table, sym_compare_sort);
144
145 /*
146 * Some functions, particularly those written in assembly, have an
147 * st_size of zero. We can approximate a size for these by assuming
148 * that they extend from their st_value to that of the next function.
149 */
150 for (i = 0; i < syms->nsymb; i++) {
151 if (syms->table[i].sym_size != 0)
152 continue;
153 /* Can't do anything for the last symbol. */
154 if (i + 1 == syms->nsymb)
155 continue;
156 diff = syms->table[i + 1].sym_value - syms->table[i].sym_value;
157 syms->table[i].sym_size = diff;
158 }
159
160 bad:
161 elf_end(elf);
162 close(fd);
163 return syms;
164 }
165
166 void
kelf_close(struct syms * syms)167 kelf_close(struct syms *syms)
168 {
169 size_t i;
170
171 if (syms == NULL)
172 return;
173
174 for (i = 0; i < syms->nsymb; i++)
175 free(syms->table[i].sym_name);
176 free(syms->table);
177 free(syms);
178 }
179
180 int
kelf_snprintsym(struct syms * syms,char * str,size_t size,unsigned long pc,unsigned long off)181 kelf_snprintsym(struct syms *syms, char *str, size_t size, unsigned long pc,
182 unsigned long off)
183 {
184 struct sym key = { .sym_value = pc + off };
185 struct sym *entry;
186 Elf_Addr offset;
187
188 if (syms == NULL)
189 goto fallback;
190
191 entry = bsearch(&key, syms->table, syms->nsymb, sizeof *syms->table,
192 sym_compare_search);
193 if (entry == NULL)
194 goto fallback;
195
196 offset = pc - (entry->sym_value + off);
197 if (offset != 0) {
198 return snprintf(str, size, "\n%s+0x%llx",
199 entry->sym_name, (unsigned long long)offset);
200 }
201
202 return snprintf(str, size, "\n%s", entry->sym_name);
203
204 fallback:
205 return snprintf(str, size, "\n0x%lx", pc);
206 }
207
208 int
sym_compare_sort(const void * ap,const void * bp)209 sym_compare_sort(const void *ap, const void *bp)
210 {
211 const struct sym *a = ap, *b = bp;
212
213 if (a->sym_value < b->sym_value)
214 return -1;
215 return a->sym_value > b->sym_value;
216 }
217
218 int
sym_compare_search(const void * keyp,const void * entryp)219 sym_compare_search(const void *keyp, const void *entryp)
220 {
221 const struct sym *entry = entryp, *key = keyp;
222
223 if (key->sym_value < entry->sym_value)
224 return -1;
225 return key->sym_value >= entry->sym_value + entry->sym_size;
226 }
227