TL;博士
如何在使用
时修改堆栈或在使用其他东西时实现类似的效果?ret
世界你好,
我正在尝试为我的语言制作一个编译器, 目前一切都是内联的,它使得 某些步骤的编译速度很慢,所以今天我决定 不过,尝试使用函数来优化它 它一直出现段错误,然后我意识到
这似乎不起作用:
;; main.s
BITS 64
segment .text
global _start
exit:
mov rax, 60 ;; Linux syscall number for exit
pop rdi ;; Exit code
syscall
ret
write:
mov rax, 1 ;; Linux syscall number for write
mov rdi, 1 ;; File descriptor (1 = stdout)
pop rsi ;; Pointer to string
pop rdx ;; String length
syscall
ret
_start:
mov rax, msg_len
push rax
mov rax, msg
push rax
call write
mov rax, 0
push rax
call exit
segment .data
msg: db "Hello, world!", 10
msg_len: equ $-msg
我的输出是......有问题的:
$ nasm -felf64 main.s
$ ld -o main main.s
$ ./main
PHello, world!
@ @ @$@ @+ @2 @main.sexitwritemsgmsg_len__bss_start_edata_end.symtab.strtab.shstrtab.text.data9! @ !77!'Segmentation fault
$?
(退出代码)是 139
(段错误)虽然所有内联都有效:
;; main1.s
BITS 64
segment .text
global _start
_start:
mov rax, msg_len
push rax
mov rax, msg
push rax
mov rax, 1 ;; Linux syscall number for write
mov rdi, 1 ;; File descriptor (1 = stdout)
pop rsi ;; Pointer to string
pop rdx ;; String length
syscall
mov rax, 0
push rax
mov rax, 60 ;; Linux syscall number for exit
pop rdi ;; Exit code
syscall
segment .data
msg: db "Hello, world!", 10
msg_len: equ $-msg
我的输出完全正常:
$ nasm -felf64 main1.s
$ ld -o main1 main1.o
$ ./main1
Hello, world!
$?
(退出代码)是0
(在汇编中指定,表示成功)所以现在我很困惑,因为我是新手 在集会时该怎么做,即使我发现相关 解决方案如
我仍然很困惑如何接受它...... 有什么办法可以做到这一点还是我只能内联?我是否应该将汇编器从 nasm 全部切换到其他东西?
提前致谢
请记住,
call
是技术上apush rip
,而ret
是技术上apop rip
,所以你在示例中几乎搞乱了你的堆栈,因为你无意中将它弹出到了错误的位置。
虽然你可能应该正确地学习调用约定是如何工作的,但我将尝试一个答案来简单地“软化”这个想法,并为了学习的乐趣。
抽象地说,为了拥有函数,你必须有一个叫做 栈帧 的东西,否则你会很难管理局部变量并让
ret
工作。在 x86_64 上,堆栈帧几乎由一些按顺序组成。
call
指令会将其压入堆栈。ret
指令将其从堆栈中弹出。只要执行保持在你的小汇编空间内,从技术上讲,你就可以自由地传递参数,但是你想要1,只要你知道像
call
和ret
这样的指令如何操作堆栈。在我看来,最简单的方法是使其基于堆栈,这样您的编译器就不需要担心寄存器分配了2。
为了简单起见,我建议使用类似 x86 约定但应用于 x86_64 的内容,因为您似乎使用的是 64 位代码。也就是说,调用者函数会将
push
所有参数放入堆栈(通常以相反的顺序),然后 call
被调用者函数。例如,对于 3 参数函数,您的堆栈最终会看起来像这样(请注意堆栈的顶部实际上位于底部)。
+----------------+
| argument 2 |
+----------------+
| argument 1 |
+----------------+
| argument 0 |
+----------------+
| return address |
+----------------+
| local state |
| ... |
+----------------+
此外,我注意到您从未真正使用过
rsp
寄存器。根据编译器的设计,从技术上讲,您可以避免这种情况。无论如何,我相信像 JVM 这样的堆栈机器完全依赖于压入和弹出。只要您的推入和弹出匹配(尤其是 call
和 ret
,它们充当特殊的推入和弹出),就应该没问题。
0 Windows 实际上在这里至少分配了额外的 32 个字节用于参数溢出,但在这种情况下您可能可以忽略它。
1 有特定的调用约定规定参数如何从调用者传递到被调用者并返回。除了您的编程练习之外,我强烈建议您阅读它们的工作原理,以便您的编译器可以输出可以轻松调用的代码,并轻松调用编译器未发出的函数,或者采用 Nate 提到的第四种方式。
2
goto 1