在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)
;; 據這篇回答(所說,在macos上呼叫c語言函式,需要將棧對齊到16位
;; 假裝要對齊的是棧頂位址。因為棧頂位址是往低位址增長的,所以只需要將位址的低16位抹掉就可以了
(and ,(format nil "$0x~x" #xfffffff0) %esp)
(call :|_exit|)))))
結果發現還是不行。最後,實在沒轍了,只好先寫一段簡單的c**,然後用gcc -s
生成彙編**,來看看究竟應當如何處理這個棧的對齊要求。一番瞎折騰之後,發現原來是要處理rsp
暫存器而不是esp
暫存器——我也不曉得這是為什麼,esp
不就是rsp
的低32位而已麼。
最後,把jjcc2
寫成下面這樣後,終於可以成功編譯(exit 1)
了
(defun jjcc2 (expr globals)
"支援兩個數的四則運算的編譯器"
(check-type globals hash-table)
(cond ((eq (first expr) '+)
`((movl ,(get-operand expr 0) %eax)
(movl ,(get-operand expr 1) %ebx)
(addl %ebx %eax)))
((eq (first expr) '-)
`((movl ,(get-operand expr 0) %eax)
(movl ,(get-operand expr 1) %ebx)
(subl %ebx %eax)))
((eq (first expr) '*)
;; 將兩個數字相乘的結果放到第二個運算元所在的暫存器中
;; 因為約定了用eax暫存器作為存放最終結果給continuation用的暫存器,所以第二個運算元應當為eax
`((movl ,(get-operand expr 0) %eax)
(movl ,(get-operand expr 1) %ebx)
(imull %ebx %eax)))
((eq (first expr) '/)
`((movl ,(get-operand expr 0) %eax)
(cltd)
(movl ,(get-operand expr 1) %ebx)
(idivl %ebx)))
((eq (first expr) 'progn)
(let ((result '()))
(dolist (expr (rest expr))
result))
((eq (first expr) 'setq)
;; 編譯賦值語句的方式比較簡單,就是將被賦值的符號視為乙個全域性變數,然後將eax暫存器中的內容移動到這裡面去
;; todo: 這裡expr的second的結果必須是乙個符號才行
;; fixme: 不知道應該賦值什麼比較好,先隨便寫個0吧
(setf (gethash (second expr) globals) 0)
;; 為了方便stringify函式的實現,這裡直接構造出rip-relative形式的字串
`((movl %eax ,(get-operand expr 0))))
globals))
;; ((eq (first expr) '_exit)
;; ;; 因為知道_exit只需要乙個引數,所以將它的第乙個運算元塞到edi暫存器裡面就可以了
;; ;; todo: 更好的寫法,應該是有乙個單獨的函式來處理這種引數傳遞的事情(以符合calling convention的方式)
;; `((movl ,(get-operand expr 0) %edi)
;; (movl #x2000001 %eax)
;; (syscall)))
((eq (first expr) '>)
;; 為了可以把比較之後的結果放入到eax暫存器中,以我目前不完整的組合語言知識,可以想到的方法如下
(let ((label-greater-than (intern (symbol-name (gensym)) :keyword))
(label-end (intern (symbol-name (gensym)) :keyword)))
;; 根據這篇文章(中的說法,大於號左邊的數字應該放在cmp指令的第二個運算元中,右邊的放在第乙個運算元中
`((movl ,(get-operand expr 0) %eax)
(movl ,(get-operand expr 1) %ebx)
(cmpl %ebx %eax)
(jg ,label-greater-than)
(movl $0 %eax)
(jmp ,label-end)
,label-greater-than
(movl $1 %eax)
,label-end)))
((eq (first expr) 'if)
;; 假定if語句的測試表示式的結果也是放在%eax暫存器中的,所以只需要拿%eax暫存器中的值跟0做比較即可(類似於c語言)
(let ((label-else (intern (symbol-name (gensym)) :keyword))
(label-end (intern (symbol-name (gensym)) :keyword)))
`((cmpl $0 %eax)
(je ,label-else))
(jjcc2 (third expr) globals)
`((jmp ,label-end)
,label-else)
(jjcc2 (fourth expr) globals)
`(,label-end))))
((member (first expr) '(_exit exit))
;; 暫時以硬編碼的方式識別乙個函式是否來自於c語言的標準庫
`((movl ,(get-operand expr 0) %edi)
;; 據這篇回答(所說,在macos上呼叫c語言函式,需要將棧對齊到16位
;; 假裝要對齊的是棧頂位址。因為棧頂位址是往低位址增長的,所以只需要將位址的低16位抹掉就可以了
(and ,(format nil "$0x~x" #xfffffffffffffff0) %rsp)
(call :|_exit|)))))
生成的彙編**如下
.data
.section __text,__text,regular,pure_instructions
.globl _main
_main:
movl $1, %edi
and $0xfffffffffffffff0, %rsp
call _exit
好了,這個時候我就在想,如果想要支援其它來自c語言標準庫的函式的話,只要依葫蘆畫瓢就好了,好像還挺簡單的——天真的我如此天真地想著。
全文完閱讀原文
C如何呼叫C 的庫
前段時間遇到了乙個c呼叫c 的介面的問題,現在把思路整理一下。提供給我們的是c 標頭檔案 h 和靜態庫 a c不可以直接呼叫c 我們採用c 呼叫c 的方法,另外建乙個適配層 在我們的c 適配層中加上extern c c呼叫c 適配層,適配層呼叫c 就ok了。下面用乙個例子來說明一下 1 模擬條件on...
C 呼叫C 庫檔案
winform下呼叫dll檔案,將dll銬入bin目錄下,using system.runtime.interopservices dllimport securitymaker.dll entrypoint security make public static extern void makes...
在C 中呼叫C語言標準動態庫方法
cisco packet tracer 5.0軟體深入詳解使用教程 3 詳解網路嗅探工具的原理 sniffer wireshark 2010 年 07 月 19 日 seth c net程式設計 go to comment out mylib.dll dll implib mylib.lib myl...