C語言提供了好幾種循環(huán)結(jié)構(gòu),,即while,、for和do-while,。匯編語言中并沒有相應(yīng)的指令存在,,作為替代,,將條件測(cè)試和跳轉(zhuǎn)組合起來實(shí)現(xiàn)循環(huán)的效果。大多數(shù)匯編器根據(jù)一個(gè)循環(huán)的do-while形式來產(chǎn)生循環(huán)代碼,,即使在實(shí)際程序中這種形式用的相對(duì)較少,。其它的循環(huán)會(huì)首先轉(zhuǎn)換成do-while形式,然后再編譯成機(jī)器代碼,。 do-while循環(huán)其通用形式是這樣的: do body-statementwhile (test-expr); 循環(huán)的效果就是重復(fù)執(zhí)行body-statement,,對(duì)test-expr求值,如果求值的結(jié)果為非零,,就繼續(xù)循環(huán)。注意,,body-statement至少執(zhí)行一次,。 do-while的通用形式可以翻譯成如下所示的條件和goto語句: loop: body-statement t = test-expr; if(t) goto loop; 也就是說每次循環(huán)程序會(huì)執(zhí)行循環(huán)體里面的語句,然后執(zhí)行測(cè)試表達(dá)式,。如果測(cè)試為真,,則回去再執(zhí)行一次循環(huán)。 下面示例用do-while循環(huán)計(jì)算函數(shù)參數(shù)的階乘,,寫作n,!只計(jì)算n>0時(shí)候n階乘的值: int fact_do(int n){ int result = 1; do { result *= n; n = n - 1; }while(n > 1); return result;} 匯編代碼是do-while循環(huán)的一個(gè)實(shí)現(xiàn)形式,這里用的gcc編譯器 gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04) 編譯參數(shù)是 $ gcc -m32 -O2 -o fact 因?yàn)榉浅2涣?xí)慣AT&T匯編形式,,所以這里用IDA pro 對(duì)得到的fact文件進(jìn)行反匯編分析,,原文的匯編形式(用edx保存參數(shù)n)我無論怎么調(diào)節(jié)參數(shù)都無法得到,。圖中是一個(gè)do-while循環(huán)的標(biāo)準(zhǔn)實(shí)現(xiàn),eax初始化為1,,epb+8地址處保存著參數(shù)n,,0x08048404 處把參數(shù)n減一,緊接著0x08048408 處把n與1比較,。如果為真則在0x0804840C處跳回循環(huán)的開始,,這里是循環(huán)的關(guān)鍵地方由它來判斷循環(huán)是繼續(xù)還是退出。 綜合0x080483F3,,0x080483FA我們可以看到eax被初始化為1,,在0x080483FD被乘法更新。如果學(xué)過x86匯編語言就知道 mul 乘法指令是離不開eax寄存器的,,而且返回值通常也用eax寄存器,。所以這里eax對(duì)應(yīng)于結(jié)果result是無懸念的。 理解產(chǎn)生的匯編代碼與原始代碼之間的關(guān)系,,關(guān)鍵是找到程序值和寄存器之間的映射關(guān)系,。對(duì)于循環(huán)fact_do來說,這個(gè)任務(wù)非常簡(jiǎn)單,,但是對(duì)于更復(fù)雜的程序來說,,就可能是更具挑戰(zhàn)性的任務(wù)。C語言編譯器常常會(huì)重組計(jì)算,,因此有些C代碼中的變量在機(jī)器代碼中沒有對(duì)應(yīng)的值,;而有時(shí),機(jī)器代碼中又會(huì)引入源代碼中不存在的新值,。此外編譯器還常常試圖將多個(gè)程序值映射到一個(gè)寄存器上,,來最小化寄存器的使用率。 上面的fact_do的過程對(duì)于逆向工程循環(huán)來說,,是一個(gè)通用的策略,。看看在循環(huán)之前如何初始化寄存器,,在循環(huán)中如何更新和測(cè)試寄存器,,以及在循環(huán)之后又如何使用寄存器。這些步驟中的每一步都提供了一個(gè)線索,,組合起來就可以解開謎團(tuán),。做好準(zhǔn)備,你會(huì)看到令人驚奇的變換,,其中有些情況很明顯是編譯器能夠優(yōu)化的代碼,,而有些情況很難解釋編譯器為什么要選用那些奇怪的策略。 while循環(huán)while語句的通用形式如下: while(test-expr) body-statement 與do-while不同的是,它對(duì)test-expr求值,,在第一次執(zhí)行body-statement之前,,循環(huán)就可能中止。將while循環(huán)翻譯成機(jī)器代碼有很多種方法,。一種常見的方法,,也就是GCC采用的方法,是使用條件分支,,在需要時(shí)省略循環(huán)體的第一次執(zhí)行,,從而將代碼轉(zhuǎn)換成do-while循環(huán),如下: if(,!test-expr) goto done;do body-statementwhile(test-expr);done: 接下來這個(gè)代碼可直接翻譯成goto代碼,,如下: if t = test-expr if(!t) goto done;loop: body-statement t = test-expr; if(t) goto loop;done: 使用這種策略,編譯器常常會(huì)優(yōu)化最開始的測(cè)試,,比如說認(rèn)為總是滿足測(cè)試條件,。 舉個(gè)例子fact_while是使用while循環(huán)的階乘函數(shù)的實(shí)現(xiàn),這個(gè)函數(shù)能正確的計(jì)算 0!=1 ,。fact_while_goto是GCC產(chǎn)生的匯編代碼的C語言翻譯,,比較fact_do 和fact_while 我們看到它們幾乎是相同的。將while循環(huán)轉(zhuǎn)換成do-while循環(huán),,以及將后者翻譯成goto代碼,。 int fact_while(int n){ int result = 1; while(n > 1){ result *= n; n = n - 1; } return result;} int fact_while_goto(int n){ int result = 1; if(n <= 1) goto done; loop: result *= n; n = n - 1; if(n > 1) goto loop; done: return result;} for循環(huán)for循環(huán)的通用形式如下 for(init-expr;test-expr;update-expr) body-statement C語言標(biāo)準(zhǔn)說明,這樣一個(gè)循環(huán)的行為與下面這段使用while循環(huán)代碼的行為一樣: init-expr;while(test-expr) { body-statement update-expr;} 程序首先對(duì)初始表達(dá)式init-expr求值,,然后進(jìn)入循環(huán),;在循環(huán)中它先對(duì)測(cè)試條件test-expr求值,如果測(cè)試結(jié)果為“假”就會(huì)退出,,否則執(zhí)行循環(huán)體body-statement,;最后對(duì)更新表達(dá)式update-expr求值。 這段代碼編譯后的形式,,基于前面講過的從while到do-while的轉(zhuǎn)換,,首先給出do-while的形式: init-expr;if(!test-expr) goto done;do{ body-statement update-expr;}while(test-expr);done: 然后將它轉(zhuǎn)換成goto代碼: init-expr; t = test-expr if(!t) goto done;loop: body-statement update-expr; t = test-expr; if(t) goto loop;done: 作為一個(gè)示例,考慮用for循環(huán)寫的階乘函數(shù): int fact_for(int n){ int i; int result = 1; for(i = 2;i <= n; i ++) result *= i; return result;} 如上述代碼所示,,用for循環(huán)編寫階乘函數(shù)最自然的方式就是將從2一直到n的因子乘起來,,因此這個(gè)函數(shù)與我們使用while或者do-while循環(huán)的代碼都不一樣。 這段代碼中for循環(huán)的不同組成部分如下: 用這些部分帶入前面給出的模板中的相應(yīng)位置,,得到下面goto代碼的版本: int fact_for_goto(int n){ int i = 2; int result = 1; if( !(i <=n ) ) goto done; loop: result *= i; i ++; if(i <= n) goto loop; done: return result;} 確實(shí)仔細(xì)查看GCC產(chǎn)生的匯編代碼會(huì)發(fā)現(xiàn)非常接近如下形式: 綜上所述,C語言中三種形式的所有循環(huán)— do-while,,while和for–都可以用一種簡(jiǎn)單的策略來翻譯,,產(chǎn)生包含一個(gè)或多個(gè)條件分支的代碼。控制的條件轉(zhuǎn)移為循環(huán)翻譯成機(jī)器代碼提供了基本機(jī)制,。 死循環(huán)選擇for還是while最后再說一下,,看到有人在網(wǎng)上討論死循環(huán)用 for(;;); 好,還是用 while(1); 好,。 自己親自測(cè)試了下,,在 -O2 參數(shù)下它們生成的匯編指令是一樣的(看來這應(yīng)該跟優(yōu)化配置和編譯器選擇有很大關(guān)系)。 |
|