• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nickg / nvc / 14534534289

18 Apr 2025 11:32AM UTC coverage: 92.317% (+0.01%) from 92.305%
14534534289

push

github

nickg
Support access and file types in VHPI. Fixes #1192

30 of 30 new or added lines in 1 file covered. (100.0%)

55 existing lines in 4 files now uncovered.

69064 of 74812 relevant lines covered (92.32%)

421751.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

63.59
/src/debug.c
1
//
2
//  Copyright (C) 2021-2025  Nick Gasson
3
//
4
//  This program is free software: you can redistribute it and/or modify
5
//  it under the terms of the GNU General Public License as published by
6
//  the Free Software Foundation, either version 3 of the License, or
7
//  (at your option) any later version.
8
//
9
//  This program is distributed in the hope that it will be useful,
10
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
//  GNU General Public License for more details.
13
//
14
//  You should have received a copy of the GNU General Public License
15
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
//
17

18
#if defined __MINGW32__ || defined __CYGWIN__
19
#define WINVER 0x0A00
20
#define _WIN32_WINNT 0x0A00
21
#include <windows.h>
22
#include <dbghelp.h>
23
#endif
24

25
#include "debug.h"
26
#include "array.h"
27
#include "hash.h"
28
#include "ident.h"
29
#include "lib.h"
30
#include "thread.h"
31

32
#include <stdlib.h>
33
#include <string.h>
34
#include <sys/types.h>
35
#include <unwind.h>
36

37
#if !defined __MINGW32__ && !defined __CYGWIN__
38
#include <unistd.h>
39
#include <fcntl.h>
40
#include <assert.h>
41
#include <dlfcn.h>
42
#include <errno.h>
43
#endif  // !__MINGW32__ && !__CYGWIN__
44

45
#if defined HAVE_LIBDW
46
#include <elfutils/libdw.h>
47
#include <elfutils/libdwfl.h>
48
#include <dwarf.h>
49
#elif defined HAVE_LIBDWARF
50
#if defined HAVE_LIBDWARF_LIBDWARF_H
51
#include <libdwarf/libdwarf.h>
52
#include <libdwarf/dwarf.h>
53
#else  // HAVE_LIBDWARF_LIBDWARF_H
54
#include <libdwarf.h>
55
#include <dwarf.h>
56
#endif  // HAVE_LIBDWARF_LIBDWARF_H
57
#endif
58

59
#ifdef __APPLE__
60
#include <mach-o/loader.h>
61
#endif
62

63
#ifdef __ARM_EABI_UNWINDER__
64
#define _URC_NORMAL_STOP _URC_END_OF_STACK
65
#endif
66

67
#define MAX_TRACE_DEPTH   25
68

69
struct debug_info {
70
   A(debug_frame_t*) frames;
71
   unsigned          skip;
72
};
73

74
////////////////////////////////////////////////////////////////////////////////
75
// Utilities
76

77
typedef struct _di_lru_cache di_lru_cache_t;
78

79
struct _di_lru_cache {
80
   di_lru_cache_t *next;
81
   di_lru_cache_t *prev;
82
   debug_frame_t   frame;
83
};
84

85
#define DI_LRU_SIZE 256
86
STATIC_ASSERT(DI_LRU_SIZE > MAX_TRACE_DEPTH);
87

88
typedef struct _debug_unwinder debug_unwinder_t;
89

90
struct _debug_unwinder {
91
   debug_unwinder_t  *next;
92
   debug_unwind_fn_t  fn;
93
   void              *context;
94
   uintptr_t          start;
95
   uintptr_t          end;
96
};
97

98
static debug_unwinder_t *unwinders = NULL;
99

100
static void di_lru_reuse_frame(debug_frame_t *frame, uintptr_t pc)
7✔
101
{
102
   free((char *)frame->module);
7✔
103
   free((char *)frame->symbol);
7✔
104
   free((char *)frame->srcfile);
7✔
105

106
   for (debug_inline_t *it = frame->inlined; it != NULL; ) {
7✔
107
      debug_inline_t *next = it->next;
×
108
      free((char *)it->symbol);
×
UNCOV
109
      free((char *)it->srcfile);
×
UNCOV
110
      free(it);
×
UNCOV
111
      it = next;
×
112
   }
113

114
   memset(frame, '\0', sizeof(debug_frame_t));
7✔
115

116
   frame->pc = pc;
7✔
117
}
7✔
118

