文章目录
完整代码
#include <string>
// 基础输出模拟
void output(int val) {
static int buffer;
buffer = val;
}
//
1. 密集连续值 - 可能生成跳转表
void switch_dense_sequential(int value) {
switch (value) {
case 1: output(100); break;
case 2: output(200); break;
case 3: output(300); break;
case 4: output(400); break;
case 5: output(500); break;
case 6: output(100); break;
case 7: output(200); break;
case 8: output(300); break;
case 9: output(400); break;
case 10: output(500); break;
default: output(0); break;
}
}
汇编部分
9: // 1. 密集连续值 - 可能生成跳转表
10: void switch_dense_sequential(int value) {
00D01A00 push ebp
00D01A01 mov ebp,esp
00D01A03 sub esp,0C4h
00D01A09 push ebx
00D01A0A push esi
00D01A0B push edi
00D01A0C lea edi,[ebp-4]
00D01A0F mov ecx,1
00D01A14 mov eax,0CCCCCCCCh
00D01A19 rep stos dword ptr es:[edi]
00D01A1B mov ecx,offset _FA199733_if语句@cpp (0D0D058h)
00D01A20 call @__CheckForDebuggerJustMyCode@4 (0D01348h)
00D01A25 nop
11: switch (value) {
00D01A26 mov eax,dword ptr [value]
00D01A29 mov dword ptr [ebp-0C4h],eax
00D01A2F mov ecx,dword ptr [ebp-0C4h]
00D01A35 sub ecx,1
00D01A38 mov dword ptr [ebp-0C4h],ecx
00D01A3E cmp dword ptr [ebp-0C4h],9
00D01A45 ja $LN13+0Fh (0D01AEBh)
00D01A4B mov edx,dword ptr [ebp-0C4h]
00D01A51 jmp dword ptr [edx*4+0D01B0Ch]
12: case 1: output(100); break;
00D01A58 push 64h
00D01A5A call output (0D0105Ah)
00D01A5F add esp,4
00D01A62 jmp $LN13+19h (0D01AF5h)
13: case 2: output(200); break;
00D01A67 push 0C8h
00D01A6C call output (0D0105Ah)
00D01A71 add esp,4
00D01A74 jmp $LN13+19h (0D01AF5h)
14: case 3: output(300); break;
00D01A76 push 12Ch
00D01A7B call output (0D0105Ah)
00D01A80 add esp,4
00D01A83 jmp $LN13+19h (0D01AF5h)
15: case 4: output(400); break;
00D01A85 push 190h
00D01A8A call output (0D0105Ah)
00D01A8F add esp,4
00D01A92 jmp $LN13+19h (0D01AF5h)
16: case 5: output(500); break;
00D01A94 push 1F4h
00D01A99 call output (0D0105Ah)
00D01A9E add esp,4
00D01AA1 jmp $LN13+19h (0D01AF5h)
17: case 6: output(100); break;
00D01AA3 push 64h
00D01AA5 call output (0D0105Ah)
00D01AAA add esp,4
00D01AAD jmp $LN13+19h (0D01AF5h)
18: case 7: output(200); break;
00D01AAF push 0C8h
00D01AB4 call output (0D0105Ah)
00D01AB9 add esp,4
00D01ABC jmp $LN13+19h (0D01AF5h)
19: case 8: output(300); break;
00D01ABE push 12Ch
00D01AC3 call output (0D0105Ah)
00D01AC8 add esp,4
00D01ACB jmp $LN13+19h (0D01AF5h)
20: case 9: output(400); break;
00D01ACD push 190h
00D01AD2 call output (0D0105Ah)
00D01AD7 add esp,4
00D01ADA jmp $LN13+19h (0D01AF5h)
21: case 10: output(500); break;
00D01ADC push 1F4h
00D01AE1 call output (0D0105Ah)
00D01AE6 add esp,4
00D01AE9 jmp $LN13+19h (0D01AF5h)
22: default: output(0); break;
00D01AEB push 0
00D01AED call output (0D0105Ah)
00D01AF2 add esp,4
23: }
24: }
00D01AF5 pop edi
00D01AF6 pop esi
00D01AF7 pop ebx
00D01AF8 add esp,0C4h
00D01AFE cmp ebp,esp
00D01B00 call __RTC_CheckEsp (0D01258h)
00D01B05 mov esp,ebp
00D01B07 pop ebp
00D01B08 ret
00D01B09 nop dword ptr [eax]
00D01B0C pop eax
00D01B0D sbb dl,al
00D01B0F add byte ptr [edi+1Ah],ah
00D01B12 rol byte ptr [eax],1
00D01B14 jbe $LN13+54h (0D01B30h)
00D01B16 rol byte ptr [eax],1
00D01B18 test dword ptr [edx],ebx
00D01B1A rol byte ptr [eax],1
00D01B1C xchg eax,esp
00D01B1D sbb dl,al
00D01B1F add byte ptr [ebx-50FF2FE6h],ah
00D01B25 sbb dl,al
00D01B27 add byte ptr [esi-32FF2FE6h],bh
00D01B2D sbb dl,al
00D01B2F add ah,bl
00D01B31 sbb dl,al
00D01B33 add ah,cl
场景解释
根据提供的汇编代码,我可以分析出这种场景下switch语句的汇编特征:
主要特征分析
1. 跳转表实现
; 计算索引值
mov eax,dword ptr [value]
mov dword ptr [ebp-0C4h],eax
mov ecx,dword ptr [ebp-0C4h]
sub ecx,1 ; value - 1 (因为case从1开始)
mov dword ptr [ebp-0C4h],ecx
; 范围检查
cmp dword ptr [ebp-0C4h],9 ; 比较是否在0-9范围内
ja $LN13+0Fh (0D01AEBh) ; 超出范围跳转到default
; 跳转表查找和执行
mov edx,dword ptr [ebp-0C4h]
jmp dword ptr [edx*4+0D01B0Ch] ; 跳转表基地址 + 索引*4
2. 跳转表位置
地址 0D01B0Ch
处存储着跳转表,包含10个32位地址指针:
00D01B0C 对应 case 1
00D01B10 对应 case 2
00D01B14 对应 case 3
...
3. 各case块的模式
每个case块遵循相同模式:
; case块开始
push 64h ; 参数压栈
call output ; 函数调用
add esp,4 ; 清理栈
jmp $LN13+19h ; 跳转到函数末尾
识别特征总结
明显的switch特征:
- 索引计算:
value - 最小值
- 范围检查:比较是否在有效范围内
- 跳转表指令:
jmp dword ptr [寄存器*4+基地址]
- 集中式跳转表:所有分支目标地址存储在连续内存中
- 统一的退出点:所有case都跳转到同一结束位置
与if-else链的区别:
- if-else会使用连续的
cmp
和jcc
指令 - switch跳转表提供O(1)时间复杂度,if-else是O(n)
- switch有集中的范围检查,if-else是分散比较
编译器优化提示:
这种跳转表优化通常出现在:
- case值密集连续(这里是1-10)
- case数量较多(≥5个)
- case值范围相对紧凑
这种实现方式在性能上优于多个if-else比较,特别是当case数量较多时。
为什么没有直接看到跳转表
您说得对!在提供的汇编代码中,确实可以看到跳转表,但它是被错误反汇编了。让我来指出具体位置:
跳转表的真实位置
00D01B0C pop eax
00D01B0D sbb dl,al
00D01B0F add byte ptr [edi+1Ah], ah
00D01B12 rol byte ptr [eax],1
00D01B14 jbe $LN13+54h (0D01B30h)
00D01B16 rol byte ptr [eax],1
00D01B18 test dword ptr [edx],ebx
00D01B1A rol byte ptr [eax],1
00D01B1C xchg eax,esp
00D01B1D sbb dl,al
00D01B1F add byte ptr [ebx-50FF2FE6h],ah
00D01B25 sbb dl,al
00D01B27 add byte ptr [esi-32FF2FE6h],bh
00D01B2D sbb dl,al
00D01B2F add ah,bl
00D01B31 sbb dl,al
00D01B33 add ah,cl
问题分析
这些看似"无意义"的指令实际上是跳转表数据被错误地当作代码反汇编了!
正确的跳转表内容(以小端序解析):
地址 0D01B0Ch
开始的32位数据实际上是:
0D01B0C
: 58 1A D0 00 = 地址00D01A58
(对应case 1)0D01B10
: 67 1A D0 00 = 地址00D01A67
(对应case 2)0D01B14
: 76 1A D0 00 = 地址00D01A76
(对应case 3)0D01B18
: 85 1A D0 00 = 地址00D01A85
(对应case 4)0D01B1C
: 94 1A D0 00 = 地址00D01A94
(对应case 5)0D01B20
: A3 1A D0 00 = 地址00D01AA3
(对应case 6)0D01B24
: AF 1A D0 00 = 地址00D01AAF
(对应case 7)0D01B28
: BE 1A D0 00 = 地址00D01ABE
(对应case 8)0D01B2C
: CD 1A D0 00 = 地址00D01ACD
(对应case 9)0D01B30
: DC 1A D0 00 = 地址00D01ADC
(对应case 10)
验证匹配
这些地址正好对应各个case的起始位置:
00D01A58
:case 1: output(100);
00D01A67
:case 2: output(200);
00D01A76
:case 3: output(300);
- …以此类推
为什么会出现这种情况?
- 反汇编器混淆:反汇编工具无法区分代码段和数据段
- 跳转表嵌入代码段:编译器将跳转表直接放在函数代码后面
- 线性扫描反汇编:工具按顺序解析字节,把数据当作指令
正确的查看方式
在专业的反汇编工具中(如IDA Pro),应该:
- 将
0D01B0C
区域标记为数据而非代码 - 或者直接查看该地址的十六进制数据
结论:跳转表确实存在,只是被错误地显示为"垃圾指令"!