Thread overview
Reducing debug info for stack traces, while preserving for gdb
Dec 14, 2022
Witold Baryluk
Dec 14, 2022
Witold Baryluk
Dec 24, 2022
Iain Buclaw
December 14, 2022

Hi,

I am trying to split debug symbols out of final binary, to reduce its size. But I would like to preserve at least line numbers in the stacktraces from Exceptions.

Linux, amd64. gdc 12.2.0 on Debian testing.

Binary is compiled using -O3 -g -g3 (and possibly -gz).

I then use objcopy to split debug info from final ELF binary file into .exe and .debug files.

I use objcopy --only-keep-debug --compress-debug-sections=zlib binary binary.debug to produce .debug file.

Then I use normally objcopy --strip-debug binary binary.exe (possibly with --compress-debug-sections=zlib) to produce binary.

This makes it possibly to use in gdb without issues (using gdb -s binary.debug -e binary for example, or by utilizing --add-gnu-debuglink= option in objcopy).

But this causes stacktraces to miss line numbers (and columns). (function names are still there, as these are derived from symbol tables instead).

I tried selectively removing DWARF debug sections, but it looks that at least these are required:

.debug_info
.debug_aranges
.debug_abbrev
.debug_line
.debug_str
.debug_line_str
.debug_rnglists

So, I can only remove these:

objcopy -R .debug_loc -R .debug_macro -R .debug_ranges -R .debug_loclists

(.debug_loc and .debug_ranges are not even generated by gcc, so it does not matter probably).

The issue is, this saves me very little space. .debug_macro and .debug_loclists are rather small.

The bulk of information is in .debug_info. But I believe it contains way more information than is really needed to just produce line numbers.

I did inspect final binaries , and it is using DWARF version 5.

I also tried -gas-loc-support , but no change.

Using -g1 makes stack traces work nicely, by making .debug_info smaller, but then debugging in gdb is very limited. One option would be to compile application twice, once with -g1 and once with -g3. But I really do not think this is supported, or reliable, even if I enable deterministic builds.