119
static bool di_lru_get(uintptr_t pc, debug_frame_t **pframe)
7✔
120
{
121
   static __thread di_lru_cache_t *lru_cache = NULL;
7✔
122

123
   unsigned size;
7✔
124
   di_lru_cache_t **it;
7✔
125
   for (it = &lru_cache, size = 0;
7✔
126
        *it != NULL && (*it)->frame.pc != pc;
22✔
127
        it = &((*it)->next), size++)
15✔
128
      ;
129

130
   if (*it != NULL && *it == lru_cache) {
7✔
UNCOV
131
      *pframe = &((*it)->frame);
×
132
      return true;
×
133
   }
134
   else if (*it != NULL) {
7✔
135
      di_lru_cache_t *tmp = *it;
×
136
      if ((*it)->next) (*it)->next->prev = (*it)->prev;
×
137
      *it = (*it)->next;
×
138
      tmp->next = lru_cache;
×
139
      tmp->prev = NULL;
×
140
      lru_cache->prev = tmp;
×
UNCOV
141
      lru_cache = tmp;
×
UNCOV
142
      *pframe = &(tmp->frame);
×
UNCOV
143
      return true;
×
144
   }
145
   else if (size < DI_LRU_SIZE) {
7✔
146
      di_lru_cache_t *new = xcalloc(sizeof(di_lru_cache_t));
7✔
147
      new->prev = NULL;
7✔
148
      new->next = lru_cache;
7✔
149
      if (lru_cache) lru_cache->prev = new;
7✔
150
      lru_cache = new;
7✔
151
      di_lru_reuse_frame(&(new->frame), pc);
7✔
152
      *pframe = &(new->frame);
7✔
153
      return false;
7✔
154
   }
155
   else {
156
      di_lru_cache_t *lru = container_of(it, di_lru_cache_t, next);
×
157
      lru->prev->next = NULL;
×
158
      lru->next = lru_cache;
×
159
      lru_cache->prev = lru;
×
160
      lru_cache = lru;
×
UNCOV
161
      di_lru_reuse_frame(&(lru->frame), pc);
×
UNCOV
162
      *pframe = &(lru->frame);
×
UNCOV
163
      return false;
×
164
   }
165
}
166

167
static bool custom_fill_frame(uintptr_t ip, debug_frame_t *frame)
6✔
168
{
169
   for (debug_unwinder_t *uw = unwinders; uw; uw = uw->next) {
6✔
UNCOV
170
      if (ip >= uw->start && ip < uw->end) {
×
UNCOV
171
         (*uw->fn)(ip, frame, uw->context);
×
UNCOV
172
         return true;
×
173
      }
174
   }
175

176
   return false;
177
}
178

179
////////////////////////////////////////////////////////////////////////////////
180
// Libdw backend
181

182
#if defined HAVE_LIBDW
183

184
static void libdw_fill_inlining(uintptr_t biased_ip, Dwarf_Die *fundie,
6✔
185
                                debug_frame_t *frame)
186
{
187
   Dwarf_Die child;
6✔
188
   if (dwarf_child(fundie, &child) == 0) {
6✔
189
      Dwarf_Die *iter = &child;
190
      do {
178✔
191
         if (dwarf_tag(iter) != DW_TAG_inlined_subroutine)
178✔
192
            continue;
178✔
193
         else if (!dwarf_haspc(iter, biased_ip))
×
194
            continue;
×
195

196
         debug_inline_t *inl = xcalloc(sizeof(debug_inline_t));
×
197
         inl->symbol = xstrdup(dwarf_diename(iter));
×
198

UNCOV
199
         Dwarf_Attribute attr;
×
200
         if (dwarf_attr(iter, DW_AT_abstract_origin, &attr) == NULL)
×
201
            continue;
×
202

UNCOV
203
         Dwarf_Die origin;
×
204
         if (dwarf_formref_die(&attr, &origin) == NULL)
×
205
            continue;
×
206

UNCOV
207
         const char *srcfile = dwarf_decl_file(iter);
×
208
         if (srcfile != NULL)
×
209
            inl->srcfile = xstrdup(srcfile);
×
210

211
         if (frame->kind == FRAME_VHDL) {
×
212
            Dwarf_Die *scopes;
×
213
            int n = dwarf_getscopes_die(&origin, &scopes);
×
UNCOV
214
            for (int i = 0; i < n; i++) {
×
UNCOV
215
               if (dwarf_tag(&(scopes[i])) == DW_TAG_module)
×
UNCOV
216
                  inl->vhdl_unit = ident_new(dwarf_diename(&(scopes[i])));
×
217
            }
218
         }
219

UNCOV
220
         Dwarf_Word call_lineno = 0;
×
221
         if (dwarf_attr(iter, DW_AT_call_line, &attr))
×
222
            dwarf_formudata(&attr, &call_lineno);
×
223

UNCOV
224
         Dwarf_Word call_colno = 0;
×
225
         if (dwarf_attr(iter, DW_AT_call_column, &attr))
×
226
            dwarf_formudata(&attr, &call_colno);
×
227

UNCOV
228
         if (frame->inlined) {
×
229
            inl->lineno = frame->inlined->lineno;
×
230
            inl->colno  = frame->inlined->colno;
×
231

UNCOV
232
            frame->inlined->lineno = call_lineno;
×
233
            frame->inlined->colno  = call_colno;
×
234
         }
235
         else {
236
            inl->lineno = frame->lineno;
×
237
            inl->colno  = frame->colno;
×
238

UNCOV
239
            frame->lineno = call_lineno;
×
240
            frame->colno  = call_colno;
×
241
         }
242

243
         inl->next = frame->inlined;
×
UNCOV
244
         frame->inlined = inl;
×
245

UNCOV
246
         libdw_fill_inlining(biased_ip, iter, frame);
×
247
      } while (dwarf_siblingof(iter, iter) == 0);
178✔
248
   }
249
}
6✔
250

