機器語言編寫helloworld
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


浙公網安備 33010602011771號