1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2021 Oxide Computer Company
14 */
15
16 /*
17 * Check that a given core dump generated as part of our test framework has the
18 * sections that we'd expect. We have here the dumper binary. In that, we expect
19 * to find the following libraries and sections:
20 *
21 * a.out: symtab, ctf, .debug_* (dwarf)
22 * ld.so.1: symtab
23 * libc.so: symtab, ctf
24 * libproc.so: symtab, ctf
25 * libdumper.so: symtab, ctf, .debug_* (dwarf)
26 *
27 * Note, there will also be additional libraries and things here can change over
28 * time (e.g. deps of libproc, etc.), but we try to ignore them generally
29 * speaking if we can know enough to do so.
30 */
31
32 #include <err.h>
33 #include <stdlib.h>
34 #include <libproc.h>
35 #include <gelf.h>
36 #include <libelf.h>
37 #include <limits.h>
38 #include <string.h>
39 #include <libgen.h>
40
41 typedef enum {
42 SECMAP_CTF,
43 SECMAP_SYMTAB,
44 SECMAP_DEBUG,
45 SECMAP_MAX
46 } secmap_type_t;
47
48 typedef struct secmap_data {
49 core_content_t sd_content;
50 const char *sd_name;
51 } secmap_data_t;
52
53 secmap_data_t secmap_data[SECMAP_MAX] = {
54 { CC_CONTENT_CTF, ".SUNW_ctf" },
55 { CC_CONTENT_SYMTAB, ".symtab" },
56 { CC_CONTENT_DEBUG, ".debug_" }
57 };
58
59 typedef struct {
60 uint64_t sm_addr;
61 char sm_obj[PATH_MAX];
62 size_t sm_nfound[SECMAP_MAX];
63 Elf *sm_elf;
64 GElf_Ehdr sm_ehdr;
65 boolean_t sm_ctf;
66 boolean_t sm_debug;
67 boolean_t sm_symtab;
68 } secmap_t;
69
70 static secmap_t *secmaps;
71 static size_t secmap_count;
72 static core_content_t secmap_content;
73
74 static int secmap_exit = EXIT_SUCCESS;
75
76 static void
secmap_fail(const char * fmt,...)77 secmap_fail(const char *fmt, ...)
78 {
79 va_list ap;
80
81 va_start(ap, fmt);
82 vwarnx(fmt, ap);
83 va_end(ap);
84 secmap_exit = EXIT_FAILURE;
85 }
86
87
88 static void
check_content(core_content_t content,struct ps_prochandle * Pr)89 check_content(core_content_t content, struct ps_prochandle *Pr)
90 {
91 secmap_content = Pcontent(Pr);
92
93 if (secmap_content == CC_CONTENT_INVALID) {
94 secmap_fail("TEST FAILED: failed to get core content");
95 return;
96 }
97
98 if (secmap_content != content) {
99 secmap_fail("TEST FAILED: core file contains different "
100 "content than expected, found 0x%x, expected 0x%x",
101 secmap_content, content);
102 }
103 }
104
105 static secmap_t *
secmap_find(uint64_t addr)106 secmap_find(uint64_t addr)
107 {
108 for (size_t i = 0; i < secmap_count; i++) {
109 if (secmaps[i].sm_addr == addr) {
110 return (&secmaps[i]);
111 }
112 }
113
114 return (NULL);
115 }
116
117 static void
secmap_matches_content(secmap_type_t type)118 secmap_matches_content(secmap_type_t type)
119 {
120 boolean_t exist = (secmap_data[type].sd_content & secmap_content) != 0;
121 boolean_t found = B_FALSE;
122
123 /*
124 * Dumping CTF data implies that some symbol tables will exist for CTF.
125 */
126 if (type == SECMAP_SYMTAB && (secmap_content & CC_CONTENT_CTF) != 0) {
127 exist = B_TRUE;
128 }
129
130 for (size_t i = 0; i < secmap_count; i++) {
131 if (secmaps[i].sm_nfound[type] != 0) {
132 found = B_TRUE;
133 }
134 }
135
136 if (exist != found) {
137 secmap_fail("content type mismatch for %s: expected %s, but "
138 "found %s", secmap_data[type].sd_name,
139 exist ? "some" : "none",
140 found ? "some" : "none");
141 }
142 }
143
144 static secmap_t *
secmap_alloc(struct ps_prochandle * Pr,uint64_t addr)145 secmap_alloc(struct ps_prochandle *Pr, uint64_t addr)
146 {
147 int fd;
148 secmap_t *sm;
149 char *base;
150
151 sm = recallocarray(secmaps, secmap_count, secmap_count + 1,
152 sizeof (secmap_t));
153 if (sm == NULL) {
154 err(EXIT_FAILURE, "TEST FAILED: failed to allocate memory for "
155 "secmap %zu", secmap_count + 1);
156 }
157
158 secmaps = sm;
159 sm = &secmaps[secmap_count];
160 sm->sm_addr = addr;
161 secmap_count++;
162
163 /*
164 * We also have some tests that we don't expect to have anything here
165 * because we only include the relevant sections. Experimentally, we
166 * know that libproc needs both anon and data mappings for this to work.
167 * So if we don't have both, then we'll not warn on that.
168 */
169 if (Pobjname(Pr, addr, sm->sm_obj, sizeof (sm->sm_obj)) == NULL) {
170 core_content_t need = CC_CONTENT_ANON | CC_CONTENT_DATA;
171
172 if ((secmap_content & need) == need) {
173 secmap_fail("TEST FAILURE: object at address 0x%lx "
174 "has no name", addr);
175 }
176
177 return (sm);
178 }
179
180 /*
181 * Since we have a name, we should be able to open this elf object and
182 * identify it as well.
183 */
184 fd = open(sm->sm_obj, O_RDONLY);
185 if (fd < 0) {
186 err(EXIT_FAILURE, "failed to open object %s", sm->sm_obj);
187 }
188
189 sm->sm_elf = elf_begin(fd, ELF_C_READ, NULL);
190 if (sm->sm_elf == NULL) {
191 err(EXIT_FAILURE, "failed to find open elf object %s: %s",
192 sm->sm_obj, elf_errmsg(elf_errno()));
193 }
194
195 if (gelf_getehdr(sm->sm_elf, &sm->sm_ehdr) == NULL) {
196 err(EXIT_FAILURE, "failed to get ehdr for %s: %s",
197 sm->sm_obj, elf_errmsg(elf_errno()));
198 }
199
200 base = basename(sm->sm_obj);
201 if (strcmp(base, "dumper.32") == 0 || strcmp(base, "dumper.64") == 0) {
202 sm->sm_debug = sm->sm_symtab = sm->sm_ctf = B_TRUE;
203 } else if (strcmp(base, "libc.so.1") == 0) {
204 sm->sm_symtab = sm->sm_ctf = B_TRUE;
205 } else if (strcmp(base, "ld.so.1") == 0) {
206 sm->sm_symtab = B_TRUE;
207 } else if (strcmp(base, "libproc.so.1") == 0) {
208 sm->sm_symtab = sm->sm_ctf = B_TRUE;
209 } else if (strcmp(base, "libdumper.so.1") == 0) {
210 sm->sm_debug = sm->sm_symtab = sm->sm_ctf = B_TRUE;
211 } else {
212 sm->sm_symtab = B_TRUE;
213 }
214
215 return (sm);
216 }
217
218 static void
secmap_data_cmp(secmap_t * sm,const char * sname,Elf_Scn * scn,GElf_Shdr * shdr)219 secmap_data_cmp(secmap_t *sm, const char *sname, Elf_Scn *scn, GElf_Shdr *shdr)
220 {
221 for (Elf_Scn *comp_scn = elf_nextscn(sm->sm_elf, NULL);
222 comp_scn != NULL; comp_scn = elf_nextscn(sm->sm_elf, comp_scn)) {
223 GElf_Shdr comp_shdr;
224 const char *comp_name;
225 Elf_Data *src_data, *comp_data;
226
227 if (gelf_getshdr(comp_scn, &comp_shdr) == NULL) {
228 secmap_fail("failed to load section header from %s "
229 "during data comparison", sm->sm_obj);
230 return;
231 }
232
233 comp_name = elf_strptr(sm->sm_elf, sm->sm_ehdr.e_shstrndx,
234 comp_shdr.sh_name);
235 if (comp_name == NULL) {
236 secmap_fail("failed to load section name from %s "
237 "with index %lu", sm->sm_obj, comp_shdr.sh_name);
238 return;
239 }
240
241 if (strcmp(comp_name, sname) != 0)
242 continue;
243
244 if (comp_shdr.sh_type != shdr->sh_type ||
245 comp_shdr.sh_addralign != shdr->sh_addralign ||
246 comp_shdr.sh_size != shdr->sh_size ||
247 comp_shdr.sh_entsize != shdr->sh_entsize) {
248 continue;
249 }
250
251 if ((src_data = elf_getdata(scn, NULL)) == NULL) {
252 secmap_fail("failed to load section data from "
253 "source to compare to %s %s", sm->sm_obj, sname);
254 return;
255 }
256
257 if ((comp_data = elf_getdata(comp_scn, NULL)) == NULL) {
258 secmap_fail("failed to load section data from "
259 "source to compare to %s %s", sm->sm_obj, sname);
260 return;
261 }
262
263 if (comp_data->d_size != src_data->d_size) {
264 secmap_fail("data size mismatch for %s: %s, core: "
265 "%zu, file: %zu", sm->sm_obj, sname,
266 src_data->d_size, comp_data->d_size);
267 return;
268 }
269
270 if (memcmp(comp_data->d_buf, src_data->d_buf,
271 comp_data->d_size) != 0) {
272 secmap_fail("data mismatch between core and source "
273 "in %s: %s", sm->sm_obj, sname);
274 return;
275 }
276
277 return;
278 }
279
280 secmap_fail("failed to find matching section for %s in %s",
281 sname, sm->sm_obj);
282 }
283
284 static void
secmap_file_check(secmap_t * sm)285 secmap_file_check(secmap_t *sm)
286 {
287 if (sm->sm_ctf && (secmap_content & CC_CONTENT_CTF) != 0 &&
288 sm->sm_nfound[SECMAP_CTF] == 0) {
289 secmap_fail("expected object %s to have CTF, but it doesn't",
290 sm->sm_obj);
291 }
292
293 if (sm->sm_symtab && (secmap_content & CC_CONTENT_SYMTAB) != 0 &&
294 sm->sm_nfound[SECMAP_SYMTAB] == 0) {
295 secmap_fail("expected object %s to have a symbol table, "
296 "but it doesn't", sm->sm_obj);
297 }
298
299 if (sm->sm_debug && (secmap_content & CC_CONTENT_DEBUG) != 0 &&
300 sm->sm_nfound[SECMAP_DEBUG] == 0) {
301 secmap_fail("expected object %s to have debug sections, "
302 "but it doesn't", sm->sm_obj);
303 }
304 }
305
306 int
main(int argc,char * argv[])307 main(int argc, char *argv[])
308 {
309 core_content_t content;
310 struct ps_prochandle *Pr;
311 int perr, fd;
312 Elf *elf;
313 Elf_Scn *scn;
314 GElf_Ehdr ehdr;
315
316 if (argc != 3) {
317 warnx("missing required file and core content");
318 (void) fprintf(stderr, "Usage: secmapper file content\n");
319 exit(EXIT_FAILURE);
320 }
321
322 if (elf_version(EV_CURRENT) == EV_NONE) {
323 errx(EXIT_FAILURE, "failed to init libelf");
324 }
325
326 Pr = Pgrab_core(argv[1], NULL, PGRAB_RDONLY, &perr);
327 if (Pr == NULL) {
328 errx(EXIT_FAILURE, "failed to open %s: %s", argv[1],
329 Pgrab_error(perr));
330 }
331
332 if ((fd = open(argv[1], O_RDONLY)) < 0) {
333 err(EXIT_FAILURE, "failed to open %s\n", argv[1]);
334 }
335
336 if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL) {
337 errx(EXIT_FAILURE, "failed to open elf file %s: %s", argv[1],
338 elf_errmsg(elf_errno()));
339 }
340
341 if (proc_str2content(argv[2], &content) != 0) {
342 err(EXIT_FAILURE, "failed to parse content %s", argv[2]);
343 }
344
345 if (gelf_getehdr(elf, &ehdr) == NULL) {
346 errx(EXIT_FAILURE, "failed to get edr: %s",
347 elf_errmsg(elf_errno()));
348 }
349
350 /*
351 * Before we go futher, make sure that we have the content in this file
352 * that we expect.
353 */
354 check_content(content, Pr);
355
356 for (scn = elf_nextscn(elf, NULL); scn != NULL;
357 scn = elf_nextscn(elf, scn)) {
358 const char *sname;
359 GElf_Shdr shdr;
360 size_t index;
361 secmap_t *secmap;
362
363 index = elf_ndxscn(scn);
364 if (gelf_getshdr(scn, &shdr) == NULL) {
365 errx(EXIT_FAILURE, "failed to get section header for "
366 "shdr %zu: %s", index, elf_errmsg(elf_errno()));
367 }
368
369 /*
370 * Skip the strtab.
371 */
372 if (shdr.sh_type == SHT_STRTAB) {
373 continue;
374 }
375
376 sname = elf_strptr(elf, ehdr.e_shstrndx, shdr.sh_name);
377 if (sname == NULL) {
378 secmap_fail("TEST FAILURE: string name missing for "
379 "shdr %zu", index);
380 continue;
381 }
382
383 /*
384 * Find or cons up a new secmap for this object.
385 */
386 secmap = secmap_find(shdr.sh_addr);
387 if (secmap == NULL) {
388 secmap = secmap_alloc(Pr, shdr.sh_addr);
389 }
390
391 if (strcmp(sname, ".symtab") == 0) {
392 secmap->sm_nfound[SECMAP_SYMTAB]++;
393 } else if (strcmp(sname, ".SUNW_ctf") == 0) {
394 secmap->sm_nfound[SECMAP_CTF]++;
395 } else if (strncmp(sname, ".debug_", strlen(".debug_")) == 0) {
396 secmap->sm_nfound[SECMAP_DEBUG]++;
397 } else {
398 continue;
399 }
400
401 /*
402 * For one of our three primary sections, make sure that the
403 * data that is in the core file that we find in it actually
404 * matches the underlying object. That is, if the secmap
405 * actually has something here.
406 */
407 if (secmap->sm_elf != NULL) {
408 secmap_data_cmp(secmap, sname, scn, &shdr);
409 }
410 }
411
412 /*
413 * Now that we have iterated over all of these sections, check and make
414 * sure certain things are true of them. In particular, go through some
415 * of the various types of data and make sure it exists at all or
416 * doesn't based on our core content.
417 */
418 secmap_matches_content(SECMAP_CTF);
419 secmap_matches_content(SECMAP_SYMTAB);
420 secmap_matches_content(SECMAP_DEBUG);
421
422 /*
423 * Finally, if we have enough information to know that we've found
424 * a file that we know it should at least have a given type of data,
425 * check for it. Here, it is OK for data to be present we don't expect
426 * (assuming the core content allows it). This makes this test less
427 * prone to broader changes in the system.
428 */
429 for (size_t i = 0; i < secmap_count; i++) {
430 secmap_file_check(&secmaps[i]);
431 }
432
433 return (secmap_exit);
434 }
435