一.准备工作
1. main.c代码如下
extern int u_n1;
extern void u_func1();
long t_n3;
long d_n4 = 3;
int d_func2(){
u_func1(u_n1, t_n3, d_n4);
}
2. 生成ELF文件命令
cc -o main.o -c main.c
二. 理论知识
1. nm命令的学习
2. ELF的格式
-
ELF文件就是我们使用上述命令生成的文件(main.o),它以.o结尾,就是我们常说的可重定位目标文件。
-
.text 存放代码节
-
.rodata 存放只读代码段
-
.rodata存放只读数据
-
.data存放已初始化数据
-
.bss存放未初始化数据
-
.symtab 就是我们博客的重点,符号表
3. .symtab
- 经过预处理,编译,汇编之后,我们得到了ELF文件,但是并不是源文件中的每一个符号都会被我们所解析,所认知。
- 正如我们提供的
main.c
一样,C++里有三类全局符号: - 引用的外部符号,以extern关键字标识
- 全局定义但为初始化的符号,形如
int xxx;
- 全局定义且初始化的符号,如
int a = 3;
- 现在我们要使用nm命令查看这三类符号
三. 使用nm命令分析ELF符号表
1. 打印.symtab
nm -f sysv -n main.o
其中-f指定了按照System5格式输出,-n指定了按照地址顺序排序
2. 分析符号表输出
- 注意看Class字段,U表示未定义,该符号的定义在别的文件中,t_func1和t_n1 是由extern关键字声明的,都是未定义的,这是符号我们预期的
- T表示该符号位于.text区中,d_func2是我们定义的函数,符合预期,
- D表示位于初始化数据段中,我们对于d_n4是给他初始化的,也符合预期
- C表示未初始化的数据段,在链接的过程中才对其分配,value为其预估的字节长度,这里t_n3需要8个字节,未初始化,均符合预期。
3. 反汇编
首先反汇编:objdump -d main.o
得到输出如下:
4. 汇编输出逐行解释
-
第一行:
我们的main.o还没有链接,所以此时的地址仍然是一个相对地址,所以d_func2的地址是从0x000…00开始,作为对比,稍后我会展示生成可执行文件后的地址 -
第二,三行
函数调用是要压栈的,所以我们要保存%rsp
栈指针的旧值,以便随后恢复上下文,我们将%rsp
放入%rbp
, 会破坏rbp
,所以我们先让rbp
的值入栈。 -
中间对程序计数器的操作
-
然后是参数入栈
-
注意寄存器的顺序,第一个参数是%rsi,第二个是%rdi,依次类推…
-
eax是返回值
-
最后是调用和现场恢复