C語言常見的自增/自減,判斷,循環等反匯編筆記


C語言中自增/自檢運算符

自增/自減運算(后綴型) 

#include <stdio.h>

int main(void)
{
	int x = 1;
	int y;

	y = x++;

	return 0;
}

反匯編如下:

     5: 	int x = 1;
00FA4398  mov         dword ptr [x],1  
     6: 	int y;
     7: 
     8: 	y = x++;
00FA439F  mov         eax,dword ptr [x]  
00FA43A2  mov         dword ptr [y],eax  
00FA43A5  mov         ecx,dword ptr [x]  
00FA43A8  add         ecx,1  
00FA43AB  mov         dword ptr [x],ecx  

dword ptr [x]指的就是變量x。dword ptr是指將目標變量的數據類型轉為dword類型。

從這段代碼我們可以看出x++是先賦值再+1的。

自增/自減運算(前綴型)

#include <stdio.h>

int main(void)
{
    int x = 1;
    int y;
    
    y = ++x;
    
    return 0;
}

反匯編如下

5: 	int x = 1;
00AA4398  mov         dword ptr [x],1  
     6: 	int y;
     7: 
     8: 	y = ++x;
00AA439F  mov         eax,dword ptr [x]  
00AA43A2  add         eax,1  
00AA43A5  mov         dword ptr [x],eax  
00AA43A8  mov         ecx,dword ptr [x]  
00AA43AB  mov         dword ptr [y],ecx  

這里的x是先+1后賦值


下面來看一下臭名昭著的譚浩強行為,這段譚浩強行為的代碼在編譯器中是怎么執行的

譚浩強行為代碼

#include <stdio.h>

int main(void)
{
	int x = 1;
	int y;

	y = x+++x;

	return 0;
}

反匯編如下

     5: 	int x = 1;
00394398  mov         dword ptr [x],1  
     6: 	int y;
     7: 
     8: 	y = x+++x;
0039439F  mov         eax,dword ptr [x]  
003943A2  add         eax,dword ptr [x]  
003943A5  mov         dword ptr [y],eax  
003943A8  mov         ecx,dword ptr [x]  
003943AB  add         ecx,1  
003943AE  mov         dword ptr [x],ecx  

可以看到,x是先與x相加后再賦值給y,然后再自加+1

但是這並不是唯一的結果。這取決於編譯器,編譯器不同最后的結果也不同。

C語言中的判斷語句

if-else

#include <stdio.h>

int main(void)
{
	int x;

	scanf("%d", &x);

	if (x <= 0)
		x++;
	else
		x--;

	return 0;
}

反匯編如下:

5: 	int x;
     6: 
     7: 	scanf("%d", &x);
00734858 8D 45 F8             lea         eax,[x]  
0073485B 50                   push        eax  
0073485C 68 CC 7B 73 00       push        offset string "%d" (0737BCCh)  
00734861 E8 C3 CB FF FF       call        __vfscanf_l (0731429h)  
00734866 83 C4 08             add         esp,8  
     8: 
     9: 	if (x <= 0)
00734869 83 7D F8 00          cmp         dword ptr [x],0  
0073486D 7F 0B                jg          main+4Ah (073487Ah)  
    10: 		x++;
0073486F 8B 45 F8             mov         eax,dword ptr [x]  
00734872 83 C0 01             add         eax,1  
00734875 89 45 F8             mov         dword ptr [x],eax  
00734878 EB 09                jmp         main+53h (0734883h)  
    11: 	else
    12: 		x--;
0073487A 8B 45 F8             mov         eax,dword ptr [x]  
0073487D 83 E8 01             sub         eax,1  
00734880 89 45 F8             mov         dword ptr [x],eax  
    13: 
    14: 	return 0;
00734883 33 C0                xor         eax,eax

我們逐步分析這段代碼,先從scanf函數開始

     7: 	scanf("%d", &x);
00734858 8D 45 F8             lea         eax,[x]  
0073485B 50                   push        eax  
0073485C 68 CC 7B 73 00       push        offset string "%d" (0737BCCh)  
00734861 E8 C3 CB FF FF       call        __vfscanf_l (0731429h)  
00734866 83 C4 08             add         esp,8  

