博客 / 詳情

返回

用匯編語言編寫計算兩整數之和的程序(下)

本文節選自《計算機是怎樣跑起來的(第2版)》第 3 章“體驗彙編語言”的草稿。在翻譯本章時,我們發現原書所使用的軟件僅提供日文界面,並且介紹的是主要用於日本計算機相關考試的 CASLⅡ 彙編語言,其通用性相對較低。為了讓內容更廣泛適用,並便於讀者實踐操作,與作者及編輯老師商議後,決定採用更為通用的 NASM 彙編語言重新編寫本章,以提升學習體驗。


在 SASM 中查看寄存器和內存存儲單元中的數據

SASM 的“寄存器組”窗格和“內存”窗格分別用於查看寄存器和內存存儲單元中的數據。這兩個窗格都只有在調試模式下才會出現。先點擊工具欄中的“調試”按鈕,然後通過分別點擊“調試”菜單中的“顯示寄存器組”和“顯示內存”這兩個菜單項就可以打開這兩個窗格。

如圖所示,“寄存器組”窗格顯示在窗口的右側,裏面列出了各個寄存器中的數據。“內存”窗格顯示在窗口上方。

點擊“內存”窗格中的“添加變量...”,然後輸入 A 並按下回車鍵,就可以看到在“值”這一列出現了數字“1”,這正是我們在數據段中定義的、存儲在 A 標籤中的整數。“內存”窗格不僅可以監視定義在數據段中的標籤的值,還可以查看 main 標籤對應的內存地址。繼續點擊“添加變量...”,然後輸入“main”,再按下回車鍵後就能看到 main 標籤對應的地址了。

使用 SASM 逐行調試代碼

下面我們在 SASM 中逐行調試這段“計算 1+2”的程序。調試時重點關注兩點:

  • eax 寄存器和內存存儲空間中的數據
  • eip 寄存器的值

點擊工具欄中的“調試”按鈕後,代碼編輯區域的左側會出現一個綠色的箭頭。箭頭此時指向了 mov ebp, esp 這條指令。在調試程序時,要時刻關注綠色箭頭的位置。綠色箭頭指向哪條指令,哪條指令就是即將執行的指令。

mov ebp, esp 這條指令是 SASM 進入調試模式後自動加入的,僅用於輔助調試,並不會影響程序的流程。我們可以直接點擊工具欄中的“跳過”按鈕,忽略這條指令。

為了便於還沒有運行 SASM 的讀者閲讀下面的內容,我將彙編語言的代碼和對應的機器語言的代碼整理到了下面的表格中
彙編語言的指令 指令的內存地址 與入口指令的偏移量 機器語言的指令 反彙編後的指令
mov eax, [A] 0x401392 +2 a1 00 20 40 00 mov eax,ds:0x402000
add eax, [B] 0x401397 +7 03 05 04 20 40 00 add eax,DWORD PTR ds:0x402004
mov [ANS], eax 0x40139d +13 a3 08 20 40 00 mov ds:0x402008, eax

執行 mov eax, [A]

此時,綠色的箭頭應該跳過了 ;write your code here 這行註釋,指向了 mov eax, [A] 這條指令。這條指令一旦執行,就會將存儲在 A 標籤中的整數 1(0x1) 複製到 CPU 的 eax 寄存器中。那現在 eax 寄存器中的值是什麼呢?

有幾種方式可以查看在 eax 寄存器中的值:

  • 查看 SASM 中的“寄存器組”窗格
  • 運行 GDB 命令:info registers eax

另外,這條指令的地址是“0x401392”,而此時,eip 寄存器的值也是“0x401392”,這似乎説明eip 寄存器的值應該就是即將執行的指令的地址

這裏還有一個小問題:mov eax, [A]mov eax,ds:0x402000 有什麼關聯呢?這兩條指令其實是同一條指令。前面講過,A 只不過是一個貼在內存存儲空間上的標籤,本質上還是數字形式的內存地址,CPU 在解釋執行指令時只會使用內存地址。這裏的 0x402000 就是 A 標籤對應的地址,ds 是數據段(.data section)的縮寫,用於説明 A 標籤對應的是位於數據段中的內存地址。

執行 add eax, [B]

繼續點擊“跳過”按鈕,eip 寄存器的值又變為了“0x401397”,通過對比 GDB 輸出的信息,這個地址正好是 add eax, [B] 這條指令的地址,而 0x402004 是 B 標籤對應的內存地址。我們注意到剛剛點擊“跳過”按鈕使 eip 寄存器的值從 0x401392 變為了 0x401397,增加了 5,這説明剛剛執行的 mov 指令對應的機器語言代碼應該佔 5 字節,而 a1 00 20 40 00 恰好是 5 字節。

該指令的作用是將存儲在 B 標籤中的整數 2 累加到 CPU 的 eax 寄存器中。這樣一來,eax 寄存器的值就應該由 0x1 變為 0x3 了。再次點擊“跳過”按鈕後,不出所料,eax 寄存器的值果然又變成了 0x3。

執行 mov [ANS], eax

現在,綠色的箭頭又指向了 mov [ANS], eax 這條指令。eip 寄存器的值又變成了 0x40139d,這正是這條指令的地址。

該指令能夠將 eax 寄存器中的兩數之和 0x3 存儲到 ANS 標籤中。為了驗證這條指令的作用,我們先把 ANS 也添加到“內存窗格”中,ANS 當前的值是 0(0x0),點擊“跳過”按鈕後,就會發現它的值真的也變成 3(0x3)了。

將 eax 寄存器清零後程序退出

再次點擊“跳過”按鈕,綠色的箭頭現在指向了 xor eax, eax,這條指令的作用是將 eax 寄存器的值清零。因為 0 表示程序正常退出。點擊“跳過”按鈕後,eax 寄存器的值的確清零了。

綠色的箭頭終於到達了最後一條指令。一旦執行完 ret 這條指令,程序就會退出。再次點擊“跳過”按鈕,隨着程序的退出,窗口的最下方出現了一行綠色的文字“調試完成。”

通過反覆觀察對比 eip 寄存器中的內存地址,我們可以得出如下結論:eip 寄存器中存儲着即將執行的指令的地址。每執行完一條指令,eip 寄存器的值都會自動更新為下一條即將執行的指令的地址。而這正是程序中的指令得以按順序執行的機制

讀到這裏,想必大家都對在執行“計算 1+2”時,計算機內部都發生了什麼有所瞭解了吧。


user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.