【问题标题】:How would you explain this disassembly listing?你会如何解释这个反汇编列表?
【发布时间】:2020-05-04 00:26:31
【问题描述】:

我有一个简单的 C 语言函数,在单独的文件 string.c 中:

void var_init(){
    char *hello = "Hello";
}

编译:

gcc -ffreestanding -c string.c -o string.o

然后我使用命令

objdump -d string.o

查看反汇编列表。我得到的是:

string.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <var_init>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 8d 05 00 00 00 00    lea    0x0(%rip),%rax        # b <var_init+0xb>
   b:   48 89 45 f8             mov    %rax,-0x8(%rbp)
   f:   90                      nop
  10:   5d                      pop    %rbp
  11:   c3                      retq

我无法理解这个列表。 “从头开始编写操作系统”一书讲述了旧的反汇编并稍微揭示了其中的奥秘,但它们的列表完全不同,我什至没有看到作者所说的数据被解释为我的代码。

【问题讨论】:

    标签: c x86-64 disassembly


    【解决方案1】:

    除了@VladfromMoscow 的解释之外,只是认为它可能有助于发帖者了解编译到程序集时会发生什么,而不是使用 objdump 来查看它,因为那时可以更清楚地看到数据(IMO)并且 RIP 相对寻址可能更有意义。

    gcc -S x.s
    

    产量

        .file   "x.c"
        .text
        .section    .rodata
    .LC0:
        .string "Hello"
        .text
        .globl  var_init
        .type   var_init, @function
    var_init:
    .LFB0:
        pushq   %rbp
        movq    %rsp, %rbp
        leaq    .LC0(%rip), %rax
        movq    %rax, -8(%rbp)
        nop
        popq    %rbp
        ret
    .LFE0:
        .size   var_init, .-var_init
        .ident  "GCC: (Alpine 8.3.0) 8.3.0"
        .section    .note.GNU-stack,"",@progbits
    
    

    【讨论】:

    • 保留所有.cfi 指令和其他指令会使它非常嘈杂;您可以手动删除它们,或查看godbolt.org 的 asm 输出。另见How to remove "noise" from GCC/clang assembly output?。此外,如果您想要该级别的详细信息,可以使用-fverbose-asm。但是,是的,显示 RIP 相对寻址所指的标签很有用。
    • 另外请注意,如果您使用-fno-pie 编译,它只会使用mov-immediate 将绝对地址存储到堆栈中,而不是使用相对于 RIP 的 LEA。 Linux 非 PIE“小”内存模型意味着静态代码/数据的地址适合 32 位,并且在链接时是已知的。 (大多数发行版默认使用 -fpie -pie 构建 GCC,但 Godbolt 没有。)
    • 谢谢@Peter!这真的很有用。我已根据您的建议删除了 .cfi 指令,但保留了其余指令,希望您现在认为没问题。
    【解决方案2】:

    这个命令

    lea    0x0(%rip),%rax
    

    将字符串文字的地址存储在寄存器rax中。

    还有这个命令

    mov    %rax,-0x8(%rbp)
    

    将地址从寄存器rax 复制到分配的堆栈内存中。从堆栈中的偏移量-0x8来看,地址占用8个字节。

    这个存储只是因为你在调试模式下编译而发生的;它通常会被优化掉。接下来发生的事情是本地变量(在堆栈指针下方的 中)在函数拆除其堆栈帧并返回时被有效地丢弃。

    您正在查看的材料可能包括sub $16, %rsp 或类似内容,用于为低于 RBP 的本地人分配空间,然后再释放该空间; x86-64 System V ABI 在叶函数中不需要它(不调用任何其他函数);他们可以只使用读取区域。 (另见Where exactly is the red zone on x86-64?)。或者使用gcc -mno-red-zone 编译,你可能想要它作为独立代码:Why can't kernel code use a Red Zone

    然后它恢复调用者的 RBP 的保存值(之前设置为帧指针;请注意,本地空间是相对于 RBP 寻址的)。

    pop    %rbp
    

    然后退出,有效地将返回地址弹出到 RIP 中

    retq
    

    【讨论】:

    • pop %rbp 不会“丢弃分配的内存”;该函数一直拥有 RSP 下方的红色区域,直到它返回为止。 x86-64 System V ABI 有一个红色区域(低于 RSP 128 字节),因此它永远不需要为指针 local var 分配空间。 pop %rbp 实际上只是恢复调用者的 RBP(在将 RBP 设置为帧指针之前已保存)。如果编译器分配了任何堆栈空间; mov %rbp, %rsp 将释放它并拆除堆栈框架。
    • @PeterCordes 对初级理解来说太复杂了。:)
    • 是的,我知道我解释它的方式不会为 OP 提供一个好的答案。如果您了解正常方式,即使完全了解红色区域也会更容易。但重要的是不要发表暗示错误的陈述。我进行了编辑;如果您认为它太多,您可能想要恢复部分/全部。
    • 请注意,lea 0x0(%rip),%rax 不是最终可执行文件中实际结束的内容 - 链接器将使用字符串文字的实际地址修补 0x0(表示为与下一个将在可执行文件中结束)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-07-09
    • 2012-06-26
    • 1970-01-01
    • 2013-06-11
    • 2022-06-15
    • 2016-05-12
    • 1970-01-01
    相关资源
    最近更新 更多