分支结构在反汇编中特征

本文会使用IDA分析C语言的分支结构(if esle,switch)在反汇编代码中特征

目录

IDA分析 if…else分支结构

Debug版本

Release发布版

if……else反汇编结构

IDA分析 if…else 多分支结构

Debug版本

if……else if……else反汇编结构

nTest输入分析多分支结构

Debug版

Release版

IDA分析switch

分支小于四

分支大于4 有序线性结构

倘如下标不从1开始

分支大于4 无序线性结构

if else和switch哪个效率高?


IDA分析 if…else分支结构

Debug版本

.text:00411E45                 mov     [ebp+var_8], 1  ; int nTest = 1; 函数参数赋值
.text:00411E4C                 cmp     [ebp+var_8], 0  ; 比较大小
.text:00411E50                 jle     short loc_411E61 ; 前一个值小于或等于后一个值,则跳转;否则,指令继续按顺序执行。
.text:00411E52                 push    offset aHelloWorld ; "Hello World!\r\n"
.text:00411E57                 call    sub_4113C5      ; 函数调用结束后,就只剩下函数参数还未平栈
.text:00411E5C                 add     esp, 4          ; 平栈
.text:00411E5F                 jmp     short loc_411E6E ; 跳转回return 0执行
.text:00411E61 ; ---------------------------------------------------------------------------
.text:00411E61
.text:00411E61 loc_411E61:                             ; CODE XREF: _main_0+30↑j
.text:00411E61                 push    offset aHelloEverybody ; "Hello everybody!\r\n"
.text:00411E66                 call    sub_4113C5
.text:00411E6B                 add     esp, 4
.text:00411E6E
.text:00411E6E loc_411E6E:                             ; CODE XREF: _main_0+3F↑j
.text:00411E6E                 xor     eax, eax
.text:00411E70                 pop     edi
.text:00411E71                 pop     esi
.text:00411E72                 pop     ebx
.text:00411E73                 add     esp, 0CCh

jle的跳转条件是 前一个数小于等于后一个数

当执行到 "jle" 指令时,会检查处理器标志寄存器中的小于等于标志位(LE)。如果 LE 为 1(被置位),表示前面的比较操作结果小于或等于零,此时程序将跳转到指定的目标地址继续执行。如果 LE 为 0(未被置位),则跳转不会发生,程序将顺序执行下一条指令

Release发布版

分支结构在反汇编中特征_第1张图片

和Debug区别在于只剩下一个分支,经过了编译优化;编译器是被cmp比较后是一个常量,就减掉了那个不会被执行的分支

if……else反汇编结构

cmp xxxx
jcc 地址
_if
    XXXX
    jmp ends
_else
    XXXX
_end
    xor eax,eax
    xxx
    ret

IDA分析 if…else 多分支结构

Debug版本

程序流程图

分支结构在反汇编中特征_第2张图片

jnz跳转条件:jnz 指令用于判断前一个操作的结果是否为零,如果结果不为零,则执行跳转操作。

当执行到 "jnz" 指令时,会检查处理器标志寄存器中的零标志位(ZF)。如果 ZF 为 0(未被置位),表示前面的操作结果不为零,此时程序将跳转到指定的目标地址继续执行。如果 ZF 为 1(被置位),则跳转不会发生,程序将顺序执行下一条指令。

汇编执行逻辑

  1. jle判断小于等于0跳转右边,否则左边
  2. 跳转到右边后,判断是否为0,为0左边,不为0右边
  3. 三条分支走向return 0
.text:00411E4C                 cmp     [ebp+var_8], 0
.text:00411E50                 jle     short loc_411E61
.text:00411E52                 push    offset aHelloWorld ; "Hello World!\r\n"
.text:00411E57                 call    sub_4113C5
.text:00411E5C                 add     esp, 4
.text:00411E5F                 jmp     short loc_411E83 ; 若结果小于等于,就跳转
.text:00411E61 ; ---------------------------------------------------------------------------
.text:00411E61
.text:00411E61 loc_411E61:                             ; CODE XREF: _main_0+30↑j
.text:00411E61                 cmp     [ebp+var_8], 0
.text:00411E65                 jnz     short loc_411E76 ; 若结果不等于0则跳转
.text:00411E67                 push    offset aHelloEverybody ; "Hello everybody!\r\n"
.text:00411E6C                 call    sub_4113C5
.text:00411E71                 add     esp, 4
.text:00411E74                 jmp     short loc_411E83
.text:00411E76 ; ---------------------------------------------------------------------------
.text:00411E76
.text:00411E76 loc_411E76:                             ; CODE XREF: _main_0+45↑j
.text:00411E76                 push    offset aHello   ; "Hello "
.text:00411E7B                 call    sub_4113C5
.text:00411E80                 add     esp, 4
.text:00411E83
.text:00411E83 loc_411E83:                             ; CODE XREF: _main_0+3F↑j
.text:00411E83                                         ; _main_0+54↑j
.text:00411E83                 xor     eax, eax

