Assembly subl在这里做什么?
所以。。。我正在编译成汇编程序,使用gcc-S-O2-m32:Assembly subl在这里做什么?,assembly,Assembly,所以。。。我正在编译成汇编程序,使用gcc-S-O2-m32: void h(int y){int x; x=y+1; f(y); f(2); } 它给了我以下信息: .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
void h(int y){int x; x=y+1; f(y); f(2); }
它给了我以下信息:
.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
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.
现在我知道了pushl和movel:它们将当前帧指针存储到堆栈上,然后将帧指针寄存器的值设置为堆栈指针的值
谢谢 编译器在堆栈上为局部变量和它可能具有的任何其他需求保留空间。我不确定它为什么要保留24个字节(它似乎不需要或不完全使用) 调用函数
f()
时,它使用一个简单的movl
到最后保留的位置,而不是使用push指令将参数放入堆栈:
movl 8(%ebp), %eax ; get the value of `y` passed in to `h()`
movl %eax, (%esp) ; put that value on the stack for call to `f()`
在我看来,这里发生的一件更有趣的事情是编译器如何处理对f(2)
的调用:
要回答您的问题,“顺便问一下immed?”-这就是指令参考用来指示值在指令操作码中进行编码,而不是出现在寄存器或内存位置等其他位置。要回答这些编号问题: 1)
subl$24,%esp
表示esp=esp-24
GNU AS使用AT&T语法,这与Intel语法相反。AT&T的目的地在右边,Intel的目的地在左边。此外,AT&T还明确了参数的大小。英特尔试图推断它或强迫你明确
堆栈在内存中向下扩展,esp处和之后的内存是堆栈内容,低于esp的地址是未使用的堆栈空间。esp指向推到堆栈上的最后一个对象
2)x86指令编码主要允许以下功能:
.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
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.
没有内存到内存的指令格式。(严格来说,您可以使用mov
或push mem
,pop mem
,执行内存到内存的操作,但都不能在同一条指令上使用两个内存操作数)
“立即”表示该值被编码到指令中。例如,要在ebx中的地址存储15,请执行以下操作:
movl$15,(%ebx)
15是“立即”值
括号使它将寄存器用作指向内存的指针
3)movl 8(%ebp),%eax
意味着
- 取ebp的值
- 添加8(但不修改ebp)
- 将其用作地址(括号)
- 从该地址读取32位值
- 并将值存储在eax中
sp
复制到bp
并使用bp作为本地帧指针。当32位模式出现(80386)时,这就完全没有必要了,它确实有一种直接使用堆栈指针的方法。不幸的是,ebp使调试更容易,所以我们最终继续在32位代码中使用ebp(如果使用ebp,则进行堆栈转储非常容易)
谢天谢地,amd64给了我们一个新的ABI,它不使用ebp作为帧指针,64位代码通常使用esp访问局部变量,ebp可用于保存变量
4)上述说明
5)leave
是一条旧指令,它只执行movl%ebp,%esp
和popl%ebp
,并保存一些代码字节。它实际上做的是撤消对堆栈的更改并恢复调用方的ebp。被调用函数必须在x86 ABI中保留ebp
在进入函数时,编译器执行subl$24,%esp以为局部变量和有时没有足够寄存器容纳的临时存储腾出空间
在你脑海中“想象”堆栈框架的最佳方式是将其视为位于堆栈上的结构。虚拟结构的第一个成员是最近“推送”的值。因此,当推送到堆栈时,想象一下在结构的开头插入一个新成员,而其他成员都没有移动。当您从堆栈中“弹出”时,您将获得虚构结构的第一个成员的值,并且该结构的(第一)行将不存在
堆栈帧操作主要是移动堆栈指针,以便在我们称为堆栈帧的虚拟结构中留出更多或更少的空间。从堆栈指针中减去只会在一个步骤中将多个假想成员放在结构的开头。添加到堆栈指针会使前这么多成员消失
您发布的代码结尾不典型。jmp
通常是ret
。编译器在这方面做得很聪明,并进行了“尾部调用优化”,这意味着它只是清理了它需要的东西