之前的文章我們主要聊了一些基本的匯編指令,并且通過一個名為 Debug 的調(diào)試軟件,讓我們看到了內(nèi)存中是如何存儲指令和數(shù)據(jù)的,在學習了這些之后,我們就可以了解匯編程序了。
系列文章:
程序的執(zhí)行過程
首先通過一個示意圖給大家介紹一下程序的執(zhí)行過程,我們以 C 語言一個簡單的 hello.c 程序為例。
這就是一個完整的 hello world 程序執(zhí)行過程,會涉及幾個核心組件:預(yù)處理器、編譯器、匯編器、連接器,下面我們逐個擊破。
匯編語言是非常有用的,因為它能夠針對不同高級語言來提供自己的一套標準輸出語言。
所以,一般來說,可執(zhí)行文件包括兩個方面
程序和數(shù)據(jù),這些是構(gòu)成可執(zhí)行文件的基本信息。
相關(guān)的描述信息,比如空間多大,程序有多大等,這些是構(gòu)成可執(zhí)行文件的必要因素。
認識匯編程序
同樣的,先上一則匯編代碼,然后下面再慢慢概述。
?assume cs:code
?code segment
? mov ax,1234H
? add ax,ax
? mov bx,1111H
? add bx,bx
?code ends
?end
這段匯編代碼有幾個地方你可能不太了解,不過 mov、add 指令你應(yīng)該知道是什么意思(如果你看完筆者之前文章并進行了仔細研究的話)。
構(gòu)成匯編程序的指令分為兩種:一種是匯編指令,一種是偽指令,匯編指令就是我們上面提到的 mov 、add 指令,這些指令有實際的意義,比如 mov 就是移動寄存器或者數(shù)據(jù),add 就是對寄存器或者數(shù)據(jù)進行加法操作。
而且 mov 和 add 這類匯編指令在內(nèi)存中有對應(yīng)的機器碼存在,最終會有 CPU 執(zhí)行。而偽指令沒有實際的意義,它們指令簡單的定義一個程序段,這些偽指令會由編譯器來直接解釋,它們在內(nèi)存中沒有對應(yīng)的機器碼,所以不會由 CPU 來執(zhí)行。
上面提到的偽指令有三種,即
?code segment
? ......
?code ends
和 ends 是一組成對出現(xiàn)的指令,而且這一對指令必須成對出現(xiàn),缺了誰都不行。這一對指令定義了一個段, 標識著段的開始,ends 標識著段的結(jié)束。code 表示段的名稱,段名稱可以隨意替換。
匯編程序由多個段組成(至少包含一個段),這些段被用來存放代碼、數(shù)據(jù)或者當做棧空間來使用。上面例子代碼中的段由代碼組成,所以叫代碼段。
除了段之外,匯編程序還需要有,這同樣是一條偽指令,它的意思是假設(shè),它假設(shè)某一段寄存器和某個段相關(guān)聯(lián),通過 來說明這種關(guān)聯(lián)關(guān)系。 不用深入理解,我們只要知道編程時將特定用途的段和相關(guān)寄存器關(guān)聯(lián)起來即可。
end是一段匯編程序結(jié)束的標志,它也是一條偽指令,編譯器在編譯匯編程序的過程中,遇到 end 就會停止編譯,所以,如果我們匯編程序?qū)懲炅耍托枰诔绦虻哪┪布由?end ,表示程序的結(jié)束。
在匯編程序中,除了匯編指令和偽指令,還有一種標號,比如上面代碼中的 code,標號位于 的前面,作為段的名稱,這個段的名稱最終將被編譯、連接處理為一個段的段地址。
再次提醒下,注意這里不要搞混了 end 和 ends ,ends 是和 一起使用的表示匯編段,而 end 是匯編結(jié)束的標識。
所以總結(jié)下,用匯編語言編寫的源程序,包括偽指令和匯編指令,偽指令是由編譯器來執(zhí)行,匯編指令可以翻譯成機器代碼并最終由 CPU 執(zhí)行。
以后,我們可以將源程序文件中的內(nèi)容稱為源程序,將源程序中最終由計算機執(zhí)行、處理的指令或數(shù)據(jù),稱為程序。程序最先以匯編指令的形式存在于原程序中,然后經(jīng)過編譯、連接后轉(zhuǎn)變?yōu)闄C器碼,存儲在可執(zhí)行文件中,如下圖所示
所以,總結(jié)一點來說,編寫一個匯編程序主要分為下面這幾步
程序返回
一個完整的程序是要有返回條件的,程序只有在執(zhí)行完相關(guān)代碼后,執(zhí)行返回條件,讓出 CPU 執(zhí)行權(quán),操作系統(tǒng)才會分配時間片給其他程序,程序不能一直霸占著 CPU 不放,這是一種資源的浪費匯編語言轉(zhuǎn)換成機器碼,而且一直占用著 CPU,也會導致程序崩潰。
匯編語言中,實現(xiàn)程序返回的指令只有兩行
?mov ax,4c00H
?int 21H
解釋下這兩句指令的意思:
mov ax,4c00H 就是把 4c00 移動到 ax,中,INT 21H 是調(diào)用系統(tǒng)中斷指令,這兩句代碼起作用的就是 AH = 4CH,意思就是調(diào)用 INT 21H 的 4CH 號中斷,該中斷就是安全退出程序。
到目前為止,我們已經(jīng)了解到了幾種和結(jié)束的相關(guān)內(nèi)容,比如段結(jié)束,匯編程序結(jié)束、還有我們剛剛說的程序返回,下表列出了這三個指令的區(qū)別。
程序錯誤
一般來說,匯編語言的程序錯誤分為兩種:即語法錯誤和邏輯錯誤。
語法錯誤很簡單,說白了就是你匯編語言指令寫錯了,這個程序編譯時期就能夠發(fā)現(xiàn)。
邏輯錯誤是在運行時發(fā)生的,一般不容易被發(fā)現(xiàn),排查起來比較困難,比如下面這段代碼不寫程序返回就是屬于邏輯錯誤。
?assume cs:code
?code segment
? mov ax,1234H
? add ax,ax
? mov bx,1111H
? add bx,bx
?code ends
?end
為什么?因為你這段代碼沒有加程序返回邏輯。類似的這種邏輯錯誤還有很多,這些錯誤需要在具體的場景中才能發(fā)現(xiàn)。
編寫匯編
下面我們開始用編輯器來編寫匯編源程序,只要將匯編存儲為文本文件,再經(jīng)過編譯器編輯,CPU 運行即可。
我們可以使用多種文本格式來編寫匯編程序,比如我們可以使用最簡單的文本文件來編寫(基于 win7 操作系統(tǒng)環(huán)境)
?assume cs:codeseg
?codeseg segment
? mov ax,0123H
? mov bx,0456H
? add ax,bx
? add ax,ax
?codeseg ends
?end
編寫完成后,存儲為.asm后綴文件,這是一種匯編格式。
編譯
一個完整的匯編程序執(zhí)行流程分為編寫、編譯、鏈接和運行,所以接下來我們需要對編寫完成的匯編程序進行編譯。在編譯之前我們需要找到一個相應(yīng)的編譯器,這里我們采用的是 masm 5.0 匯編編譯器,執(zhí)行程序是masm.exe。
(關(guān)注CPP開發(fā)者公號,發(fā)消息masm即可獲取)
說到使用 masm 5.0 的這個過程我踩了很多坑,這里給大家提示下,及時閉坑!!!
安裝完成后,我們打開 cmd ,進入下載并解壓好的 masm 5.0 文件夾下。
然后直接鍵入 masm。
運行 masm 后,首先會顯示一些版本信息,然后輸入需要被編譯的原程序文件名稱,這里需要注意一下,[.ASM]提示我們,默認的文件擴展名是 asm,比如我們要編譯的源程序文件名是test.asm,這里直接輸入 asm 即可。如果源程序文件不是以 .asm 為后綴,需要輸入它的全名,也就是 test.txt。
這里我們輸入的是 test,因為我們編寫的文件是 .asm 后綴。
輸入源程序文件名后,按 enter 鍵,程序會提示我們輸入要編譯出的目標文件名稱,目標文件名稱是我們對源程序進行編譯后的最終結(jié)果。
的后綴名是.obj,因為 .asm 文件會自動編譯為 .obj 文件,所以我們不必再指定文件名,直接按 enter 鍵,會直接生成 .obj 文件。
確定了目標文件名稱后,會出現(xiàn) ,這是提示我們要輸入列表文件的名稱,這個文件是編譯器將源程序編譯為目標文件的過程中產(chǎn)生的中間結(jié)果,可以讓編譯器不生成這個文件,直接鍵入 enter 即可。如果編譯器要生成這個文件,它的后綴名是.lst。
然后繼續(xù)提示出 Cross- ,這是提示我們要輸入交叉引用文件名稱,這個文件和 一樣,是編譯器產(chǎn)生的中間結(jié)果,可以不讓編譯器生成這個文件,我們直接按 enter 即可。如果編譯器要生成這個文件,它的后綴名是.crf。
最后編譯器會進行一個結(jié)果輸出,這個輸出結(jié)果會顯示警告錯誤和必須要改正的錯誤,可以從上圖中看出來,我們程序沒有警告和編譯錯誤。
在輸入源程序文件名的時候要指出所在路徑,如果遇到 to open input file 這個問題,最好把匯編程序直接放在 C 盤,我放在桌面上,也就是 C:\Users\\ 下,也會出現(xiàn)此錯誤。
連接
在對源程序編譯后得到目標文件后,我們需要對目標文件進行連接,從而得到可執(zhí)行文件。上一步我們得到了 .obj文件,現(xiàn)在我們需要將 .obj 文件連接成為 .exe 也就是可執(zhí)行文件。
為了實現(xiàn)我們的需求,我們需要借助微軟的 3.60 連接器,文件名為 link.exe,這個應(yīng)用程序不用再次下載(在我公眾號回復拿到的軟件會包括編譯器和連接器,解壓后,它們都會在 masm 文件夾下)。
現(xiàn)在我們進入 DOS,cd 到 masm 文件中,鍵入link。
運行 link 后,會出現(xiàn)一些版本信息,然后提示需要被連接的目標文件名稱,這里仍需要注意,默認文件是 .obj 結(jié)尾,所以如果你需要連接的文件是 obj 文件,就不用輸入后綴名,如果不是 obj 文件,則需要輸入全名。
我們剛剛編譯了一個 test.obj 文件,所以我們直接對這個 obj 文件進行連接。
輸入要連接的文件名(這里仍需要輸入 obj 所在的路徑),按 enter 。
輸入 enter 后,會繼續(xù)來一個三連提示。
第一個提示表明程序繼續(xù)提示我們輸入要生成可執(zhí)行文件的名稱,可執(zhí)行文件是我們對一個程序進行連接要得到的最終結(jié)果,默認的 .exe 文件是 TEST.EXE ,所以我們不再需要指定文件名。這里也可以指定生成可執(zhí)行文件所在的目錄,我們也不需要,繼續(xù)向下走。
第二個提示是連接程序提示輸入映像文件的名稱,這個文件是連接程序?qū)⒛繕宋募B接為可執(zhí)行文件過程中的中間結(jié)果,也可以讓連接程序不生成這個文件,繼續(xù)向下走。
第三個提示是連接程序提示輸入庫文件的名稱,庫文件包含了一些可以調(diào)用的子程序,如果程序調(diào)用了庫中的子程序,就需要指定,否則不需要。
最后會出現(xiàn)一個: no stack ,我曾一直以為出現(xiàn)這個提示就不會再生成最終執(zhí)行文件,但是當我仔細檢查之后我才發(fā)現(xiàn)這只是一個 ,最終的執(zhí)行文件在 masm 文件夾下,我截個圖給你看。
這個提示只是告訴我們沒有棧段,我們可以完全忽略這個提示,當然如果你的程序有問題,是無法生成連接之后的文件的。
連接這個過程很有用,歸結(jié)來說,主要有三個作用
執(zhí)行應(yīng)用程序
現(xiàn)在我左手一個 asm 文件,右手一個 obj 文件,嘴里叼著一個 exe 文件,所以我就是嘴遁王者。廢了半天勁,終于將 asm 搞成 exe 文件了,累屁了,不過先別急著休息,還差最后一步,執(zhí)行它!
于是我們執(zhí)行以下 TEST.EXE 文件
我有點蒙,這怎么啥都沒有啊,輸出結(jié)果呢?。。。。。。
細想了一下,哦,我們沒有用任何庫來向控制臺輸出信息,我們只是做了一些數(shù)據(jù)和寄存器的移動、相加操作。
當然我們可以向控制臺輸出信息,不過這個我們后面在演示。
簡單聊聊程序的裝載過程
我們大家知道,一個程序如果要執(zhí)行,就需要裝載進入內(nèi)存,然后 CPU 從內(nèi)存中取指執(zhí)行命令。
那么,當我們使用 DOS 的時候,誰負責將可執(zhí)行程序裝載進入內(nèi)存的呢?
在 DOS 中,有一個叫做命令解釋器這個玩意兒,它也是 DOS 系統(tǒng)的 shell。
DOS 啟動后,會先進行初始化,然后運行 , 運行后,執(zhí)行完其他相關(guān)任務(wù)后,會在屏幕上顯示提示符,等待用戶輸入。
如果用戶輸入要執(zhí)行的命令,比如 cd匯編語言轉(zhuǎn)換成機器碼, 等,這些命令由 執(zhí)行,執(zhí)行完成后再次等待用戶輸入。
如果用戶輸入要執(zhí)行的程序, 會通過文件名找到可執(zhí)行文件,然后將它載入內(nèi)存,設(shè)置 CS:IP 執(zhí)行入口,然后 暫停運行,CPU 執(zhí)行程序,程序執(zhí)行完成后,返回 , 再次等待用戶輸入。
所以,一個完整的匯編程序的執(zhí)行過程如下。
- EOF -