超级牛的 Java 反编译大法(二): IF 语句解析

 前文指路:

超级牛的Java反编译_java 控制流cfg-优快云博客

在 JVM 中,if 语句对应的字节码指令有:

OP_IFEQ, OP_IFNE, OP_IFLE, OP_IFLT, OP_IFGT, OP_IFGE,OP_IF_ACMPEQ, OP_IF_ACMPNE,OP_IF_ICMPLT, OP_IF_ICMPGE, OP_IF_ICMPGT, OP_IF_ICMPNE,OP_IF_ICMPEQ, OP_IF_ICMPLE,OP_IFNONNULL, OP_IFNULL

这些指令与源码中的 if 语句一一对应。

源码:

void main() {    int a = 1;    if (a > 1) {        a = 2;    } else {        a = 3;    }    return;}

对应字节码:

0: iconst_11: istore_12: iload_13: iconst_14: if_icmple     127: iconst_28: istore_19: goto          1412: iconst_313: istore_114: return

if_icmple 表示:当栈顶两个元素满足 <= 时跳转。翻译后逻辑为:

​​​​​​​

if (a <= 1) {    a = 3;} else {    a = 2;}

可以看到,字节码中的 if 条件与源码相反,if 和 else 的 body 也被交换,但逻辑等价。

结论: 字节码中大多数 if 条件是反转的,因此在反编译时应对条件取反,同时互换 if 和 else 的 body。

流程图如下:

可以看出从 if 相关指令开始,出现两条分支,分别对应 if body 和 else body 的代码块。两个 body 最终在一个点汇合标志着if语句的结束。

流程图如下:

可以看出 if 指令指向两个节点,一个节点是 if body,另一个节点是 if 语句的结束。

一般来说,if 语句的 cfg 应该是一个树状的,不存在一个代码块同时属于多个 body的情况。

如图:

但逻辑运算表达式对应字节码的 cfg 会出现两个 if 语句 body 重叠的情况,如下:

源码:

​​​​​​​

if (a > 1 || a > 0){    a = 2;}

问题:如果直接翻译会出现下面的情况,源码中的 if body 出现了两次。

if (a > 1) {    a = 2;} else {    if (a > 0) {       a = 2; // 这里的body和if (a > 1)的body相同    }}

为了解决这种问题,需要对指令中的 if 语句化简,将这种特殊情况的 if 语句嵌套转为逻辑运算。

break 和 continue 在字节码中表现为 goto,会干扰 if body 的范围识别。

如图:

可以看出,a = 1 是循环的入口点,end 为循环结束。图中的 goto 指向循环结束,说明 goto 其实是 break 语句。

可以将上面的图等价变为下面的图:

解决方案:在解析 if 语句前,先识别所有循环结构,并将 goto 恢复为 break / continue,补充 LoopEnd 节点。

在之前的案例中,if 语句的 body 从开始到结束,虚拟机栈深是不变的,也就是 body 内是完整的代码块。

但在三元表达式的情况下,body 可能是向栈 push 一个 constant。

源码:

int a = b > c ? 1 : 0;

字节码:

0: iload_11: iload_22: if_icmple     53: iconst_14: goto          65: iconst_06: istore_3

问题:

  • if 的两个分支都从开始到结束栈深发生变化

  • istore 才是真正的赋值语句

  • if body / else body 无法单独构成完整语句

【解决方案】

方案一:引入中间变量

int tmp;if (b > c) {    tmp = 1;} else {    tmp = 0;}int a = tmp;

方案二:栈模拟

  • 模拟所有指令的栈深

  • 在汇合节点处栈深发生变动,说明是三元表达式,针对三元表达式进行化简

  • 将 if 替换为 ?: 表达式

小结

实际情况更复杂,例如在 body 中执行函数、执行赋值、嵌套等操作:​​​​​​​

int a = b > 1 ? getValue() : b < 2 ? c = b : c = 1;int a = (b > 1 ? object1 : object2).getValue();

经过验证,栈模拟的方案才能解决这种复杂的情况。在实际反编译过程中需要将整个 if 语句替换为一个占位变量,在还原并化简 if 语句后,尝试将 if condition 和栈中的操作数替换到占位变量中。

在 java 中的比较运算也是通过 if 指令实现,例如:

bool a = b > 1;

上面的代码等价于:

bool a = b > 1 ? true : false

所以同理的,可以转为三元表达式的情况处理,最后再对三元表达式化简从而还原源码。

if 的结束点的必要条件

  1. 被 if 节点支配

  2. 被 if body 起始节点支配

  3. 被 else body 起始节点支配

条件表达式的简化

  1. 将前缀表达式转为后缀表达式

  2. 对逻辑表达式进行归约与优化

解析 if 语句

  • if 指令跳转地址可能是 body 开始,也可能是结束

  • 汇合节点即为 if 的结束点(不一定被 if 指令支配)

  • 若两个 if 指向同一个 body,且汇合节点相同,可尝试合并为逻辑表达式

  • 反编译时需注意 if 条件的反转

  • 特殊情况如 break / continue、三元表达式需额外处理

本篇文章深入分析了 Java 字节码中 if 语句的结构、识别方式及反编译策略。通过流程图、控制流图、支配关系等方法,可以准确地识别 ifelse、三元表达式等结构,并为后续的语法还原和优化打下基础。

——END——

YAK官方资源】 

Yak 语言官方教程:
https://yaklang.com/docs/intro/
Yakit 视频教程:
https://space.bilibili.com/437503777
Github下载地址:
https://github.com/yaklang/yakit
Yakit官网下载地址:
https://yaklang.com/
Yakit安装文档:
https://yaklang.com/products/download_and_install
Yakit使用文档:
https://yaklang.com/products/intro/
常见问题速查:
https://yaklang.com/products/FAQ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值