switch case 跳转表 解析

完整代码

#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特征:

  1. 索引计算value - 最小值
  2. 范围检查:比较是否在有效范围内
  3. 跳转表指令jmp dword ptr [寄存器*4+基地址]
  4. 集中式跳转表:所有分支目标地址存储在连续内存中
  5. 统一的退出点:所有case都跳转到同一结束位置

与if-else链的区别:

  • if-else会使用连续的cmpjcc指令
  • 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);
  • …以此类推

为什么会出现这种情况?

  1. 反汇编器混淆:反汇编工具无法区分代码段和数据段
  2. 跳转表嵌入代码段:编译器将跳转表直接放在函数代码后面
  3. 线性扫描反汇编:工具按顺序解析字节,把数据当作指令

正确的查看方式

在专业的反汇编工具中(如IDA Pro),应该:

  • 0D01B0C 区域标记为数据而非代码
  • 或者直接查看该地址的十六进制数据

结论:跳转表确实存在,只是被错误地显示为"垃圾指令"!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丁金金_chihiro_修行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值