2016-07-20 25 views
1

根據這個虛構的微處理器架構的指令集: https://github.com/mertyildiran/DASM如何在彙編器中實現CALL&RET?

在我們用C寫的玩具組裝,我們已經實現了PUSH & POP指令,如下圖所示:

PUSH:

這基本上是4 DEC和1 ST指令的組合。

 else if (strcmp(token,"push")==0) // PUSH instruction: combination of 4 DEC and 1 ST instruction on Stack Pointer (SP) 
     { 
      op1 = strtok(NULL,"\n\t\r "); 
      op2[0] = sp; // Let's say address of SP is 9 
      printf("\n\t%s\t%s\n",strupr(token),op1); 
      ch = (op2[0]-48) | ((op2[0]-48)<<3); // Prepare bitwise instruction format for DEC instructions 
      program[counter]=0x7800+((ch)&0x00ff); // Decrease Stack Pointer 4 times 
      printf("> %d\t%04x\n",counter,program[counter]); 
      counter++; 
      program[counter]=0x7800+((ch)&0x00ff); // Decrease Stack Pointer 4 times 
      printf("> %d\t%04x\n",counter,program[counter]); 
      counter++; 
      program[counter]=0x7800+((ch)&0x00ff); // Decrease Stack Pointer 4 times 
      printf("> %d\t%04x\n",counter,program[counter]); 
      counter++; 
      program[counter]=0x7800+((ch)&0x00ff); // Decrease Stack Pointer 4 times 
      printf("> %d\t%04x\n",counter,program[counter]); 
      counter++; 

      ch = ((op1[0]-48) << 2) | ((op2[0]-48) << 6); // Prepare bitwise instruction format for ST instruction 
      program[counter]=0x3000+((ch)&0x00ff); // Store the value in Stack 
      printf("> %d\t%04x\n",counter,program[counter]); 
      counter++; 
     } 

POP:

這基本上是1個LD和4個INC指令的組合。

 else if (strcmp(token,"pop")==0) // POP instruction: combination of 1 LD and 4 INC instructions on Stack Pointer (SP) 
     { 
      op1 = strtok(NULL,"\n\t\r "); 
      op2[0] = sp; // Let's say address of SP is 9 
      printf("\n\t%s\t%s\n",strupr(token),op1); 

      ch = (op1[0]-48) | ((op2[0]-48) << 3); // Prepare bitwise instruction format for LD instruction 
      program[counter]=0x2000+((ch)&0x00ff); // Store the value in Stack 
      printf("> %d\t%04x\n",counter,program[counter]); 
      counter++; 

      ch = (op2[0]-48) | ((op2[0]-48)<<3); // Prepare bitwise instruction format for INC instructions 
      program[counter]=0x7700+((ch)&0x00ff); // Decrease Stack Pointer 4 times 
      printf("> %d\t%04x\n",counter,program[counter]); 
      counter++; 
      program[counter]=0x7700+((ch)&0x00ff); // Decrease Stack Pointer 4 times 
      printf("> %d\t%04x\n",counter,program[counter]); 
      counter++; 
      program[counter]=0x7700+((ch)&0x00ff); // Decrease Stack Pointer 4 times 
      printf("> %d\t%04x\n",counter,program[counter]); 
      counter++; 
      program[counter]=0x7700+((ch)&0x00ff); // Decrease Stack Pointer 4 times 
      printf("> %d\t%04x\n",counter,program[counter]); 
      counter++; 
     } 

所以我的問題是我們如何能夠實現使用堆棧CALL & RET指令?

我知道CALL指令會將PC的當前狀態存儲在堆棧中,這樣程序就能夠通過RET指令返回它離開的地方。但它導致了我兩個子問題:

  1. CALL後執行,程序如何獲得以前存儲在堆棧地址,如果在子過程,指令壓入一些東西到堆棧或覆蓋調用的返回地址。
  2. 如何將相關標籤的地址傳遞給機器碼級的CALL?在我們的彙編程序JMP &中,由於相同的原因,jZ指令也未完成。