251
static void platform_fill_frame(uintptr_t ip, debug_frame_t *frame)
7✔
252
{
253
   static Dwfl *handle = NULL;
7✔
254
   static Dwfl_Module *home = NULL;
7✔
255

256
   if (handle == NULL) {
7✔
257
      static Dwfl_Callbacks callbacks = {
2✔
258
         .find_elf = dwfl_linux_proc_find_elf,
259
         .find_debuginfo = dwfl_standard_find_debuginfo,
260
         .debuginfo_path = NULL
261
      };
262

263
      if ((handle = dwfl_begin(&callbacks)) == NULL)
2✔
UNCOV
264
         fatal("failed to initialise dwfl");
×
265

266
      dwfl_report_begin(handle);
2✔
267
      if (dwfl_linux_proc_report(handle, getpid()) < 0)
2✔
UNCOV
268
         fatal("dwfl_linux_proc_report failed");
×
269
      dwfl_report_end(handle, NULL, NULL);
2✔
270

271
      home = dwfl_addrmodule(handle, (uintptr_t)platform_fill_frame);
2✔
272
   }
273

274
   Dwfl_Module *mod = dwfl_addrmodule(handle, ip);
7✔
275
   frame->kind = (mod == home) ? FRAME_PROG : FRAME_LIB;
7✔
276
   frame->pc   = ip;
7✔
277

278
   const char *module_name = dwfl_module_info(mod, 0, 0, 0, 0, 0, 0, 0);
7✔
279
   const char *sym_name = dwfl_module_addrname(mod, ip);
7✔
280

281
   Dwarf_Addr mod_bias = 0;
7✔
282
   Dwarf_Die *cudie = dwfl_module_addrdie(mod, ip, &mod_bias);
7✔
283

284
   if (cudie == NULL) {
7✔
285
      // Clang does not emit aranges so search each CU
286
      cudie = dwfl_module_nextcu(mod, NULL, &mod_bias);
1✔
287
   }
288

289
   Dwarf_Die *fundie = NULL, *module = NULL, child, child1;
7✔
290
   do {
124✔
291
      if (dwarf_child(cudie, &child) != 0)
124✔
UNCOV
292
         continue;
×
293

294
      Dwarf_Die *iter = &child;
295
      do {
35,407✔
296
         switch (dwarf_tag(iter)) {
35,407✔
297
         case DW_TAG_inlined_subroutine:
14,968✔
298
         case DW_TAG_subprogram:
299
            if (dwarf_haspc(iter, ip - mod_bias)) {
14,968✔
300
               fundie = iter;
6✔
301
               goto found_die_with_ip;
6✔
302
            }
303
            break;
304
         case DW_TAG_module:
×
305
            if (dwarf_child(&child, &child1) == 0) {
×
306
               Dwarf_Die *iter1 = &child1;
UNCOV
307
               do {
×
308
                  switch (dwarf_tag(iter1)) {
×
309
                  case DW_TAG_inlined_subroutine:
×
310
                  case DW_TAG_subprogram:
311
                     if (dwarf_haspc(iter1, ip - mod_bias)) {
×
UNCOV
312
                        module = iter;
×
UNCOV
313
                        fundie = iter1;
×
314
                        goto found_die_with_ip;
×
315
                     }
316
                  }
UNCOV
317
               } while (dwarf_siblingof(iter1, iter1) == 0);
×
318
            }
319
            break;
320
         }
321
      } while (dwarf_siblingof(iter, iter) == 0);
35,401✔
322
   } while ((cudie = dwfl_module_nextcu(mod, cudie, &mod_bias)));
118✔
323

324
 found_die_with_ip:
7✔
325
   if (cudie != NULL) {
7✔
326
      Dwarf_Line *srcloc = dwarf_getsrc_die(cudie, ip - mod_bias);
6✔
327
      const char *srcfile = dwarf_linesrc(srcloc, 0, 0);
6✔
328

329
      dwarf_lineno(srcloc, (int *)&(frame->lineno));
6✔
330
      dwarf_linecol(srcloc, (int *)&(frame->colno));
6✔
331

332
      if (module != NULL) {
6✔
333
         // VHDL compilation units are wrapped in a DWARF module which
334
         // gives the unit name
UNCOV
335
         frame->kind = FRAME_VHDL;
×
UNCOV
336
         frame->vhdl_unit = ident_new(dwarf_diename(module));
×
337
      }
338

339
      if (srcfile != NULL)
6✔
340
         frame->srcfile = xstrdup(srcfile);
6✔
341

342
      if (fundie != NULL)
6✔
343
         libdw_fill_inlining(ip - mod_bias, fundie, frame);
6✔
344
   }
345

346
   if (sym_name != NULL)
7✔
347
      frame->symbol = xstrdup(sym_name);
7✔
348
   if (module_name != NULL)
7✔
349
      frame->module = xstrdup(module_name);
7✔
350
}
7✔
351

