subl在这里做什么?

所以...我编译成汇编器,用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)
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

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

  • 但我不知道subl $ 24,%esp是什么。 我知道它将堆栈指针向下移动了24个字节。 正确?
  • 这是怎么回事?
  • 为什么movl 8(%ebp),%eax使用8? 它是8字节吗? 这是为了容纳返回值+参数y到h吗? 或者我完全离开这里。 所以这意味着从堆栈指针回看8个字节?
  • movl $ 2,8(%ebp)是做什么的? 它将Contant 2复制到帧指针之前的8个字节位置。 当我们调用f时帧指针是否改变? 如果是 - 那么8(%ebp)指向f的参数位置。
  • 请假是什么做的? 它如何“移除”堆栈框架? 我的意思是你不能删除一块内存。 在文档中,它表示它会执行mov(esp,ebp),pop ebp
  • 谢谢!


    编译器为本地人保留堆栈空间以及其它任何需要的内容。 我不确定它为什么保留了24个字节(它似乎并不需要或不使用它)。

    当调用函数f() ,不是使用push指令将参数放在堆栈上,而是使用一个简单的movl到它保留的最后一个位置:

    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)的调用:

    movl    $2, 8(%ebp)      ; store 2 in the `y` argument passed to `h()`
                             ;     since `h()` won't be using `y` anymore
    leave                    ; get rid of the stackframe for `h()`
    jmp f                    ; jump to `f()` instead of calling it - it'll return
                             ;     directly to whatever called `h()`
    

    为了回答你的问题,“马上行动?” - 指令引用用来指示该值在指令操作码中编码,而不是像寄存器或内存位置那样到达其他地方。


    要回答这些编号问题:

    1) subl $24,%esp

    意味着esp = esp - 24

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

    堆栈在内存中增长,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.
    

    没有存储器到存储器的指令格式。 (严格地说,您可以使用movspush mempop 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代码中,没有办法将堆栈指针用作指针(很难相信,对吗?)。 所以人们做的是将sp复制到bp并使用bp作为本地帧指针。 当32位模式出现时(80386),这变得完全没有必要,它确实有办法直接使用堆栈指针。 不幸的是,ebp使得调试变得更容易,所以我们最终继续在32位代码中使用ebp(如果使用ebp,那么进行堆栈转储非常容易)。

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

    4)上面的解释

    5) leave是一个简单的movl %ebp,%esppopl %ebp并保存少量代码字节的旧指令。 它实际上做的是撤消对堆栈的更改并恢复调用者的ebp。 被调用函数必须在x86 ABI中保留ebp

    在进入函数时,编译器执行subl $ 24,%esp来为本地变量腾出空间,有时候临时存储器没有足够的寄存器来保存。

    在你脑海中“想象”堆栈框架的最好方法是将它看作坐在堆栈上的结构。 想象结构的第一个成员是最近“推动”的价值观。 所以当你推入堆栈时,想象一下在结构的开始处插入一个新成员,而其他成员没有移动。 当你从堆栈中“弹出”时,你会得到虚构结构的第一个成员的值,并且该结构的第一行会从存在中消失。

    堆栈帧操作通常只是移动堆栈指针,以便在我们称为堆栈帧的虚构结构中创建更多或更少的空间。 从堆栈指针中减去只是在结构的起始处一步放入多个虚构成员。 添加到堆栈指针使得第一个这么多的成员消失。

    您发布的代码的结尾并不典型。 该jmp通常是一个ret 。 编译器很聪明,并做了“尾部调用优化”,这意味着它只是清理它对堆栈做了什么并跳转到f 。 当f(2)返回时,它实际上会直接返回给调用者(而不是返回到您发布的代码)

    链接地址: http://www.djcxy.com/p/80335.html

    上一篇: What does subl do here?

    下一篇: 64 assembly output with gcc?