這段代碼表示出了scanf進入函數前的操作(scanf在匯編里的具體實現過程中比較復雜,這里就不講了),如果沒有特別設置則函數調用約定的話默認是cdecl。cdecl調用約定中函數的參數自右向左入棧,入棧的目的就是保護參數的數據防止在調用函數后被修改,這里將x的地址和字符串壓入了棧中。而esp的值增加了8,是因為函數調用完后要將調用函數后壓入棧中的參數進行清理,在cdecl下由調用方清理。

offset是匯編語言的一個指令,意為獲取變量的地址。

9: 	if (x <= 0)
00734869 83 7D F8 00          cmp         dword ptr [x],0  
0073486D 7F 0B                jg          main+4Ah (073487Ah)

這段代碼中將x與0進行了比較,若jg(意為大於就跳轉)成立則跳轉至else(else的地址就是0073487A)

    10: 		x++;
0073486F 8B 45 F8             mov         eax,dword ptr [x]  
00734872 83 C0 01             add         eax,1  
00734875 89 45 F8             mov         dword ptr [x],eax  
00734878 EB 09                jmp         main+53h (0734883h) 

前三行即為x++的操作過程,這里不再贅述。操作結束后跳轉至return 0;(return 0;的地址為00734883)

    12: 		x--;
0073487A 8B 45 F8             mov         eax,dword ptr [x]  
0073487D 83 E8 01             sub         eax,1  
00734880 89 45 F8             mov         dword ptr [x],eax

與上述相同,這里不再贅述。不過值得注意的是這里沒有jmp指令,因為下面的指令就是return 0;(xor eax, eax)


下面講一講比較煩人的if-else嵌套語句在匯編語言中是怎么實現的,下面是個簡單的三個數比大小的算法

嵌套的if-else

#include <stdio.h>

int main(void)
{
	int x, y, z;
	int min;

	scanf("%d%d%d", &x, &y, &z);

	if (x < y && x < z)
		min = x;

	else if (y < z)
		min = y;

	else
		min = z;

	printf("%d\n", min);

	return 0;
}

反匯編如下

5: 	int x, y, z;
     6: 	int min;
     7: 
     8: 	scanf("%d%d%d", &x, &y, &z);
00C15098 8D 45 E0             lea         eax,[z]  
00C1509B 50                   push        eax  
00C1509C 8D 4D EC             lea         ecx,[y]  
00C1509F 51                   push        ecx  
00C150A0 8D 55 F8             lea         edx,[x]  
00C150A3 52                   push        edx  
00C150A4 68 CC 7B C1 00       push        offset string "%d" (0C17BCCh)  
00C150A9 E8 7B C3 FF FF       call        _main (0C11429h)  
00C150AE 83 C4 10             add         esp,10h  
     9: 
    10: 	if (x < y && x < z)
00C150B1 8B 45 F8             mov         eax,dword ptr [x]  
00C150B4 3B 45 EC             cmp         eax,dword ptr [y]  
00C150B7 7D 10                jge         main+59h (0C150C9h)  
00C150B9 8B 45 F8             mov         eax,dword ptr [x]  
00C150BC 3B 45 E0             cmp         eax,dword ptr [z]  
00C150BF 7D 08                jge         main+59h (0C150C9h)  
    11: 		min = x;
00C150C1 8B 45 F8             mov         eax,dword ptr [x]  
00C150C4 89 45 D4             mov         dword ptr [min],eax  
00C150C7 EB 16                jmp         main+6Fh (0C150DFh)  
    12: 
    13: 	else if (y < z)
00C150C9 8B 45 EC             mov         eax,dword ptr [y]  
00C150CC 3B 45 E0             cmp         eax,dword ptr [z]  
00C150CF 7D 08                jge         main+69h (0C150D9h)  
    14: 		min = y;
00C150D1 8B 45 EC             mov         eax,dword ptr [y]  
00C150D4 89 45 D4             mov         dword ptr [min],eax  
00C150D7 EB 06                jmp         main+6Fh (0C150DFh)  
    15: 
    16: 	else
    17: 		min = z;