352
////////////////////////////////////////////////////////////////////////////////
353
// Libdwarf backend
354

355
#elif defined HAVE_LIBDWARF
356

357
typedef struct {
358
   Dwarf_Debug   debug;
359
   Dwarf_Arange *aranges;
360
   Dwarf_Signed  arange_count;
361
} libdwarf_handle_t;
362

363
static bool libdwarf_die_has_pc(libdwarf_handle_t *handle, Dwarf_Die die,
364
                                Dwarf_Addr pc)
365
{
366
   Dwarf_Addr low_pc = 0, high_pc = 0;
367

368
   if (dwarf_lowpc(die, &low_pc, NULL) == DW_DLV_OK) {
369
      Dwarf_Half form;
370
      enum Dwarf_Form_Class class;
371
      if (dwarf_highpc_b(die, &high_pc, &form, &class, NULL) == DW_DLV_OK) {
372
         if (class == DW_FORM_CLASS_CONSTANT)
373
            high_pc += low_pc;   // DWARF4
374
         return pc >= low_pc && pc < high_pc;
375
      }
376
   }
377

378
   Dwarf_Attribute attr;
379
   if (dwarf_attr(die, DW_AT_ranges, &attr, NULL) != DW_DLV_OK)
380
      return false;
381

382
   Dwarf_Off offset;
383
   if (dwarf_global_formref(attr, &offset, NULL) != DW_DLV_OK)
384
      return false;
385

386
   Dwarf_Ranges *ranges;
387
   Dwarf_Signed ranges_count = 0;
388
   Dwarf_Unsigned byte_count = 0;
389
   bool result = false;
390

391
   if (dwarf_get_ranges_b(handle->debug, offset, die, NULL, &ranges,
392
                          &ranges_count, &byte_count, NULL) == DW_DLV_OK) {
393
      for (int i = 0; i < ranges_count; i++) {
394
         if (ranges[i].dwr_addr1 != 0 &&
395
             pc >= ranges[i].dwr_addr1 + low_pc &&
396
             pc < ranges[i].dwr_addr2 + low_pc) {
397
            result = true;
398
            break;
399
         }
400
      }
401
      dwarf_dealloc_ranges(handle->debug, ranges, ranges_count);
402
   }
403

404
   return result;
405
}
406

407
#ifdef __APPLE__
408
static void get_macho_uuid(const char *fname, uint8_t uuid[16])
409
{
410
   FILE *f = fopen(fname, "r");
411
   if (f == NULL) {
412
      warnf("open: %s", fname);
413
      return;
414
   }
415

416
   struct mach_header_64 fhdr;
417
   if (fread(&fhdr, sizeof(fhdr), 1, f) != 1)
418
      fatal_errno("error reading %s", fname);
419

420
   if (fhdr.magic != MH_MAGIC_64) {
421
      warnf("bad Mach-O magic %x", fhdr.magic);
422
      goto out_close;
423
   }
424

425
   void *cmdbuf = xmalloc(fhdr.sizeofcmds);
426
   if (fread(cmdbuf, fhdr.sizeofcmds, 1, f) != 1)
427
      fatal_errno("error reading %s", fname);
428

429
   void *rptr = cmdbuf;
430
   for (int i = 0; i < fhdr.ncmds; i++) {
431
      const struct load_command *load = rptr;
432
      if (load->cmd == LC_UUID) {
433
         memcpy(uuid, ((struct uuid_command *)rptr)->uuid, 16);
434
         goto out_free;
435
      }
436

437
      rptr += load->cmdsize;
438
   }
439
   assert(rptr == cmdbuf + fhdr.sizeofcmds);
440

441
   warnf("could not read UUID from %s", fname);
442
   memset(uuid, '\0', 16);
443

444
 out_free:
445
   free(cmdbuf);
446
 out_close:
447
   fclose(f);
448
}
449
#endif
450

