ARM与Thumb 模式下switch的不同实现有序switch的具体实现在ARM模式、Thumb模式下都不相同。arm 指令集编译模式下,有序switch 采用跳转表模式。Thumb 模式下,采用的是跳转函数与跳转表结合使用的模式。
ARM 模式下的实现参考源代码:
#include <stdio.h>
int main ()
{
int user_in = 0;
printf("You can input a number:");
scanf("%d",&user_in);
printf("The number is : %d\n",user_in);
switch(user_in){ // 有序switch
case 4:
printf("4 boys.\n");
break;
case 5:
printf("5 girls.\n");
break;
case 6:
printf("6 girls.\n");
break;
case 7:
printf("7 girls.\n");
break;
default:
printf("Haha \n");
};
printf("below is switch2 \n");
switch(user_in)
{
case 1:
printf("*1 boys.\n");
break;
case 10:
printf("*10 boys.\n");
break;
case 30:
printf("*30 girls \n");
break;
case 45:
printf("*45 boys.\n");
break;
default:
printf("NNNN \n");
}
return 0;
}
有序switch指的是case值是连续的,参考switch(user_in) 的case,该switch的case测试值的区间是4,5,6,7,所以是连续的case值。
进入Switch 语句块之前,会对测试数据进行预处理,一般通过减法指令使测试数据向0对齐,也就是最小测试数据为被映射为0,然后将处理后的数值作为索引访问跳转地址表。
一般流程如下:
数据预处理(一般为减法指令) -> 判断default 条件(cmp) -> PC相对跳转到跳转指令表n项(ADD{FLAG})->跳转表n项强制跳转到n对应代码块。
根据代码总结通用格式
sub rx,rx,xx
ADDxx PC,PC,Rx,LSL # 2 // 因为跳转向量表的每一项指令长度都为4字节,LSL#2 对R3 进行x4 寻址。
B xxx1 // 跳转表,跳转表第0项一般为default 跳转向量。 每一个跳转向量都是4字节ARM B指令。
B xxx2
B xxx3
B xxx n
xxx2:
code...
[b exit] // 可选,break指令编译后效果
xxx3:
code...
[b exit]
xxx4:
code...
[b exit]
xxxn:
code...
[b exit]
default:
code....
exit:
xxxx //这里是switch 结构之外的代码
switch 的 case块的代码执行完后必须要break的谜团就在这里,因为在编译后每一个case的代码块也是连续的,需要手动的将程序流程改向switch 外部。
参考如下,R3为switch 输入测试数据。
.text:00000594 SUB R3, R3, #4 //R3 减去4向0对齐
.text:00000598 CMP R3, #3 //(最大值7 - 4 = 3,与3比较。)
.text:0000059C ADDLS PC, PC, R3,LSL#2 //PC相对寻址
// 相对寻址公式: PC = PC + R3 * 4
// 在ARM模式下,PC的值为当前指令地址 + 8 ,R3最终被作为索引。
// 如果R3 = 0,所以当case 0成立时 跳转的目标地址为:0x59C + 0x8 = 0x5A4,在该地址之前还有0x5A0,则为default 实现的代码。
//
.text:000005A0 ; ---------------------------------------------------------------------------
.text:000005A0
.text:000005A0 loc_5A0 ; 这个是default 跳转
.text:000005A0 B loc_604 ; jumptable 0000059C default case
.text:000005A4 ; ---------------------------------------------------------------------------
.text:000005A4
.text:000005A4 loc_5A4 ; 这个是case 0+4
.text:000005A4 B loc_5B4 ; jumptable 0000059C case 0
.text:000005A8 ; ---------------------------------------------------------------------------
.text:000005A8
.text:000005A8 loc_5A8 ; 这个是case 1+4
.text:000005A8 B loc_5C8 ; jumptable 0000059C case 1
.text:000005AC ; ---------------------------------------------------------------------------
.text:000005AC
.text:000005AC loc_5AC ; 这个是case 2+4
.text:000005AC B loc_5DC ; jumptable 0000059C case 2
.text:000005B0 ; ---------------------------------------------------------------------------
.text:000005B0
.text:000005B0 loc_5B0 ; 这个是case 3+4
.text:000005B0 B loc_5F0 ; jumptable 0000059C case 3
.text:000005B4 ; ---------------------------------------------------------------------------
.text:000005B4
.text:000005B4 loc_5B4 ; 这个是switch之外的代码块。
.text:000005B4 ; main:loc_5A4↑j
每一个独立的代码块格式基本如下
.text:000005B4 loc_5B4 ; CODE XREF: main+5C↑j
.text:000005B4 ; main:loc_5A4↑j
.text:000005B4 LDR R3, =(a4Boys - 0x5C0) ; jumptable 0000059C case 0
.text:000005B8 ADD R3, PC, R3 ; "4 boys."
.text:000005BC MOV R0, R3 ; char *
.text:000005C0 BL puts
.text:000005C4 B loc_614 // 跳到出口,是break 语句所致
.text:000005C8 ; ---------------------------------------------------------------------------
.text:000005C8
.text:000005C8 loc_5C8 ; CODE XREF: main+5C↑j
.text:000005C8 ; main:loc_5A8↑j
.text:000005C8 LDR R3, =(a5Girls - 0x5D4) ; jumptable 0000059C case 1
.text:000005CC ADD R3, PC, R3 ; "5 girls."
.text:000005D0 MOV R0, R3 ; char *
.text:000005D4 BL puts
.text:000005D8 B loc_614 // 跳到出口
.text:000005DC ; ---------------------------------------------------------------------------
.text:000005DC
.text:000005DC loc_5DC ; CODE XREF: main+5C↑j
.text:000005DC ; main:loc_5AC↑j
.text:000005DC LDR R3, =(a6Girls - 0x5E8) ; jumptable 0000059C case 2
.text:000005E0 ADD R3, PC, R3 ; "6 girls."
.text:000005E4 MOV R0, R3 ; char *
.text:000005E8 BL puts
.text:000005EC B loc_614 //跳到出口
.text:000005F0 ; ---------------------------------------------------------------------------
.text:000005F0
.text:000005F0 loc_5F0 ; CODE XREF: main+5C↑j
.text:000005F0 ; main:loc_5B0↑j
.text:000005F0 LDR R3, =(a7Girls - 0x5FC) ; jumptable 0000059C case 3
.text:000005F4 ADD R3, PC, R3 ; "7 girls."
.text:000005F8 MOV R0, R3 ; char *
.text:000005FC BL puts
.text:00000600 B loc_614 // 跳到出口
.text:00000604 ; ---------------------------------------------------------------------------
.text:00000604
.text:00000604 loc_604 ; CODE XREF: main+5C↑j
.text:00000604 ; main:loc_5A0↑j
.text:00000604 LDR R3, =(aHaha - 0x610) ; jumptable 0000059C default case
.text:00000608 ADD R3, PC, R3 ; "Haha "
.text:0000060C MOV R0, R3 ; char *
.text:00000610 BL puts
.text:00000614 // 这里就是出口
Thumb 模式下的实现Thumb 模式下,有序switch 有一些小变化。
Thumb 模式下,跳转向量表中将没有default 块的向量,在预处理之后,会首先判断default条件,如果满足default则直接跳转,如果不满足,再调用__gnu_thumb1_case_si 函数跳转,这是一个永远不会返回的函数。Thumb模式下的switch跳转表是纯数值偏移。每一项的值等于代码块地址到跳转表基质的相对偏移。
.text:00000570 LDR R3, [SP,#0x10+var_C]
.text:00000572 SUBS R3, #4 数据预处理
.text:00000574 CMP R3, #3
.text:00000576 BHI def_57A 判断default
.text:00000578 MOVS R0, R3 R0 传递参数
.text:0000057A BL __gnu_thumb1_case_si ; switch jump
.text:0000057A ; ---------------------------------------------------------------------------
.text:0000057E ALIGN 0x10 这是一个纯数值偏移跳转表。
.text:00000580 jpt_57A DCD loc_590 - 0x580 ; jump table for switch statement
.text:00000584 DCD loc_59C - 0x580 ; jumptable 0000057A case 5
.text:00000588 DCD loc_5A8 - 0x580 ; jumptable 0000057A case 6
.text:0000058C DCD loc_5B4 - 0x580 ; jumptable 0000057A case 7
.text:00000590 ; ---------------------------------------------------------------------------
.text:00000590
.text:00000590 loc_590 ; CODE XREF: main+3A↑j
.text:00000590 ; DATA XREF: main:jpt_57A↑o
.text:00000590 LDR R3, =(a4Boys - 0x596) ; jumptable 0000057A case 4
.text:00000592 ADD R3, PC ; "4 boys."
.text:00000594 MOVS R0, R3
.text:00000596 BL j_puts
.text:0000059A B loc_5CA
.text:0000059C ; ---------------------------------------------------------------------------
.text:0000059C
.text:0000059C loc_59C ; CODE XREF: main+3A↑j
.text:0000059C ; DATA XREF: main+44↑o
.text:0000059C LDR R3, =(a5Girls - 0x5A2) ; jumptable 0000057A case 5
.text:0000059E ADD R3, PC ; "5 girls."
.text:000005A0 MOVS R0, R3
.text:000005A2 BL j_puts
.text:000005A6 B loc_5CA
.text:000005A8 ; ---------------------------------------------------------------------------
.text:000005A8
.text:000005A8 loc_5A8 ; CODE XREF: main+3A↑j
.text:000005A8 ; DATA XREF: main+48↑o
.text:000005A8 LDR R3, =(a6Girls - 0x5AE) ; jumptable 0000057A case 6
.text:000005AA ADD R3, PC ; "6 girls."
.text:000005AC MOVS R0, R3
.text:000005AE BL j_puts
.text:000005B2 B loc_5CA
.text:000005B4 ; ---------------------------------------------------------------------------
.text:000005B4
.text:000005B4 loc_5B4 ; CODE XREF: main+3A↑j
.text:000005B4 ; DATA XREF: main+4C↑o
.text:000005B4 LDR R3, =(a7Girls - 0x5BA) ; jumptable 0000057A case 7
.text:000005B6 ADD R3, PC ; "7 girls."
.text:000005B8 MOVS R0, R3
.text:000005BA BL j_puts
.text:000005BE B loc_5CA
.text:000005C0 ; ---------------------------------------------------------------------------
.text:000005C0
.text:000005C0 def_57A ; CODE XREF: main+36↑j
.text:000005C0 LDR R3, =(aHaha - 0x5C6) ; jumptable 0000057A default case
.text:000005C2 ADD R3, PC ; "Haha "
.text:000005C4 MOVS R0, R3
.text:000005C6 BL j_puts
.text:000005CA
.text:000005CA loc_5CA ; CODE XREF: main+5A↑j
分析辅助跳转函数
.text:00000668 __gnu_thumb1_case_si ; CODE XREF: main+3A↑p
.text:00000668 PUSH {R0,R1} // R0 是要判断的case值
.text:0000066A MOV R1, LR // R1 = 0x57A(调用处地址) + 2(下一条指令相对)
.text:0000066C ADDS R1, #2 // R1 = 0x57A + 2 + 2 = 0x57E
.text:0000066E LSRS R1, R1, #2
.text:00000670 LSLS R0, R0, #2 // 索引case * 4
.text:00000672 LSLS R1, R1, #2 // R1 = 0x580 (跳转向量表基地址)
.text:00000674 LDR R0, [R1,R0] // 读取向量表第n项偏移地址
.text:00000676 ADDS R0, R0, R1 // 将偏移地址加上跳转表基地址得到代码块地址
.text:00000678 MOV LR, R0 // 返回地址设置为目标代码块地址
.text:0000067A POP {R0,R1}
.text:0000067C MOV PC, LR // 返回
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课