dex2jar 反编译后崩溃 ArrayIndexOutOfBoundsException

2,432 阅读2分钟

现象:ArrayIndexOutOfBoundsException

  • 经过反编译插桩后重打包的apk运行时报错,关键信息如下
java.lang.ArrayIndexOutOfBoundsException: src.length=4 srcPos=-1 dst.length=4 dstPos=0 length=1
at java.lang.System.arraycopy(Native Method)
at android.support.v4.util.SimpleArrayMap.put(Unknown Source)

定位问题

  • 排除了插桩等其它环节的影响后,最终定位到问题出在使用dex2jar将dex转jar的时候,并在github上找到别人提交的issue: github.com/pxb1988/dex…,他帮忙定位出了问题的关键 “~运算符在反编译代码中消失了” ,接下来就是验证问题是否真的出在~运算符上。
  • 使用反编译工具jadx查看经过dex2jar反编译前后代码差别,到这里就确定是dex2jar转换后丢失了~运算符导致了这个数组越界问题。

解决问题

  • 在前面提到的issue中,我们了解到“~位补码运算符在旧的编译器中被编译成IXOR-1,而D8将其编译成NOT-INT,not-int不是Java字节码指令,所以它失败了”。查看旧的编译器中SimpleArrayMap源码,发现确实如此。

  • 通过上述信息,在源码中搜索IXOR、NOT-INT、NOT关键词,一通乱改各种尝试后,最终定位到函数 com.googlecode.d2j.converter.IR2JConverter#reBuildE1Expression,修改时确实找不到not-int对应的字节码,那就按照旧编译器的规则来,用IXOR-1来修改,最终修改如下

  • com.googlecode.d2j.converter.IR2JConverter#reBuildE1Expression 修改后完整代码如下
private static void reBuildE1Expression(E1Expr e1, MethodVisitor asm) {
    accept(e1.getOp(), asm);
    switch (e1.vt) {
    case STATIC_FIELD: {
        FieldExpr fe = (FieldExpr) e1;
        asm.visitFieldInsn(GETSTATIC, toInternal(fe.owner), fe.name, fe.type);
        break;
    }
    case FIELD: {
        FieldExpr fe = (FieldExpr) e1;
        asm.visitFieldInsn(GETFIELD, toInternal(fe.owner), fe.name, fe.type);
        break;
    }
    case NEW_ARRAY: {
        TypeExpr te = (TypeExpr) e1;
        switch (te.type.charAt(0)) {
        case '[':
        case 'L':
            asm.visitTypeInsn(ANEWARRAY, toInternal(te.type));
            break;
        case 'Z':
            asm.visitIntInsn(NEWARRAY, T_BOOLEAN);
            break;
        case 'B':
            asm.visitIntInsn(NEWARRAY, T_BYTE);
            break;
        case 'S':
            asm.visitIntInsn(NEWARRAY, T_SHORT);
            break;
        case 'C':
            asm.visitIntInsn(NEWARRAY, T_CHAR);
            break;
        case 'I':
            asm.visitIntInsn(NEWARRAY, T_INT);
            break;
        case 'F':
            asm.visitIntInsn(NEWARRAY, T_FLOAT);
            break;
        case 'J':
            asm.visitIntInsn(NEWARRAY, T_LONG);
            break;
        case 'D':
            asm.visitIntInsn(NEWARRAY, T_DOUBLE);
            break;
        }
    }
        break;
    case CHECK_CAST:
    case INSTANCE_OF: {
        TypeExpr te = (TypeExpr) e1;
        asm.visitTypeInsn(e1.vt == VT.CHECK_CAST ? CHECKCAST : INSTANCEOF, toInternal(te.type));
    }
        break;
    case CAST: {
        CastExpr te = (CastExpr) e1;
        cast2(e1.op.valueType, te.to, asm);
    }
        break;
    case LENGTH:
        asm.visitInsn(ARRAYLENGTH);
        break;
    case NEG:
        asm.visitInsn(getOpcode(e1, INEG));
        break;
    case NOT: // fix issues#207 修复新编译器中~位补码运算符丢失问题
        if (e1.getOp().valueType.equals("I")) {
            asm.visitLdcInsn(-1);
            asm.visitInsn(getOpcode(e1, IXOR));
        } else if (e1.getOp().valueType.equals("J")) { // 不区分Long型的话在jar转回dex时会报错
            asm.visitLdcInsn(-1L);
            asm.visitInsn(getOpcode(e1, IXOR)); // 上面的-1控制为Long,这边需用IXOR,不能用LXOR,不知道为什么,否则转回dex会报错
        }
        break;
    }
}
  • 查看修改后的dex2jar将dex转为jar的代码