此信息可在二进制文件的符号表中找到,尽管它可能与您期望的不同。
编译器获取一个或多个源文件,将代码编译为目标代码,并生成一个目标文件(Unix 上为 .o,Windows 上为 .obj)。源文件中引用的所有变量和函数都在符号表中提及。源文件中定义的变量和函数具有特定的地址和大小,而源文件中未定义的符号则标记为未定义,并且必须稍后链接。所有符号都相对于特定部分列出。常见的部分是“.text”用于可执行代码,“.bss”用于在程序启动时初始化为零的变量,“.data”用于使用非零值初始化的变量。
链接器获取一个或多个目标文件,组合这些部分(将每个目标文件中的所有代码和数据放入一个大的代码和数据部分),然后写入一个输出文件。此输出文件可能是可执行文件,也可能是共享库。磁盘上的可执行文件仍然没有每个变量的指针;它仍然将节开头的偏移量存储到变量中。
运行可执行文件时,操作系统的动态加载程序会读取可执行文件,找到每个部分,并为该部分分配内存。 (它还可能在每个部分上设置不同的权限——“.text”段通常被标记为只读,并且(在支持它的处理器上)数据段有时被标记为不可执行。)只有这样变量获取指针——当代码需要访问一个特定的变量时,它会将节的开头地址加上节开头的偏移量来获取指针。
您可以使用各种工具来调查每个二进制文件的符号表。 GNU 工具链的objdump(在 Linux 上使用)就是这样一种工具。
对于一个简单的 C hello-world 程序:
#include <stdio.h>
const char message[] = "Hello world!\n";
int main(int argc, char ** argv) {
printf(message);
return 0;
}
我在我的 Linux 机器上编译(但不链接)它:
$ gcc -c hello.c -o hello.o
现在我可以查看符号表了:
$ objdump -t hello.o
hello.o: file format elf32-i386
SYMBOL TABLE:
00000000 l df *ABS* 00000000 hello.c
00000000 l d .text 00000000 .text
00000000 l d .data 00000000 .data
00000000 l d .bss 00000000 .bss
00000000 l d .rodata 00000000 .rodata
00000000 l d .note.GNU-stack 00000000 .note.GNU-stack
00000000 l d .comment 00000000 .comment
00000000 g O .rodata 0000000e message
00000000 g F .text 0000002b main
00000000 *UND* 00000000 puts
第一列是每个符号的地址,相对于节的开头。每个符号都有不同的标志,其中一些符号用作工具链和调试器其余部分的提示。 (如果我使用调试符号构建,我也会看到许多专门针对它们的条目。)我的简单程序只有一个变量:
00000000 g O .rodata 0000000e message
第五列告诉我符号 message 的大小为 0xe -- 14 字节。