由一個vc內(nèi)嵌asm的BUG引出的思考

字號:

在語法上, 我們通常認(rèn)為以下的兩條語句是等價的:
    mov ecx, offset DATA_LABLE //其中DATA_LABLE是數(shù)據(jù)定義標(biāo)簽
    lea ecx, DATA_LABLE
    而更進一步, 我們也會認(rèn)為以下兩句是等價的:
    mov ecx, ebp-8
    lea ecx, [ebp-8]
    第一種, 用的是存儲器尋址方式; 而第二種, 用的是寄存器尋址和寄存器間接尋址方式. 讓我意想不到的是, 在第二種情況下, vc的處理并沒有讓寄存器尋址和寄存器間接尋址方式的mov和lea兩者之間實現(xiàn)等價. 在使用 _asm{} 的方式將"mov ecx, ebp-8"這條語句括起來編譯之后, 很遺憾地, 我在vc的反匯編窗口發(fā)現(xiàn)它變成了這樣的一條語句: "mov ecx, ebp". 啊哦, 我的"-8"竟然不翼而飛了! 到目前為止, 我尚沒有查到造成這種現(xiàn)象的原因所在, 我只能暫時將它歸為vc的bug了.
    對gcc下會不會存在這個問題呢? 為更進一步證實, 我使用gcc重新寫了這句代碼: "mov ecx, ebp-8", 但重寫后的代碼由當(dāng)初的一句變成了這樣的兩句:
    movl %ebp, %ecx
    subl $8, %ecx
    之所以改寫成這樣的兩句, 是因為我發(fā)現(xiàn)在AT&T的匯編語法中, 對于雙寄存器尋址的操作, 不能對寄存器取的值作任何變換, 也就是說不能寫成"movl %ebp-8, %ecx"的形式, 而寄存器間接尋址的操作就可以作變換, 比如:
    movl -8(%ebp), %ecx 此句相當(dāng)于intel asm里的: mov ecx, [ebp-8]
    movl (%ebp, %eax), %ecx 此句相當(dāng)于intel asm里的: mov ecx, [ebp+eax]
    movl (%ebp, %eax, 4), %ecx 此句相當(dāng)于intel asm里的: mov ecx, [ebp+eax*4]
    movl -8(%ebp, %eax, 4), %ecx 此句相當(dāng)于intel asm里的: mov ecx, [ebp+eax*4-8]
    從以上幾條語句來看, 似乎AT&T語法對寄存器間接尋址方式的支持沒有intel asm更具人性化, 但我猜想AT&T之所以采用這樣的方法, 可能一定程度上也是為了提高微指令級的執(zhí)行效率.
    當(dāng)然, "mov ecx, ebp-8"這句也可以改寫成這樣的兩句:
    subl $8, %ebp
    movl %ebp, %ecx
    但一般不會這么作, 道理是很顯然的, ebp通常會作為函數(shù)內(nèi)的基址寄存器, 用于存放函數(shù)入口點的堆棧首地址, 這個值的改變會直接影響其后語句對局部變量以及函數(shù)參數(shù)的引用發(fā)生變化, 所以, 在函數(shù)首部之后的執(zhí)行體中, ebp通常是不允許被改變的, 這也是我們設(shè)計自己的匯編代碼時所應(yīng)該遵循的原則.
    不知道vc為什么會將ebp之后的立即數(shù)作丟棄處理, 這顯然是沒有道德的行為. 這讓我想起了這樣的一句話: 不要試圖幫助用戶去糾正錯誤, 而是當(dāng)錯誤發(fā)生時去提醒用戶, 因為程序再聰明也不會始終明白設(shè)計者的真正意圖 ,我們所需要作的就是"為異常捕獲提供盡可能詳細(xì)的日志, 并及時通知用戶這種異常, 試圖糾正異常的作法從方法上就是錯誤和愚蠢的".