其发布版也是进行了优化

if……else if……else反汇编结构

cmp XXXX
jcc _elseif else

_if
    XXX
    jmp _end

_elseif:
    cmp XXX
    jcc _else
    XXXX
    jmp _end

else:
    XXXX

_end:
    xor eax,eax
    ret

nTest输入分析多分支结构

Debug版

分支结构在反汇编中特征_第3张图片

Release版

使用其他汇编指令来优化

分支结构在反汇编中特征_第4张图片

IDA分析switch

分支小于四

测试代码

#include 

int main()
{
	int n = 0;
	scanf_s("%d", &n);
	switch (n)
	{
	case 1:
		printf("n==1");
		break;
	case 2:
		printf("n==2");
		break;
	case 3:
		printf("n==3");
		break;
	}
	return 0;
}

当执行到 "jz" 指令时,会检查处理器标志寄存器中的零标志位(ZF)。如果 ZF 为 1(被置位),表示前面的操作结果为零,此时程序将跳转到指定的目标地址继续执行。如果 ZF 为 0(未被置位),则跳转不会发生,程序将顺序执行下一条指令。

反汇编分析

.text:004150FF                 mov     dword ptr [ebp+n], 0 ; int n = 0;
.text:00415106                 lea     eax, [ebp+n]
.text:00415109                 push    eax             ; char
.text:0041510A                 push    offset aD       ; 这是个全局变量
.text:0041510F                 call    sub_4113E3      ; 调用scanf函数
.text:00415114                 add     esp, 8          ; 平栈
.text:00415117                 mov     eax, dword ptr [ebp+n] ; switch (n)
.text:0041511A                 mov     [ebp+var_D4], eax ; switch (n),局部变量之间赋值也是借助寄存器
.text:00415120                 cmp     [ebp+var_D4], 1 ; case 1
.text:00415127                 jz      short loc_41513D ; ZF=1,跳转,说明两个比较值相等;ZF=0,反之
.text:00415129                 cmp     [ebp+var_D4], 2 ; case 2
.text:00415130                 jz      short loc_41514C
.text:00415132                 cmp     [ebp+var_D4], 3 ; case 3
.text:00415139                 jz      short loc_41515B
.text:0041513B                 jmp     short loc_415168 ; 如果三个都没匹配到,就会自导跳转到为末尾
.text:0041513D ; ---------------------------------------------------------------------------
.text:0041513D
.text:0041513D loc_41513D:                             ; CODE XREF: _main_0+57↑j
.text:0041513D                 push    offset aN1      ; "n==1"
.text:00415142                 call    sub_4113C5      ; 调用printf
.text:00415147                 add     esp, 4
.text:0041514A                 jmp     short loc_415168
.text:0041514C ; ---------------------------------------------------------------------------
.text:0041514C
.text:0041514C loc_41514C:                             ; CODE XREF: _main_0+60↑j
.text:0041514C                 push    offset aN2      ; "n==2"
.text:00415151                 call    sub_4113C5      ; 调用printf
.text:00415156                 add     esp, 4
.text:00415159                 jmp     short loc_415168
.text:0041515B ; ---------------------------------------------------------------------------
.text:0041515B
.text:0041515B loc_41515B:                             ; CODE XREF: _main_0+69↑j
.text:0041515B                 push    offset aN3      ; "n==3"
.text:00415160                 call    sub_4113C5      ; 调用printf
.text:00415165                 add     esp, 4
.text:00415168
.text:00415168 loc_415168:                             ; CODE XREF: _main_0+6B↑j
.text:00415168                                         ; _main_0+7A↑j ...
.text:00415168                 xor     eax, eax
.text:0041516A                 push    edx
.text:0041516B                 mov     ecx, ebp        ; Esp
.text:0041516D                 push    eax
.text:0041516E                 lea     edx, Fd         ; Fd
.text:00415174                 call    j_@_RTC_CheckStackVars@8 ; _RTC_CheckStackVars(x,x)
case 语句块小于 4 时, switch 语句所反汇编的代码有区别于 if else if else 是在判断语句后紧跟代码 块,而switch 语句是将所有的代码块放置在一起,上半部分是判断语句,下半块是代码块。效率和 if else相同

