减少调试符号的占用空间(可执行文件膨胀到 4 GB)

Reducing the footprint of debug symbols (executable is bloated to 4 GB)

因此,基本问题是我构建的可执行文件大小为 4GB,并且启用了调试符号(介于 75 MB 和 300 MB 之间,没有调试符号和不同的优化级别)。我如何 diagnose/analyse 所有这些符号来自哪里,以及哪些是占用 space 的最大罪犯?我发现了一些关于减少非调试可执行文件大小的问题(尽管它们并没有非常有启发性),但在这里我主要关心减少调试符号的混乱。可执行文件太大以至于 gdb 需要花费大量时间来加载所有符号,这阻碍了调试。也许减少代码膨胀是根本任务,但我首先想知道我的 4GB 空间用在了哪里。

运行 通过 'size --format=SysV' 的可执行文件我得到以下输出:

section                    size       addr
.interp                      28    4194872
.note.ABI-tag                32    4194900
.note.gnu.build-id           36    4194932
.gnu.hash                714296    4194968
.dynsym                 2728248    4909264
.dynstr                13214041    7637512
.gnu.version             227354   20851554
.gnu.version_r              528   21078912
.rela.dyn                 37680   21079440
.rela.plt                 15264   21117120
.init                        26   21132384
.plt                      10192   21132416
.text                  25749232   21142608
.fini                         9   46891840
.rodata                 3089441   46891872
.eh_frame_hdr            584228   49981316
.eh_frame               2574372   50565544
.gcc_except_table       1514577   53139916
.init_array                2152   56753888
.fini_array                   8   56756040
.jcr                          8   56756048
.data.rel.ro             332264   56756064
.dynamic                    992   57088328
.got                        704   57089320
.got.plt                   5112   57090048
.data                     22720   57095168
.bss                    1317872   57117888
.comment                     44          0
.debug_aranges          2978704          0
.debug_info           278337429          0
.debug_abbrev           1557345          0
.debug_line            13416850          0
.debug_str           3620467085          0
.debug_loc            236168202          0
.debug_ranges          37473728          0
Total                4242540803

我想我们可以从中看到 'debug_str' 占用 ~3.6 GB。我不是 100% 知道 "debug_str" 是什么,但我猜它们可能确实是调试符号的字符串名称?那么这是否告诉我我的符号的 de-mangled 名称非常大?我怎样才能找出哪些问题并修复它们?

我想我可以用 'nm' 做点什么,直接检查符号名称,但输出量很大,我不确定如何最好地搜索它。有什么工具可以做这种分析吗?

使用的编译器是'c++ (GCC) 4.9.2'。我想我应该提到我在 linux 环境中工作。

我使用的一个技巧是在可执行文件上使用 运行 strings,这将打印所有那些长的(可能是由于模板)和大量的(同上)调试符号名称。您可以将其通过管道传输到 sort | uniq -c | sort -n 并查看结果。在许多大型 C++ 可执行文件中,您会看到这样的模式:

my_template<std::basic_string<char, traits, allocator>, std::unordered_map<std::basic_string<char, traits, allocator>, 1L>
my_template<std::basic_string<char, traits, allocator>, std::unordered_map<std::basic_string<char, traits, allocator>, 2L>
my_template<std::basic_string<char, traits, allocator>, std::unordered_map<std::basic_string<char, traits, allocator>, 3L>

你懂的。

在某些情况下,我决定简单地减少模板的数量。有时它会失控。其他时候,您可能会通过使用显式模板实例化或编译项目的特定部分而不调试符号来赢得一些东西,或者如果您不依赖 dynamic_casttypeid.[=15= 甚至禁用 RTTI ]

I guess I can somehow do something with 'nm', directly inspecting the symbol names, but the output is enormous and I'm not sure how best to search it. Are there any tools to do this kind of analysis?

您可以运行以下命令按符号长度对所有nm的符号输出进行排序:

nm --no-demangle -a -P --size-sort myexecutable \
    | awk '{ print length, [=10=] }' | sort -n -s | cut -d" " -f2-

(第一个 | 之后的所有内容都对 Sort a text file by line length including spaces 表示敬意。)这将最后显示最长的名称。您可以进一步将输出通过管道传输到 c++filt -t 以获得经过整理的名称,这可能有助于您的搜索。

根据您的情况,将可执行文件及其调试符号拆分到单独的文件中可能会很有用,这样您就可以将不那么臃肿的可执行文件分发到您的目标 environments/clients/etc,并保留调试如果需要,将符号放在一个位置。有关详细信息,请参阅 How to generate gcc debug symbol outside the build target?

所以我主要根据 通过执行以下操作找到了罪魁祸首。本质上,我只是按照他的建议在可执行文件上 运行 "string" 并分析了输出。

strings my_executable > exec_strings.txt

然后我主要按照 :

对输出进行排序
cat exec_strings.txt | awk '{ print length, [=11=] }' | sort -n -s | cut -d" " -f2- > exec_strings_sorted.txt

并查看了最长的字符串。事实上,这一切似乎都是来自某个特定库的一些疯狂的模板膨胀。然后我做了更多的计数,比如:

cat exec_strings.txt | wc -l
2928189
cat exec_strings.txt | grep <culprit_libname> | wc -l
1108426

在提取的大约 300 万个字符串中,似乎有大约 100 万个字符串来自该库。最后,做

cat exec_strings.txt | wc -c
3659369876
cat exec_strings.txt | grep <culprit_libname> | wc -c
3601918899

很明显,这百万个字符串都超长,构成了调试符号垃圾的绝大部分。所以至少现在我可以专注于这个库,同时尝试消除问题的根源。