一丶Switch Case語句在匯編中的第一種表達方式 (引導性跳轉表)
第一種表達方式生成條件:
case 個數偏少,那么匯編中將會生成引導性的跳轉表,會做出 if else的情況(類似,但還是能分辨出來的)
1.高級代碼:
// MyCode.cpp : Defines the entry point for the console application. // #include "stdafx.h" int main(int argc, char* argv[]) { switch(argc) { case 0: printf("case 0\n"); break; case 1: printf("case 1\n"); break; case 2: printf("case 2\n"); break; } printf("HelloWorld\n"); return 0; }
2.匯編代碼在Debug版本下:
可以看出,生成的跳轉表
比較和跳轉在一起,而且跳轉的時候是一個跳轉表. (注意,這里可能不是比較,只要影響標志位即可,也可能是 Dec inc add ....等等但是沒有實質性的代碼)
注意最后一個跳轉 JMP,當JMP的位置,或者代碼中JMP的位置(代碼中也就是 跳轉地址過去后的地方)都是同一個地址,那么則是跳轉到SWITCH_END(也就是switch case 語句塊結束)
注意這里是代碼邏輯分開的.
先判斷邏輯,然后進行跳轉執行.而在跳轉過后的里面判斷是否有JMP,有JMP的話,則代表是break
如果內部沒有JMP則代表沒有Break,那么語法是支持這樣寫法的.
3.匯編中Release版本
Release版本其實是一樣的.只不過需要注意的是,它有代碼外提的情況下.
因為我們每一個case里面都有打印輸出函數,而且其參數都是一樣的,所以跳轉過來之后,里面只需要PUSH即可.Switch case完畢之后則會調用printf打印.
4.擴展知識(Default在case上面,或者中間)
4.1直接偽代碼:
case 0 ... break;
default .... break
case 2 ....break
4.2匯編代碼 Debug
debug版本不用說,直接JMP位置到default位置.
4.3.Release版本下.
對於Release版本,有的時候可能直接變為Default語句
有的時候會變成JNZ執行.這個時候JNZ下面要還原成原來的CASE語句
第一變化的還原手法
第一變化還原挺簡單的
1.遇到cmp比較的, 和誰比較,那么跳轉的地址變為對應的Case即可.注意,你點擊地址過去之后則是Case語句的代碼,但是要注意是否代碼里面有JMP(沒有則沒有break)
2.有的時候不是CMP, 有的時候是 dec Reg32,這個時候就要算一下的,比如 dec Reg32結果是多少,則對應還原成多少,比如dec Reg32結果是0,那么其還原成Case0,但是還原第二個的時候要注意
第一個已經Dec了一下,第二個又還原的Dec 所以要和第一個相關聯才可以.
二丶Switch Case語句在匯編中的第二種表達方式(簡介尋址表)
case 語句塊有多個,且其中間隔不算多, 間隔的地方填寫Default
2.1高級代碼:
// MyCode.cpp : Defines the entry point for the console application. // #include "stdafx.h" int main(int argc, char* argv[]) { switch(argc) { case 0: printf("case 0\n"); break; case 1: printf("case 1\n"); break; case 2: printf("case 2\n"); break; case 3: printf("case 3\n"); case 5: printf("case 5\n"); break; case 6: printf("case 6\n"); break; case 7: printf("case 7\n"); break; default: printf("default \n"); break; } printf("HelloWorld\n"); return 0; }
2.2 Debug版本下的匯編代碼表現形式
首先這里有兩個問題
1. 為什么是JA (A是無符號的高於比較)
2. 為什么出現了數組尋址公式.
解答第一個問題:
是這樣的,第二種優化方式它會生成跳轉表,而跳轉表就是在第二種里面出現的尋址.而做表的前提下就是要保證Case語句是排序好的.
所以在這里 case語句首先排序好,而后坐標平移到0位置
什么是坐標平移到0位置?
比如我們有代碼
case -1
case 2
case 3
那么此時坐表的條件就是
case -1 變為case 0
case 2 變為 2
這樣坐表之后則是從0開始,而里面保存的則是case的地址.
這個時候只會和case最大值作比較.
我們看下動態調試中是什么表現形式把.
坐標平移之后,則可以建立一個表格,用的時候查表即可.所以我們查表的時候可以看到很多case語句的地址
從0開始. 沒有的則填寫Default即可. 一般JA后面的地址就是Default或者Switch End地址,從表中我們也看出來了.第五項填寫的是Switch End的地址.
正好我們的代碼中缺少的第五項沒有
Debug還原手法:
Debug還原很簡單,首先是比較case最大值,(看下是否調整了,也就是坐標平移了,如果平移了,比如以前有個-1,而case 最大值是3,那么會比較 4,因為-1 要+1其余各個的case語句也要加1)
4.3Release版本下的匯編代碼
Release版本下也是一樣,此時它也是會用 case值和最大值比較,然后去數組中尋址查找表去跳轉
注意: 這里數組尋址*4的意思是,地址是4個字節對其
比如我們的表格中存放的是case地址
00401000 case0 Address
00401004 case1 Address
00401008 case 2 Address
首先會和最大值比較,沒有超過,那么直接用case的值 *4去表中找地址即可.
比如我們的case值是2, 比較是否大於7,
然后 jA指令判斷是否高於7,高於7跳轉到 Default或者Switch End,不高於,則 根據case值去表中查找跳轉
比如現在是2,那么 2* 4 = 8 + 數組首地址(00401000) = 401008 然后取出內容跳轉,此時內容也是case2的地址.
擴展思路:
上面講的是Switch原型知識,我們要學會擴展.比如如果我case中有個-1怎么辦
那么此時-1 +1 坐標平移到0的位置,然后其余各個的case +1
那么比較的時候和case最大的值相比,現在最大的值也+1了. 所以還原的時候,最大值要-1,其余各個case值都要-1則可以.
代碼還原方式.
第二種代碼還原方式
1.判斷是否調整,調整了可以得出最小值,比如 add eax,2 那么得出最小值是-2,因為坐表要從0開始,這里調整則得出-2了,
2.得出case最大值比較,這里也要看是否調整了,沒有調整那么最大值是多少就是多少,調整的那么case最大值則是 當前值 - 調整的值
3.得出最小值和最大值之后,去地址表中尋址即可.因為是排好序的,如果沒有調整,那么就是從0開始,還原的時候就是case0
調整過后,那么減去調整的值即可,比如調整了2,那么第0項則是 0 - 2 則是case - 2 比較.
三丶Switch Case語句在匯編中的第三種表達方式(多表聯合查詢)
這種表達式相比前兩種有點難理解,但是我們說過,編譯器做的越多,那么我們還原的越快.
第三種表達式形成的條件:
當最大case值 - 最小case值的值在255直接,則使用第三種優化方式.
3.1高級代碼
// MyCode.cpp : Defines the entry point for the console application. // #include "stdafx.h" int main(int argc, char* argv[]) { switch(argc) { case 0: printf("case 0\n"); break; case 100: printf("case 100\n"); break; case 200: printf("case 200\n"); break; case 35: printf("case 35\n"); case 45: printf("case 45\n"); break; case 60: printf("case 60\n"); break; case 70: printf("case 70\n"); break; default: printf("default \n"); break; } printf("HelloWorld\n"); return 0; }
3.2 Debug版本下的匯編代碼
此時我們可以看出有兩次查表的動作
下標表:
這里我們說下第一個表,第一個表我們成為下標表
下標表,里面存放的是Case排好序的值的位置,比如我們的02 在下表中找一下,則是對應45的位置. 那么此時從里面取出來做第二個case,去地址表中尋到case 45 的地址.
下標表的作用,這里的下標表主要是一種優化,一種空間換時間的做法,這里的優化主要是,比如我們屏幕的顏色,白色很多,那么我就優化為,當有白色的時候,去下標表中尋找白色
就和我們的下標表中的07一樣,只要知道這個,那么都是default
地址表:
地址表則和第二種優化一樣,存放的case的真實地址.
debug還原手法:
1.首先判斷是否調整,調整了,可以得出最小case的值, (0 - 調整的值) 沒調整則默認從0開始
2.比較的時候會和最大case值比較(沒有調整則最大case值就是本身)調整了(最大case值 - 調整后的值 = 真實的最大case值)
3.遇到ja則后邊現修改為 default或者switchend
4.從下標表中尋得case當前的位置,(比如存放的是3,其位置在下表表中是35)那么此時代表的就是 case 35 (屬於第三個case語句)
5.從下標表中得出case語句的位置,(第幾個case)以及case的值是多少(case 35)那么去地址表中尋找其case35的地址即可.
3.3 Release版本下的匯編
和Debug版本下相差無異,只不過有代碼外提
此時還原很好還原
1.展開下表表格
2.展開地址表格
展開后則是上面的模樣,第一個是地址表,第二個是下標表格.
注意展開的時候可能會遇到問題
1.不是我這種顯示,沒有這么整齊, 此時 按鍵盤上的 * 鍵,可以設置按照數組顯示, 設置數組大小,間隔.等等..
2.沒有顯示 offset addr... 可能顯示的直接是一個地址, 此時可以選擇快捷鍵 ctrl + 0 顯示成當前段偏移
3.數據類型不對,我的是dd 而你們的可能解釋為 db dw 等等.. 此時按鍵盤快捷鍵 d即可.可以調整數據類型
還原方法;
和Debug一樣, (因為我沒有調整所以不用算了,而且從0開始)此時先去下標表中尋找0, 這個0找的的是case語句的位置 也就是第一條case語句是0
然后在尋找在數組的那個位置, 這個位置指的是 case xxx xxx的值是多少
此時我能找到0,那么去數組表中尋找0地址,改為case 0, 按n鍵修改
修改1的值,也就是找case 第2條語句是什么
在35位置尋找到1,那么此時 第二條語句代表的case則是case 35
利用里面的下邊去地址表中尋址,則找到第二個地址則是case 35
我們修改過后則上面會顯示了.此時我們正常的還原即可.
四丶Switch Case語句在匯編中的第四種表達方式(二分優化查表,四種變化混合)
第四種表達方式,生成條件,其case值之間的間隔大於255則會使用第四種表達方式.
4.1高級代碼:
// MyCode.cpp : Defines the entry point for the console application. // #include "stdafx.h" int main(int argc, char* argv[]) { switch(argc) { case 0: printf("case 0\n"); break; case 256: printf("case 256\n"); break; case 34: printf("case 34\n"); break; case 500: printf("case 500\n"); break; case 510: printf("case 510\n"); break; default: printf("default \n"); break; } printf("HelloWorld\n"); return 0; }
首先說下如果遇到這種情況,編譯器會怎么做.
因為對於編譯器來說,不知道你命中率高的那個case語句會執行,所以只能從中間切開
會生成匯編代碼 jg 或者 jle (大於或者小於代碼 )還有中間判斷相等.
比如我們有一組case值
0 |
200 |
300 |
310 |
320 |
此時匯編代碼會先判斷是否 jz(等於,可能不是jz反正判斷相等的跳轉即可) 找到中間的case,所以此時還原這個值即可.
不用管大於小於,最后的時候管.
4.2Debug下的匯編代碼:
可以看出一大堆的判斷 相等的代碼.
還原手法:
看到JE比較,那么看上方條件和誰比較(沒有調整的情況下)那么還原當前地址為這個case即可.
4.3Release版本下的匯編.
這個是還原過后的,只需要判斷 相等即可,根據它的條件來判斷,只不過Release版本有代碼外提
JNZ先修改為 Default ,此時如果進去每個case語句塊中,按到的JMP的跳轉地址和JNZ跳轉的地址是一樣的話,那么就是SWITCH_END
只需要還原相等即可.
注意:
當我還原case 510的時候,有沒有發現其上面還原500的時候都是減掉了,所以510的時候又減掉了一次所以此時判斷是510
大體的規模已經出來了,下面就是還原具體代碼了,代碼很簡單,不還原了,看下和高級代碼對比即可.
看看case是否一樣.