subl在这做什么?

And*_*yuk 2 assembly

所以...我正在使用gcc -S -O2 -m32编译成汇编程序:

void h(int y){int x; x=y+1; f(y); f(2); }
Run Code Online (Sandbox Code Playgroud)

它给了我以下内容:

.file   "sample.c"
.text
.p2align 4,,15
.globl h
.type   h, @function
 h:
pushl   %ebp
movl    %esp, %ebp
subl    $24, %esp
movl    8(%ebp), %eax
movl    %eax, (%esp)
call    f
movl    $2, 8(%ebp)
leave
jmp f
.size   h, .-h
.ident  "GCC: (GNU) 4.4.3 20100127 (Red Hat 4.4.3-4)"
.section    .note.GNU-stack,"",@progbits
Run Code Online (Sandbox Code Playgroud)

现在我知道pushl和movel是什么:它们将当前帧指针存储到堆栈中,然后将帧指针寄存器的值设置为堆栈指针的值.

  1. 但我不知道subl $ 24,%esp是什么.我明白它会将堆栈指针向下移动24个字节.正确?
  2. 什么是这样的方式?
  3. 为什么movl 8(%ebp),%eax 使用8?是8个字节吗?这是为了适应返回值+参数y到h吗?或者我完全离开这里.那么这意味着从堆栈指针中回顾8个字节?
  4. 是什么MOVL $ 2,8(%EBP)吗?它将第2个副本复制到帧指针之前的8个字节的位置.当我们调用f时,帧指针是否改变了?如果是 - 那么8(%ebp)指向f的参数位置.
  5. 什么离开呢?它如何"移除"堆栈框架?我的意思是你不能删除一块记忆.它在文档中说它确实是mov(特别是ebp),pop ebp.

谢谢!

dou*_*536 5

要回答这些有问题的问题:

1) subl $24,%esp

手段 esp = esp - 24

GNU AS使用AT&T语法,这与英特尔语法相反.AT&T的目的地在右侧,英特尔的目的地在左侧.AT&T也明确了论证的大小.英特尔试图推断它或迫使你明确.

堆栈在内存中增长,esp 中和 esp 之后的内存是堆栈内容,低于esp的地址是未使用的堆栈空间.esp指向推入堆栈的最后一件事.

2) x86指令编码主要允许以下内容:

movl rm,r   ' move value from register or memory to a register
movl r,rm   ' move a value from a register to a register or memory
movl imm,rm ' Move immediate value.
Run Code Online (Sandbox Code Playgroud)

没有内存到内存的指令格式.(严格地说,您可以使用movs或通过push mem,执行内存到内存操作pop mem,但不能在同一指令上执行两个内存操作数)

"立即"表示该值被编码到指令中.例如,要在ebx中的地址存储15:

movl $15,(%ebx)

15是"立即"值.

括号使它使用寄存器作为指向内存的指针.

3) movl 8(%ebp),%eax

手段,

  • 取ebp的值
  • 添加8(虽然不修改ebp),
  • 用它作为地址(括号),
  • 从该地址读取32位值,
  • 并将值存储在eax中

esp是堆栈指针.在32位模式下,堆栈上的每个推送和弹出都是4个字节宽.通常,大多数变量无论如何都占用4个字节.所以你可以说8(%ebp)意味着,从堆栈顶部开始,给我2个值(4 x 2 = 8)int进入堆栈.

通常,32位代码使用ebp指向函数中局部变量的开头.在16位x86代码中,没有办法将堆栈指针用作指针(很难相信,对吧?).那么是什么人做的是拷贝spbp和使用BP作为本地帧指针.当32位模式出现时,这变得完全没必要了(80386),它确实有一种直接使用堆栈指针的方法.不幸的是,ebp使调试更容易,所以我们最终继续在32位代码中使用ebp(如果使用ebp,很容易进行堆栈转储).

值得庆幸的是,amd64给了我们一个新的ABI,它不使用ebp作为帧指针,64位代码通常使用esp来访问局部变量,ebp可用于保存变量.

4)上面解释

5) leave是一个古老的指令,仅仅确实movl %ebp,%esppopl %ebp并保存几个码字节.它实际上做的是撤消对堆栈的更改并恢复调用者的ebp.被调用的函数必须保留ebp在x86 ABI中.

在进入函数时,编译器做了subl $ 24,%esp为局部变量腾出空间,有时临时存储它没有足够的寄存器来保存.

在你的脑海中"想象"堆栈框架的最好方法是将其视为一个坐在堆栈上的结构.虚构结构的第一个成员是最近"推"的值.所以当你推到一个堆栈时,想象一下在结构的开头插入一个新成员,而其他成员都没有移动.当您从堆栈"弹出"时,您将获得虚构结构的第一个成员的值,并且该结构的(第一)行将从存在中消失.

堆栈帧操作主要是移动堆栈指针,以便在我们称之为堆栈帧的虚构结构中产生更多或更少的空间.从堆栈指针中减去只需将多个虚构成员放在结构的开头一步.添加到堆栈指针会使第一个成员消失.

您发布的代码的结尾并不典型.那jmp通常是一个ret.编译器很聪明,并进行了"尾部调用优化",这意味着它只是清理它对堆栈所做的操作并跳转到f.当f(2)返回时,它实际上将直接返回给调用者(不还给你发布的代码)