滴水逆向三期笔记与作业——02C语言——10 Switch语句反汇编
- 一、Switch语句
- 1、switch语句 是if语句的简写
- 2、break加与不加有什么特点?default语句可以省略吗?
- 3、游戏中的switch语句(示例)
- 4、添加case后面的值,一个一个增加,观察反汇编代码的变化(何时生成大表).
- 5、将3中的常量值的顺序打乱,观察反汇编代码(观察顺序是否会影响生成大表).
- 6、将case后面的值改成从100开始到109(连续),观察汇编变化(观察值较大时是否生成大表).
- 7、将连续的10项中抹去1项或者2项,观察反汇编有无变化(观察大表空缺位置的处理).
- 8、在10项中连续抹去,不要抹去最大值和最小值(观察何时生成小表).
- 9、将case后面常量表达式改成毫不连续的值,观察反汇编变化.
- 10、部分连续,部分差值非常大
- 二、do/while反汇编
- 1、do/while的语法
- 2、do/while的反汇编
- 3、example
- 4、总结
- 三、while反汇编
- 1、while语句的语法
- 2、while语句反汇编
- 3、example
- 4、总结
- 四、for循环反汇编
- 1、for语句的语法
- 2、for循环的执行次序
- 3、example
- 4、总结
- 五、作业
- 1、写一个Switch语句,不生产大表也不生产小表,贴出对应反汇编
- 2、写一个Switch语句,只生成大表,贴出对应反汇编
- 3、写一个Switch语句,生成大表和小表,贴出对应反汇编
- 4、为do/while语句生成的反汇编填写注释
- 5、为while语句生成的反汇编填写注释
- 6、为for语句生成的反汇编填写注释
一、Switch语句
1、switch语句 是if语句的简写
- if语句
if(表达式 == 常量1)
{//...代码
}
else if(表达式 == 常量2)
{//...代码
}
else if(表达式 == 常量3)
{//...代码
}
else
{//...代码
}
- switch语句
switch(表达式)
{ case 常量表达式1:语句;break;case 常量表达式2:语句;break;case 常量表达式3:语句;break;case 常量表达式3:语句;break;default: 语句;break;
}
switch要求:
1、case后面必须是常量表达式
2、case后常量表达式的值不能一样
3、switch后面表达式必须为整数
2、break加与不加有什么特点?default语句可以省略吗?
不写break时,编译可以通过,但会将不写break的case全部执行一遍。
default语句可以省略,当所有条件都不满足的时候,会默认执行default中的代码,如果不存在default,但所有条件不满足,则不执行代码。
3、游戏中的switch语句(示例)
F1 F2 F3 F4 F5 F6 F7 F8
0 1 2 3 4 5 6 7
switch(表达式)
{case 1:打坐....break;case 2:加红.... break;case 3:加蓝.... break;case 4:释放技能....break;default:语句;break;
}
4、添加case后面的值,一个一个增加,观察反汇编代码的变化(何时生成大表).
我的环境是64位的vscode,与海哥教程中存在较大差异。
- 少分支Switch结构
void Function(int x){
switch (x){
case 1:
printf("1");
break;
case 2:
printf("2");
break;
case 3:
printf("3");
break;
default:
printf("4");
break;
}
}int main(int argc, char* argv[]){
Function(2);return 0;
}
由汇编可见,少分支的Switch与if相似,所以正向代码中,分支较少时建议不使用Switch
- 多分支Switch结构
void Function(int x){
switch (x){
case 1:
printf("1");
break;
case 2:
printf("2");
break;
case 3:
printf("3");
break;
case 4:
printf("4");
break;
default:
printf("5");
break;
}
}int main(int argc, char* argv[]){
Function(2);return 0;
}
未发现大表寻址过程
- 多分支Switch结构
void Function(int x){
switch (x){
case 1:
printf("1");
break;
case 2:
printf("2");
break;
case 3:
printf("3");
break;
case 4:
printf("4");
break;
case 5:
printf("5");
break;
case 6:
printf("6");
break;
default:
printf("7");
break;
}
}
int main(int argc, char* argv[]){
Function(3);
return 0;
}
存在大表寻址过程,已优化
- 总结
1、分支少于4的时候,使用Switch没有意义,因为编译器会生成类似if/else之类的反汇编;
2、case后面的常量表达式可以是无序的,并不影响大表的生成
• 分支较少时,不生成大表,也不生成小表,会生成if…else语句
• 分支达到一定数量时,生成大表,且大表跟顺序无关
• 大表可以理解为一个存储了多个地址的连续表,通过Register*4可以来寻址。
• 分支达到一定数量,生成大表,但是中间缺少很多case时,还会生成一张小表。
• 小表的作用可以理解为把大表的空缺地址,移动到了小表,把空缺的case值所在的地方填为default的地址
5、将3中的常量值的顺序打乱,观察反汇编代码(观察顺序是否会影响生成大表).
并不影响大表生成
6、将case后面的值改成从100开始到109(连续),观察汇编变化(观察值较大时是否生成大表).
- 代码
void Function(int x){
switch (x){
case 100:
printf("100");
break;
case 101:
printf("101");
break;
case 102:
printf("102");
break;
case 103:
printf("103");
break;
case 104:
printf("104");
break;
case 105:
printf("105");
break;
case 106:
printf("106");
break;
case 107:
printf("107");
break;
case 108:
printf("108");
break;
default:
printf("109");
break;
}
}
int main(int argc, char* argv[]){
Function(103);
return 0;
}
- 反汇编
如果[参数-100]大于[max-min],说明传入参数小于case最小值或者大于case最大值,此时直接执行default即可;
反之说明参数在min到max之间,而此时eax中存储的是[参数-100]的数值,而编译器已经维护了一张表,根据eax的位置,像一维数组查数一样根据公式,eax=基址+eax代表的内存的数据,跳转到eax的执行地址即可(jmp rax)。
7、将连续的10项中抹去1项或者2项,观察反汇编有无变化(观察大表空缺位置的处理).
被删除的部分并不会被填充0,大表会把抹去的分支项原先所对应的地址全部给填充为default默认地址.
8、在10项中连续抹去,不要抹去最大值和最小值(观察何时生成小表).
小表是将大表的地址移动到小表,空缺的地方填充为到default的偏移量。
海哥的教程中删除一定的case分支后,生成小表,但本地环境win64+VSCOde中,未生成小表,删除一定的分支后,直接转换成if/else形式(我也很疑惑……)。
9、将case后面常量表达式改成毫不连续的值,观察反汇编变化.
类似于if/else的结构
10、部分连续,部分差值非常大
- 代码
void Function(int x){
switch (x){
case 300:
printf("300");
break;
case 301:
printf("301");
break;
case 302:
printf("302");
break;
case 303:
printf("303");
break;
case 304:
printf("304");
break;
case 305:
printf("305");
break;
case 306:
printf("306");
break;
case 307:
printf("307");
break;
case 3:
printf("3");
break;
default:
printf("309");
break;
}
}
int main(int argc, char* argv[]){
Function(303);
return 0;
}
- 反汇编
与if/else相同,下图为海哥教程图
二、do/while反汇编
1、do/while的语法
do
{//执行代码
}while(表达式)
2、do/while的反汇编
3、example
4、总结
- 根据条件跳转指令所跳转到的地址,可以得到循环语句块的起始地址。
- 根据条件跳转指令所在的地址,可以得到循环语句块的结束地址。
- 条件跳转的逻辑与源码相同。
三、while反汇编
1、while语句的语法
while(表达式)
{//执行代码
}
2、while语句反汇编
3、example
4、总结
- 根据条件跳转指令所跳转到的地址,可以得到循环语句块的结束地址;
- 根据jmp 指令所跳转到的地址,可以得到循环语句块的起始地址;
- 在还原while 比较时,条件跳转的逻辑与源码相反。
四、for循环反汇编
1、for语句的语法
for(表达式1;表达式2;表达式3)
{
//执行的代码
}
2、for循环的执行次序
表达式1
表达式2
执行的代码(大括号里面的内容)
表达式3
表达式2 //如果表达式2成立
执行的代码(大括号里面的内容)
表达式3
表达式2 //如果表达式2成立
执行的代码(大括号里面的内容)
表达式3
表达式2 //如果不成立
跳出循环
3、example
4、总结
- 第一个jmp 指令之前为赋初值部分
- 第一个jmp 指令所跳转的地址为循环条件判定部分起始
- 判断条件后面的跳转指令条件成立时跳转的循环体外面
- 条件判断跳转指令所指向的地址上面有一个jmp jmp地址为表达式3的起始位置
五、作业
1、写一个Switch语句,不生产大表也不生产小表,贴出对应反汇编
void Function(int x)
{
switch(x)
{
case 1:printf("1");break;
case 2:printf("2");break;
case 3:printf("3");break;
default:printf("Error");break;
}
}int main(int argc, char* argv[]){Function(2);return 0;
}
2、写一个Switch语句,只生成大表,贴出对应反汇编
void Function(int x)
{
switch(x)
{
case 1:printf("1");break;
case 2:printf("2");break;
case 3:printf("3");break;
case 4:printf("4");break;
case 5:printf("5");break;
case 6:printf("6");break;
default:printf("Error");break;
}
}int main(int argc, char* argv[]){
Function(5);return 0;
}
3、写一个Switch语句,生成大表和小表,贴出对应反汇编
void Function(int x)
{
switch(x)
{
case 100:printf("100");break;
case 101:printf("101");break;
case 110:printf("110");break;
case 3:printf("3");break;
case 1:printf("1");break;
default:printf("Error");break;
}
}
int main(int argc, char* argv[]){Function(101);return 0;
}
我的是if/else结构,博客站中是大小表结构。
4、为do/while语句生成的反汇编填写注释
void Function(int i)
{do{printf("%d\n", i);i++;} while (i>3);}
int main(int argc, char* argv[]){Function(0);return 0;
}
5、为while语句生成的反汇编填写注释
void Function(int i)
{while (i<3){printf("%d\n", i);i++;}}
int main(int argc, char* argv[]){Function(0);return 0;
}
6、为for语句生成的反汇编填写注释
void Function(int x)
{for (int i = 0; i < x; i++){printf("%d\n", i);}
}
int main(int argc, char* argv[]){Function(5);return 0;
}