00C150D9 8B 45 E0             mov         eax,dword ptr [z]  
00C150DC 89 45 D4             mov         dword ptr [min],eax  
    18: 
    19: 	printf("%d\n", min);
00C150DF 8B 45 D4             mov         eax,dword ptr [min]  
00C150E2 50                   push        eax  
00C150E3 68 D4 7B C1 00       push        offset string "%d\n" (0C17BD4h)  
00C150E8 E8 50 C3 FF FF       call        __vfscanf_l (0C1143Dh)  
00C150ED 83 C4 08             add         esp,8  
    20: 
    21: 	return 0;
00C150F0 33 C0                xor         eax,eax

先從第一個if-else開始看起

10: 	if (x < y && x < z)
00C150B1 8B 45 F8             mov         eax,dword ptr [x]  
00C150B4 3B 45 EC             cmp         eax,dword ptr [y]  
00C150B7 7D 10                jge         main+59h (0C150C9h)  
00C150B9 8B 45 F8             mov         eax,dword ptr [x]  
00C150BC 3B 45 E0             cmp         eax,dword ptr [z]  
00C150BF 7D 08                jge         main+59h (0C150C9h)  
    11: 		min = x;
00C150C1 8B 45 F8             mov         eax,dword ptr [x]  
00C150C4 89 45 D4             mov         dword ptr [min],eax  
00C150C7 EB 16                jmp         main+6Fh (0C150DFh)  
    12: 
    13: 	else if (y < z)
00C150C9 8B 45 EC             mov         eax,dword ptr [y]  
00C150CC 3B 45 E0             cmp         eax,dword ptr [z]  
00C150CF 7D 08                jge         main+69h (0C150D9h)  
    14: 		min = y;
00C150D1 8B 45 EC             mov         eax,dword ptr [y]  
00C150D4 89 45 D4             mov         dword ptr [min],eax  
00C150D7 EB 06                jmp         main+6Fh (0C150DFh)

x先與y比較,若條件成立(jge意為大於等於就跳轉)則跳轉至else if(0C150C9);然后x再與y比較,若條件成立也跳轉至else if。最后將x賦值給min最后跳轉至printf;(00C150DF)。可以看到if中有邏輯運算符&&,但是在匯編語言中則沒有明顯的體現出來,邏輯運算符會放在后面講


switch

#include <stdio.h>

int main(void)
{
	int x;

	scanf("%d", &x);

	switch (x)
	{
	case 1:
		x = x + 1;
		break;
	case 2:
		x = x + 1;
		break;

	default:
		x = x;
	}

	return 0;
}

反匯編如下

5: 	int x;
     6: 
     7: 	scanf("%d", &x);
00B84858 8D 45 F8             lea         eax,[x]  
00B8485B 50                   push        eax  
00B8485C 68 CC 7B B8 00       push        offset string "%d" (0B87BCCh)  
00B84861 E8 C3 CB FF FF       call        _main (0B81429h)  
00B84866 83 C4 08             add         esp,8  
     8: 
     9: 	switch (x)
00B84869 8B 45 F8             mov         eax,dword ptr [x]  
00B8486C 89 85 30 FF FF FF    mov         dword ptr [ebp-0D0h],eax  
00B84872 83 BD 30 FF FF FF 01 cmp         dword ptr [ebp-0D0h],1  
00B84879 74 0B                je          main+56h (0B84886h)  
00B8487B 83 BD 30 FF FF FF 02 cmp         dword ptr [ebp-0D0h],2  
00B84882 74 0D                je          main+61h (0B84891h)  
00B84884 EB 16                jmp         main+6Ch (0B8489Ch)  
    10: 	{
    11: 	case 1:
    12: 		x = x + 1;
00B84886 8B 45 F8             mov         eax,dword ptr [x]  
00B84889 83 C0 01             add         eax,1  
00B8488C 89 45 F8             mov         dword ptr [x],eax  
    13: 		break;
00B8488F EB 11                jmp         main+72h (0B848A2h)  
    14: 	case 2:
    15: 		x = x + 1;
00B84891 8B 45 F8             mov         eax,dword ptr [x]  
00B84894 83 C0 01             add         eax,1  
00B84897 89 45 F8             mov         dword ptr [x],eax  
    16: 		break;
00B8489A EB 06                jmp         main+72h (0B848A2h)  
    17: 
    18: 	default:
    19: 		x = x;
00B8489C 8B 45 F8             mov         eax,dword ptr [x]  
00B8489F 89 45 F8             mov         dword ptr [x],eax  
    20: 	}
    21: 
    22: 	return 0;
