【问题标题】:Why do symbol tables still exist after compilation为什么编译后符号表仍然存在
【发布时间】:2020-03-19 01:55:20
【问题描述】:

我了解符号表是由编译器创建以帮助其处理的。 当它们被链接在一起时,它们存在于每个目标文件中。

假设:

void test(void){
 //
}
void main(){
  return 0;
}

用 gcc 编译上面并运行nm a.out 显示:

0000000100000fa0 T _main
0000000100000f90 T _test

为什么仍然需要这些符号?为什么链接器在完成后不删除它们?对黑客阅读源代码而言,它们不是潜在的安全风险吗?

编辑

这就是你调试发布二进制文件的意思吗(编译时没有-g)?

假设:

int test2(){
 int *p = (int*) 0x123;
 return *p;
}

int test1(){
 return test2();  
}

int main(){
 return test1();
}

test2 上的哪些段错误。正在做gdb ./a.out > where 显示:

(gdb) where
#0  0x000055555555460a in test2 ()
#1  0x000055555555461c in test1 ()
#2  0x000055555555462c in main ()

但是剥离a.out 并做同样的事情:

(gdb) where
#0  0x000055555555460a in ?? ()
#1  0x000055555555461c in ?? ()
#2  0x000055555555462c in ?? ()

这就是keeping symbol tables for debugging release builds 的意思吗?这是正常的做法吗?是否使用了其他工具?

【问题讨论】:

  • 你显然从未使用过调试器。
  • 可能比你用得更多。 gcc -g 用于 gdb。没有-g 的 gcc 仍然有符号表。
  • -g 将更多调试信息添加到图像中。但这是符号提供的额外内容。如果没有符号,除非您知道加载地址,否则您将无法在 test 函数上设置断点。试试看:在可执行文件上运行strip,然后尝试在任何函数上设置断点。
  • @Josh:当然,没有理由调试发布版本,因为它们从来没有错误并且总是与调试版本完全相同。
  • @Josh 您的船舶版本是为您的客户构建的,对吗?那么,如果您交付的程序崩溃并且您的客户有核心转储,那么您如何理解没有符号的情况?您不必仅仅因为链接器将符号提供给您就发送符号。

标签: c linker symbol-table


【解决方案1】:

为什么仍然需要这些符号?

它们不是正确执行所必需的,但它们有助于调试。

一些程序可以记录自己的堆栈跟踪(例如TCMalloc 执行分配采样),并在崩溃(或其他类型的错误)时报告它。

虽然所有此类堆栈跟踪都可以离线符号化(给定一个确实包含符号的二进制文件),但程序生成符号化堆栈跟踪通常更方便,所以你不要' t 需要找到匹配的二进制文件。

假设您在云中运行了 1000 多个不同版本的不同应用程序,并且收到了 100 份崩溃报告。它们是同一个崩溃,还是有不同的原因?

如果你只有一堆十六进制数字,那就很难分辨了。您必须为每个实例找到一个匹配的二进制文件,对其进行符号化,并与所有其他实例进行比较(自动化在这里可以提供帮助)。

但如果你有符号化形式的堆栈跟踪,一目了然就很容易分辨。

这确实会带来一些成本:您的二进制文件可能比它们必须的大 1%。

为什么链接器在完成后不删除它们?

您必须记住传统的 UNIX 根源。在开发 UNIX 的环境中,每个人都可以访问所有 UNIX 实用程序的源代码(包括ld),可调试性比保密更重要。所以我对选择这个默认值(保留符号)一点也不感到惊讶。

比较微软做出的选择——将所有内容保存到.DBG(后来的.PDB文件)。

它们不会对黑客阅读源代码构成潜在的安全风险吗?

它们对逆向工程很有帮助,是的。它们不包含源,因此除非源已经打开,否则它们不会添加太多

不过,如果您的程序包含 CheckLicense() 之类的内容,这有助于黑客集中精力绕过您的许可证检查。

这就是为什么商业二进制文件通常是完全剥离的原因。

更新:

这就是你为调试发布版本保留符号表的意思吗?

是的。

这是正常的做法吗?

这是一种方法。

是否使用了其他工具?

是的:请参阅下面的最佳做法。

附:最佳做法是使用 full 调试信息构建您的二进制文件:

gcc -c -g -O2 foo.c bar.c
gcc -g -o app.dbg foo.o bar.o ...

然后保留完整的调试二进制文件 app.dbg 以供您在需要调试崩溃时使用,但将完全剥离的版本 app 发送给您的客户:

strip app.dbg -o app

附言

gcc -g 用于 gdb。没有 -g 的 gcc 仍然有符号表。

迟早你会发现你必须对一个没有-g构建的二进制文件执行调试(例如当二进制文件没有-g构建时)崩溃,但使用-g 构建的不会崩溃)。

当那个时刻到来时,如果二进制文件仍然有符号表,你的工作会轻松很多

【讨论】:

  • 非常感谢您提供此信息。我在上面的Edit 下添加了一个问题来清除一些东西。是真的吗?这是正常的做法吗?是否使用了其他工具?我知道 pstack 只用于 32 位,现在已经过时了。
  • 谢谢,最后一个问题,是否可以在没有原始可执行文件的情况下分析核心转储?我知道gdb不可能。还有其他工具吗?
  • @Josh 这取决于您要执行的确切分析。 一些分析可以在没有原始可执行文件的情况下使用 GDB、readelfobjdump 等进行。这确实值得单独提出一个问题。
猜你喜欢
  • 2015-09-30
  • 1970-01-01
  • 1970-01-01
  • 2011-08-03
  • 1970-01-01
  • 2019-02-13
  • 1970-01-01
  • 1970-01-01
  • 2018-09-18
相关资源
最近更新 更多