分支大于有序线性结构

测试代码

#include 
int main()
{
    int n = 0;
    scanf_s("%d", &n);
    switch (n)
    {
    case 1:
        printf("n==1");
        break;
    case 2:
        printf("n==2");
        break;
    case 3:
        printf("n==3");
        break;
    case 5:
        printf("n==5");
        break;
    }
    return 0;
}

 输入参数N传入switch的n

.text:00415117                 mov     eax, dword ptr [ebp+n] ; switch (n)
.text:0041511A                 mov     [ebp+switch_n], eax ; switch (n)
.text:00415120                 mov     ecx, [ebp+switch_n] ; switch (n)

把输入的n减1

.text:00415126                 sub     ecx, 1         

把减去的值赋值给ecx,并且与case最大值减一比较(5-1)

.text:00415129                 mov     [ebp+switch_n], ecx
.text:0041512F                 cmp     [ebp+switch_n], 4

倘若前者大于后者,就跳转,说明超过了case的界限

.text:00415136                 ja      short def_41513E ; switch_n大于4就跳转

对于case数大于4,且线性结构,编译器就会使用比例因子寻址

.text:00415138                 mov     edx, [ebp+switch_n]
.text:0041513E                 jmp     ds:jpt_41513E[edx*4] ; switch jump

地址jpt_41513E这里存储了一个跳转表,会根据edx*4进行偏移

4 case 语句一共 5 个地址,其中第四个地址是为了补上 case 4 的缺失,指向的是 switch 语句的结束位 置。让整个表构成有序的线性结构,从而提高执行效率。
这个缺失的地址必须补上,如果没有补上跳转,则无法用数组寻址的方式跳转到对应的 case 语句,整个 跳转表从未补齐的那一个地址开始全部都是错误的。
找到对应的地址进行跳转

分支结构在反汇编中特征_第5张图片

倘如下标不从1开始

测试代码

#include 
int main()
{
int n = 0;
scanf_s("%d", &n);
switch (n)
{
case 5:
printf("n==1");
break;
case 6:
printf("n==2");
break;
case 7:
printf("n==3");
break;
case 10:
printf("n==5");
break;
}
return 0;
}

查看核心反汇编代码

把传入的switch_n - case(max);之后和最大的case(5)比较,前者大于后者,则跳转;然后跳转至跳转表;

分支结构在反汇编中特征_第6张图片

分支大于4 无序线性结构

测试代码

#include 
int main()
{
int n = 0;
scanf_s("%d", &n);
switch (n)
{
case 5:
printf("n==1");
break;
case 7:
printf("n==2");
break;
case 9:
printf("n==3");
break;
case 11:
printf("n==5");
break;
case 6:
printf("n==6");
break;
case 255:
printf("n==255");
break;
}
return 0;
}

查看反汇编核心代码

传入的switch_n减去case(5),之后和case(max)比较,大于则跳转;

把这个值放到edx中,把它作为下标,在byte_41520C这个数组中取值

分支结构在反汇编中特征_第7张图片

这里也有一张表,这张表叫索引表。索引表中保存的是跳转表的数组下标。

case 5->0
case 6->1
case 7->2
case 8->6 因为没有case8 所以这里被置成了6
case 9->3
.....
因为这种无序的 switch case 会有一个索引不方便的问题,所以说他设计了一张索引表,把所有的 case 后面跟的数字都制作成下标统一放在这个索引表里面。
这样的话就有一个好处,不管你的 case 语句后面跟着的数值是多少,我都能直接拿到我要的索引。
比如说
case 9 减去5=6
那么我就找这张表下标为 6 的数值,取出来进行寻址

分支结构在反汇编中特征_第8张图片

这样就可以把 4 取出来了。
索引表就解决了 case 语句块无序的问题,这样就可以再用跳转表进行寻址了
.text:00415148                 jmp     ds:jpt_415148[eax*4] ; switch jump
然后用比例因子寻址的方式跳转到对应的地址

case 的值没有明显的线性关系时,就会出现两张表,一张索引表,保存下标,一张线性表,保
存地址

但是还有个效率的问题:无序线性结构会有两次查表的过程,因此效率会有所下降

if elseswitch哪个效率高?

switch分支小于4时,其反汇编采用的结构和if else相同,两者效率一样。

switch 分支大于 4 时, switch 采用跳转表的方式来执行代码块,此时效率比 if else 高。

你可能感兴趣的:(C与汇编,c++,汇编)