- .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(未被置位),则跳转不会发生,程序将顺序执行下一条指令
和Debug区别在于只剩下一个分支,经过了编译优化;编译器是被cmp比较后是一个常量,就减掉了那个不会被执行的分支
- cmp xxxx
- jcc 地址
- _if
- XXXX
- jmp ends
- _else
- XXXX
- _end
- xor eax,eax
- xxx
- ret
程序流程图
jnz跳转条件:jnz指令用于判断前一个操作的结果是否为零,如果结果不为零,则执行跳转操作。
当执行到 "jnz" 指令时,会检查处理器标志寄存器中的零标志位(ZF)。如果 ZF 为 0(未被置位),表示前面的操作结果不为零,此时程序将跳转到指定的目标地址继续执行。如果 ZF 为 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_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
其发布版也是进行了优化
- cmp XXXX
- jcc _elseif else
-
- _if
- XXX
- jmp _end
-
- _elseif:
- cmp XXX
- jcc _else
- XXXX
- jmp _end
-
- else:
- XXXX
-
- _end:
- xor eax,eax
- ret
使用其他汇编指令来优化
测试代码
- #include <stdio.h>
-
- 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 <stdio.h>
- 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语句,整个 跳转表从未补齐的那一个地址开始全部都是错误的。
找到对应的地址进行跳转
测试代码
- #include <stdio.h>
- 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)比较,前者大于后者,则跳转;然后跳转至跳转表;
测试代码
- #include <stdio.h>
- 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这个数组中取值
这里也有一张表,这张表叫索引表。索引表中保存的是跳转表的数组下标。
- 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的数值,取出来进行寻址
这样就可以把4取出来了。
索引表就解决了case语句块无序的问题,这样就可以再用跳转表进行寻址了
- .text:00415148 jmp ds:jpt_415148[eax*4] ; switch jump
然后用比例因子寻址的方式跳转到对应的地址
当case的值没有明显的线性关系时,就会出现两张表,一张索引表,保存下标,一张线性表,保
存地址
但是还有个效率的问题:无序线性结构会有两次查表的过程,因此效率会有所下降
当switch分支小于4时,其反汇编采用的结构和if else相同,两者效率一样。
当switch分支大于4时,switch采用跳转表的方式来执行代码块,此时效率比if else高。