ID **超 SA1****256
测试环境
Ubuntu 12.10
Linux-kernel: 3.5.0-17-generic
gcc 版本gcc.real (Ubuntu/Linaro 4.7.2-2ubuntu1) 4.7.2
本次为我的实验作业,并对linux进行了进一步的加深理解。
首先,概述一下编译链接的过程:C语言的编译链接过程是将程序源代码转换成可以运行的可执行文件,需要进行编译和链接。编译就是把文本形式源代码(高级语言指令)转换为功能等效的汇编代码。链接是把目标文件、操作系统的启动代码和用到的库文件链接最终生成二进制可执行代码的过程。完整过程可以描述如下():
1.预处理(.c)
2.编译成汇编代码(.s)
3.汇编成目标代码(.o)
4.链接(可执行文件)
实验代码:test.c源码
步骤一:
int g(int x)
{
return x + 3;
}
int f(int x)
{
return g(x);
}
int main()
{
return f(8) + 1;
}
步骤二: gcc -E -o test.cpp test.c
# 1 "test.c"
# 1 "<command-line>"
# 1 "test.c"
int g(int x)
{
return x + 3;
}
int f(int x)
{
return g(x);
}
int main()
{
return f(8) + 1;
}
步骤三:
然后是编译成汇编代码gcc -x cpp-output -S -o test.s test.cpp ,
其中参数-x说明根据指定的步骤进行工作,cpp-output指明从预处理得到的文件开始编译,-S说明生成汇编代码后停止工作。生成的汇编代码如下;
生成 test.s 如下
<span style="font-size:14px;"> .file "test.c"
.text
.globl g
.type g, @function
g:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
movl 8(%ebp), %eax
addl $3, %eax
popl %ebp
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size g, .-g
.globl f
.type f, @function
.LFB1:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
call g
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE1:
.size f, .-f
.globl main
.type main, @function
main:
.LFB2:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $4, %esp
movl $8, (%esp)
call f
addl $1, %eax</span><pre name="code" class="cpp"> subl $4, %esp
movl $8, (%esp)</pre><br>
leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc.LFE2: .size main, .-main .ident "GCC: (Ubuntu/Linaro 4.7.2-2ubuntu1) 4.7.2" .section .note.GNU-stack,"",@progbits
接下来我们来分析一下程序运行时栈的变化 (注:栈在内存中是从高地址往低地址的方向!)
程序入口为 main
说明:先是用pushl 命令将ebp 压入栈中,然后再讲esp的地址赋给ebp,即esp和ebp现在都指向了栈顶。如下图所示
pushl %ebp
movl %esp, %ebp
在接下来看
subl $4, %esp
movl $8, (%esp)
因为栈是从高地址往低地址,
subl $4, %esp
是esp往下移动4个地址,然后将8压入到esp所指的地址当中,见下图
然后是call f ,将eip入栈,即保存返回地址,然后将函数f的地址给eip,然后开始执行函数f
执行函数f的过程,首先仍然是保存旧栈,为了让之后能够顺利返回
pushl %ebp
movl %esp, %ebp
处理过程下图所示:
然后和之前类似
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
过程如下图所示:
然后call g 过程如和上面调用f一样
pushl %ebp
movl %esp, %ebp
上面为保护入口地址
接来来的指令为计算,并将结果存入eax寄存器中
popl %ebp
ret
返回 f
接下来执行
leave
ret
其中
leave等价为
movl %ebp , %esp;、popl %ebp;
ret 等价为
popl %eip
所以栈中现在的情况如下图所示
回到main中继续执行以下代码
addl $1, %eax
leave
ret
至此,结果存于eax寄存器当中,代码执行过程结束了。单任务模式:CPU通过读取EIP寄存器的值得值下一条指令的内存地址,整个过程中,始终由EIP保存将要执行的下一条指令的地址。函数调用过程中,需要保存现场,执行完后要返回,主要是通过EBP,ESP等寄存器实现的。多任务模式:则需要诸如中断,进程调度等结合分析。