如何在64位NASM中使用malloc和free?

5
在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

3
malloc函数会把指向内存块的指针返回到_RAX_寄存器。你需要将_RAX_中的值移动到_RDI_寄存器中(或者将array_pointer储存的地址移动到_RDI_中),以便释放这块内存(因为遵循64位System V ABI协议的所有函数都是通过_RDI_来传递第一个参数)。在调用free函数时,不需要使用push rdiadd rsp, 8。这样会破坏堆栈的对齐方式。 - Michael Petch
3
C标准库已经被规范化,你可以在 cppreference 网站上找到所有的函数和它们的参数。关于内存函数可以在这里找到:http://en.cppreference.com/w/c/memory 。无论是在C语言还是汇编中使用,free 函数只需要一个参数(指针),没有返回值。malloc 函数需要一个参数(字节大小),并返回一个指针(在_RAX_寄存器中)。C标准库的索引在cpp reference中可以找到,链接是:http://en.cppreference.com/w/c。 - Michael Petch
3
通常我这样做(不使用堆栈框架,且不需要担心取消),在函数开始时从 RSP 中减去 40 字节 +(所需本地变量的字节数(向上舍入到最接近的 16))。 这意味着在一条指令中,我可以重新对齐到 16 字节边界,一次性分配所有本地空间和临时空间。优点是我不需要在函数的任何时刻担心对齐(和临时空间),因为我已经确保在开始时分配了所有这些,并对栈进行了对齐。 我的本地变量将从 RSP+32 开始。 - Michael Petch
3
错误。malloc 返回一个16字节对齐的地址,因此如果您使用需要在该指针上进行对齐访问的指令(例如SSE),则不会失败。但是,在调用 mallocfree(以及任何其他遵守Windows 64位调用约定的函数)之前,仍需确保栈是16字节对齐的,因为这些函数可能使用需要适当的栈对齐才能正常运行的CPU指令。如果某个函数在堆栈未对齐的情况下不会失败,请不要假定将来也会如此。 - Michael Petch
3
我经常看到人们说“没有适当对齐也能运行,所以已经足够好了”,然后当他们的代码突然开始崩溃并想知道原因时,他们就寻求帮助,而我们不得不回头告诉他们“对齐很重要,文档中有说明的原因”。 - Michael Petch
显示剩余21条评论
2个回答

3

首先我们来看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博客作者)撰写了一系列关于调用约定的文章,您可能会觉得有趣,从这里开始。


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

-7
汇编语言没有标准库。因此,这不一定是一个汇编语言问题,而是我有一组符合这种调用约定或由X编译器和版本制作的库,并且希望从汇编语言中链接并使用这些库。首先,只需用该语言编写代码并编译并保存临时文件或编译为汇编语言,然后从那段代码开始。或者反汇编这样的代码以发现调用约定,并将其与阅读有关此目标平台和编译器的调用约定时找到的内容进行比较。
如果这是一个系统调用,而您想直接执行它,而不是库调用,则需要阅读此平台和操作系统的系统调用接口,没有理由假设任何两个都相同(Linux、BSD、Windows等)。也不要认为每个主要版本都相同,尽管它们可能是...然后编写符合您找到的任何一个的代码。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接