【问题标题】:How to use malloc and free in 64-bit NASM?如何在 64 位 NASM 中使用 malloc 和 free?
【发布时间】:2018-02-07 20:40:05
【问题描述】:

在 64 位 NASM 中,我使用 C 库中的 malloc() 分配一个 8000 字节的内存块,当我完成它时,我通过调用 free() 来释放它。

我的研究提出了很多关于如何在 64 位 NASM 中执行此操作的相互矛盾的信息,其中大部分信息是 32 位的,其中调用约定不同,或者是 C 或 C++,而不是 NASM .

我认为我的 malloc 部分是正确的,但我不确定 free 部分。我发布这个问题是因为我不想测试它并且分配了一个内存块但没有释放。

所以我的两个问题很简单:
(1) 我对 64 位 NASM 有这个权利吗?
(2) Windows 和 Linux 的语法一样吗?

我只展示了我的程序的 malloc 和 free 部分:

extern malloc
extern free

push rdi

; Allocate the memory buffer
mov rdi,8000
call malloc
mov [array_pointer],rax ;array_pointer is initialized in .data

; Code that uses the buffer goes here.  

; Free the memory buffer
push rdi
call free
add rsp,8

pop rdi
ret

【问题讨论】:

  • malloc 返回 RAX 中的指针。您需要将 RAX 移动到 RDI(或将存储在array_pointer的地址移动到 RDI)以释放它(作为第一个参数free 是通过 RDI 传递的,就像每个符合 64 位 System V ABI 的函数一样)。您也不需要call free 周围的push rdiadd rsp, 8。这样做会弄乱堆栈对齐。
  • C 库是标准化的。您可以在 cppreference 站点找到所有函数及其参数。记忆功能可以在这里找到:en.cppreference.com/w/c/memory。无论是从 C 还是程序集调用 free 都采用单个参数(指针)并且没有返回值。 .malloc 采用单个参数(以字节为单位的大小)并返回一个指针(在 RAX 中)。cpp 参考中 C 库的索引在这里:@ 987654322@
  • 通常我这样做的方式(没有堆栈帧并且不担心展开)是从 RSP 中减去 40 字节 +(本地所需的字节数(四舍五入到最接近的 16))。 . 在函数的开头。这意味着在一条指令中,我重新对齐 16 字节边界,分配所有本地空间,并一次性分配暂存空间。优点是我不需要担心函数中任何一点的对齐(和暂存空间),因为我已经确保所有分配和堆栈在开始时对齐。然后,我的本地变量将从 RSP+32 开始。
  • 不正确。 malloc 返回一个 16 字节对齐的地址,因此如果您使用需要对该指针进行对齐访问的指令(如 SSE),则它不会失败。但是,在调用 mallocfree(以及使用 Windows 64 位调用约定的任何其他符合的函数)之前,您仍然需要确保堆栈是 16 字节对齐的,因为这些函数可能使用需要适当的 CPU 指令堆栈对齐以正确运行。如果堆栈未对齐时函数没有失败,请不要假设将来会出现这种情况。
  • 我经常看到人们说“它可以在没有正确对齐的情况下工作,所以它已经足够好了”,然后当他们的代码意外开始崩溃并想知道为什么我们不得不回去时,他们会去寻求帮助说“对齐很重要,这样记录是有原因的”

标签: assembly malloc nasm x86-64 free


【解决方案1】:

让我们从 Windows x64 开始。一个整数大小的参数(给mallocfree)在rcx 寄存器中传递,一个整数返回值被放入rax 寄存器中。

基本规则是对前四个整数参数使用rcxrdxr8r9,对任何其他整数参数使用堆栈。非整数参数会使事情稍微复杂一些,但是因为在 mallocfree 调用中没有这些参数,所以我不会在这里介绍。如果您需要更多信息,Microsoft 在X64 Calling Convention 上有一篇很好的文章。

因此,用于分配和立即释放块的简单代码类似于 AT&T 语法,如果不同,则在注释后的括号中给出:

mov  rcx, 1000          ; Allocate a block (mov $1000, %rcx).
call malloc             ; Allocate, address returned in rax.

mov  rcx, rax           ; Address needed in rcx (mov %rax, %rcx).
call free               ; And free it.

请注意,此示例和下面的示例仅说明了寄存器的用法,您还需要考虑其他事项,例如阴影空间和对齐要求。


Linux 使用不同的方法(尽管仍然使用寄存器来提高效率)。它使用 System V AMD64 ABI,在这种情况下,您会发现 rax 仍用于返回值,但 rdi 用于参数。

此 ABI 从 { rdi, rsi, rdx, rcx, r8, r9 } 提取其整数寄存器集,并在堆栈上传递任何额外参数。

因此,Linux 的代码更改将相当简单,使用 rdi 代替 rcx

mov  rdi, 1000          ; Allocate a block (mov $1000, %rdi).
call malloc             ; Allocate, address returned in rax.

mov  rdi, rax           ; Address needed in rdi (mov %rax, %rdi).
call free               ; And free it.

Raymond Chen(The Old New Thing 成名)有一系列关于调用约定的系列,您可能会觉得有趣,从 here 开始。

【讨论】:

  • 不要忘记 Windows x64 有影子空间,即函数可以踩到其返回地址上方的 32 个字节。如果您将其与问题中的代码一起使用,它可能会踩到您自己的返回地址,因为您在之前/之后省略了sub rsp, 32 / add rsp, 32。 (如果您删除了 RDI 的其他无用的推送/弹出,则为 40 - Windows 和 SysV 都需要 16 字节堆栈对齐。malloc/free 特别不太可能因堆栈未对齐而失败,但通常这是调用函数的重要部分。 ) 很可能在某处有重复...
  • Agner Fog 的指南之一 (agner.org/optimize/#manuals) 是关于调用约定以及它们在不同操作系统上的差异。
【解决方案2】:

汇编语言没有标准库。因此,这不一定是汇编语言问题,这是我有一组符合此调用约定或由 X 编译器和具有此类设置的版本制作的库。我想从汇编语言中链接并使用这些库。首先,最重要的是用那种语言编写它并编译并保存临时文件或编译为程序集,然后从该代码开始。或者反汇编此类代码以发现调用约定,并将其与您在使用此编译器阅读此目标平台的调用约定时发现的内容进行比较。

如果它是系统调用并且您想直接执行此操作,而不是库调用,那么您需要阅读此平台和操作系统的系统调用接口,没有理由假设任何两个是相同的( Linux、BSD、Windows 等)。也不是每个的主要版本都相同,尽管它们可能是......

然后编写您的代码以符合您找到的任何一个。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-11-19
    • 1970-01-01
    • 2014-05-05
    • 1970-01-01
    • 2013-05-30
    相关资源
    最近更新 更多