kvmtool下載編譯
git clone https://github.com/kvmtool/kvmtool.git 下載後進入到目錄執行make即可。
補碼
計算機怎麼表示負數?以四位有符號數為例,使用高位作為符號位,最高位為0表示正數,為1表示負數,其餘三位用來表示值。在計算機中,我們將這種表示方式稱為原碼。例如:
十進位制 | 二進位制 | 十進位制 | 二進位制 |
---|---|---|---|
2 | 0010 | -2 | 1010 |
使用原碼錶示時,數字0有兩種編碼:0000和1000。因此,如果使用原碼,在設計系統時就需要額外的電路區分+0和-0更糟糕的是如果一個數的正數相加應為0,2 - 2為例:2 - 2 = 2 + (-2) = 0010 + 1010 = 1100 = -4
這明顯不對,也就是說,原碼不能正確計算減法。怎麼辦呢?聰明的人類發現使用補碼錶示數字就可以加減法了。
即:正數的反碼和補碼都是原碼,負數的反碼是除符號位以外按位取反,補碼是在反碼基礎上+1。使用補碼重新計算 2 - 2:2 + (-2) = 0010 + 1110 = 0000
ASCII碼
計算機用高低電平分別表示0和1,所有的資料在儲存和運算時都要使用二進位制表示,如果我們想用數字表示文字,就要對每一個文字進行編碼,目的就是文字轉換成數值。具體用哪些二進位制是數字表示哪個符號,美國國家標準協會制定了美國資訊交換標準程式碼(American Standard Code for Information Interchange)簡稱ASCII。ASCII使用8位編碼,最多可以表示256個字元,hello world使用的編碼就是為:68 65 6C 6C 6F 77 6F 72 6C 64
寫外設指令
操作碼 | 指令 | 描述 |
---|---|---|
EE | out DX,AL | 將暫存器AL中的位元組輸出到暫存器DX中的I/O埠地址 |
第一列為操作碼,第二列為彙編語法描述的指令,第三列是指令意義的詳細描述。從描述中我們可以得到,EE操作碼它會自動到暫存器AX中讀取源運算元,到暫存器DX中讀取目的運算元。out為操作碼的助記符,AL為8位暫存器,DX是16位暫存器,ASCII碼是8位的,串列埠的地址0x3F8(IBM計算機外設地址)是需要使用16位的表示的。
根據寫串列埠指令的格式,我們的程式流程細化為:在執行EE命令前,將串列埠地址0x3F8存入暫存器DX,將字元存入暫存器AL。
準備源運算元
由out指令可見,在執行指令之前需要將字元A儲存到暫存器AL中。x86提供了資料複製指令mov,用於將資料從原運算元複製到目的運算元。原運算元是一個8位立即數,目的運算元是r8,表示一個8位暫存器。
操作碼 | 指令 | 描述 |
---|---|---|
B0+rb | mov r8,imm8 | 將一個8位立即數複製到一個8位暫存器 |
指令編碼為(B0+rb)ib。根據x86手冊,其中rb表示使用操作碼的低三位編碼目的運算元,即將目的操作暫存器r8的編碼嵌入操作嗎的低三位。暫存器AL對應的編碼為0,因此B0+rb最終編碼為B0。
指令編碼中的ib對應原運算元,其中i表示立即數,b表示立即數的寬度是一個位元組。所以,ib表示跟在操作碼之後的是一個8位立即數,和字元對應的ASCII碼組合最終(B0+rb)ib的編碼分別為:B068 B065 B06C B06C B06F B077 B06F B072 B06C B064。
準備目的運算元
我們需要將串列埠地址寫到暫存器DX中,實質是將一個16位立即數複製到一個16位暫存器, 這顯然還是一個資料複製操作
操作碼 | 指令 | 描述 |
---|---|---|
B8+rw | mov r16,imm16 | 將一個16位立即數複製到一個16位暫存器 |
這個格式mov指令也接受兩個運算元,只不過是16位的。指令編碼中的rw表示使用操作碼的低三位編碼目的運算元,即將目的操作暫存器r16的編碼嵌入操作碼的低三位。根據x86手冊,暫存器DX對應的編碼為2,因此B8+rw編碼為BA。
指令編碼的iw中的i表示立即數,w表示立即數的寬度是一個字,即兩個位元組。所以iw表示跟在操作碼之後的是一個16位立即數,這裡即串列埠地址。不過x86處理器使用小端模式,所以最終(B8+rw)iw的編碼為BAF803。
跳轉指令
我們的程式迴圈向串列埠輸出字串helloworld,在向串列埠輸出後,需要跳轉到程式開始的位置,因此需要一個跳轉指令jmp,格式如下。
操作碼 | 指令 | 描述 |
---|---|---|
EB cb | jmp rel8 | 跳轉到指令指標+rel8的位置 |
jmp指令後接一個rel8。rel是relative的縮寫,表示相對的意思,8代表8位,因此可以跳轉到相對這條指令-128~127的範圍。在指令編碼中操作碼EB之後“cb”就是這個8位的相對偏移。
如果我們想讓指令指標指向程式開頭,即B0所在的記憶體地址,那麼jmp需要向後跳轉35個位元組。因為是向後跳轉,所以是-35,又因為計算機中使用的是補碼,所以我們需要轉換一下,根據原碼轉補碼的規則-35的原碼是10100011,補碼就為11011101,使用16進製表示,即DD。
至此,我們就完成了這段程式的機器語言編碼:
ba f8 03
b0 68
ee
b0 65
ee
b0 6c
ee
b0 6c
ee
b0 6f
ee
b0 77
ee
b0 6f
ee
b0 72
ee
b0 6c
ee
b0 64
ee
eb dd
建立程式檔案
我們使用vim以二進位制模式開啟一個檔案,vim -b hello_world.bin。按i進入插入模式,然後輸入我們的程式碼
。輸入完成後,按下ESC返回標準模式。在標準模式下按下“:”鍵,進入命令列模式。在“:”的後面輸入將16進位制轉為二進位制命令:
:%! xxd -p -r
"%"表示整個檔案內容,”%!“一起使用表示將整個檔案內容作為後面xxd的輸入,然後使用xxd從16進位制轉為二進位制的輸出替換整個檔案內容。-p 表示不需要任何格式,-r意為反過來。最後輸入:“wq”儲存退出
使用kvmtool執行程式
sudo kvmtool/lkvm run -c 1 -k hello_world.bin