视频加载失败

课程

9155 字
约 27 分钟

Answer

计算机系统基础exercises/Homework/RainClass/chapter03·更新于 2026-09-15

计算机系统基础---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

地址内容espebp
bc 00 00 30funct开始执行时
line 13 之后
bc 00 00 2c
bc 00 00 28
bc 00 00 24
bc 00 00 20funct开始执行时
line 13 之后
bc 00 00 1c旧的 ebpline 2 之后line 3 之后
bc 00 00 18x
bc 00 00 14y
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" = 0x804c000line 4 之后
bc ff ff f0scanf返回地址line 10

(1)0xbc00001c,0xbc00001c,0xbc000030

(2)0xbc00001c,0xbbfffff0,0xbc000020

(3)0xbc000018,0xbc000014

MY

地址内容espebp
bc 00 00 30funct开始执行时
line 13 之后
bc 00 00 2c
bc 00 00 28
bc 00 00 24
bc 00 00 20旧的 ebpfunct开始执行时
line 13 之后
bc 00 00 1cline 2 之后line 3 之后
bc 00 00 18x
bc 00 00 14y
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" = 0x804c000line 4 之后
bc ff ff f0

pushl 和 call(?) 之后都会改变esp的值

GPT wrong

已知条件

  1. 函数开始执行时:

    • R[esp] = 0xbc000020
    • R[ebp] = 0xbc000030
  2. scanf

    从标准输入读取的值为:

    • x = 0x16
    • y = 0x100
  3. 指向字符串 "%x %x" 的指针是 0x804c000。


(1) 执行第3、10和13行的指令后,寄存器EBP中的内容

  1. 第3行:movl %esp, %ebp
    • esp 的值赋给 ebp
    • 执行前 esp = 0xbc000020,所以此时 ebp = 0xbc000020
  2. 第10行:call scanf
    • 调用函数时,将当前 eip (返回地址) 压栈,同时 esp 减少 4。
    • 但此操作不改变 ebp,所以 ebp = 0xbc000020
  3. 第13行:leave
    • 等价于:
      • movl %ebp, %esp (恢复栈指针)
      • popl %ebp (弹出旧的基址指针)
    • 恢复了上一栈帧的 ebp 值,即 ebp = 0xbc000030

结论:

  • 第3行:0xbc000020
  • 第10行:0xbc000020
  • 第13行:0xbc000030

(2) 执行第3、10和13行的指令后,寄存器ESP中的内容

  1. 第3行:movl %esp, %ebp
    • 未修改 esp,仍为 0xbc000020
  2. 第10行:call scanf
    • 调用函数会先压入返回地址 (4 字节),并分配参数所需空间。
    • esp 在调用前已经被减小了 40 (第4行 subl $40, %esp)。
    • 再调用 scanf 时,esp = 0xbc000020 - 40 - 4 = 0xbc00000c
  3. 第13行:leave
    • 恢复上一栈帧的栈指针:
      • movl %ebp, %espesp 设置为当前 ebp 值,即 esp = 0xbc000020

结论:

  • 第3行:0xbc000020
  • 第10行:0xbc00000c
  • 第13行:0xbc000020

(3) 局部变量 x 和 y 的存储单元地址

根据汇编代码:

  • x 的地址为 -8(%ebp)0xbc000020 - 8 = 0xbc000018
  • y 的地址为 -4(%ebp)0xbc000020 - 4 = 0xbc00001c

结论:

  • x 的地址:0xbc000018
  • y 的地址:0xbc00001c

(4) 执行第10行指令后 funct 的栈帧

栈帧内容和地址分布如下:

地址内容
0xbc000030调用者的旧 ebp 值 (0xbc000030)
0xbc00002c返回地址 (假设为 0x80484ab)
0xbc000028scanf 的第3个参数 (未使用)
0xbc000024scanf 的第2个参数 (变量 y 的地址 0xbc00001c)
0xbc000020scanf 的第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 的地址:0xbc000018
  • y 的地址: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值
0xbc00002cy的值 (0x100)
0xbc000028x的值 (0x16)
0xbc000024scanf的返回地址
0xbc000020scanf的第一个参数(指向字符串”%x %x”的指针0x804c000)
0xbc00001cscanf的第二个参数(指向y的指针0xbc00002c)
0xbc000018scanf的第三个参数(指向x的指针0xbc000028)
0xbc000014未使用
0xbc000010未使用
0xbc00000c未使用

注意:栈是从高地址向低地址增长的。

栈帧是函数调用期间在栈上分配的一块内存,用于存储函数的局部变量、参数、返回地址以及保存的寄存器值。下面解释 funct 函数的栈帧结构:

  1. 函数入口

    2   pushl       %ebp
    
    3   movl        %esp, %ebp
    • pushl %ebp:将调用者的基址指针(EBP)压入栈中,保存调用者的栈帧。
    • movl %esp, %ebp:将当前栈指针(ESP)的值赋给基址指针(EBP),建立当前函数的栈帧基址。
  2. 分配局部变量空间

    4   subl        $40, %esp
    • subl $40, %esp:在栈上分配40字节的空间,用于存储局部变量。
  3. 准备调用 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 的第一个参数。
  4. 调用 scanf 函数

    10  call        scanf
    • call scanf:调用 scanf 函数,返回地址压入栈中。
  5. 计算并返回结果

    11  movl        -4(%ebp), %eax
    
    12  subl        -8(%ebp), %eax
    
    13  leave
    
    14  ret
    • movl -4(%ebp), %eax:将局部变量 x 的值加载到 eax 寄存器中。
    • subl -8(%ebp), %eax:将局部变量 y 的值从 eax 中减去,结果存入 eax
    • leave:恢复调用者的栈帧,等价于 movl %ebp, %esppopl %ebp
    • ret:从函数返回,弹出返回地址并跳转。

栈帧结构示意图

执行第10行指令后,栈帧结构如下:

地址内容
0xbc000030旧的EBP值
0xbc00002c局部变量 y 的值 (0x100)
0xbc000028局部变量 x 的值 (0x16)
0xbc000024scanf 的返回地址
0xbc000020scanf 的第一个参数(指向字符串”%x %x”的指针0x804c000)
0xbc00001cscanf 的第二个参数(指向 y 的指针0xbc00002c)
0xbc000018scanf 的第三个参数(指向 x 的指针0xbc000028)
0xbc000014未使用
0xbc000010未使用
0xbc00000c未使用

注意:栈是从高地址向低地址增长的。

Profile Image of the Author
Sonder
好想要技术
这是公告标题
这只是一个公告
分类
标签
站点信息
构建平台
GitHub Actions
博客版本
Firefly v6.16.7
文章许可
CC BY-NC-SA 4.0
文章目录