vc++ 中開(kāi)發(fā)匯編語(yǔ)言
vc++ 中開(kāi)發(fā)匯編語(yǔ)言?
匯編程序結(jié)構(gòu) 一個(gè)顯示字符串的匯編程序程序格式 一、模式定義二、includelib語(yǔ)句三、函數(shù)聲明語(yǔ)句四、數(shù)據(jù)和代碼部分Visual C/C++環(huán)境 建立工程匯編程序的調(diào)試 一、設(shè)置斷點(diǎn)二、內(nèi)存窗口三、寄存器窗口四、監(jiān)視窗口常用調(diào)試命令字符串輸入、輸出 printfsprintfscanf常用Windows API調(diào)用 MessageBox確定函數(shù)的聲明語(yǔ)句和庫(kù)文件讀取CPU標(biāo)識(shí)WinDbg調(diào)試工具實(shí)驗(yàn)題:用MessageBox函數(shù)顯示CPU信息
Windows、Linux等現(xiàn)代操作系統(tǒng)都運(yùn)行于CPU的保護(hù)模式下。學(xué)習(xí)保護(hù)模式的匯編語(yǔ)言編程,要選用合適的編譯、調(diào)試工具,編譯工具決定了匯編程序的語(yǔ)法、結(jié)構(gòu),而調(diào)試工具則能夠幫助我們迅速查找程序中的錯(cuò)誤,提高調(diào)試效率。
本實(shí)驗(yàn)指導(dǎo)書(shū)采用Microsoft公司的MASM 6.14作為編譯工具,Microsoft Visual C/C++作為開(kāi)發(fā)調(diào)試環(huán)境。
1.1 匯編程序結(jié)構(gòu)
和其他語(yǔ)言一樣,匯編語(yǔ)言的源程序也要符合一定的格式,才能被編譯程序所識(shí)別和處理。學(xué)習(xí)和掌握這些格式,是進(jìn)行匯編編程的第一步。
1.1.1 一個(gè)顯示字符串的匯編程序
???????下面是一個(gè)簡(jiǎn)單的匯編程序。
;程序清單:test.asm(在控制臺(tái)上顯示一個(gè)字符串)
.386
.model flat, stdcall
option casemap:none
; 說(shuō)明程序中用到的庫(kù)、函數(shù)原型和常量
includelib????? msvcrt.lib
printf????????? PROTO C :ptr sbyte, :vararg
; 數(shù)據(jù)區(qū)
.data
szMsg?????????? byte????“Hello World!”, 0ah, 0
; 代碼區(qū)
.code
start:
??????????????? mov??? eax, OFFSET szMsg
??????????????? invoke printf, eax
??????????????? ret
end???????????? start
1.1.2 程序格式
在源程序test.asm中,以分號(hào)(;)開(kāi)始的行是注釋行。注釋行對(duì)程序的編譯和執(zhí)行沒(méi)有影響。
一、模式定義
程序的第一部分是有關(guān)模式定義的3條語(yǔ)句:
.386
.model flat, stdcall
option casemap:none
這些語(yǔ)句定義了程序使用的指令集、工作模式。
(1)指令集
.386語(yǔ)句是匯編語(yǔ)言的偽指令,說(shuō)明本程序使用的指令集是哪一種CPU的。還可以使用:.8086、.186、.286、.386、.386p、.486、.486p、.586、.586p等。
后面帶p的偽指令則表示程序中可以使用特權(quán)指令。
(2)工作模式
.model語(yǔ)句用來(lái)定義程序工作的模式,它的格式是:
.model 內(nèi)存模式[, 調(diào)用規(guī)則][, 其他模式]
內(nèi)存模式的定義影響最后生成的可執(zhí)行文件,可執(zhí)行文件的規(guī)??梢杂泻芏喾N類(lèi)型,在Windows環(huán)境下,內(nèi)存模式為flat,可執(zhí)行文件最大可以用4 GB內(nèi)存。
在test.asm中,.model語(yǔ)句指明使用stdcall調(diào)用規(guī)則。調(diào)用規(guī)則就是子程序的調(diào)用方式,即調(diào)用子程序時(shí)參數(shù)傳遞的次序和堆棧平衡的方法。
(3). option語(yǔ)句
option語(yǔ)句有許多選項(xiàng),這里介紹一種:
option casemap:none
這條語(yǔ)句說(shuō)明程序中的變量和子程序名是否對(duì)大小寫(xiě)敏感。對(duì)大小寫(xiě)敏感表示區(qū)分大寫(xiě)、小寫(xiě)形式,例如變量XYZ和xyz是兩個(gè)不同的變量。對(duì)大小寫(xiě)不敏感則不區(qū)分大寫(xiě)、小寫(xiě)形式,變量XYZ和xyz是同一個(gè)變量。
由于Windows API函數(shù)中的函數(shù)名稱(chēng)是區(qū)分大小寫(xiě)的,所以應(yīng)該指定這個(gè)選項(xiàng)“casemap:none”,否則在調(diào)用函數(shù)的時(shí)候會(huì)出現(xiàn)問(wèn)題。
二、includelib語(yǔ)句
和C程序一樣,在匯編程序中也需要調(diào)用一些外部模塊(子程序/函數(shù))來(lái)完成部分功能。例如,在hello.asm中,就需要調(diào)用printf函數(shù)將字符串顯示在屏幕上。
printf函數(shù)屬于C語(yǔ)言的庫(kù)函數(shù)。它的執(zhí)行代碼放在一個(gè)動(dòng)態(tài)鏈接庫(kù)DLL(dynamic-load library)中,這個(gè)動(dòng)態(tài)庫(kù)的名字叫msvcrt.dll。
在匯編源程序中,需要用includelib語(yǔ)句指出庫(kù)文件的名稱(chēng),鏈接時(shí)LINK就從庫(kù)文件中找出了函數(shù)的位置,避免出現(xiàn)上面的錯(cuò)誤提示。這種庫(kù)文件也叫導(dǎo)入庫(kù)(import library)。例如:
includelib????? msvcrt.lib
一個(gè)DLL文件對(duì)應(yīng)一個(gè)導(dǎo)入庫(kù),如msvcrt.dll的導(dǎo)入庫(kù)是msvcrt.lib;kernel32.dll的導(dǎo)入庫(kù)是kernel32.lib;user32.dll的導(dǎo)入庫(kù)是user32.lib等。導(dǎo)入庫(kù)文件在Visual C/C++的庫(kù)文件目錄中,在鏈接生成可執(zhí)行文件時(shí)使用。
可執(zhí)行文件執(zhí)行時(shí),只需要DLL文件,不需要導(dǎo)入庫(kù)。
三、函數(shù)聲明語(yǔ)句
對(duì)于所有要用到的庫(kù)函數(shù)(或Windows API函數(shù)),在程序的開(kāi)始部分必須預(yù)先聲明。包括函數(shù)的名稱(chēng)、參數(shù)的類(lèi)型等,如:
在匯編語(yǔ)言程序中,函數(shù)聲明為:
函數(shù)名稱(chēng)??????????? PROTO [調(diào)用規(guī)則] :[第一個(gè)參數(shù)類(lèi)型] [,:后續(xù)參數(shù)類(lèi)型]
其中,PROTO后的調(diào)用規(guī)則是可選項(xiàng)。如果不寫(xiě),則使用model語(yǔ)句中指定的調(diào)用規(guī)則。
如果函數(shù)使用C調(diào)用規(guī)則,則PROTO后跟一個(gè)C。接下來(lái)是參數(shù)的說(shuō)明。如果參數(shù)個(gè)數(shù)、類(lèi)型不定,則用VARARG說(shuō)明(varible argument)。
先看在C語(yǔ)言頭文件stdio.h中printf的函數(shù)聲明:
_CRTIMP int __cdecl printf(const char *, ...);
可知printf函數(shù)的調(diào)用規(guī)則為C調(diào)用規(guī)則(__cdecl,?即c declare),第一個(gè)參數(shù)是字符串指針,后面的參數(shù)數(shù)量及類(lèi)型不定。
這里,用ptr sbyte代表const char *。
printf????????? PROTO C :ptr sbyte,:vararg
四、數(shù)據(jù)和代碼部分
程序中的數(shù)據(jù)部分和代碼部分是分開(kāi)定義的,數(shù)據(jù)部分從這一行開(kāi)始:
.data
代碼部分從這一行開(kāi)始:
.code
遇到end語(yǔ)句時(shí),代碼部分結(jié)束。
end語(yǔ)句一般是整個(gè)程序的最后一條語(yǔ)句。end語(yǔ)句后面跟的是起始標(biāo)號(hào)。它指出了程序執(zhí)行的第一條指令的位置。在例子中,使用start作為起始標(biāo)號(hào),程序從start處開(kāi)始執(zhí)行。注意,程序并不一定要從代碼部分的第一行開(kāi)始執(zhí)行。例如,start前面可以寫(xiě)一些子程序等。
end???????????? 起始標(biāo)號(hào)
如果要定義堆棧部分,可以使用堆棧定義語(yǔ)句:
.stack????????? [堆棧大小]
1.2 Visual C/C++環(huán)境
Microsoft Visual C/C++(簡(jiǎn)稱(chēng)VC)是一個(gè)典型的集成開(kāi)發(fā)環(huán)境(IDE,integrated development environment),在國(guó)內(nèi)外十分流行。集成開(kāi)發(fā)環(huán)境大大地提高了程序開(kāi)發(fā)過(guò)程的效率,而且它還能夠動(dòng)態(tài)地調(diào)試程序。除了可以編寫(xiě)調(diào)試C/C++程序外,VC還可以用來(lái)編輯、修改、編譯、調(diào)試匯編程序。本書(shū)使用的版本是Microsoft Visual C/C++ 6.0。
1.2.1 建立工程
首先,按照以下步驟建立一個(gè)能編譯、調(diào)試匯編程序的工程:
(1)????啟動(dòng)VC后,從菜單中選擇“File”→“New”。
(2)????如圖1-1所示,在打開(kāi)的“New”對(duì)話(huà)框頂部,單擊“Projects”,再選中“Win32 Console Application”。在Location編輯框中輸入“c:"asm”,再在“Project name”中輸入“test”。輸入“test”時(shí),它自動(dòng)地添加到Location編輯框中“c:"asm”的后面。
圖1-1?建立匯編程序工程之一
(3)????單擊“OK”鍵后,出現(xiàn)一個(gè)新的對(duì)話(huà)框,單擊“Finish”。
(4)????接下來(lái),VC的窗口的左邊顯示出“test classes”,下面有“ClassView”和“FileView”兩種視圖,如圖1-2所示。
(5)????這時(shí),可將hellow.asm(或其他的一個(gè).asm源程序文件)復(fù)制到c:"asm"test中,并改名為test.asm;也可以將其他的匯編程序源文件復(fù)制到c:"asm"test"test.asm。
圖1-2?建立匯編程序工程之二
(6)????接下來(lái),再?gòu)牟藛沃羞x擇“Project”→“Add to Projects”→“Files”,在該對(duì)話(huà)框中的文件名處輸入“c:"asm"test"test.asm”,如圖1-3所示。
圖1-3?建立匯編程序工程之三
(7)????在VC窗口左邊的視圖中,展開(kāi)“FileView”中的“Source Files”,顯示出“test.asm”。在“test.asm”上,單擊鼠標(biāo)右鍵,出現(xiàn)如圖1-4所示的菜單。
圖1-4?建立匯編程序工程之四
(8)????在菜單中選擇“Setting”。彈出另一個(gè)對(duì)話(huà)框,如圖1-5所示。在“Commands”編輯框中輸入“ml /c /coff /Zi test.asm”,在“Outputs”編輯框中輸入“test.obj”。再單擊“OK”。
圖1-5?建立匯編程序工程之五
(9)????最后,再將“ML.EXE”和“ML.ERR”兩個(gè)文件復(fù)制到“c:"windows”。如果Windows安裝到其他目錄,則需要把這兩個(gè)文件復(fù)制到相應(yīng)的目錄??捎谩皊et windir”命令顯示出Windows的安裝目錄。
(10)最后,驗(yàn)證是否能在VC中編譯test.asm。在VC中按F7鍵,應(yīng)該自動(dòng)編譯生成test.exe。如果源程序中有錯(cuò)誤,編譯后將錯(cuò)誤信息顯示在“Output”的“Build”視圖中。點(diǎn)擊該錯(cuò)誤信息,光標(biāo)自動(dòng)定位到出現(xiàn)錯(cuò)誤的程序行(也可以按F4鍵定位到錯(cuò)誤的程序行)。
為了使VC適合于匯編語(yǔ)言的調(diào)試,可對(duì)它進(jìn)行如下設(shè)置,如圖1-6所示。
(11)從“Tools”菜單中選擇“Options…”,再選擇“Debug”頁(yè),選中“Disassembly window”中的“Code bytes”(前面打上對(duì)勾)。
(12)在“Memory window”中,選中“Fixed width”,在后面填入數(shù)字16。
(13)在“General”中,選中“Hexdecimal display”。
(14)不選“View floating point registers”。
圖1-6 VC的調(diào)試設(shè)置選項(xiàng)
程序編譯成功后,按Ctrl+F5可以運(yùn)行已編譯好的程序。
1.2.2 匯編程序的調(diào)試 一、設(shè)置斷點(diǎn)
如果程序運(yùn)行的結(jié)果不正確,可以在VC中調(diào)試。單擊“FileView”視圖中的test.asm,這個(gè)源程序就會(huì)自動(dòng)地進(jìn)入VC的編輯窗口。
將光標(biāo)移動(dòng)到程序入口所在的程序行上,按F9鍵。就在該行設(shè)置了一個(gè)斷點(diǎn)。斷點(diǎn)的程序行前有一個(gè)紅色的小圓點(diǎn),如圖1-7所示。
按F5鍵在Debug狀態(tài)下執(zhí)行程序,或者從菜單中選擇“Build”→“Start Debug”→“Go”。
這時(shí),當(dāng)前窗口顯示出程序中的指令序列,有一個(gè)黃色箭頭,它就是程序要執(zhí)行的下一條指令,如圖1-8所示。
二、內(nèi)存窗口
從菜單中選擇“View”→“Debug Windows”→“Memory”,打開(kāi)內(nèi)存窗口,在地址“Address:”后面的編輯框中可以輸入內(nèi)存變量的名稱(chēng),這里輸入szTitle。內(nèi)存窗口中就顯示出該變量所在內(nèi)存單元的值。前面的部分是以十六進(jìn)制的形式顯示出來(lái)的,后面是以ASCII字符的形式顯示出來(lái)的。
在內(nèi)存窗口上單擊鼠標(biāo)右鍵,可以選擇:按字節(jié)、字、雙字顯示內(nèi)存單元的值。
三、寄存器窗口
從菜單中選擇“View”→“Debug Windows”→“Registers”,打開(kāi)寄存器窗口。在寄存器窗口中,顯示了各個(gè)32位寄存器和段寄存器的值。
在調(diào)試程序時(shí),如果某一個(gè)寄存器或內(nèi)存單元的值被改變,則它的值用紅色顯示出來(lái)。
在寄存器窗口中的最后一行,顯示的內(nèi)存單元就是當(dāng)前指令要讀或?qū)懙牟僮鲾?shù)。
EFLAGS狀態(tài)寄存器的值是按位顯示的。但是,VC并沒(méi)有使用我們所熟悉的OF、DF、IF、SF、ZF、AF、PF、CF名稱(chēng),而是用它自己的一套名稱(chēng),如表1-1所示。
表1-1 VC中的EFLAGS標(biāo)志位
VC格式
OV
UP
EI
PL
ZR
AC
PE
CY
FLAGS位
OF
DF
IF
SF
ZF
AF
PF
CF
含義
溢出
方向
中斷允許
符號(hào)
為零
輔助進(jìn)位
奇偶
進(jìn)位
例如UP=0表示DF=0。
四、監(jiān)視窗口
從菜單中選擇“View”→“Debug Windows”→“Watch”,打開(kāi)監(jiān)視窗口。在“Name”一欄下面,可以輸入想要監(jiān)視的變量或寄存器名稱(chēng)。監(jiān)視窗口會(huì)隨時(shí)將這些變量的值顯示出來(lái)。
要在調(diào)試過(guò)程中改動(dòng)寄存器或內(nèi)存變量的值,可以在Watch窗口的該寄存器或變量的內(nèi)容(在Value列)用鼠標(biāo)左鍵單擊,修改其值后,按回車(chē)鍵即可。
也可以在內(nèi)存窗口中修改變量的值。在要修改的內(nèi)存單元上點(diǎn)擊,直接輸入新的內(nèi)容即可。
另外一種方法是按Shift+F9。彈出對(duì)話(huà)框后,在“Expression”處輸入寄存器或內(nèi)存變量的名稱(chēng),再在下面的Value一列處修改其內(nèi)容。最后,按“OK”。
圖1-7?編輯、編譯匯編源程序并設(shè)置斷點(diǎn)
圖1-8中為打開(kāi)內(nèi)存窗口、監(jiān)視窗口和寄存器窗口后的屏幕顯示。
調(diào)試過(guò)程中,編輯窗口中顯示出匯編源程序。如果要查看程序的實(shí)際執(zhí)行代碼,從菜單中選擇“View”→“Debug Windows”→“Disassmebly”。在運(yùn)行過(guò)程中,實(shí)際上運(yùn)行的是機(jī)器代碼,而不是匯編源程序。機(jī)器代碼及其反匯編的指令和源程序混合顯示在編輯窗口中。反匯編中的程序地址和指令中的數(shù)據(jù)都是用十六進(jìn)制顯示的。在調(diào)試過(guò)程中,使用十六進(jìn)制來(lái)表示地址和(變量或寄存器的)數(shù)值更方便。
按F10鍵可一步一步地執(zhí)行程序。執(zhí)行過(guò)程中,可以在內(nèi)存窗口中觀察變量的變化;在寄存器窗口中可以看到寄存器的變化;更加方便的是,可以把鼠標(biāo)移動(dòng)到編輯窗口中的寄存器或變量上,停留幾秒鐘后,VC會(huì)自動(dòng)地顯示它們的值。
按Shift+F5鍵,可結(jié)束調(diào)試。
圖1-8 VC調(diào)試環(huán)境:編輯窗口、內(nèi)存窗口、監(jiān)視窗口和寄存器窗口
1.2.3 常用調(diào)試命令
常用的調(diào)試命令如表1-2所示。
表1-2 VC的常用調(diào)試命令
功能鍵
作??用
描??述
F11
單步執(zhí)行
Step Into
F10
執(zhí)行
Step Over
Ctrl+F10
執(zhí)行到當(dāng)前光標(biāo)的位置的指令
Run to Cursor
F9
在當(dāng)前光標(biāo)的位置的指令上設(shè)置/清除斷點(diǎn)
Set/Clear Breakpoint at Cursor
F5
執(zhí)行程序
Go
Shift+F5
終止程序,退出程序
Stop Debugging
設(shè)置當(dāng)前指令
將光標(biāo)處的指令設(shè)為當(dāng)前指令
Set Next Statement
Shift+F11
當(dāng)前子程序執(zhí)行結(jié)束
Step Out
l?????????F11:?jiǎn)尾綀?zhí)行當(dāng)前指令。當(dāng)前指令在反匯編窗口中用一個(gè)黃色箭頭指示,CS:EIP指向當(dāng)前指令。按F11鍵后,當(dāng)前指令執(zhí)行,黃色箭頭和EIP隨之變化,指向新的當(dāng)前指令。
l?????????F10:執(zhí)行當(dāng)前進(jìn)程指令。F10和F11在執(zhí)行一般指令時(shí)沒(méi)有區(qū)別。在當(dāng)前指令是一條CALL、INT指令的情況下有所區(qū)別。如果當(dāng)前指令是CALL指令,按F11后,進(jìn)入到子程序的第一條指令,子程序執(zhí)行前就進(jìn)入調(diào)試狀態(tài),可調(diào)試子程序的執(zhí)行過(guò)程;按F10后,子程序執(zhí)行完畢后才回到調(diào)試狀態(tài),不需要調(diào)試子程序的執(zhí)行過(guò)程。
l?????????Ctrl+F10:先把光標(biāo)移動(dòng)到一條指令上,可以用鍵盤(pán)上的上、下箭頭移動(dòng)光標(biāo),或者在某一行上點(diǎn)擊。再按Ctrl+F10,程序就從當(dāng)前指令處開(kāi)始執(zhí)行,一直到光標(biāo)處的指令再停下來(lái)。
l?????????F9:先把光標(biāo)移動(dòng)到一條指令上,按F9,就在該指令上設(shè)置了一個(gè)斷點(diǎn)。再按F9,這個(gè)斷點(diǎn)就清除了。設(shè)置斷點(diǎn)后,指令的前面標(biāo)有一個(gè)紅色的圓點(diǎn)。程序運(yùn)行到斷點(diǎn)時(shí),會(huì)停下來(lái),這時(shí)就可以檢查各個(gè)變量、寄存器的內(nèi)容以及程序的執(zhí)行流程是否正確,以查找程序中的錯(cuò)誤。
l?????????F5:從當(dāng)前指令開(kāi)始執(zhí)行程序,直到遇到斷點(diǎn)或程序結(jié)束時(shí)為止。
l?????????Shift+F5:終止程序,不再執(zhí)行后面的程序。終止后,可以再按F11鍵(或Ctrl+Shift+F5)重新開(kāi)始調(diào)試過(guò)程。
l?????????設(shè)置當(dāng)前指令:在調(diào)試時(shí),可能希望跳過(guò)一部分程序不執(zhí)行,也可能想將已執(zhí)行過(guò)的一段程序再執(zhí)行一遍。這可以通過(guò)改變當(dāng)前指令來(lái)實(shí)現(xiàn)。在新的當(dāng)前指令上按下鼠標(biāo)右鍵,彈出一個(gè)菜單,在其中選擇“Set Next Statement”。這時(shí),黃色箭頭就指到新設(shè)置的當(dāng)前指令上。
l?????????Shift+F11:先按住Shift鍵,再按下F11。當(dāng)前指令在子程序中時(shí),如果想使整個(gè)子程序執(zhí)行完畢,返回到主程序,則使用Shift+F11。
某些功能也可以從“Debug”菜單中選擇。如圖1-9所示。
圖1-9 VC的部分Debug菜單項(xiàng)
1.3 字符串輸入、輸出
在C語(yǔ)言中,常用printf、scanf、sprintf等函數(shù)來(lái)實(shí)現(xiàn)字符串的的輸入輸出,在匯編語(yǔ)言中,可以調(diào)用這些函數(shù)。
1. printf
在前面的程序例子中已經(jīng)用到過(guò)printf。在程序中,要指明printf的調(diào)用規(guī)則,以及它的參數(shù)類(lèi)型。
printf????????? PROTO C :dword,:vararg
printf使用C調(diào)用規(guī)則(參數(shù)自右至左入棧,由主程序平衡堆棧)。第1個(gè)參數(shù)是一個(gè)雙字(:dword),即字符串的地址,后面的其他參數(shù)個(gè)數(shù)可變,可以1個(gè)沒(méi)有,也可以跟多個(gè)參數(shù)。
以下C語(yǔ)句輸出3個(gè)整數(shù)A、B、R和一個(gè)字符Op:
printf ("%d %c %d = %d"n" , A, Op, B, R);
在匯編語(yǔ)言中,在數(shù)據(jù)區(qū)中要定義szOutputFmtStr:
szOutputFmtStr?????? byte??? '%d %c %d = %d', 0ah, 0
使用invoke語(yǔ)句調(diào)用printf。Printf后面跟的第1個(gè)參數(shù)是格式字符串的地址;第2、3、4個(gè)參數(shù)分別是要輸出的整數(shù)。
invoke printf, offset szOutputFmtStr, A, Op, B, R
在程序中,還需要包括以下語(yǔ)句,指示鏈接程序在msvcrt.lib庫(kù)文件尋找鏈接信息。
includelib????? msvcrt.lib
2. sprintf
sprintf與printf相似,它將輸出保存在第1個(gè)字符串szStr中。
invoke sprintf, offset szStr, offset szOutputFmtStr, A, Op, B, R
3. scanf
scanf是從控制臺(tái)將用戶(hù)的輸入讀入到程序的變量中,變量的類(lèi)型可以是整數(shù)、字符、字符串等。
scanf的調(diào)用規(guī)則和參數(shù)類(lèi)型說(shuō)明為:
scanf?????????? PROTO C :dword,:vararg
scanf的鏈接信息也包括在msvcrt.lib庫(kù)文件中。
程序中需要輸入A、Op、B時(shí),A、B是整數(shù),OP是字符。它的第1個(gè)參數(shù)是格式字符串的地址;第2、3、4個(gè)參數(shù)分別是A、Op、B的地址。
szInputFmtStr?? byte??? '%d %c %d', 0
invoke????????? scanf,offset szInputFmtStr,offset A,offset Op,offset B
其效果等價(jià)于:
scanf("%d %c %d", &A, &Op, &B);
下面的程序首先調(diào)用printf顯示字符串,提示用戶(hù)輸入要計(jì)算的表達(dá)式,再調(diào)用scanf接收用戶(hù)的輸入。根據(jù)輸入的運(yùn)算符Op,通過(guò)條件跳轉(zhuǎn)指令實(shí)現(xiàn)對(duì)加、減、乘、除的判斷和處理。最后,調(diào)用printf輸出計(jì)算結(jié)果。其執(zhí)行結(jié)果為:
input (a Op b, Op=+-/*, ex: 28-2): 4*5
4 * 5 = 20
;程序清單:equation.asm(四則運(yùn)算)
.386
.model flat,stdcall
Option casemap:none
includelib????? msvcrt.lib
scanf?????????? PROTO C :dword,:vararg
printf????????? PROTO C :dword,:vararg
.data
szInPmt???????? byte??? 'input (a Op b, Op=+-/*, ex: 28-2): ', 0 ;
szInputFmtStr?? byte??? '%d %c %d', 0
szOutputFmtStr byte??? '%d %c %d = %d', 0ah, 0
szErrMsg??????? byte??? 'invalid Operation. ', 0ah, 0
A?????????????? sdword ?
B?????????????? sdword ?
Op????????????? dword?? ?
R?????????????? sdword ?
.code
start:
??????????????? invoke printf, offset szInPmt
??????????????? invoke scanf, offset szInputFmtStr,
??????????????????????? offset A,
??????????????????????? offset Op,
??????????????????????? offset B
??????????????? mov???? eax, A
??????????????? mov???? ebx, B
??????????????? cmp???? Op, '+'
??????????????? jz????? lab_add
??????????????? cmp???? Op, '-'
??????????????? jz????? lab_sub
??????????????? cmp???? Op, '*'
??????????????? jz????? lab_mul
??????????????? cmp???? Op, '/'
??????????????? jz????? lab_div
lab_err:??????????????
??????????????? invoke printf, offset szErrMsg
??????????????? jmp???? lab_exit
lab_add:??????
??????????????? add???? eax, ebx
??????????????? mov???? R, eax
??????????????? jmp???? lab_output
lab_sub:??????
??????????????? sub???? eax, ebx
??????????????? mov???? R, eax
??????????????? jmp???? lab_output
lab_mul:??????
??????????????? imul??? eax, ebx
??????????????? mov???? R, eax
??????????????? jmp???? lab_output
lab_div:??????
??????????????? cdq
??????????????? idiv??? ebx
??????????????? mov???? R, eax
??????????????? jmp???? lab_output
lab_output:???
??????????????? invoke printf, offset szOutputFmtStr, A, Op, B, R
lab_exit:?????
??????????????? ret
end???????????? start
equation.asm在C:"asm"sample"chap-01目錄中,編譯連接的步驟為:
(1)?進(jìn)入C:"asm"sample"chap-01目錄
cd C:"asm"sample"chap-01
(2)?設(shè)置編譯環(huán)境
c:"asm"bin"asmvars.bat
(3)?編譯連接
ml /coff equation.asm /link /subsystem:console
1.4 常用Windows API調(diào)用
API是application programming interface的縮寫(xiě),代表應(yīng)用程序編程接口。API一般使用stdcall調(diào)用規(guī)則。
1. MessageBox
printf和scanf適用于控制臺(tái)程序(鏈接選項(xiàng)為/subsystem:console),而帶窗口的Windows程序(鏈接選項(xiàng)為/subsystem:windows)不能使用printf和scanf。這里介紹一個(gè)輸出信息用的API-MessageBox。
它的調(diào)用規(guī)則和參數(shù)類(lèi)型說(shuō)明為:
MessageBoxA PROTO :DWORD,:DWORD,:DWORD,:DWORD
MessageBoxA的鏈接信息包括在user32.lib庫(kù)文件中。
MessageBoxA的C語(yǔ)言原型在VC附帶的"winuser.h"中提供。
2.?確定函數(shù)的聲明語(yǔ)句和庫(kù)文件
在編程中,應(yīng)盡可能地利用已有的C的庫(kù)函數(shù)和Windows API函數(shù),以減少編程的工作量。
通過(guò)查閱在MSDN(或VC等工具)資料和幫助文件、閱讀示例程序等方法,弄清函數(shù)的功能以及入口、出口參數(shù),以及每一個(gè)參數(shù)的用法。根據(jù)函數(shù)的名稱(chēng)、參數(shù)的個(gè)數(shù)、類(lèi)型、調(diào)用規(guī)則等,寫(xiě)出這樣的聲明語(yǔ)句:
printf????????? PROTO C :dword,:vararg
再確定它屬于哪一個(gè)庫(kù)文件。常用的庫(kù)文件有:msvcrt.lib、kernel32.lib、user32.lib等。
函數(shù)可能返回的是一個(gè)整數(shù)、指針或其他類(lèi)型。無(wú)論如何,返回值都在EAX中。要注意有些函數(shù)是通過(guò)傳遞地址指針的方式來(lái)改變參數(shù)的值,如scanf。
1.5 讀取CPU標(biāo)識(shí)
CPUID指令是獲得CPU信息的匯編指令,它可以返回CPU類(lèi)型、型號(hào)、制造商信息、商標(biāo)信息、序列號(hào)、緩存等CPU相關(guān)信息。
CPUID指令使用eax作為輸入?yún)?shù),eax、ebx、ecx、edx作為輸出參數(shù)。例如:
??????????????? mov???? eax, 0
??????????????? cpuid
執(zhí)行結(jié)果為:EAX = 00000002 EBX = 756E6547 ECX = 6C65746E EDX = 49656E69。結(jié)果以十六進(jìn)制顯示。EBX、ECX、EDX中各存儲(chǔ)4個(gè)字符,全部12個(gè)字符為:GenuineIntel。
使用eax=3作為輸入?yún)?shù)時(shí),在ECX、EDX中返回CPU序列號(hào)的第0~31位、第32~63位。
??????????????? mov???? eax, 0
??????????????? cpuid
;程序清單:cpuid.asm(讀取CPU標(biāo)識(shí))
.586
.model flat,stdcall
Option casemap:none
includelib????? msvcrt.lib
printf????????? PROTO C :dword,:vararg
.data
szVendorID????? byte 13 dup (0)
szFormatStr???? byte 'VendorID = %s; Processor SN = %08X%08X', 0ah
.code
start:
??????????????? mov???? eax, 0
??????????????? cpuid
??????????????? mov???? dword ptr szVendorID, ebx
??????????????? mov???? dword ptr szVendorID+4, edx
??????????????? mov???? dword ptr szVendorID+8, ecx
???????????????
??????????????? mov???? eax, 3
??????????????? cpuid
??????????????? invoke printf, offset szFormatStr,
??????????????????????? offset szVendorID, ecx, edx
??????????????? ret
end???????????? start
程序運(yùn)行結(jié)果如下所示:
VendorID = GenuineIntel; Processor SN = 00000000007B7040
1.6 WinDbg調(diào)試工具
WinDbg是微軟公司發(fā)布的免費(fèi)源碼級(jí)調(diào)試工具。Windbg可以用于Kernel模式調(diào)試和用戶(hù)模式調(diào)試,還可以調(diào)試Dump文件。
使用WinDbg調(diào)試匯編程序的主要步驟為:
1.首先,使用下面的命令編譯、鏈接,產(chǎn)生.EXE文件。/coff選項(xiàng)要求MASM生成鏈接器所需要的COFF格式的.obj文件,/Zi則指定編譯生成的目標(biāo)文件中含有調(diào)試信息,/link表示要生成.EXE文件,而/subsystem:console則表示生成控制臺(tái)程序。
ml /coff /Zi cpuid.asm /link /subsystem:console
2.啟動(dòng)WinDbg,從菜單File→Open Executable裝入.EXE文件。
3.在WinDbg命令窗口的最下方,可輸入命令,按回車(chē)鍵執(zhí)行。如:
x cpuid!
在窗口內(nèi)顯示出cpuid.exe內(nèi)的所有數(shù)據(jù)變量:
0:000> x cpuid!
*** WARNING: Unable to verify checksum for cpuid.exe
00404000 cpuid!szVendorID = 0x00 ''
0040400d cpuid!szFormatStr = 0x56 'V'
4.輸入u命令,可以查看匯編指令:
0:000> u start l e
cpuid!start [cpuid.asm @ 11]:
00401010 b800000000????? mov???? eax,0
00401015 0fa2??????????? cpuid
00401017 891d00404000 mov???? dword ptr [cpuid!szVendorID (00404000)],ebx
0040101d 891504404000 mov???? dword ptr [cpuid!szVendorID+0x4 (00404004)],edx
00401023 890d08404000 mov???? dword ptr [cpuid!szVendorID+0x8 (00404008)],ecx
00401029 b803000000????? mov???? eax,3
0040102e 0fa2??????????? cpuid
00401030 52????????????? push??? edx
00401031 51????????????? push??? ecx
00401032 6800404000????? push??? offset cpuid!szVendorID (00404000)
00401037 680d404000????? push??? offset cpuid!szFormatStr (0040400d)
0040103c e811000000????? call??? cpuid!printf (00401052)
00401041 83c410????????? add???? esp,10h
00401044 c3????????????? ret
5.輸入bp命令,可以設(shè)置斷點(diǎn),例如:
bp start
6.從菜單中選擇Debug→Go,或者按F5鍵,可以執(zhí)行到斷點(diǎn)處。
7.從菜單中選擇View→Disassembly,可以看到斷點(diǎn)處的指令以高亮方式顯示。
8.從菜單中選擇View→Register,打開(kāi)一個(gè)窗口,顯示寄存器的當(dāng)前值。
9.在WinDbg命令窗口輸入r命令,也可以顯示寄存器的當(dāng)前值。還可以修改寄存器的值,如“r eax=5”。
10.在WinDbg命令窗口輸入輸入d命令,可以查看內(nèi)存內(nèi)容,如:“d szVendorID L c”。將d換為db、dw、dd,則指定用字節(jié)、字、雙字的格式查看。L后面跟的數(shù)字則表示查看的單元個(gè)數(shù)。
11.按F11、F10、Shift+F11、Ctrl+F10分別表示單步執(zhí)行當(dāng)前指令、執(zhí)行完畢當(dāng)前指令、執(zhí)行完當(dāng)前函數(shù)、執(zhí)行到光標(biāo)處,其用法與表1-2相同。
1.7 實(shí)驗(yàn)題:用MessageBox函數(shù)顯示CPU信息
cpuid.asm采用的是控制臺(tái)模式(鏈接選項(xiàng)為/subsystem:console),調(diào)用printf輸出信息。
要求:
1.????使用VC 6.0建立工程文件;
2.????采用帶窗口的Windows程序(鏈接選項(xiàng)為/subsystem:windows);
3.????使用sprintf?、MessageBoxA函數(shù);
4.????輸出制造商信息、序列號(hào);
5.????輸出CPU商標(biāo)信息,執(zhí)行CPUID的輸入?yún)?shù)為80000002H、80000003H、80000004H,具體格式可查閱CPU指令手冊(cè)。
運(yùn)行結(jié)果如圖1-10所示:
圖1-10?在對(duì)話(huà)框中顯示CPUID執(zhí)行結(jié)果