减少调试符号的占用空间(可执行文件膨胀到 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_cast
或 typeid
.[=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
很明显,这百万个字符串都超长,构成了调试符号垃圾的绝大部分。所以至少现在我可以专注于这个库,同时尝试消除问题的根源。
因此,基本问题是我构建的可执行文件大小为 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_cast
或 typeid
.[=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?。
所以我主要根据
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
很明显,这百万个字符串都超长,构成了调试符号垃圾的绝大部分。所以至少现在我可以专注于这个库,同时尝试消除问题的根源。