可执行程序
可执行文件:状态机的描述
一个描述了状态机初始状态 + 迁移的数据结构
- 寄存器:大部分由ABI规定,操作系统负责设置。例如初始化pc。
- 地址空间:二进制文件+ABI共同决定。例如argv和envp的存储。
- 其他有用的信息(例如调试和core dump的信息)
可执行文件里面应该有什么?
可执行程序描述了状态机重置后的状态,那状态有什么呢?
无非就是寄存器和内存(地址空间)
操作系统上的可执行程序
需要满足以下条件:
具有执行(x)权限
执行./a.c命令 (出现permission denied错误)
但如果先执行 chmod +x a.c命令,会出现加载器不能正确识别题。加载器能够识别的可执行文件
常见的可执行文件
就是操作系统里面的一个普通对象。
UNIX/Linux
- a.out
- ELF
- She-bang
She-bang是什么呢?其实就是一个“偷换参数”地execve。
1 |
|
再执行命令 chmod +x a.c。上面这个文件就可以执行了。
如果加载器这样一个程序的时候,如果它发现一个#!开头的,就会在execve偷换一下,把#!后面的填入execve的第一个参数,该文件名填入第二个参数。
从C代码到二进制文件
一段简单的C代码(main.c):
1 |
|
gcc -O2 -c main.c得到main.o文件, 然后objdump -d main.o得到反汇编
1 |
|
我们可以看到 0xa 地址处,由于不知道hello函数的地址,这里暂时填为 0
hello.c的代码块:
1 |
|
解释一下:在我们的main函数要调用hello函数时,此时pc会指向这条call指令的下一条指令的地址,也就是pc指针会是(char*)main + 0xf,由于是相对寻址(call指令的语义),所以会跳转到pc + offset的位置,offset也就是待填地址处的值。而跳转后的地址要是hello的地址。
我们 readelf -a main.o 来看一下有什么输出信息,其中有一部分是这样的(这里复制过来格式有点不正确):
1 |
|
重新理解编译、链接流程
编译器
High-level semantics(高级C状态机) -> Low-level semantics(汇编状态机)
汇编器
Low-level semantics -> Binary semantics(状态机的容器)
- “一一对应”地翻译成二进制代码,sections, symbols, debug info……
- 不能决定的要留下“之后要怎么办”的信息(relocations重定位)
链接器
合并所有的容器,得到一个完整的状态机