如果你想看看全貌:

.data 
    count: 60 
    array: .space 10 
    char: 0xfe 
.code 
     ldi 0 count 
     ld 0 0 
     ldi 1 array 
     ldi 2 char 
     ld 2 2 
lpp  st 1 2 
     inc 1 
     dec 0 
     jz loop 
     jmp lpp 
loop sub 1 2 3 
lp1  jmp lp1 
+1

「如果在子程序中,程序如何獲取先前存儲在堆棧中的地址,則指令將某些東西壓入堆棧或覆蓋CALL的返回地址。 ... CPU不關心,下一個「RET」將返回到錯誤的地址(當調用ret時,堆棧前面的新/修改值)。 – Ped7g

+0

@ Ped7g那麼這將是彙編程序的錯誤,而不是彙編程序的錯誤,對吧? – mertyildiran

+1

'call label'例如可以分解爲'push next_instruction'' jmp label'' next_instruction:...'('next_instruction:'是彙編程序內部使用的虛構隱式標籤,實際上你可以做'$ + size_of_call '沒有定義符號)...是的,程序負責保持棧值有意義,就像你必須在寄存器中保存有意義的值並執行指令一樣。 CPU就是這樣做的,它告訴了什麼。所以在子例程中,如果你在堆棧上「推」三個字,在調用ret之前,你應該彈出三個字的大小,或者添加sp,3 *字。 – Ped7g

回答

3

https://github.com/mertyildiran/DASM/blob/master/assembler.c

對於2 subquestion(JMP/CALL)你可以或許jmp lpp行解釋它在這個例子中「彙編程序如何將標籤地址傳遞給JMP指令?」

這應該是顯而易見的機器/ CPU架構,它如何解碼指令。我對你的問題感到有點困惑,不管你的問題是不是化妝CPU已經是最終的了(那麼它應該描述一下,你如何加載pc register =基本上是什麼跳躍,加上它可能是有條件的),或者你正在做兩件事項目在一個,創建HW機器規格,併爲它的彙編程序。而且我懶得再讀一次,所以我只會向你展示來自現實世界的常用方法。

ARM - 類RISC指令集,固定操作碼大小:

無論是在16B(拇指)和32b模式操作碼的前幾個位指定指令(B, BL, BLX,B是純跳躍,BL類似於CALL,但不使用堆棧,而是使用「鏈接」寄存器來存儲返回地址),其餘位指定保存目標地址的寄存器(所以調用函數foo,你可以做load r0,foobl r0)或立即值相對於目前的PC

這意味着,在16b模式下,您可以無條件跳+ -2kiB,或者-252+258有條件地(條件變體以更多位編碼,從直接中取走一些)。

這有時會導致情況,高級編譯器要麼使用寄存器變體,要麼跳轉到足夠接近另一跳躍指令,甚至進一步跳躍。

在32b模式下,爲即時保留的剩餘位提供了更好的範圍,對於所有變體約爲+ -32MiB。 (還有一種模式,32b「拇指」,它有一個更多不同的編碼,但在這個例子中是無關緊要的)。

有趣的是,地址中的位0確實指示是否存在拇指模式下的指令或全32b模式,因爲所有地址必須在ARM上對齊,所以跳轉到地址0x00000001時跳轉到0x00000000, CPU進入拇指模式。

86 - CISC般的歷史超出味道不錯

這其中有幾乎所有可能的變體(除了你真正需要的是長期的.asm你一直在寫過去的3個月)。指令編碼具有可變長度,所以指令確實使用盡可能多的字節,正如英特爾決定需要的那樣。

  • 相對跳躍(短單字節-128 ... 127) - 檢查
  • 絕對跳轉到地址(編碼爲jmp操作碼字節,根據需要進行編碼的地址然後儘可能多的字節)
  • 寄存器跳轉(存儲在寄存器中的地址)
  • 條件相對跳轉(不只是「擴展」 jmp編碼爲ARM情況下,但具體操作碼)
  • 像演員loopjcxz/jecxz,也許一些我甚至FO rgot。

