一步步编写操作系统 65 标准调用约定stdcall 汇编实战
因為c語言遵循的調用約定是cdecl,咱們也自然要遵守cdecl約定了。不過為了起到對比的作用,除了介紹cdecl外,也會介紹下stdcall。
既然咱們用的是調用約定是cdecl,那對它的介紹最好讓它離下一節的內容近一些,所以先說一下咱們不用的stdcall吧^_^,其實這兩個差別就在于由誰來回收棧空間。
stdcall的調用約定意味著:
這兩點在上文的表中大家已有所了解,下面咱們將理論實踐一下,還是拿上面說過的函數舉例:
1 int subtract(int a, int b); //被調用者 2 int sub = subtract (3,2); //主調用者第1行是個函數聲明,其實現已經在前面看到了,就是“return a-b”。
第2行進行函數調用,實參分別是3和2。在實際調用中,參數按照從右向左的順序,參數b會先被壓入棧,然后是參數a壓入棧。在stdcall調用約定下,這個c代碼被編譯后的匯編語句是:
主調用者:
; 從右到左將參數入棧 1 push 2 ;壓入參數b 2 push 3 ;壓入參數a 3 call subtract ;調用函數subtract以上是主調函數,現在看下被調函數subtract中做了什么。
被調用者:
1 push ebp ;壓入ebp備份。 2 mov ebp,esp ;將esp賦值給ebp, ;用ebp做為基址來訪問棧中參數。 3 mov eax,[ebp+0x8] ;偏移8字節處為第1個參數a。 4 add eax,[ebp+0xc] ;偏移0xc字節處是第2個參數b, ;參數a和b相加后存入eax。 5 mov esp,ebp ;為防止中間有入棧操作,用ebp恢復esp。 ;本句在此例子中可有可無,屬于通用代碼。 6 pop ebp ;將ebp恢復 7 ret 8 ;數字8表示返回后使esp+8。;函數返回時由被調函數清理了棧中參數。當執行流進入到subtract后,在它的內部為了用ebp做為基址引用棧中參數,先執行了push ebp來備份ebp,再將棧指針賦給了ebp。目前棧中布局如圖
?
大家根據上圖很容易地看出ebp偏移為8字節是參數a,偏移12字節是參數b。以上代碼值得說一下的是ret 8這句。stdcall是被調用者負責清理棧空間,這里的被調用者是函數subtract。也就是說,subtract需要在返回前或返回時完成。在返回前清理棧相對困難一些,清理棧是指將棧頂回退到參數之前。因為返回地址在參數之下,ret指令執行時必須保證當前棧頂是返回地址。所以通常在返回時“順便”完成。于是ret指令便有了這樣的變體,其格式為:
ret 16位立即數
這是允許在返回時順便再將棧指針esp修改的指令。順便說一句,由于32位下push指令不是壓入字就是壓入雙字,所以ret的參數必須是偶數。在ret 8執行之前,當前棧頂必須是返回地址,即使沒有第5行的代碼,當前esp也是等同于ebp,因為之前沒有任何push壓棧操作,這是編譯器為了通用性而加進去的,所以我們在注釋中寫到,此句可有可無。在經過第6行將棧頂(當前esp指向的內存)彈出到ebp之后,ebp被恢復,此時esp指向了+4字節的位置。即當前棧頂為主調函數的返回地址。結合上圖,ret指令將棧頂的數據彈出到寄存器eip后,棧指針esp自加4,由于還有個參數8,所以esp又被加了8,從而跳過了參數a和b,順利地完成了被調用者清理棧的任務。
stdcall是調用者在棧中壓入參數,由被調用者回收棧空間。貌似分工很明確,配合很默契。因為被調用者知道自己需要幾個參數,所以知道要回收多少棧空間。 但轉念一想,凡事都要自己親力親為才放心,調用者壓入的參數,萬一被調用者忘記回收棧空間該怎么辦(這一點由高級語言編譯器保證,一般不會,大伙兒放心,本段這么寫是為了表述下一種調用約定方式的特點),參數多了棧會溢出的。下面咱們就要介紹這種“親力親為”的調用約定,即:調用者自己向棧中壓入參數,還是由調用者自己回收棧空間。
好啦,stdcall調用約定就到此為止。大爺再來玩哦。
總結
以上是生活随笔為你收集整理的一步步编写操作系统 65 标准调用约定stdcall 汇编实战的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 便宜3.3万元!苹果M2性能有多强?挑落
- 下一篇: java信息管理系统总结_java实现科