一个C源文件到可执行文件 [反汇编-函数栈帧 编译 链接]
《Linux C 编程一站式学习》第18,19章练习。
平台:x86/Debian GNU/Linux gcc
1 C源文件代码对应的指令
计算机是由数字电路组成的运算机器,只能对数字做运算。加载到内存中运行的文件被称之为可执行文件,可执行文件中的二进制对应着C源代码的标识符和数据。由一个C源文件到可执行文件可分为两个阶段:编译和链接。对可执行文件进行反汇编可以看到C代码中的每个语句所对应的机器指令bytes(left)及反汇编代码(right)。如以下C语言程序可用gcc编译器来描述其生成可执行文件的过程:
/* Filename: c_to_elf.c
* Brife: To see c source file to be elf file by compiling and link
* Date: 2014.7.20 - Sunday
* Author: One fish
*/
#include <stdio.h>
//------Global varible-----------
static int i;
//------Functions decaration
int add_ij(int i, int j);
//---Main the entry of this application
int main(void)
{
int j, k = 1;
i = k;
j = 2;
add_ij(i, j);
return 0;
}
int add_ij(int i, int j)
{
int k;
k = i + j;
return k;
}
(1) gcc命令选项
(2) 目标文件
选择由C源文件汇编文件得到目标文件:
gcc -g -S c_to_elf.c [生成c_to_elf.s,是c_to_elf.c对应的汇编代码]
gcc -g -c c_to_elf.s[生成c_to_elf.o,即为目标文件]
-g选项是使生成的文件能够关联上C源代码。
(3) 反汇编--可执行文件
选择由(2)中生成的目标文件得到可执行文件c_to_elf:gcc -g c_to_elf.o -o c_to_elf。目标文件也可以进行反汇编。
用objdump命令可对可执行文件进行反汇编: objdump –dS c_to_elf > exe_disassembly.txt 。对c_to_elf的反汇编信息都在exe_disassembly.txt文件中,只看C代码对应指令部分[第一列为指令地址,中间的几列为机器指令,最后的是汇编代码]:080483dc <main>:
int add_ij(int i, int j);
//---Main the entry of this application
int main(void)
{
80483dc: 55 push %ebp
80483dd: 89 e5 mov %esp,%ebp
80483df: 83 e4 f0 and $0xfffffff0,%esp
80483e2: 83 ec 20 sub $0x20,%esp
int j, k = 1;
80483e5: c7 44 24 1c 01 00 00 movl $0x1,0x1c(%esp)
80483ec: 00
i = k;
80483ed: 8b 44 24 1c mov 0x1c(%esp),%eax
80483f1: a3 84 96 04 08 mov %eax,0x8049684
j = 2;
80483f6: c7 44 24 18 02 00 00 movl $0x2,0x18(%esp)
80483fd: 00
add_ij(i, j);
80483fe: a1 84 96 04 08 mov 0x8049684,%eax
8048403: 8b 54 24 18 mov 0x18(%esp),%edx
8048407: 89 54 24 04 mov %edx,0x4(%esp)
804840b: 89 04 24 mov %eax,(%esp)
804840e: e8 07 00 00 00 call 804841a <add_ij>
return 0;
8048413: b8 00 00 00 00 mov $0x0,%eax
}
8048418: c9 leave
8048419: c3 ret
0804841a <add_ij>:
int add_ij(int i, int j)
{
804841a: 55 push %ebp
804841b: 89 e5 mov %esp,%ebp
804841d: 83 ec 10 sub $0x10,%esp
int k;
k = i + j;
8048420: 8b 45 0c mov 0xc(%ebp),%eax
8048423: 8b 55 08 mov 0x8(%ebp),%edx
8048426: 01 d0 add %edx,%eax
8048428: 89 45 fc mov %eax,-0x4(%ebp)
return k;
804842b: 8b 45 fc mov -0x4(%ebp),%eax
}
804842e: c9 leave
804842f: c3 ret
(4) 由反汇编分析程序运行时堆栈内存的变化
虽然程序中的内存地址都是虚拟地址,但可根据虚拟内存与物理内存的映射关系用虚拟内存地址来模拟堆栈内存上物理地址的变化。
[1]进入main函数main栈帧的开始
[2]main函数内的局部变量的栈内存
[3] main函数栈帧的形成
[4] 子函数add_ij()栈帧的分配与回收
[5]main函数栈帧的回收
main()是一个具入口性质的函数(首先被调用),当main函数执行完毕后。它的函数栈帧以相同的方式被回收,ebp与esp会返回到到上一层栈帧(如果有)。
2 编译----C源文件到目标文件
编译是指将C代码翻译成汇编或机器指令的过程。
(1) C程序对应的汇编代码
C源文件可被gcc编译为汇编文件。即此笔记前提到的c_to_elf.s文件,摘main和add_ij()函数对应的汇编代码如下:
Figure7.main和add_ij()对应的汇编代码
(2) C程序的目标文件
笔记前提到的c_to_elf.o为c_to_elf.c对应的目标文件,在linux下用readelf –a c_to_elf.o > obj.txt命令将目标文件中的内容读到obj.txt文件中,截取一部分出来瞧瞧:
[1] ELF Header
Figure8.目标文件中的ELF Header
[2] Section headers
截图看以下section headers的信息:
3 链接----C源文件到可执行文件
链接主要有两个作用,一是修改目标文件中的信息,对地址做重定位;二是把多个目标文件合并成一个可执行文件。目标文件需要经过链接才可以成为可执行文件,才能被操作系统加载到内存中运行。可执行文件是指前笔记中生成的c_to_elf。[1] ELF Header
[2] Section Headers
截取一部分如下图:
[3] Program Headers
Program Headers用来描述Segment的信息。Segment由多个Section组成。一般是将具有共同属性如.data和.bss汇聚为一个Segement加入到内存。个人理解目标文件以Section的形式存在,可执行文件以Segement的形式存在以方便加载到内存中运行。
4 小结
一个C源文件经过编译和链接可形成可执行文件。编译的过程C源码翻译成机器指令或汇编代码。对目标文件进行反汇编可以看到,程序中地址如函数的地址是用的相对地址。经过链接的目标文件为程序分配了虚拟地址,程序中使用的是绝对地址(反汇编查看)。
虚拟内存可解决链接时为可执行文件加载到内存中的地址冲突问题。如果直接使用物理地址,怎么敢保证链接时为程序分配的地址没有被用到。而有了MMU后,跟程序有关的地址都是虚拟地址,操作系统会根据内存中的页表将程序加载到可用的内存中去。
更多推荐
所有评论(0)