所以它歸結要麼值加載到某個寄存器,或編碼直接到指令操作碼,然後要麼把它當作一個絕對地址(例如在32B的平臺,在這裏你管理爲地址編碼25位,將允許您尋址32MiB的RAM;而不是4GiB)或相對地址(25位=>pc + -16Mi)。


附錄,這可能是您的問題的一部分? 就像如何「知道」未來某個標籤上的地址是什麼?

大部分彙編程序都是兩遍的,所以它們首先生成指令操作碼(並且可以計算源碼的每個部分的長度),並且收集符號表中的所有符號,然後根據操作碼長度和org指令。然後在第二遍中,通過特定符號值填充操作碼中的所有即時值。

這也顯示了爲什麼雙程彙編程序不能自動處理jmp rel8/rel16(由8b或16b立即定義的相對跳轉),但程序員必須指定他想使用哪一個。 (或使用多路彙編程序,它將首先嚐試rel8,如果失敗,它將用rel16重新編碼jmp,並移動+重新編譯超出該點的所有內容)。

看看您的push編碼的示例源代碼,我覺得您有一些工作可以改變您的彙編程序的工作方式......(無論如何,這很醜陋,就像創建操作碼和在屏幕上打印輸出一樣 - 不要毫不猶豫地再寫一遍,我敢打賭它會變得更乾淨和簡單)。


編輯:最後,我大概理解那塊彙編器更好。

所以programuint16_t[],對吧? (應該是問題的一部分)。

而指令機器碼也是固定大小,16b也是。

但是那麼你的counter以16b字計數......所以目標架構的尋址模式是這樣的嗎?

在地址0存儲16b字,並在地址1存儲下一個16b字? (並且它不重疊,如在x86處,其中地址以8b字節計數=>在這種情況下,第一指令字將在地址01處,並且第二字將在地址23處。確保您的counter遵循正確尋址方案(否則你就必須定義標籤的符號價值時將其轉換)

而且你的彙編是生產像0x0000 0x7802輸出:!地址0,字7802,做一些指令78使用參數2(字節序不清楚,你不是幸運的是在編譯主機時有相同的一個,否則你將以錯誤的機器碼和每個字中的交換字節結束),下一個操作碼0x0001 0x7803,...等等。

所以看起來你已經修復了16b大小的指令編碼,不確定jmp會像存儲/加載指令一樣吃掉整個8b,還是一個是特殊的,保留更多位以便立即編碼。如果只有其他字節可用,則只有使用它的有意義的方式是作爲相對跳轉簽名8b -128..+127

如果尋址與16b字一起工作,那麼您可以有效地跳轉-128 .. + 127指令後退/前進。如果尋址與8b字節一起工作,則範圍將降至-64 .. + 63指令。很難說,因爲我沒有弄清楚有關目標平臺的任何細節(您應該添加一些鏈接或其他內容,至少是如何編碼jmp以及如何映射內存)。

+0

您能解釋一下我在機器問題(十六進制)中的例子嗎?從'lpp st 1 2'到'jmp lpp'?因爲我想了解** jmp **和** loop **在機器代碼中是如何工作的。彙編程序基本上是通過彙編的機器代碼生成這個文件,所以你可以用這種格式解釋:https://github.com/mertyildiran/DASM/blob/master/RAM – mertyildiran

+0

如果我問太多了,但對我來說這是很難理解的話題。請解釋它像解釋一個假人。 – mertyildiran

+1

@mertyildiran我添加了一個段落,這種回答你的「逐行」。這是不可能的(只適用於跳回,符號,你已經編譯並可以存儲在符號表中)。我很難理解'lpp st 1 2 jmp lpp'的意思......但是機器代碼應該是'<1><2><-5>'其中'<-2>'跳躍自己在無限循環中。我認爲你不用'st'三個字節,'1 2'與''一起編碼?所以調整它,但我希望這個原理可見。 – Ped7g