编译 一个程序将人类可读的源文件(如 set x = 3)转换为汇编语言(如 load 3 into register),最终转换为机器语言(如 101010101…)。
调试 是逐步执行程序以识别错误原因的过程。但是,如何逐步执行一个存储为底层机器指令的程序呢?(有些天赋异禀的人能够阅读并解读原始计算机指令——我不行)。
符号表(Symbol Table)
这时符号表就派上用场了。该表将编译后的二进制程序中的指令映射到源代码中对应的变量、函数或行。这种映射可能类似于:
- 程序指令 => 项目名称, 项目类型, 原始文件, 定义的行号...
[旁注:我不完全确定符号表是如何实现的。它们可以在源代码中添加标签(如 SymbolID),或者存储指令的地址并将其映射到变量声明、变量使用、行号等。]
符号表可以嵌入到程序中,也可以作为单独的文件存储。
符号表可能默认不创建——必须告诉编译器创建一个带有符号表的“调试”版本(对于 GCC 编译器,使用“-g”选项)。没有符号表的程序称为“零售”构建,更难进行逆向工程——它没有将二进制程序映射回原始源代码的信息。
符号表不包含源代码,但可以通过引用实际的变量和函数名来提供线索。编译后的二进制程序中没有变量名——所有操作都使用编号的寄存器完成。
调试器(Debugger)
“调试器”是一个读取符号表并允许程序员遍历被调试程序的应用程序。它可以执行并逐步执行程序,显示源代码的行。这对于修复错误非常有用——如果程序崩溃,可以重现该行为,并查看导致崩溃的源代码中的确切行。修复错误,然后重试。
调试器还可以:
- 设置断点 — 当程序到达某行代码时暂停(用于检查错误状况)
- 设置变量 — 更改内部程序变量,查看软件如何响应
- 设置监视和条件 — 当满足特定条件时暂停(亦称中断)程序,例如变量达到某个值
我们可以推断出关于符号表的一些事实:
- 符号表适用于程序的特定版本 – 如果程序改变,必须创建新表。
- 调试构建通常比零售(非调试)构建更大且更慢;调试构建包含符号表和其他辅助信息。
- 如果您希望调试未自行编译的二进制程序,必须从作者处获取符号表。
更多信息: