汇编中的内存分配和寻址

Posted

技术标签:

【中文标题】汇编中的内存分配和寻址【英文标题】:Memory allocation and addressing in Assembly 【发布时间】:2019-06-11 06:18:34 【问题描述】:

我正在尝试学习汇编,还有一些我不完全理解其目的的说明。

C 代码

#include <stdio.h>

int main(int argc, char* argv[])

    printf("Argument One - %s\n", argv[1]);
    return 0;

组装

    .section    __TEXT,__text,regular,pure_instructions
    .build_version macos, 10, 14
    .intel_syntax noprefix
    .globl  _main                   ## -- Begin function main
    .p2align    4, 0x90
_main:                                  ## @main
## %bb.0:
    push    rbp
    mov rbp, rsp
    sub rsp, 32
    lea rax, [rip + L_.str]
    mov dword ptr [rbp - 4], 0
    mov dword ptr [rbp - 8], edi
    mov qword ptr [rbp - 16], rsi
    mov rsi, qword ptr [rbp - 16]
    mov rsi, qword ptr [rsi + 8]
    mov rdi, rax
    mov al, 0
    call    _printf
    xor ecx, ecx
    mov dword ptr [rbp - 20], eax ## 4-byte Spill
    mov eax, ecx
    add rsp, 32
    pop rbp
    ret
                                        ## -- End function
    .section    __TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
    .asciz  "Argument One - %s\n"


.subsections_via_symbols

第一季度。 sub rsp, 32

没有局部变量时,为什么要分配 32 个字节的空间?我相信 argc 和 argv 分别保存在寄存器 edi 和 rsi 中。如果它可以将它们移动到堆栈上,那不是只需要 12 个字节吗?

第二季度。 lea rax, [rip + L_.str]mov rdi, rax

我是否正确理解 L_.str 具有字符串 ""Argument One - %s\n" 的地址?据我了解,printf 通过寄存器 rdi 访问该字符串。那么,为什么指令 mov rdi, L_.str 不工作吗?

第三季度。 mov dword ptr [rbp - 4], 0

为什么将零压入堆栈?

第四季度。 mov dword ptr [rbp - 8], edimov qword ptr [rbp - 16], rsi

我相信这些指令是将 argc 和 argv 放入堆栈。使用 edi 和 rsi 是纯粹的约定吗?

Q5。 mov dword ptr [rbp - 20], eax

我不知道这是做什么的。

【问题讨论】:

其中大部分是来自未优化代码的噪音和开销,例如无缘无故地将 args 从寄存器复制到堆栈,并且 (Q5) 将未使用的 printf 返回值溢出到堆栈空间。使用-O3-O2 编译以获得有趣的部分。 How to remove "noise" from GCC/clang assembly output? 是的,有一个标准指定如何将参数传递给函数,因此编译器可以生成可以调用其他编译器代码的代码。在您的情况下,它是 x86-64 System V ABI。请参阅What are the calling conventions for UNIX & Linux system calls on i386 and x86-64 和What registers are preserved through a linux x86-64 function call 的函数调用部分。有关更多文档链接,另请参阅 ***.com/tags/x86/info。 您正在编译而没有优化。这会导致编译器生成大量无用的指令。至少通过-O1,更好的是-O2,这样编译器才能生成合理的代码。 【参考方案1】:

第一季度。副 rsp, 32

这是分配用于存储一些数据的空间。虽然它分配了 32 个字节,但代码只使用了该分配空间的前 16 个字节,一个位于 [rbp-8] (0:edi) 的 qword 和一个位于 [rbp-16] (rdi) 的 qword。

第二季度。 lea rax, [rip + L_.str] 和 mov rdi, rax

lea 正在获取存储在“代码”段中的字符串的地址。它已移至 rdi,用作 printf 的参数之一。

第三季度。 mov dword ptr [rbp - 4], 0 ... mov dword ptr [rbp - 8], edi

这会在 [rbp - 8] 处存储由 0:edi 组成的 64 位小端值。我不确定它为什么这样做,因为它以后再也不会从那个 qword 加载了。

未优化的代码将其寄存器参数存储到内存是正常的,其中调试信息可以告诉调试器在哪里查找和修改它们,但不清楚为什么clang将edi中的argc零扩展为64位.

0 dword 更可能是独立的,因为如果编译器真的想要存储零扩展 argc,编译器将在具有 32 位 mov 的寄存器中进行零扩展,例如 @987654327 @; mov [rbp-8], rcx。可能这个额外的零是一个临时的返回值,它后来决定不使用它,因为显式的return 0; 而不是隐式的从main 的末尾脱落? (main 很特别,我认为 clang 确实为返回值创建了一个内部临时变量。)

Q4 mov qword ptr [rbp - 16], rsi ... mov rsi, qword ptr [rbp - 16]

优化关闭?它存储 rsi,然后从 [rbp - 16] 加载 rsi。 rsi 保存您的 argv 函数 arg ( == &amp;argv[0])。 x86-64 System V ABI passes integer/pointer args in RDI, RSI, RDX, RCX, R8, R9, then on the stack。

... mov rsi, qword ptr [rsi + 8]

这是用argv[1] 的内容加载rsi,作为printf 的第二个参数。 (出于同样的原因,main 的第二个参数在 rsi 中)。

x86-64 System V 调用约定也是在调用没有 FP args 的 varargs 函数之前将 AL 归零的原因。

Q5。 mov dword ptr [rbp - 20], eax

优化关闭?它存储了 printf 的返回值,但从不使用它。

【讨论】:

这是 MacOS,不是 Windows x86-64 ABI。 Linux 或 BSD 的 64 位 ABI 中没有影子空间。 我应该指出,我假设 MacOS 在他们的输出中给出了这一行 .build_version macos, 10, 14 是的,优化已关闭。另外,为什么不使用 mov rdi, L_.str 来将字符串的地址移动到 rdi 中呢? @DKar :因为lea rax, [rip + L_.str] 使代码位置独立。 @MichaelPetch 很抱歉,我是 ASM 新手。能否详细说明您所说的与职位无关的含义?

以上是关于汇编中的内存分配和寻址的主要内容,如果未能解决你的问题,请参考以下文章

如何用汇编语言读取内存值

汇编:没有 malloc 和系统调用的动态内存分配? [FreeDOS 应用程序]

x86 汇编 (AT&T):如何在运行时为变量动态分配内存?

汇编书中“存于寄存器内的地址可用来指向内存的某个位置,即寻址”

汇编和硬件级内存获取、处理、分段、偏移、内存寻址范围等的混淆

带 masm 的寄存器 edx::eax 中的 mul 和内存分配