00B848A2 33 C0                xor         eax,eax

先看看這段

9: 	switch (x)
00B84869 8B 45 F8             mov         eax,dword ptr [x]  
00B8486C 89 85 30 FF FF FF    mov         dword ptr [ebp-0D0h],eax  
00B84872 83 BD 30 FF FF FF 01 cmp         dword ptr [ebp-0D0h],1  
00B84879 74 0B                je          main+56h (0B84886h)  
00B8487B 83 BD 30 FF FF FF 02 cmp         dword ptr [ebp-0D0h],2  
00B84882 74 0D                je          main+61h (0B84891h)  
00B84884 EB 16                jmp         main+6Ch (0B8489Ch)

可以看到00B84872和00B84879這兩段就是跳轉語句,分別對應x值為1和2的情況(je指令意為等於就跳轉)。若x的值兩者都不是則執行00B84884語句,即跳轉至default(0B8489C)。這里我們將1賦值給x,x進入case 1

11: 	case 1:
    12: 		x = x + 1;
00B84886 8B 45 F8             mov         eax,dword ptr [x]  
00B84889 83 C0 01             add         eax,1  
00B8488C 89 45 F8             mov         dword ptr [x],eax  
    13: 		break;
00B8488F EB 11                jmp         main+72h (0B848A2h)

在對x的操作結束后就會直接跳轉到return 0;(0B848A2)如果是default中的操作則會繼續執行后面的操作,不會跳轉。


這里我們就可以從匯編角度去理解為什么switch中每一個選項(除了default)都要有break語句

沒有break語句的switch

#include <stdio.h>

int main(void)
{
	int x;

	scanf("%d", &x);

	switch (x)
	{
	case 1:
		x = x + 1;

	case 2:
		x = x + 1;
		

	default:
		x = x;
	}

	return 0;
}

反匯編如下

5: 	int x;
     6: 
     7: 	scanf("%d", &x);
00D04858 8D 45 F8             lea         eax,[x]  
00D0485B 50                   push        eax  
00D0485C 68 CC 7B D0 00       push        offset string "%d" (0D07BCCh)  
00D04861 E8 C3 CB FF FF       call        _main (0D01429h)  
00D04866 83 C4 08             add         esp,8  
     8: 
     9: 	switch (x)
00D04869 8B 45 F8             mov         eax,dword ptr [x]  
00D0486C 89 85 30 FF FF FF    mov         dword ptr [ebp-0D0h],eax  
00D04872 83 BD 30 FF FF FF 01 cmp         dword ptr [ebp-0D0h],1  
00D04879 74 0B                je          main+56h (0D04886h)  
00D0487B 83 BD 30 FF FF FF 02 cmp         dword ptr [ebp-0D0h],2  
00D04882 74 0B                je          main+5Fh (0D0488Fh)  
00D04884 EB 12                jmp         main+68h (0D04898h)  
    10: 	{
    11: 	case 1:
    12: 		x = x + 1;
00D04886 8B 45 F8             mov         eax,dword ptr [x]  
00D04889 83 C0 01             add         eax,1  
00D0488C 89 45 F8             mov         dword ptr [x],eax  
    13: 
    14: 	case 2:
    15: 		x = x + 1;
00D0488F 8B 45 F8             mov         eax,dword ptr [x]  
00D04892 83 C0 01             add         eax,1  
00D04895 89 45 F8             mov         dword ptr [x],eax  
    16: 		
    17: 
    18: 	default:
    19: 		x = x;
00D04898 8B 45 F8             mov         eax,dword ptr [x]  
00D0489B 89 45 F8             mov         dword ptr [x],eax  
    20: 	}
    21: 
    22: 	return 0;