In one article ( https://support.backtrace.io/hc/en-us/articles/360040105792-DWARF#RemovingDebugInformation ) I read that for C/C++ in GCC, it is enough to preserve only .debug_frame and .debug_line to get function, source filenames and line numbers. But that is not true for gdc and its stacktrace handler.

I did read about this gdb extension, which is interesting, https://sourceware.org/gdb/onlinedocs/gdb/MiniDebugInfo.html , but I did not try, and it is probably only supported in gdb (i.e. addr2libe, Phobos, libunwind do not support it).

Any ideas?

December 14, 2022

For example to give you some context.

This is an example small app, the actual app is significantly larger (binary with debug symbols is about 20MB, without debug symbols only 2-3MB).

$ ~/bloaty/build/bloaty binary-O3-g-g3-gz  # (original binary)
    FILE SIZE        VM SIZE
 --------------  --------------
  19.8%   107Ki  39.3%   107Ki    .text
  17.0%  93.0Ki   0.0%       0    .strtab
  13.3%  72.4Ki   0.0%       0    .debug_info                   # 72kB
  12.0%  65.3Ki  23.8%  65.3Ki    .dynstr
   5.2%  28.5Ki  10.4%  28.5Ki    .rodata
   5.1%  27.9Ki  10.2%  27.9Ki    .eh_frame
   5.0%  27.5Ki   0.0%       0    .debug_loclists               # 27kB
   4.5%  24.8Ki   0.0%       0    .symtab
   3.7%  20.4Ki   0.0%       0    .debug_str                    # 20kB
   3.4%  18.5Ki   0.0%       0    .debug_line                   # 18.5kB
   3.2%  17.6Ki   6.4%  17.6Ki    .dynsym
   1.3%  7.25Ki   0.0%       0    [Unmapped]
   1.2%  6.61Ki   2.1%  5.64Ki    [27 Others]
   1.1%  6.18Ki   2.3%  6.18Ki    .eh_frame_hdr
   0.9%  5.08Ki   1.9%  5.08Ki    .gnu.hash
   0.8%  4.31Ki   1.6%  4.31Ki    .data.rel.ro
   0.8%  4.12Ki   1.5%  4.12Ki    .rela.dyn
   0.5%  2.69Ki   0.0%       0    [ELF Section Headers]
   0.4%  2.38Ki   0.0%       0    .debug_rnglists               # small
   0.3%  1.74Ki   0.0%       0    .debug_abbrev                 # small
   0.3%  1.52Ki   0.6%  1.52Ki    .rela.plt
 100.0%   545Ki 100.0%   273Ki    TOTAL
$ ~/bloaty/build/bloaty binary-O3-g-g1-gz # binary with -g1, and compressed debug sections
    FILE SIZE        VM SIZE
 --------------  --------------
  24.4%   107Ki  39.3%   107Ki    .text
  21.1%  93.0Ki   0.0%       0    .strtab
  14.8%  65.3Ki  23.8%  65.3Ki    .dynstr
   6.5%  28.5Ki  10.4%  28.5Ki    .rodata
   6.3%  27.9Ki  10.2%  27.9Ki    .eh_frame
   5.6%  24.8Ki   0.0%       0    .symtab
   4.0%  17.6Ki   6.4%  17.6Ki    .dynsym
   3.5%  15.5Ki   0.0%       0    .debug_line              # smaller
   2.8%  12.2Ki   0.0%       0    .debug_str               # smaller
   2.4%  10.4Ki   0.0%       0    .debug_info              # way smaller
   1.6%  7.25Ki   0.0%       0    [Unmapped]
   1.4%  6.18Ki   2.3%  6.18Ki    .eh_frame_hdr
   1.2%  5.08Ki   1.9%  5.08Ki    .gnu.hash
   1.0%  4.31Ki   1.6%  4.31Ki    .data.rel.ro
   1.0%  4.20Ki   0.9%  2.43Ki    [25 Others]
   0.9%  4.12Ki   1.5%  4.12Ki    .rela.dyn
   0.6%  2.56Ki   0.0%       0    [ELF Section Headers]
   0.3%  1.52Ki   0.6%  1.52Ki    .rela.plt
   0.3%  1.47Ki   0.5%  1.47Ki    .gnu.version
   0.2%  1.03Ki   0.4%  1.03Ki    .plt
   0.2%     728   0.3%     728    [ELF Program Headers]
 100.0%   441Ki 100.0%   273Ki    TOTAL

(.debug_rnglists and .debug_abbrev do not even register).

$ ~/bloaty/build/bloaty binary-O3-g-g3.exe  # without debug sections (broken lines in stack trace)
    FILE SIZE        VM SIZE
 --------------  --------------
  26.9%   107Ki  39.3%   107Ki    .text
  23.2%  92.9Ki   0.0%       0    .strtab
  16.3%  65.3Ki  23.8%  65.3Ki    .dynstr
   7.1%  28.5Ki  10.4%  28.5Ki    .rodata
   6.9%  27.9Ki  10.2%  27.9Ki    .eh_frame
   6.2%  24.7Ki   0.0%       0    .symtab
   4.4%  17.6Ki   6.4%  17.6Ki    .dynsym
   1.8%  7.25Ki   0.0%       0    [Unmapped]
   1.5%  6.18Ki   2.3%  6.18Ki    .eh_frame_hdr
   1.3%  5.08Ki   1.9%  5.08Ki    .gnu.hash
   1.1%  4.31Ki   1.6%  4.31Ki    .data.rel.ro
   1.0%  4.12Ki   1.5%  4.12Ki    .rela.dyn
   0.5%  2.12Ki   0.0%       0    [ELF Section Headers]
   0.4%  1.52Ki   0.6%  1.52Ki    .rela.plt
   0.4%  1.47Ki   0.5%  1.47Ki    .gnu.version
   0.3%  1.18Ki   0.3%     881    [18 Others]
   0.3%  1.03Ki   0.4%  1.03Ki    .plt
   0.2%     728   0.3%     728    [ELF Program Headers]
   0.1%     552   0.2%     552    .data
   0.1%     544   0.2%     544    .got.plt
   0.1%     512   0.2%     512    .dynamic
 100.0%   401Ki 100.0%   273Ki    TOTAL
$ ~/bloaty/build/bloaty binary-O3-g-g3.debug   # only debug sections
    FILE SIZE        VM SIZE
 --------------  --------------
  31.0%   178Ki   0.0%       0    .debug_info
   0.0%       0  39.3%   107Ki    .text
  18.5%   106Ki   0.0%       0    .debug_loclists
  16.8%  96.9Ki   0.0%       0    .debug_str
  16.1%  93.0Ki   0.0%       0    .strtab
   0.0%       0  23.8%  65.3Ki    .dynstr
  10.5%  60.7Ki   0.0%       0    .debug_line
   0.0%       0  10.4%  28.5Ki    .rodata
   0.0%       0  10.2%  27.9Ki    .eh_frame
   4.3%  24.8Ki   0.0%       0    .symtab
   0.0%       0   6.4%  17.6Ki    .dynsym
   0.0%       0   2.3%  6.18Ki    .eh_frame_hdr
   1.0%  5.86Ki   0.0%       0    .debug_rnglists
   0.0%       0   1.9%  5.08Ki    .gnu.hash
   0.8%  4.54Ki   0.0%       0    .debug_abbrev
   0.0%       0   1.6%  4.31Ki    .data.rel.ro
   0.5%  2.79Ki   1.5%  4.17Ki    [27 Others]
   0.0%       0   1.5%  4.12Ki    .rela.dyn
   0.5%  2.69Ki   0.0%       0    [ELF Section Headers]
   0.0%       0   0.6%  1.52Ki    .rela.plt
   0.0%       0   0.5%  1.47Ki    .gnu.version
 100.0%   576Ki 100.0%   273Ki    TOTAL
$ ~/bloaty/build/bloaty binary-O3-g-g3.debug-zlib   # compressed debug sections
    FILE SIZE        VM SIZE
 --------------  --------------
   0.0%       0  39.3%   107Ki    .text
  35.0%  93.0Ki   0.0%       0    .strtab
  27.3%  72.4Ki   0.0%       0    .debug_info
   0.0%       0  23.8%  65.3Ki    .dynstr
   0.0%       0  10.4%  28.5Ki    .rodata
   0.0%       0  10.2%  27.9Ki    .eh_frame
  10.4%  27.5Ki   0.0%       0    .debug_loclists
   9.4%  24.8Ki   0.0%       0    .symtab
   7.7%  20.4Ki   0.0%       0    .debug_str
   7.0%  18.5Ki   0.0%       0    .debug_line
   0.0%       0   6.4%  17.6Ki    .dynsym
   0.0%       0   2.3%  6.18Ki    .eh_frame_hdr
   0.0%       0   1.9%  5.08Ki    .gnu.hash
   0.0%       0   1.6%  4.31Ki    .data.rel.ro
   0.7%  1.89Ki   1.5%  4.17Ki    [27 Others]
   0.0%       0   1.5%  4.12Ki    .rela.dyn
   1.0%  2.69Ki   0.0%       0    [ELF Section Headers]
   0.9%  2.38Ki   0.0%       0    .debug_rnglists
   0.7%  1.74Ki   0.0%       0    .debug_abbrev
   0.0%       0   0.6%  1.52Ki    .rela.plt
   0.0%       0   0.5%  1.47Ki    .gnu.version
 100.0%   265Ki 100.0%   273Ki    TOTAL
December 24, 2022

Hi,

On Wednesday, 14 December 2022 at 22:23:49 UTC, Witold Baryluk wrote:>

>

The bulk of information is in .debug_info. But I believe it contains way more information than is really needed to just produce line numbers.

You're observation is correct. GDC uses libbacktrace to produce stack traces, and this indeed does traverse the .debug_info section in order to achieve its job.

https://github.com/gcc-mirror/gcc/blob/8ec139af5ea9657c7517c1483c7a577815bea48e/libbacktrace/dwarf.c#L2116-L2120

>

I did inspect final binaries , and it is using DWARF version 5.

I also tried -gas-loc-support , but no change.

Using -g1 makes stack traces work nicely, by making .debug_info smaller, but then debugging in gdb is very limited. One option would be to compile application twice, once with -g1 and once with -g3. But I really do not think this is supported, or reliable, even if I enable deterministic builds.

I'm not sure on this, I'd have to give it some thought.

>

In one article ( https://support.backtrace.io/hc/en-us/articles/360040105792-DWARF#RemovingDebugInformation ) I read that for C/C++ in GCC, it is enough to preserve only .debug_frame and .debug_line to get function, source filenames and line numbers. But that is not true for gdc and its stacktrace handler.

C++ stdlib doesn't give stack traces AFAIU, and if it does, it would be using libbacktrace too. I guess then the article must be referring to the implementation of execinfo.h and/or dladdr.

Perhaps a run-time fallback could be added to gdc's stack trace support to use the execinfo.h backtrace functions if the .debug_info (or whatever platform equivalent) section does not exist. From what I recall, execinfo is not quite as accurate as libbacktrace though.

Iain.