451
static libdwarf_handle_t *libdwarf_handle_for_file(const char *fname)
452
{
453
   static shash_t *hash = NULL;
454

455
   if (hash == NULL)
456
      hash = shash_new(64);
457

458
   libdwarf_handle_t *handle = shash_get(hash, fname);
459

460
   if (handle == (void *)-1)
461
      return NULL;
462
   else if (handle == NULL) {
463
      Dwarf_Debug debug = NULL;
464
      Dwarf_Error err = NULL;
465
      char true_path[PATH_MAX] = "";
466
      int ret = dwarf_init_path(fname, true_path, sizeof(true_path),
467
                                DW_GROUPNUMBER_ANY, NULL, NULL,
468
                                &debug, &err);
469

470
#ifdef __APPLE__
471
      uint8_t exe_uuid[16], dsym_uuid[16];
472
      get_macho_uuid(fname, exe_uuid);
473
      get_macho_uuid(true_path, dsym_uuid);
474

475
      if (memcmp(exe_uuid, dsym_uuid, 16) != 0)
476
         warnf("UUID of %s does not match %s, symbols may be incorrect",
477
               fname, true_path);
478
#endif
479

480
      if (ret == DW_DLV_ERROR) {
481
         warnf("dwarf_init: %s: %s", fname, dwarf_errmsg(err));
482
         dwarf_dealloc_error(debug, err);
483
         shash_put(hash, fname, (void *)-1);
484
         return NULL;
485
      }
486
      else if (ret == DW_DLV_NO_ENTRY) {
487
         shash_put(hash, fname, (void *)-1);
488
         return NULL;
489
      }
490

491
      handle = xcalloc(sizeof(libdwarf_handle_t));
492
      handle->debug = debug;
493

494
      shash_put(hash, fname, handle);
495
   }
496

497
   return handle;
498
}
499

500
static void libdwarf_get_symbol(libdwarf_handle_t *handle, Dwarf_Die die,
501
                                Dwarf_Unsigned rel_addr, debug_frame_t *frame)
502
{
503
   Dwarf_Die child, prev = NULL;
504
   if (dwarf_child(die, &child, NULL) != DW_DLV_OK)
505
      return;
506

507
   do {
508
      if (prev != NULL)
509
         dwarf_dealloc(handle->debug, prev, DW_DLA_DIE);
510
      prev = child;
511

512
      Dwarf_Half tag;
513
      if (dwarf_tag(child, &tag, NULL) != DW_DLV_OK)
514
         continue;
515
      else if (tag == DW_TAG_module) {
516
         libdwarf_get_symbol(handle, child, rel_addr, frame);
517
         if (frame->symbol == NULL)
518
            continue;
519

520
         char *name;
521
         if (dwarf_diename(child, &name, NULL) == DW_DLV_OK) {
522
            frame->vhdl_unit = ident_new(name);
523
            frame->kind = FRAME_VHDL;
524
            dwarf_dealloc(handle->debug, name, DW_DLA_STRING);
525
            break;
526
         }
527

528
         continue;
529
      }
530
      else if (tag != DW_TAG_subprogram && tag != DW_TAG_inlined_subroutine)
531
         continue;
532
      else if (!libdwarf_die_has_pc(handle, child, rel_addr))
533
         continue;
534

535
      char *name;
536
      if (dwarf_diename(child, &name, NULL) == DW_DLV_OK) {
537
         frame->symbol = xstrdup(name);
538
         dwarf_dealloc(handle->debug, name, DW_DLA_STRING);
539
      }
540

541
      break;
542
   } while (dwarf_siblingof_b(handle->debug, child, true,
543
                              &child, NULL) == DW_DLV_OK);
544

545
   if (prev != NULL)
546
      dwarf_dealloc(handle->debug, prev, DW_DLA_DIE);
547
}
548

549
static void libdwarf_get_srcline(libdwarf_handle_t *handle, Dwarf_Die die,
550
                                 Dwarf_Unsigned rel_addr, debug_frame_t *frame)
