文章也同時(shí)在個(gè)人博客 http://kimihe.com/更新
引言
本文亦是《讀筆 匯編語(yǔ)言-基于Linux環(huán)境(第7章-跟蹤指令:與機(jī)器指令親密接觸I)》找颓。
本文將會(huì)以一個(gè)簡(jiǎn)單的.ASM程序援奢,step by step地幫助大家快速入門(mén)GDB何暇,并通過(guò)GDB調(diào)試散吵,深入底層闡述高級(jí)語(yǔ)言(如C語(yǔ)言)中循環(huán)結(jié)構(gòu)和指針的由來(lái)。
通過(guò)閱讀本文曙旭,你將知道:
- 如何快速在Linux下進(jìn)行匯編開(kāi)發(fā)涯贞。
- 如何快速入門(mén)GDB。
- 高級(jí)語(yǔ)言循環(huán)結(jié)構(gòu)的原理星持。
- 指針到底是什么抢埋。
構(gòu)建匯編程序
原料
- Linux環(huán)境,筆者是Ubuntu 12.04 LTS。
- 安裝NASM: 新立得軟件包管理器揪垄。
- 安裝Kate編輯器和KWrite編輯器: 新立得軟件包管理器穷吮。
- 安裝konsole:
> sudo apt-get install konsole
第一個(gè)匯編程序
切到你喜歡的工作目錄下,執(zhí)行> kate
以啟動(dòng)Kate編輯器饥努,啟動(dòng)后界面類似于這樣:
左側(cè)是導(dǎo)航欄捡鱼,右側(cè)是代碼編輯區(qū),下方是終端控制區(qū)(若要啟用此特性請(qǐng)務(wù)必先安裝konsle)肪凛。
新建一個(gè)文件堰汉,命名為sandbox.asm辽社,在其中輸入如下內(nèi)容:
section .data
Snippet db "KANGAROO"
section .text
global _start
_start:
nop
; Put your experiments between the two nops...
mov ebx, Snippet
mov eax, 8
DoMore: add byte [ebx], 32
inc ebx
dec eax
jnz DoMore
; Put your experiments between the two nops...
nop
這段代碼是我們第一個(gè)匯編小例子伟墙,用于闡明循環(huán)結(jié)構(gòu)的原理,請(qǐng)確保文章例子和你的完全一致滴铅。
循環(huán)結(jié)構(gòu)的原理
如果是首次接觸匯編戳葵,你可能會(huì)一頭霧水,在這里你不必在意匯編的語(yǔ)法汉匙,只需要理解我對(duì)代碼的說(shuō)明即可拱烁。
此處請(qǐng)先注意語(yǔ)句Snippet db "KANGAROO"
,其中Snippet代表一個(gè)字符串噩翠,內(nèi)容為KANGAROO戏自。然后注意語(yǔ)句mov ebx, Snippet
,這一步相當(dāng)于獲取字符串的首地址伤锚。緊接著的mov eax, 8
用于獲知字符串的長(zhǎng)度擅笔。這兩步很平常,高級(jí)語(yǔ)言的字符串處理也需要獲知字符串地址以及相應(yīng)的長(zhǎng)度屯援。
然后請(qǐng)關(guān)注如下四條語(yǔ)句:
DoMore: add byte [ebx], 32
inc ebx
dec eax
jnz DoMore
此處的jnz DoMore
語(yǔ)句便是循環(huán)結(jié)構(gòu)的核心猛们。其含義是:jnz(Jump if Not Z-Flag)進(jìn)行判斷,如果零標(biāo)志位ZF不為0狞洋,就跳轉(zhuǎn)到DoMore語(yǔ)句處弯淘。
于是你可以想到,只要這個(gè)ZF標(biāo)志位不是0吉懊,程序就會(huì)不停地循環(huán)跳轉(zhuǎn)(loop)庐橙,循環(huán)結(jié)構(gòu)由此而來(lái)。
你可能會(huì)想問(wèn):什么時(shí)候ZF會(huì)變成0借嗽?這個(gè)問(wèn)題很好态鳖,試想一下高級(jí)語(yǔ)言的while(n)循環(huán),我們必然需要一個(gè)操作步驟來(lái)改變n的值淹魄,使其在某一時(shí)刻變成0郁惜,從而跳出while。
此處,眼尖的讀者可能發(fā)現(xiàn)了dec指令兆蕉,還記得一開(kāi)始的獲取字符串長(zhǎng)度為8嗎羽戒?我們把8存在了eax寄存器中(如果你不清楚寄存器是什么,也沒(méi)有關(guān)系虎韵,把它想象成一個(gè)可以存放數(shù)值容器即可)易稠。通過(guò)dec eax
指令,我們會(huì)不斷地對(duì)eax中的8進(jìn)行遞減包蓝,類似于int eax = 8; eax--;
總有一天驶社,eax中的值會(huì)從8減到0,此時(shí)我們的x86 Intel CPU就會(huì)執(zhí)行一項(xiàng)既定的操作测萎,把ZF標(biāo)志設(shè)為1,以代表此標(biāo)志位處于激活狀態(tài)亡电。于是,jnz在判斷的時(shí)候就發(fā)現(xiàn)ZF已經(jīng)被激活為1了硅瞧,不需要再跳轉(zhuǎn)份乒,循環(huán)結(jié)果宣告結(jié)束。
此外腕唧,不知道你有沒(méi)有對(duì)于jnz跳轉(zhuǎn)指令產(chǎn)生一些聯(lián)想:它是不是很像函數(shù)指針或辖?(jnz到一個(gè)地方,那個(gè)地方叫做DoMore枣接,然后執(zhí)行一段過(guò)程颂暇。)當(dāng)然關(guān)于函數(shù)指針詳細(xì)的說(shuō)明,本文篇幅可就不夠了但惶,筆者會(huì)考慮以后單獨(dú)寫(xiě)一篇文章詳細(xì)說(shuō)明耳鸯,敬請(qǐng)期待~
什么是指針
有讀者可能會(huì)問(wèn),還有兩行代碼沒(méi)有解釋呢榆骚。不要著急片拍,這兩行代碼蘊(yùn)含著指針的奧秘。聽(tīng)起來(lái)可能有點(diǎn)令人驚奇妓肢,但實(shí)際情況確實(shí)如此捌省。讓我們來(lái)看一下這兩行代碼:
DoMore: add byte [ebx], 32
inc ebx
注意add byte [ebx], 32
這句話,它的專業(yè)術(shù)語(yǔ)叫做寄存器間接尋址碉钠。它是如此神奇纲缓,毫不夸張地說(shuō),如果沒(méi)有它喊废,我們?nèi)粘K?jiàn)的絕大部分程序?qū)㈦y以構(gòu)建祝高。
這句話解釋一下就是這樣:有一個(gè)內(nèi)存單元,它有一個(gè)byte大小的空間污筷,里面存有一個(gè)數(shù)值n(具體是多少工闺,現(xiàn)在不用關(guān)心)。把數(shù)值32 Add到這個(gè)n上,就是相當(dāng)于n+=32
陆蟆。然后關(guān)鍵點(diǎn)來(lái)了雷厂,為了加上32,我們需要知道這個(gè)內(nèi)存區(qū)域在哪兒叠殷。在哪兒呢改鲫?在ebx里存著呢!
內(nèi)存就像一個(gè)個(gè)信箱林束,每個(gè)信箱都有自己的編號(hào)像棘,當(dāng)我們尋找自家的信箱時(shí),會(huì)根據(jù)信箱的編號(hào)去尋找它壶冒。這里ebx就存著我們要的內(nèi)存區(qū)域的編號(hào)缕题,這個(gè)編號(hào)叫做地址,根據(jù)這個(gè)地址依痊,我們找到了那個(gè)內(nèi)存單元的具體位置避除,然后知道了其中存了一個(gè)數(shù)n,最后把32給加到了n上胸嘁。
這里,你應(yīng)該可以看到凉逛,我們并不是直接去訪問(wèn)那個(gè)數(shù)值n的性宏,而是先去找存放它的內(nèi)存單元。這里面存在一層間接状飞。正是有了這層間接毫胜,我們才能在高級(jí)語(yǔ)言中構(gòu)筑起各種華麗的調(diào)用操作。
于是指針的原理也顯而易見(jiàn)了诬辈,對(duì)于
char arr[4] = "abcd";
char *p = arr;
p+=3;
printf("*p: %c\\n", *p);
我們char *p = arr;
操作定位的arr數(shù)組的首地址酵使。arr信箱有四個(gè)格子,我們定位到第一個(gè)焙糟,然后p+=3;
并不是直接給信箱什么的加3口渔,這明顯不符合邏輯,而是操作信箱的編號(hào)(地址)穿撮。加3意味著往后數(shù)三個(gè)缺脉,定位到第四個(gè)格子,最后打印里面的東西悦穿,就是字符d攻礼。
內(nèi)存中的數(shù)據(jù)有兩種,分別是數(shù)據(jù)和地址栗柒,數(shù)據(jù)就是普通的變量礁扮,地址就是指針。希望你不要混淆。
使用GDB
下面進(jìn)入最后一個(gè)知識(shí)點(diǎn)太伊,快速入門(mén)GDB负蠕。在此之前,我們需要把編寫(xiě)的.ASM程序編譯鏈接運(yùn)行起來(lái)倦畅。你可能聽(tīng)說(shuō)過(guò)Linux下的make工具遮糖,說(shuō)白了就是個(gè)配置文件,告訴NASM叠赐,gcc等編譯器怎么有效地編譯我們的源碼欲账,避免重復(fù)勞動(dòng)。make配合makefile文件工作芭概,如果你不知道這到底是什么赛不,也完全沒(méi)有關(guān)系,畢竟這不是本文的重點(diǎn)罢洲,只是順帶提一下踢故。
你可以在Kate編輯器中再新建一個(gè)文本,名為makefile惹苗,請(qǐng)確保它和我們的sandbox.asm在同一個(gè)目錄下殿较。向其中輸入如下內(nèi)容:
sandbox: sandbox.o
ld -o sandbox sandbox.o
sandbox.o: sandbox.asm
nasm -f elf64 -g -F stabs sandbox.asm -l sandbox.lst
你可以完全不必理會(huì)這四句話到底代表了什么,只需要明白它們會(huì)讓NASM正確地生成我們的.ASM程序桩蓉。
有了這個(gè)makefile淋纲,接下來(lái)可以在Kate編輯器下方的terminal中輸入> make -k
,或者你自己?jiǎn)?dòng)shell院究,切到你的工作目錄洽瞬,執(zhí)行上述命令。如果正確編譯完成业汰,那么看起來(lái)就像這樣子:
接下來(lái)伙窃,我們要使用GDB了,在Terminal中鍵入:> gdb sandbox
以啟用gdb調(diào)試样漆。
調(diào)試为障,我們一般都會(huì)需要設(shè)置斷點(diǎn),來(lái)看看各變量的情況氛濒。這里我們已經(jīng)更加深入到底層产场,不在內(nèi)存中操作了,直接來(lái)到了CPU內(nèi)部的寄存器中舞竿。鍵入:> b 10
即在DoMore: add byte [ebx], 32
語(yǔ)句處加入斷點(diǎn)京景。
然后,鍵入:> r
然程序開(kāi)始運(yùn)行骗奖。程序會(huì)停在DoMore語(yǔ)句那里确徙,看起來(lái)就像這樣:
接著醒串,鍵入:i r
查看個(gè)寄存器狀態(tài),就像這樣:
你可以看到高亮的綠色部分鄙皇,rax中存有字符串長(zhǎng)度8芜赌,rbx中存有字符串地址。
啥伴逸?為什么不是eax和ebx缠沈?嗯,很有價(jià)值的問(wèn)題错蝴,eax和ebx是32位CPU架構(gòu)下的寄存器洲愤,而如今64位已經(jīng)普及,我們的寄存器也隨之升級(jí)了顷锰。
然后按一下Enter鍵柬赐,或者輸入return,可以看到下一頁(yè)未顯示完全的一些寄存器:
注意到綠色高亮部分的eflags標(biāo)志位官紫,我們發(fā)現(xiàn)其中除了IF什么都沒(méi)有肛宋,這表明我們上文提到的ZF標(biāo)志還沒(méi)有被激活。
接下來(lái)束世,鍵入:s
酝陈,它代表單步執(zhí)行一行語(yǔ)句,請(qǐng)先執(zhí)行一次良狈,然后再鍵入:i r
看一下結(jié)果寄存器狀態(tài):
可以看到rax寄存器內(nèi)部的值從8減到7后添,表明執(zhí)行了一次循環(huán)中的dec指令。接下來(lái)你可以繼續(xù)單步執(zhí)行7次薪丁,即鍵入7次s
。每一次都查看一下寄存器的狀態(tài)馅精,你會(huì)發(fā)現(xiàn)rax不斷遞減严嗜,直到0。7次單步之后洲敢,再次鍵入i r
進(jìn)行查看:
你會(huì)發(fā)現(xiàn)rax變成0了漫玄,此時(shí)Enter到下一頁(yè),我們發(fā)現(xiàn):
沒(méi)錯(cuò)压彭!eflags中出現(xiàn)了ZF標(biāo)志睦优,表明其被激活,這樣jnz就不會(huì)再跳到DoMore壮不,循環(huán)終于結(jié)束了汗盘。
最后請(qǐng)鍵入q
,然后y
退出GDB询一。我們的GDB快速入門(mén)到此告一段落隐孽。
留一個(gè)小問(wèn)題
看到這兒癌椿,相信你已經(jīng)大概理解了循環(huán)結(jié)構(gòu)和指針的原理,對(duì)匯編工具以及GDB的使用也略知一二菱阵。那么我在這里提一個(gè)小問(wèn)題:這段匯編代碼到底是做什么的踢俄?請(qǐng)你積極思考哦~
答案我會(huì)在留言中說(shuō)明。
總結(jié)
本篇文章通過(guò)Linux下的一個(gè)最簡(jiǎn)易的匯編開(kāi)發(fā)流程晴及,帶領(lǐng)大家熟悉了開(kāi)發(fā)工具的使用都办,并入門(mén)了GDB這一神器。同時(shí)通過(guò)閱讀匯編代碼虑稼,從底層理解了循環(huán)結(jié)構(gòu)和指針的原理琳钉。希望對(duì)大家有所啟迪,感謝閱讀动雹!