00D0489E 33 C0                xor         eax,eax

取部分選項

11: 	case 1:
    12: 		x = x + 1;
00D04886 8B 45 F8             mov         eax,dword ptr [x]  
00D04889 83 C0 01             add         eax,1  
00D0488C 89 45 F8             mov         dword ptr [x],eax  
    13: 
    14: 	case 2:
    15: 		x = x + 1;
00D0488F 8B 45 F8             mov         eax,dword ptr [x]  
00D04892 83 C0 01             add         eax,1  
00D04895 89 45 F8             mov         dword ptr [x],eax

我們可以看到如果沒有break語句,那么就沒有jmp指令,若case 1操作結束后則指令會直接從00D0488F運行下去,不會直接跳轉至return 0;


C語言中的循環

在C語言中有三種常見的循環:forwhiledo...while

先介紹for循環

for循環

#include <stdio.h>

int main(void)
{
	int x = 0;

	for (int i = 0; i < 5; i++)
		x = i;


	return 0;
}

反匯編如下

5: 	int x = 0;
000E4398 C7 45 F8 00 00 00 00 mov         dword ptr [x],0  
     6: 
     7: 	for (int i = 0; i < 5; i++)
000E439F C7 45 EC 00 00 00 00 mov         dword ptr [ebp-14h],0  
000E43A6 EB 09                jmp         main+41h (0E43B1h)  
000E43A8 8B 45 EC             mov         eax,dword ptr [ebp-14h]  
000E43AB 83 C0 01             add         eax,1  
000E43AE 89 45 EC             mov         dword ptr [ebp-14h],eax  
000E43B1 83 7D EC 05          cmp         dword ptr [ebp-14h],5  
000E43B5 7D 08                jge         main+4Fh (0E43BFh)  
     8: 		x = i;
000E43B7 8B 45 EC             mov         eax,dword ptr [ebp-14h]  
000E43BA 89 45 F8             mov         dword ptr [x],eax  
000E43BD EB E9                jmp         main+38h (0E43A8h)  
     9: 
    10: 
    11: 	return 0;
000E43BF 33 C0                xor         eax,eax

在這里dword ptr [ebp-14h]指的就是變量i,意思就是將ebp的地址減14后指向的空間作為變量i的空間。000E43A6與000E43B1之間的代碼就是i++操作,而000E43B5與000E43BD之間的代碼就是i < 5以及循環下的x = i操作。

單循環比較簡單,下面看看for循環的嵌套形式

for循環的嵌套形式

#include <stdio.h>

int main(void)
{
	int x = 0;

	for (int j = 0; j < 5; j++)
		for (int i = 0; i < 5; i++)
			x = i;


	return 0;
}

反匯編如下

     5: 	int x = 0;
006B4398 C7 45 F8 00 00 00 00 mov         dword ptr [x],0  
     6: 
     7: 	for (int j = 0; j < 5; j++)
006B439F C7 45 EC 00 00 00 00 mov         dword ptr [ebp-14h],0  
006B43A6 EB 09                jmp         main+41h (06B43B1h)  
006B43A8 8B 45 EC             mov         eax,dword ptr [ebp-14h]  
006B43AB 83 C0 01             add         eax,1  
006B43AE 89 45 EC             mov         dword ptr [ebp-14h],eax  
006B43B1 83 7D EC 05          cmp         dword ptr [ebp-14h],5  
006B43B5 7D 22                jge         main+69h (06B43D9h)  
     8: 		for (int i = 0; i < 5; i++)
006B43B7 C7 45 E0 00 00 00 00 mov         dword ptr [ebp-20h],0  
006B43BE EB 09                jmp         main+59h (06B43C9h)  
006B43C0 8B 45 E0             mov         eax,dword ptr [ebp-20h]  
006B43C3 83 C0 01             add         eax,1  
006B43C6 89 45 E0             mov         dword ptr [ebp-20h],eax  
006B43C9 83 7D E0 05          cmp         dword ptr [ebp-20h],5  
006B43CD 7D 08                jge         main+67h (06B43D7h)  
     9: 			x = i;