551
{
552
   Dwarf_Small tablecount;
553
   Dwarf_Line_Context linecontext;
554
   Dwarf_Signed idx = 0;
555
   if (dwarf_srclines_b(die, NULL, &tablecount,
556
                        &linecontext, NULL) == DW_DLV_OK) {
557
      Dwarf_Line*  linebuf = NULL;
558
      Dwarf_Signed linecount = 0;
559
      if (dwarf_srclines_from_linecontext(linecontext, &linebuf,
560
                                          &linecount, NULL) == DW_DLV_OK) {
561
         Dwarf_Unsigned pladdr = 0;
562
         for (Dwarf_Signed i = 0; i < linecount; i++) {
563
            Dwarf_Unsigned laddr;
564
            if (dwarf_lineaddr(linebuf[i], &laddr, NULL) != DW_DLV_OK)
565
               break;
566
            else if (rel_addr == laddr) {
567
               idx = i;
568
               break;
569
            }
570
            else if (rel_addr < laddr && rel_addr > pladdr) {
571
               idx = i - 1;
572
               break;
573
            }
574
            pladdr = laddr;
575
         }
576

577
         if (idx >= 0) {
578
            Dwarf_Unsigned lineno;
579
            if (dwarf_lineno(linebuf[idx], &lineno, NULL) == DW_DLV_OK)
580
               frame->lineno = lineno;
581

582
            char *srcfile;
583
            if (dwarf_linesrc(linebuf[idx], &srcfile, NULL) == DW_DLV_OK) {
584
               frame->srcfile = xstrdup(srcfile);
585
               dwarf_dealloc(handle->debug, srcfile, DW_DLA_STRING);
586
            }
587
         }
588

589
         dwarf_srclines_dealloc_b(linecontext);
590
      }
591
   }
592
}
593

594
static bool libdwarf_scan_aranges(libdwarf_handle_t *handle,
595
                                  Dwarf_Unsigned rel_addr,
596
                                  debug_frame_t *frame)
597
{
598
   Dwarf_Off cu_header_offset = 0;
599
   bool      found = false;
600

601
   if (handle->aranges == NULL) {
602
      if (dwarf_get_aranges(handle->debug, &(handle->aranges),
603
                            &(handle->arange_count), NULL) != DW_DLV_OK)
604
         return false;
605
   }
606

607
   Dwarf_Arange arange;
608
   if (dwarf_get_arange(handle->aranges, handle->arange_count, rel_addr,
609
                        &arange, NULL) != DW_DLV_OK)
610
      return false;
611

612
   if (dwarf_get_cu_die_offset(arange, &cu_header_offset, NULL) != DW_DLV_OK)
613
      return false;
614

615
   Dwarf_Die die = NULL;
616
   if (dwarf_offdie_b(handle->debug, cu_header_offset,
617
                      true, &die, NULL) != DW_DLV_OK)
618
      return false;
619

620
   Dwarf_Half tag = 0;
621
   if (dwarf_tag(die, &tag, NULL) != DW_DLV_OK)
622
      goto free_die;
623
   else if (tag != DW_TAG_compile_unit)
624
      goto free_die;
625

626
   if (libdwarf_die_has_pc(handle, die, rel_addr)) {
627
      libdwarf_get_srcline(handle, die, rel_addr, frame);
628
      libdwarf_get_symbol(handle, die, rel_addr, frame);
629
      found = true;
630
   }
631

632
 free_die:
633
   dwarf_dealloc(handle->debug, die, DW_DLA_DIE);
634

635
   return found;
636
}
637

638
static bool libdwarf_scan_cus(libdwarf_handle_t *handle,
639
                              Dwarf_Unsigned rel_addr,
640
                              debug_frame_t *frame)
641
{
642
   Dwarf_Unsigned next_cu_offset;
643
   bool found = false;
644
   while (dwarf_next_cu_header_d(handle->debug, true, NULL, NULL, NULL, NULL,
645
                                 NULL, NULL, NULL, NULL,
646
                                 &next_cu_offset, NULL, NULL) == DW_DLV_OK) {
647

648
      if (found)
649
         continue;   // Read all the way to the end to reset the iterator
650

651
      Dwarf_Die die = NULL;
652
      if (dwarf_siblingof_b(handle->debug, 0, true, &die, NULL) != DW_DLV_OK)
653
         continue;
654

655
      Dwarf_Half tag = 0;
656
      if (dwarf_tag(die, &tag, NULL) != DW_DLV_OK)
657
         goto free_die;
658
      else if (tag != DW_TAG_compile_unit)
659
         goto free_die;
660

661
      if (libdwarf_die_has_pc(handle, die, rel_addr)) {
662
         libdwarf_get_srcline(handle, die, rel_addr, frame);
663
         libdwarf_get_symbol(handle, die, rel_addr, frame);
664
         found = true;
665
      }
666

667
   free_die:
668
      dwarf_dealloc(handle->debug, die, DW_DLA_DIE);
669
      die = NULL;
670
   }
671

672
   return found;
673
}
674

