本周学习了《How the Computer Works》,学习了一些常见的汇编指令,了解了计算机内部工作的原理:
一实验
(一)编写C程序,并编译
// main.c
int g(int x)
{
return x + 1;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(1) + 1;
}
$ gcc –S –o main.s main.c -m32
编译后
删除以“.”开头的辅助信息后
g:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
addl $1, %eax
popl %ebp
ret
f:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
call g
leave
ret
main:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl $1, (%esp)
call f
addl $1, %eax
leave
ret
(二)下面分析过程
1.首先执行main函数,
pushl %ebp
保存调用函数的基址(ebp中)到栈中,压栈操作此时esp自动下移一个地址空间(4个字节)
movl %esp, %ebp
将esp的内容放到ebp中,设置ebp为当前被调用函数的基址指针,即当前栈顶
subl $4, %esp
esp下移一个地址空间(4个字节),为当前函数分配1个栈空间(4个字节)用于存储局部变量
movl $1, (%esp)
将1放到esp当前指向的位置
call f
=pushl %eip(*) movl f %eip(*)
将此时eip中的值(下一条指令addl $1, %eax
)压栈,然后将f函数的地址放到eip中,这样程序转而执行f函数。
2.执行f函数
pushl %ebp
保存调用函数的基址(ebp中)到栈中,压栈操作此时esp自动下移一个地址空间(4个字节)
movl %esp, %ebp
将esp的内容放到ebp中,设置ebp为当前被调用函数的基址指针,即当前栈顶
subl $4, %esp
esp下移一个地址空间(4个字节),为当前函数分配1个栈空间(4个字节)用于存储局部变量
movl 8(%ebp), %eax
ebp中的地址上移2个(8个字节)地址空间的内容放到eax中,也就是将数字1放到eax中
movl %eax, (%esp)
将eax中的内容放到esp当前指向的位置
call g
=pushl %eip(*) movl g %eip(*)
将此时eip中的值(下一条指令leave
)压栈,然后将g函数的地址放到eip中,这样程序转而执行g函数。
3.执行g函数
pushl %ebp
保存调用函数的基址(ebp中)到栈中,压栈操作此时esp自动下移一个地址空间(4个字节)
movl %esp, %ebp
将esp的内容放到ebp中,设置ebp为当前被调用函数的基址指针,即当前栈顶
movl 8(%ebp), %eax
将ebp上移2个地址空间的内容放到eax中,就是数字1
addl $1, %eax
将1与eax中的数相加,也就是2放到eax中
popl %ebp
恢复ebp为调用函数基址
ret
=popl %eip(*)
就是f函数中的leave
4.返回执行f函数
leave
=movl %ebp,%esp popl %ebp
将ebp中的那内容放到esp,将回复ebp为调用函数基址
ret
=popl %eip(*)
就是main函数中的addl $1, %eax
5.返回执行main函数
addl $1, %eax
将1与eax中的内容相加,也就是1+2=3,将3放到eax中
leave
=movl %ebp,%esp popl %ebp
将ebp中的那内容放到esp,将回复ebp为调用函数基址
ret
返回main函数之前的堆栈
二、遇到的问题
1.没有正确理解寄存器间接寻址的方式
寄存器间接寻址是使用的(%reg),这样表示的是寄存器中存的地址所指向的内存单元的数据,例如
movl %eax, (%esp)
esp如果没加括号,那么是将eax的内容放到esp中,esp加了括号,那么是将eax的内容放到esp中的地址所指向的内存单元中
2.没有正确理解立即数的含义
'$' 前缀表示一个立即操作数,'$1'就表示1这个立即数,例如
movl $1,%edx
是将数1存入edx中,
movl 1,%edx
这是一个直接寻址,其中1表示的是个地址,表示的则是将地址1中的数据存入edx
3.不理解Linux下进程地址空间的布局,一开始不理解为什么压栈地址空间是向下减,查看了相关资料,
参考,http://www.minazuki.cn/post/blog_os/blog_os-1btagnqm2aabq/blog_os-1bt8jo9suer6l/
进程用户空间的最高位置是用来存放程序运行时的命令行参数及环境变量的,在这段地址空间的下方和bss段的上方还留有一个很大的空洞,而作为进程动态运行环境的堆栈和堆就栖身其中,其中堆栈向下伸展,堆向上伸展。
堆栈帧的顶部为函数的实参,下面是函数的返回地址以及前一个堆栈帧的指针,最下面是分配给函数的局部变量使用的空间。一个堆栈帧通常都有两个指针,其中一个称为堆栈帧指针(ebp),另一个称为栈顶指针(esp)。前者所指向的位置是固定的,而后者所指向的位置在函数的运行过程中可变。因此,在函数中访问实参和局部变量时都是以堆栈帧指针为基址,再加上一个偏移。对照图4可知,实参的偏移为正,局部变量的偏移为负。