调用C标准库的exit函数
在上一篇文章中,实现了对大于号(>)的处理,那么对if表达式的编译也就是信手拈来的事了,不解释太多。在本篇中,将会讲述一下如何产生可以调用来自于C语言标准库的exit(3)函数的汇编代码。 在Common Lisp中并没有一个叫做EXIT的内置函数,所以如同之前实现的_exit一样,我会新增一种需要识别的(first expr),即符号exit。为了可以调用C语言标准库中的exit函数,需要遵循调用约定。对于exit这种只有一个参数的函数而言,情形比较简单,只需要跟对_exit一样处理即可。刚开始,我写下的代码是这样的 (defun jjcc2 (expr globals) ;; 省略不必要的内容 (cond ;; 省略不必要的内容 ((member (first expr) '(_exit exit)) ;; 暂时以硬编码的方式识别一个函数是否来自于C语言的标准库 `((movl ,(get-operand expr 0) %edi) (call :|_exit|)))))对(exit 1)进行编译,会得到如下的代码 .data .section __TEXT,__text,regular,pure_instructions .globl _main_main: MOVL $1, %EDI CALL _exit不过这样的代码经过编译链接之后,一运行就会遇到段错误(segmentation fault)。经过一番放狗搜索后,才知道原来在macOS上调用C函数的时候,需要先将栈对齐到16字节——我将其理解为将指向栈顶的指针对齐到16字节。于是乎,我将jjcc2修改为如下的形式 (defun jjcc2 (expr globals) ;; 省略不必要的内容 (cond ;; 省略不必要的内容 ((member (first expr) '(_exit exit)) ;; 暂时以硬编码的方式识别一个函数是否来自于C语言的标准库 `((movl ,(get-operand expr 0) %edi) ;; 据这篇回答(https://stackoverflow.com/questions/12678230/how-to-print-argv0-in-nasm)所说,在macOS上调用C语言函数,需要将栈对齐到16位 ;; 假装要对齐的是栈顶地址。因为栈顶地址是往低地址增长的,所以只需要将地址的低16位抹掉就可以了 (and ,(format nil "$0x~X" #XFFFFFFF0) %esp) (call :|_exit|)))))结果发现还是不行。最后,实在没辙了,只好先写一段简单的C代码,然后用gcc -S生成汇编代码,来看看究竟应当如何处理这个栈的对齐要求。一番瞎折腾之后,发现原来是要处理RSP寄存器而不是ESP寄存器——我也不晓得这是为什么,ESP不就是RSP的低32位而已么。 ...