675
static void platform_fill_frame(uintptr_t ip, debug_frame_t *frame)
676
{
677
#if defined __MINGW32__
678
   HANDLE hProcess = GetCurrentProcess();
679

680
   frame->kind = FRAME_PROG;
681

682
   static DWORD64 home_fbase = 0;
683
   INIT_ONCE({
684
         IMAGEHLP_MODULE module = { .SizeOfStruct = sizeof(module) };
685
         if (SymGetModuleInfo(hProcess, ip, &module))
686
            home_fbase = module.BaseOfImage;
687
      });
688

689
   char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
690
   PSYMBOL_INFO psym = (PSYMBOL_INFO)buffer;
691
   memset(buffer, '\0', sizeof(SYMBOL_INFO));
692
   psym->SizeOfStruct = sizeof(SYMBOL_INFO);
693
   psym->MaxNameLen = MAX_SYM_NAME;
694

695
   const char *fallback_symbol = NULL;
696
   DWORD64 disp;
697
   if (SymFromAddr(hProcess, ip, &disp, psym)) {
698
      frame->disp = disp;
699
      fallback_symbol = psym->Name;
700
   }
701

702
   IMAGEHLP_MODULE module = { .SizeOfStruct = sizeof(module) };
703
   if (!SymGetModuleInfo(hProcess, ip, &module))
704
      return;
705

706
   if (module.BaseOfImage != home_fbase)
707
      frame->kind = FRAME_LIB;
708

709
   frame->module = xstrdup(module.ImageName);
710

711
   Dwarf_Addr rel_addr = ip - module.BaseOfImage;
712

713
#else
714
   Dl_info dli;
715
   if (!dladdr((void *)ip, &dli))
716
      return;
717

718
   static void *home_fbase = NULL;
719
   if (home_fbase == NULL) {
720
      Dl_info dli_home;
721
      extern int main(int, char **);
722
      if (dladdr(main, &dli_home))
723
         home_fbase = dli_home.dli_fbase;
724
   }
725

726
   frame->kind   = dli.dli_fbase == home_fbase ? FRAME_PROG : FRAME_LIB;
727
   frame->module = xstrdup(dli.dli_fname);
728
   frame->disp   = ip - (uintptr_t)dli.dli_saddr;
729

730
   const char *fallback_symbol = dli.dli_sname;
731

732
#if defined __FreeBSD__
733
   Dwarf_Addr rel_addr;
734
   if (frame->kind == FRAME_PROG)
735
      rel_addr = ip;
736
   else
737
      rel_addr = ip - (uintptr_t)dli.dli_fbase;
738
#elif defined __APPLE__
739
   Dwarf_Addr rel_addr = ip - (uintptr_t)dli.dli_fbase + 0x100000000;
740
#else
741
   Dwarf_Addr rel_addr = ip - (uintptr_t)dli.dli_fbase;
742
#endif
743
#endif
744

745
   libdwarf_handle_t *handle = libdwarf_handle_for_file(frame->module);
746
   if (handle != NULL) {
747
      if (!libdwarf_scan_aranges(handle, rel_addr, frame)) {
748
         // Clang does emit aranges so we have to search each compilation unit
749
         libdwarf_scan_cus(handle, rel_addr, frame);
750
      }
751
   }
752

753
   if (frame->symbol == NULL && fallback_symbol != NULL) {
754
      // Fallback: just use the nearest global symbol
755
      frame->symbol = xstrdup(fallback_symbol);
756
   }
757
}
758

759
////////////////////////////////////////////////////////////////////////////////
760
// Windows backend
761

762
#elif defined __MINGW32__ || defined __CYGWIN__
763

764
static void platform_fill_frame(uintptr_t ip, debug_frame_t *frame)
765
{
766
   HANDLE hProcess = GetCurrentProcess();
767

768
   frame->kind = FRAME_PROG;
769

770
   static DWORD64 home_fbase = 0;
771
   INIT_ONCE({
772
         IMAGEHLP_MODULE module = { .SizeOfStruct = sizeof(module) };
773
         if (SymGetModuleInfo(hProcess, ip, &module))
774
            home_fbase = module.BaseOfImage;
775
      });
776

777
   char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
778
   PSYMBOL_INFO psym = (PSYMBOL_INFO)buffer;
779
   memset(buffer, '\0', sizeof(SYMBOL_INFO));
780
   psym->SizeOfStruct = sizeof(SYMBOL_INFO);
781
   psym->MaxNameLen = MAX_SYM_NAME;
782

783
   DWORD64 disp;
784
   if (SymFromAddr(hProcess, ip, &disp, psym)) {
785
      frame->disp   = disp;
786
      frame->symbol = xstrdup(psym->Name);
787
   }
788

789
   IMAGEHLP_MODULE module = { .SizeOfStruct = sizeof(module) };
790
   if (SymGetModuleInfo(hProcess, ip, &module)) {
791
      frame->module = xstrdup(module.ModuleName);
792

793
      if (module.BaseOfImage != home_fbase)
794
         frame->kind = FRAME_LIB;
795
   }
796
}
797

798
////////////////////////////////////////////////////////////////////////////////
799
// Generic Unix fallback
800

801
#else
802