006B43CF 8B 45 E0             mov         eax,dword ptr [ebp-20h]  
006B43D2 89 45 F8             mov         dword ptr [x],eax  
006B43D5 EB E9                jmp         main+50h (06B43C0h)  
006B43D7 EB CF                jmp         main+38h (06B43A8h)  
    10: 
    11: 
    12: 	return 0;
006B43D9 33 C0                xor         eax,eax

從006B43B7到006B43D5為內循環操作,操作完后再執行006B43D7跳轉至006B43A8中完成一次外循環操作,周而復始,最后執行006B43B5跳轉至return 0;

while循環

#include <stdio.h>

int main(void)
{
	int x = 0;

	while (x < 5)
		x++;


	return 0;
}

反匯編如下

5: 	int x = 0;
00444398 C7 45 F8 00 00 00 00 mov         dword ptr [x],0  
     6: 
     7: 	while (x < 5)
0044439F 83 7D F8 05          cmp         dword ptr [x],5  
004443A3 7D 0B                jge         main+40h (04443B0h)  
     8: 		x++;
004443A5 8B 45 F8             mov         eax,dword ptr [x]  
004443A8 83 C0 01             add         eax,1  
004443AB 89 45 F8             mov         dword ptr [x],eax  
004443AE EB EF                jmp         main+2Fh (044439Fh)  
     9: 
    10: 
    11: 	return 0;
004443B0 33 C0                xor         eax,eax  

可以看的出來while循環經過反匯編后得到的匯編代碼比較符合C語言while循環的操作步驟

do...while循環

#include <stdio.h>

int main(void)
{
	int x = 0;

	do
	{
		x++;
	} while (x < 5);

	return 0;
}

反匯編如下

5: 	int x = 0;
00044398 C7 45 F8 00 00 00 00 mov         dword ptr [x],0  
     6: 
     7: 	do
     8: 	{
     9: 		x++;
0004439F 8B 45 F8             mov         eax,dword ptr [x]  
000443A2 83 C0 01             add         eax,1  
000443A5 89 45 F8             mov         dword ptr [x],eax  
    10: 	} while (x < 5);
000443A8 83 7D F8 05          cmp         dword ptr [x],5  
000443AC 7C F1                jl          main+2Fh (04439Fh)  
    11: 
    12: 	return 0;
000443AE 33 C0                xor         eax,eax  
    13: }

do...while循環與while循環最大的不同就在於do...while循環無論循環條件是什么都會先進行一次操作。這里也可以看的出來反匯編后的匯編代碼比較符合C語言中do...while循環的操作步驟,當do...while的循環條件不滿足時就會從000443AC跳出(jl指令意為小於就跳出)


總結:

  1. C語言中的自增/自減運算經過反匯編后,並沒有使用匯編自帶的自增/自減指令INC/DEC,而是使用add/sub R, 1的形式(R為寄存器)

  2. C語言的判斷語句在經過反匯編后,匯編代碼大量使用cmp與跳轉指令(如je、jge、jmp等)相互搭配組合的形式來實現

  3. C語言的函數調用約定默認為cdecl,這種調用約定的特點即為參數從右往左壓棧,並且調用函數前壓入棧的參數由調用方清理

  4. C語言中的for、while、do...while循環中,while和do...while循環反匯編后的匯編代碼比較符合C語言執行這兩個循環的操作步驟,for循環則有些差別。

  5. 在用VS2019或其他C/C++編譯器時盡量關閉源代碼和符號名提示,以提高對匯編的理解

本文中涉及到的匯編指令:

Size ptr :意為改變目標變量的數據類型,Size可以是byte(字節)、word(字)、dwrod(雙字)、qword(四字)

offset:意為獲取變量的地址。

jg:意為大於跳轉,j為jump,g為great

jmp:無條件跳轉

jge:意為大於等於就跳轉

je:意為等於就跳轉

jl:意為小於就跳出


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM