为什么编译后符号表还存在
Why do symbol tables still exist after compilation
我知道符号表是由编译器创建的,以帮助其处理。
当它们被链接在一起时,它们存在于每个目标文件中。
假设:
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
的意思吗?这是正常的做法吗?是否使用了其他工具?
Why are these symbols still needed?
执行的正确性不需要它们,但它们有助于调试。
一些程序可以记录自己的堆栈跟踪(例如 TCMalloc
执行分配采样),并在崩溃(或其他类型的错误)时报告它。
虽然所有这些堆栈跟踪都可以离线符号化(给定一个 确实 包含符号的二进制文件),但程序生成符号化堆栈跟踪通常要方便得多,因此您不需要找到匹配的二进制文件。
考虑这样一种情况,您在云中有 1000 个不同应用程序 运行 的多个版本,并且您收到 100 份崩溃报告。它们是相同的崩溃,还是有不同的原因?
如果你只有一堆十六进制数字,那就很难说了。您必须为每个实例找到一个匹配的二进制文件,对其进行符号化,然后与所有其他实例进行比较(自动化可以在此处提供帮助)。
但是如果你有符号形式的堆栈跟踪,一眼就很容易分辨出来。
这确实会带来一些成本:您的二进制文件可能比它们必须的大 1%。
why doesn't the linker remove them once done?
您必须记住传统的 UNIX 根源。在开发 UNIX 的环境中,每个人都可以访问所有 UNIX 实用程序(包括 ld
)的源代码,并且可调试性比保密更重要。所以我一点也不惊讶选择这个默认值(保留符号)。
比较 Microsoft 的选择 -- 将所有内容保存到 .DBG
(后来的 .PDB
个文件)。
aren't they potentially a security risk for hackers to read the source?
它们对逆向工程很有帮助,是的。他们不包含源代码,因此除非源代码已经打开,否则他们不会添加那个太多。
不过,如果您的程序包含类似 CheckLicense()
的内容,这有助于黑客集中精力绕过您的许可证检查。
这就是为什么商业二进制文件经常被完全剥离的原因。
更新:
Is this what you mean by keeping symbol tables for debugging release builds?
是的。
is this the normal way of doing it?
这是一种方法。
are there other tools used?
是:请参阅下面的最佳做法。
P.S。最佳做法是使用 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
P.P.S.
gcc -g is used for gdb. gcc without -g still has symbol tables.
迟早你会发现你必须对构建的二进制文件执行调试 没有 -g
(例如当没有 -g
构建的二进制文件崩溃时,但是使用 -g
构建的二进制文件不会崩溃)。
当那一刻到来时,如果二进制文件仍然有符号 table.
,您的工作将 容易得多
我知道符号表是由编译器创建的,以帮助其处理。 当它们被链接在一起时,它们存在于每个目标文件中。
假设:
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
的意思吗?这是正常的做法吗?是否使用了其他工具?
Why are these symbols still needed?
执行的正确性不需要它们,但它们有助于调试。
一些程序可以记录自己的堆栈跟踪(例如 TCMalloc
执行分配采样),并在崩溃(或其他类型的错误)时报告它。
虽然所有这些堆栈跟踪都可以离线符号化(给定一个 确实 包含符号的二进制文件),但程序生成符号化堆栈跟踪通常要方便得多,因此您不需要找到匹配的二进制文件。
考虑这样一种情况,您在云中有 1000 个不同应用程序 运行 的多个版本,并且您收到 100 份崩溃报告。它们是相同的崩溃,还是有不同的原因?
如果你只有一堆十六进制数字,那就很难说了。您必须为每个实例找到一个匹配的二进制文件,对其进行符号化,然后与所有其他实例进行比较(自动化可以在此处提供帮助)。
但是如果你有符号形式的堆栈跟踪,一眼就很容易分辨出来。
这确实会带来一些成本:您的二进制文件可能比它们必须的大 1%。
why doesn't the linker remove them once done?
您必须记住传统的 UNIX 根源。在开发 UNIX 的环境中,每个人都可以访问所有 UNIX 实用程序(包括 ld
)的源代码,并且可调试性比保密更重要。所以我一点也不惊讶选择这个默认值(保留符号)。
比较 Microsoft 的选择 -- 将所有内容保存到 .DBG
(后来的 .PDB
个文件)。
aren't they potentially a security risk for hackers to read the source?
它们对逆向工程很有帮助,是的。他们不包含源代码,因此除非源代码已经打开,否则他们不会添加那个太多。
不过,如果您的程序包含类似 CheckLicense()
的内容,这有助于黑客集中精力绕过您的许可证检查。
这就是为什么商业二进制文件经常被完全剥离的原因。
更新:
Is this what you mean by keeping symbol tables for debugging release builds?
是的。
is this the normal way of doing it?
这是一种方法。
are there other tools used?
是:请参阅下面的最佳做法。
P.S。最佳做法是使用 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
P.P.S.
gcc -g is used for gdb. gcc without -g still has symbol tables.
迟早你会发现你必须对构建的二进制文件执行调试 没有 -g
(例如当没有 -g
构建的二进制文件崩溃时,但是使用 -g
构建的二进制文件不会崩溃)。
当那一刻到来时,如果二进制文件仍然有符号 table.
,您的工作将 容易得多