课程
Answer
计算机系统基础---3.6 C语言程序的机器级表示(下) - 知乎
int funct (void) {
int x, y;
scanf("%x %x", &x, &y);
return x - y;
}
1 funct :
2 pushl %ebp
3 movl %esp, %ebp
4 subl $40, %esp
5 leal -8(%ebp), %eax
6 movl %eax, 8(%esp)
7 leal -4(%ebp), %eax
8 movl %eax, 4(%esp)
9 movl $.LC0, (%esp)
10 call scanf
11 movl -4(%ebp), %eax
12 subl -8(%ebp), %eax
13 leave
14 ret
int funct (void) {
int x, y;
scanf("%x %x", &x, &y);
return x - y;
}
1 funct :
2 pushl %ebp // 保存调用者的基址指针, 压栈,占4个字节, esp -= 4,esp = 0xbc00001c
3 movl %esp, %ebp // 设置基址指针, ebp = esp, ebp = 0xbc00001c
4 subl $40, %esp // 分配空间, esp -= (40)_10, esp = 0xbc00001c - (40)_10 -> (28)_16 = 0xbbfffff4
5 leal -8(%ebp), %eax // 计算y的地址, eax = ebp - 8 = 0xbc00001c - 8 = 0xbc000014
6 movl %eax, 8(%esp) // 将y的地址存入栈中, esp + 8 = 0xbbfffffc, 0xbbfffffc = 0xbc000014
7 leal -4(%ebp), %eax // 计算x的地址, eax = ebp - 4 = 0xbc00001c - 4 = 0xbc000018
8 movl %eax, 4(%esp) // 将x的地址存入栈中, esp + 4 = 0xbbfffff8, 0xbbfffff8 = 0xbc000018
9 movl $.LC0, (%esp) // 将格式字符串的地址存入栈中, esp = 0xbbfffff4, 0xbbfffff4 = $.LC0
10 call scanf // 调用scanf函数, esp -= 4, esp = 0xbbfffff0
11 movl -4(%ebp), %eax // 将x的值加载到eax寄存器中, eax = 0xbc000018
12 subl -8(%ebp), %eax // 将y的值从eax中减去, 结果存入eax
13 leave
14 ret
假设函数funct开始执行时,R[esp]=0xbc000020,R[ebp]=0xbc000030,执行第10行call指令后,scanf从标准输入读入的值为0x16和0x100,指向字符串”%x %x”的指针为0x804c000。回答下列问题或完成下列任务。
(1)执行第3、10和13行的指令后,寄存器EBP中的内容分别是什么?
(2)执行第3、10和13行的指令后,寄存器ESP中的内容分别是什么?
(3)局部变量x和y所在存储单元的地址分别是什么?
(4)画出执行第10行指令后funct的栈帧,指出栈帧中的内容及其地址。
Answer
| 地址 | 内容 | esp | ebp |
|---|---|---|---|
| bc 00 00 30 | funct开始执行时line 13 之后 | ||
| bc 00 00 2c | |||
| bc 00 00 28 | |||
| bc 00 00 24 | |||
| bc 00 00 20 | funct开始执行时 line 13 之后 | ||
| bc 00 00 1c | 旧的 ebp | line 2 之后 | line 3 之后 |
| bc 00 00 18 | x | ||
| bc 00 00 14 | y | ||
| bc 00 00 10 | |||
| bc 00 00 0c | |||
| bc 00 00 08 | |||
| bc 00 00 04 | |||
| bc 00 00 00 | |||
| bb ff ff fc | &y = 0xbc000014 | ||
| bb ff ff f8 | &x = 0xbc000018 | ||
| bc ff ff f4 | .LC0 "%x %x" = 0x804c000 | line 4 之后 | |
| bc ff ff f0 | scanf返回地址 | line 10后 |
(1)0xbc00001c,0xbc00001c,0xbc000030
(2)0xbc00001c,0xbbfffff0,0xbc000020
(3)0xbc000018,0xbc000014
MY
| 地址 | 内容 | esp | ebp |
|---|---|---|---|
| bc 00 00 30 | funct开始执行时line 13 之后 | ||
| bc 00 00 2c | |||
| bc 00 00 28 | |||
| bc 00 00 24 | |||
| bc 00 00 20 | 旧的 ebp | funct开始执行时 line 13 之后 | |
| bc 00 00 1c | line 2 之后 | line 3 之后 | |
| bc 00 00 18 | x | ||
| bc 00 00 14 | y | ||
| bc 00 00 10 | |||
| bc 00 00 0c | |||
| bc 00 00 08 | |||
| bc 00 00 04 | |||
| bc 00 00 00 | |||
| bb ff ff fc | &y = 0xbc000014 | ||
| bb ff ff f8 | &x = 0xbc000018 | ||
| bc ff ff f4 | .LC0 "%x %x" = 0x804c000 | line 4 之后 | |
| bc ff ff f0 |
pushl 和 call(?) 之后都会改变esp的值
GPT wrong
已知条件
-
函数开始执行时:
- R[esp] = 0xbc000020
- R[ebp] = 0xbc000030
-
scanf
从标准输入读取的值为:
- x = 0x16
- y = 0x100
-
指向字符串
"%x %x"的指针是 0x804c000。
(1) 执行第3、10和13行的指令后,寄存器EBP中的内容
- 第3行:movl %esp, %ebp
- 将
esp的值赋给ebp。 - 执行前
esp = 0xbc000020,所以此时ebp = 0xbc000020。
- 将
- 第10行:call scanf
- 调用函数时,将当前
eip(返回地址) 压栈,同时esp减少 4。 - 但此操作不改变
ebp,所以 ebp = 0xbc000020。
- 调用函数时,将当前
- 第13行:leave
- 等价于:
movl %ebp, %esp(恢复栈指针)popl %ebp(弹出旧的基址指针)
- 恢复了上一栈帧的
ebp值,即 ebp = 0xbc000030。
- 等价于:
结论:
- 第3行:
0xbc000020 - 第10行:
0xbc000020 - 第13行:
0xbc000030
(2) 执行第3、10和13行的指令后,寄存器ESP中的内容
- 第3行:movl %esp, %ebp
- 未修改
esp,仍为0xbc000020。
- 未修改
- 第10行:call scanf
- 调用函数会先压入返回地址 (4 字节),并分配参数所需空间。
- esp 在调用前已经被减小了 40 (第4行
subl $40, %esp)。 - 再调用
scanf时,esp = 0xbc000020 - 40 - 4 = 0xbc00000c。
- 第13行:leave
- 恢复上一栈帧的栈指针:
movl %ebp, %esp将esp设置为当前ebp值,即esp = 0xbc000020。
- 恢复上一栈帧的栈指针:
结论:
- 第3行:
0xbc000020 - 第10行:
0xbc00000c - 第13行:
0xbc000020
(3) 局部变量 x 和 y 的存储单元地址
根据汇编代码:
x的地址为-8(%ebp)→0xbc000020 - 8 = 0xbc000018y的地址为-4(%ebp)→0xbc000020 - 4 = 0xbc00001c
结论:
x的地址:0xbc000018y的地址:0xbc00001c
(4) 执行第10行指令后 funct 的栈帧
栈帧内容和地址分布如下:
| 地址 | 内容 |
|---|---|
0xbc000030 | 调用者的旧 ebp 值 (0xbc000030) |
0xbc00002c | 返回地址 (假设为 0x80484ab) |
0xbc000028 | scanf 的第3个参数 (未使用) |
0xbc000024 | scanf 的第2个参数 (变量 y 的地址 0xbc00001c) |
0xbc000020 | scanf 的第1个参数 (变量 x 的地址 0xbc000018) |
0xbc00001c | 局部变量 y 的值 (0x100) |
0xbc000018 | 局部变量 x 的值 (0x16) |
栈帧结构图:
plaintext
复制代码
+---------------+--------------------+
| 0xbc000030 | 调用者的 ebp |
+---------------+--------------------+
| 0xbc00002c | 返回地址 |
+---------------+--------------------+
| 0xbc000028 | 参数3 (未使用) |
+---------------+--------------------+
| 0xbc000024 | 参数2 (y 的地址) |
+---------------+--------------------+
| 0xbc000020 | 参数1 (x 的地址) |
+---------------+--------------------+
| 0xbc00001c | y 的值 (0x100) |
+---------------+--------------------+
| 0xbc000018 | x 的值 (0x16) |
+---------------+--------------------+
最终答案
(1)
- 第3行:
0xbc000020 - 第10行:
0xbc000020 - 第13行:
0xbc000030
(2)
- 第3行:
0xbc000020 - 第10行:
0xbc00000c - 第13行:
0xbc000020
(3)
x的地址:0xbc000018y的地址:0xbc00001c
(4) 见上方栈帧结构图。
Copilot wrong
(1) 执行第3、10和13行的指令后,寄存器EBP中的内容分别是:
- 第3行:0xbc000020
- 第10行:0xbc000020
- 第13行:0xbc000030
(2) 执行第3、10和13行的指令后,寄存器ESP中的内容分别是:
- 第3行:0xbc000020
- 第10行:0xbc00000c
- 第13行:0xbc000030
(3) 局部变量x和y所在存储单元的地址分别是:
- x的地址:0xbc000028
- y的地址:0xbc00002c
(4) 执行第10行指令后funct的栈帧如下:
| 地址 | 内容 |
|---|---|
| 0xbc000030 | 旧的EBP值 |
| 0xbc00002c | y的值 (0x100) |
| 0xbc000028 | x的值 (0x16) |
| 0xbc000024 | scanf的返回地址 |
| 0xbc000020 | scanf的第一个参数(指向字符串”%x %x”的指针0x804c000) |
| 0xbc00001c | scanf的第二个参数(指向y的指针0xbc00002c) |
| 0xbc000018 | scanf的第三个参数(指向x的指针0xbc000028) |
| 0xbc000014 | 未使用 |
| 0xbc000010 | 未使用 |
| 0xbc00000c | 未使用 |
注意:栈是从高地址向低地址增长的。
栈帧是函数调用期间在栈上分配的一块内存,用于存储函数的局部变量、参数、返回地址以及保存的寄存器值。下面解释 funct 函数的栈帧结构:
-
函数入口:
2 pushl %ebp 3 movl %esp, %ebppushl %ebp:将调用者的基址指针(EBP)压入栈中,保存调用者的栈帧。movl %esp, %ebp:将当前栈指针(ESP)的值赋给基址指针(EBP),建立当前函数的栈帧基址。
-
分配局部变量空间:
4 subl $40, %espsubl $40, %esp:在栈上分配40字节的空间,用于存储局部变量。
-
准备调用 scanf 函数的参数:
5 leal -8(%ebp), %eax 6 movl %eax, 8(%esp) 7 leal -4(%ebp), %eax 8 movl %eax, 4(%esp) 9 movl $.LC0, (%esp)leal -8(%ebp), %eax:计算局部变量y的地址并存入eax。movl %eax, 8(%esp):将y的地址存入栈中,作为scanf的第二个参数。leal -4(%ebp), %eax:计算局部变量x的地址并存入eax。movl %eax, 4(%esp):将x的地址存入栈中,作为scanf的第一个参数。movl $.LC0, (%esp):将格式字符串的地址存入栈中,作为scanf的第一个参数。
-
调用 scanf 函数:
10 call scanfcall scanf:调用scanf函数,返回地址压入栈中。
-
计算并返回结果:
11 movl -4(%ebp), %eax 12 subl -8(%ebp), %eax 13 leave 14 retmovl -4(%ebp), %eax:将局部变量x的值加载到eax寄存器中。subl -8(%ebp), %eax:将局部变量y的值从eax中减去,结果存入eax。leave:恢复调用者的栈帧,等价于movl %ebp, %esp和popl %ebp。ret:从函数返回,弹出返回地址并跳转。
栈帧结构示意图
执行第10行指令后,栈帧结构如下:
| 地址 | 内容 |
|---|---|
| 0xbc000030 | 旧的EBP值 |
| 0xbc00002c | 局部变量 y 的值 (0x100) |
| 0xbc000028 | 局部变量 x 的值 (0x16) |
| 0xbc000024 | scanf 的返回地址 |
| 0xbc000020 | scanf 的第一个参数(指向字符串”%x %x”的指针0x804c000) |
| 0xbc00001c | scanf 的第二个参数(指向 y 的指针0xbc00002c) |
| 0xbc000018 | scanf 的第三个参数(指向 x 的指针0xbc000028) |
| 0xbc000014 | 未使用 |
| 0xbc000010 | 未使用 |
| 0xbc00000c | 未使用 |
注意:栈是从高地址向低地址增长的。













