本文标题中的case语句是Ada语言中的一种控制结构,它与C语言的swich语句类似。
我正在阅读的一个exe文件是从Ada源程序编译生成的,其中有一类case语句的应用代码被IDA翻译为如下伪代码:
if ( a2 > 0x1Au )
__gnat_rcheck_06("page_s_arrival.adb", 1825); result = (unsigned __int8)(a2 - 14);
switch ( a2 )
{
case 0xEu:
//略
break;
case 0xFu:
//略
break;
case 0x10u:
//略
break;
case 0x14u:
//略
break;
case 0x15u:
//略
break;
case 0x16u:
//略
break;
case 0x1Au:
//略
break;
default:
return result;
}
对应的汇编代码是:
..text:00AA5507 cmp bl, 1Ah
.text:00AA550A jbe short loc_AA551E
.text:00AA550C sub esp, 8
.text:00AA550F push 721h
.text:00AA5514 push 0CDF9F4h
.text:00AA5519 call ___gnat_rcheck_06
.text:00AA551E
.text:00AA551E loc_AA551E:
.text:00AA551E lea eax, [ebx-0Eh]
.text:00AA5521 cmp al, 0Ch ; switch 1 cases
.text:00AA5523 ja loc_AA5A22 ; jumptable 00A9DCD4 cases 68-70,74-76
.text:00AA5529 and eax, 0FFh
.text:00AA552E jmp ds:off_CDFA20[eax*4] ; switch jump
.text:00AA5535
.text:00AA5535 loc_AA5535: ; jumptable 00A9DCD4 case 66
;略
.text:00AA558B jmp loc_AA5A22 ; jumptable 00A9DCD4 cases 68-70,74-76
.text:00AA5590
.text:00AA5590 loc_AA5590: ; jumptable 00A9DCD4 case 72
;略
.text:00AA5626 jmp loc_AA5A22 ; jumptable 00A9DCD4 cases 68-70,74-76
.text:00AA562B
.text:00AA562B loc_AA562B: ; jumptable 00A9DCD4 case 67
;略
.text:00AA57B8 jmp loc_AA5A22 ; jumptable 00A9DCD4 cases 68-70,74-76
.text:00AA57BD
.text:00AA57BD loc_AA57BD: ; jumptable 00A9DCD4 case 73
. ;略
.text:00AA5973 jmp loc_AA5A22 ; jumptable 00A9DCD4 cases 68-70,74-76
.text:00AA5978
.text:00AA5978 loc_AA5978: ; jumptable 00A9DCD4 case 65
. ;略
.text:00AA598B jmp loc_AA5A22 ; jumptable 00A9DCD4 cases 68-70,74-76
.text:00AA5990
.text:00AA5990 loc_AA5990: ; jumptable 00A9DCD4 case 77
. ;略
.text:00AA59FB jmp short loc_AA5A22 ; jumptable 00A9DCD4 cases 68-70,74-76
.text:00AA59FD
.text:00AA59FD loc_AA59FD: ; jumptable 00A9DCD4 case 71
;略
.text:00AA5A22 loc_AA5A22: ; jumptable 00A9DCD4 cases 68-70,74-76
ds:off_CDFA20中的内容是各种case处理代码的起始地址:
.rdata:00CDFA20 off_CDFA20 dd offset loc_AA5978 ; jump table for switch statement
.rdata:00CDFA24 dd offset loc_AA5535 ; jumptable 00A9DCD4 case 66
.rdata:00CDFA28 dd offset loc_AA562B ; jumptable 00A9DCD4 case 67
.rdata:00CDFA2C dd offset loc_AA5A22 ; jumptable 00A9DCD4 cases 68-70,74-76
.rdata:00CDFA30 dd offset loc_AA5A22 ; jumptable 00A9DCD4 cases 68-70,74-76
.rdata:00CDFA34 dd offset loc_AA5A22 ; jumptable 00A9DCD4 cases 68-70,74-76
.rdata:00CDFA38 dd offset loc_AA59FD ; jumptable 00A9DCD4 case 71
.rdata:00CDFA3C dd offset loc_AA5590 ; jumptable 00A9DCD4 case 72
.rdata:00CDFA40 dd offset loc_AA57BD ; jumptable 00A9DCD4 case 73
.rdata:00CDFA44 dd offset loc_AA5A22 ; jumptable 00A9DCD4 cases 68-70,74-76
.rdata:00CDFA48 dd offset loc_AA5A22 ; jumptable 00A9DCD4 cases 68-70,74-76
.rdata:00CDFA4C dd offset loc_AA5A22 ; jumptable 00A9DCD4 cases 68-70,74-76
.rdata:00CDFA50 dd offset loc_AA5990 ; jumptable 00A9DCD4 case 77
与以上汇编代码更相近的伪代码应是:
If ((a1 – 14) <= 12)
{
switch ( a2 )
{
case 0xFu:
//略
break;
case 0x15u:
//略
break;
case 0x10u:
//略
break;
case 0x16u:
//略
break;
case 0xEu:
//略
break;
case 0x1Au:
//略
break;
case 0x14u:
//略
break;
default:
return result;
}
}
以上伪代码与IDA生成的伪代码之间的差别是:
- 恢复了最外层的if语句;
- 按各个case的处理代码的起始地址排序。
我用以下Ada程序来模拟上述代码的处理逻辑:
with Ada.Text_IO;
use Ada.Text_IO;
procedure try_case_main is
case_number : Integer range 14 .. 26;
begin
case_number := Integer'Value (Get_Line);
case case_number is
when 15 => Put ("case 15");
when 21 => Put ("case 21");
when 16 => Put ("case 16");
when 22 => Put ("case 22");
when 14 => Put ("case 14");
when 26 => Put ("case 26");
when 20 => Put ("case 20");
when 17 | 18 | 19 | 23 | 24 | 25 => null;
end case;
end try_case_main;
以上Ada程序中case处理的顺序与case值的大小顺序无关。
用GNAT GPL 2016编译后生成了exe文件,其中case处理的代码顺序与源程序的case处理的顺序相同。
再用IDA对GNAT GPL 2016编译生成的exe文件生成了以下伪代码:
v4 = system__val_int__value_integer(line__4, v3);
if ( v4 <= 13 || v4 > 26 )
__gnat_rcheck_CE_Range_Check((const system__address)"try_case_main.adb", 7);
v6[3] = v4;
switch ( v4 )
{
case 14:
ada__text_io__put__4("case 14case 26case 20", dword_4360A8);
break;
case 15:
ada__text_io__put__4("case 15case 21case 16case 22case 14case 26case 20", dword_4360A8);
break;
case 16:
ada__text_io__put__4("case 16case 22case 14case 26case 20", dword_4360A8);
break;
case 20:
ada__text_io__put__4("case 20", dword_4360A8);
break;
case 21:
ada__text_io__put__4("case 21case 16case 22case 14case 26case 20", dword_4360A8);
break;
case 22:
ada__text_io__put__4("case 22case 14case 26case 20", dword_4360A8);
break;
case 26:
ada__text_io__put__4("case 26case 20", dword_4360A8);
break;
default:
break;
}
以上伪代码中的case顺序与源程序中的顺序不同。
汇编中在case语句前面还有以下代码:
.text:004016AA loc_4016AA:
.text:004016AA mov eax, [ebp+var_1C]
.text:004016AD sub eax, 0Eh ; switch 13 cases
.text:004016B0 cmp eax, 0Ch
.text:004016B3 ja def_4016C0 ; jumptable 004016C0 default case, cases 17-19,23-25
.text:004016B9 mov eax, ds:jpt_4016C0[eax*4]
.text:004016C0 jmp eax ; switch jump
以上标红的汇编代码没有对应的源代码,可见这是Ada编译器生成的。
这段标红的汇编代码与前例中可翻译为If ((a1 – 14) <= 12)的标红的汇编代码相似,其作用都是检查case表达式的合法性。
但是,在这之前都已经有相同作用的检查代码。可见,IDA智能地省略了冗余的检查。
另外,源程序中的case处理代码不管按何种顺序排列,Ada编译器生成的exe文件都是按jumptable来选择执行某个case的。
因此,IDA按case表达式值的大小为序重新排列case的顺序是可以接受的。