803
static void platform_fill_frame(uintptr_t ip, debug_frame_t *frame)
804
{
805
   Dl_info dli;
806
   if (!dladdr((void *)ip, &dli))
807
      return;
808

809
   static void *home_fbase = NULL;
810
   if (home_fbase == NULL) {
811
      Dl_info dli_home;
812
      extern int main(int, char **);
813
      if (dladdr(main, &dli_home))
814
         home_fbase = dli_home.dli_fbase;
815
   }
816

817
   frame->kind   = dli.dli_fbase == home_fbase ? FRAME_PROG : FRAME_LIB;
818
   frame->module = xstrdup(dli.dli_fname);
819
   frame->disp   = ip - (uintptr_t)dli.dli_saddr;
820

821
   if (dli.dli_sname)
822
      frame->symbol = xstrdup(dli.dli_sname);
823
}
824

825
#endif
826

827
static _Unwind_Reason_Code unwind_frame_iter(struct _Unwind_Context* ctx,
7✔
828
                                             void *param)
829
{
830
   debug_info_t *di = param;
7✔
831

832
   if (di->skip > 0) {
7✔
833
      di->skip--;
1✔
834
      return _URC_NO_REASON;
1✔
835
   }
836

837
   int ip_before_instruction = 0;
6✔
838
   uintptr_t ip = _Unwind_GetIPInfo(ctx, &ip_before_instruction);
6✔
839

840
   if (ip == 0)
6✔
841
      return _URC_NO_REASON;
842
   else if (!ip_before_instruction)
6✔
843
      ip -= 1;
6✔
844

845
   debug_frame_t *frame;
6✔
846
   if (!di_lru_get(ip, &frame) && !custom_fill_frame(ip, frame))
6✔
847
      platform_fill_frame(ip, frame);
6✔
848

849
   APUSH(di->frames, frame);
6✔
850

851
   if (frame->symbol != NULL && strcmp(frame->symbol, "main") == 0)
6✔
852
      return _URC_NORMAL_STOP;
853
   else
854
      return _URC_NO_REASON;
5✔
855
}
856

857
////////////////////////////////////////////////////////////////////////////////
858
// Public interface
859

860
__attribute__((noinline))
861
debug_info_t *debug_capture(void)
1✔
862
{
863
#ifdef __MINGW32__
864
   INIT_ONCE(SymInitialize(GetCurrentProcess(), NULL, TRUE));
865
#endif
866

867
   debug_info_t *di = xcalloc(sizeof(debug_info_t));
1✔
868

869
   static __thread bool in_progress = false;
1✔
870
   if (in_progress)
1✔
871
      return di;   // Guard against re-entrancy
872

873
   in_progress = true;
1✔
874

875
   // The various DWARF libraries do not seem to be thread-safe
876
   static nvc_lock_t lock;
1✔
877
   SCOPED_LOCK(lock);
1✔
878

879
   di->skip = 1;
1✔
880
   _Unwind_Backtrace(unwind_frame_iter, di);
1✔
881

882
   in_progress = false;
1✔
883
   return di;
1✔
884
}
885

886
void debug_free(debug_info_t *di)
1✔
887
{
888
   ACLEAR(di->frames);
1✔
889
   free(di);
1✔
890
}
1✔
891

892
unsigned debug_count_frames(debug_info_t *di)
1✔
893
{
894
   return di->frames.count;
1✔
895
}
896

897
const debug_frame_t *debug_get_frame(debug_info_t *di, unsigned n)
2✔
898
{
899
   return AGET(di->frames, n);
2✔
900
}
901

902
void debug_add_unwinder(void *start, size_t len, debug_unwind_fn_t fn,
3,597✔
903
                        void *context)
904
{
905
   debug_unwinder_t *uw = xmalloc(sizeof(debug_unwinder_t));
3,597✔
906
   uw->next    = unwinders;
3,597✔
907
   uw->fn      = fn;
3,597✔
908
   uw->context = context;
3,597✔
909
   uw->start   = (uintptr_t)start;
3,597✔
910
   uw->end     = (uintptr_t)start + len;
3,597✔
911

912
   unwinders = uw;
3,597✔
913
}
3,597✔
914

915
void debug_remove_unwinder(void *start)
3,595✔
916
{
917
   for (debug_unwinder_t **p = &unwinders; *p; p = &((*p)->next)) {
3,595✔
918
      if ((*p)->start == (uintptr_t)start) {
3,595✔
919
         debug_unwinder_t *tmp = *p;
3,595✔
920
         *p = (*p)->next;
3,595✔
921
         free(tmp);
3,595✔
922
         return;
3,595✔
923
      }
924
   }
925

926
   fatal_trace("no unwinder registered for %p", start);
927
}
928

929
const char *debug_symbol_name(void *addr)
1✔
930
{
931
   debug_frame_t *frame;
1✔
932
   if (!di_lru_get((uintptr_t)addr, &frame))
1✔
933
      platform_fill_frame((uintptr_t)addr, frame);
1✔
934

935
   return frame->symbol;
1✔
936
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc