实验4高级语言的机器级表示
实验序号:4 实验名称:高级语言的机器级表示
适用专业:软件工程 学 时 数:2学时
一、实验目的
1、理解函数调用的机器级表示方法。
2、了解复杂数据结构的机器级表示方法。
3、理解分支和循环语句的机器级表示方法。
二、实验要求
按照实验题目的要求,编写程序并上机调试
三、实验设备、环境
计算机、Windows 7 、Visual C++ 6.0
四、实验步骤及内容
写一段C语言程序包含循环分支和过程调用,查阅并分析汇编代码,下面是参考可写的程序,可以不按照以下程序来写,特别注意可以没有数组。
定义一个数组int sum[5];
通过循环从屏幕输入5个数进数组sum
调用函数int Sum(int sum[])计算数组中所有元素的和并返回
如果返回值大于50则输出平均值大于10,否则输出平均值小于10.
编译通过后查阅汇编代码并结合你写的C语言程序书写报告描述以下问题:
- 函数调用的汇编代码是如何描述的,参数放在什么地址(可以假设%ebp和%esp地址已知)
- 数组的汇编描述方法
- 分支语句的汇编描述方法
- 循环的汇编描述方法
提示:
查看汇编代码方法:
编译
设置断点 F11或在Build -> Start Debug ->Step Into
右键点击断点箭头,Go to disassembly
C语言代码
#include <stdio.h>
int g(int x)
{
return x + 99;
}
int f(int x)
{
return g(x);
}
void printStar(){
for(int i = 0;i < 10; i++)
{
printf("*");
}
}
int main() {
int a = 0;
int b = 0;
int op1 = 1, op2 = 2;
if (op1 = op2) {
a = 1;
b = 2;
}
return f(22) + 36;
}
汇编代码
g:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
addl $99, %eax
popl %ebp
ret
f:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
call g
leave
ret
printStar:
pushl %ebp
movl %esp, %ebp
subl $40, %esp
movl $0, -12(%ebp)
jmp .L6
.L7:
movl $42, (%esp)
call putchar
addl $1, -12(%ebp)
.L6:
cmpl $9, -12(%ebp)
jle .L7
leave
ret
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $528, %esp
movl $0, 524(%esp)
movl $0, 520(%esp)
movl $1, 516(%esp)
movl $2, 512(%esp)
movl 516(%esp), %eax
cmpl 512(%esp), %eax
jne .L9
movl $1, 524(%esp)
movl $2, 520(%esp)
.L9:
movl $66, 24(%esp)
movl $666, 28(%esp)
call printStar
movl $22, (%esp)
call f
addl $36, %eax
leave
ret
代码解释:
push %ebp, movl %esp, %ebp
两行代码,将ebp压入栈
然后esp的值赋给ebp,ebp指向与esp相同的位置
andl $-16,%esp
esp 与0xfffffff0 与运算,使得它对齐寻址空间,加快cpu处理速度
subl $528, %esp
指令,在栈中开辟528字节的空间。编译器会事先扫描函数中局部变量个数和大小,预分配给一个空间,供存储局部变量和参数调用。因为下面定义了一个122长的int数组,加上其他int变量,所以共需要开辟528字节空间存储局部变量。
movl $0, 524(%esp)
movl $0, 520(%esp)
movl $1, 516(%esp)
movl $2, 512(%esp)
这四句话是分别定义a,b,op1,op2四个变量,并分别将0,0,1,2赋值给这四个变量
1.分支语句的汇编描述方法
movl 516(%esp), %eax
cmpl 512(%esp), %eax
jne .L9
将op1赋给eax,然后cmpl让op1和eax中的op2进行比较
Jne指令为不等于跳转,若op1不等于op2则跳转到.L9
若等于则执行下面的指令
movl $1, 524(%esp)
movl $2, 520(%esp)
给a赋值为1,给b赋值为2
2.数组的汇编描述方法
接上述,如果不等于则跳转至.L9
movl $66, 24(%esp)
movl $666, 28(%esp)
给数组进行赋值内存中会分配一个sizeof(T)*N字节的连续的区域。
这里数组的基地址放在了esp中,先给arr[0]赋值66,接着基地址加上int的4,就是arr[1],给他赋值为666.
3.循环的汇编描述方法
上述过程执行完,就会执行函数调用
call printStar
将调用printStar函数
在printStar函数中
printStar:
pushl %ebp
movl %esp, %ebp
subl $40, %esp
movl $0, -12(%ebp)
jmp .L6
.L7:
movl $42, (%esp)
call putchar
addl $1, -12(%ebp)
.L6:
cmpl $9, -12(%ebp)
jle .L7
leave
ret
前两句是保存旧的帧,创建新的栈帧。
第三局开辟40字节的空间,因为接下来会循环十次,调用十个int i
接着给i赋值为0,jmp是无条件跳转,所以直接跳转到.L6
Cmpl 将i与9比较,如果比9小,将跳转.L7
movl $42, (%esp)
call putchar
addl $1, -12(%ebp)
接着将42给esp,42在ascill中对应的就是’’
接下来调用putchar
将输出
addl $1, -12(%ebp)
这句指令是对i进行加一
4.函数调用的汇编代码是如何描述的,参数放在什么地址(可以假设%ebp和%esp地址已知)
上述过程执行完后
movl $22, (%esp)
call f
将22压入栈(这里其实是定义接下来调用的f的形参int x,并赋值)
调用f,这里先保存eip(PC计数器)的值入栈,esp的值-4,然后将f的地址赋给eip
进入f函数
执行创建新的栈帧的两条指令,进入新的函数调用栈
将ebp向前8个字节位置的值(main中压栈的22)压栈,esp-4
这里是定义g的形参并赋值
调用g,eip先压栈,然后eip变为g的地址
进入g函数:
首先执行创建新的栈帧的两条指令,进入新的调用栈(同上)
movl 8(%ebp), %eax
首先寻址,将f中压栈的数(也就是g的形参x),赋给eax
addl $99, %eax
eax中得值+99,对应C语句 x + 99
popl %ebp
这里因为g中没有开辟新的栈空间(esp的值没有变化),所以这句和leave语句等价
执行完,这一句,ebp恢复到之前的值(f中得状态)
执行ret,其实是执行popl eip,将栈中保存的eip的值,赋给eip
此时ebp、esp和eip均恢复到调用f中得状态
退出g,返回值保存在eax中
回到f函数:
执行leave 和 ret,恢复esp、ebp、eip的值,同上
f仅仅返回g的返回值,这里不改动eax,里面保存着g的返回值
回到main函数:
addl $36, %eax
f(22)的返回值+36,相当于C中得 f(22) + 36
main函数leave ret 返回上一层
gcc中应该为__libc_start_main,它可以接收到main的返回值,在eax中