【问题标题】:What problems would arise in constructing a Java Decompiler through a grammar?通过语法构造Java Decompiler会出现什么问题?
【发布时间】:2012-06-20 10:01:02
【问题描述】:

我目前正在构建一个 Java 反编译器。

为了辅助模式识别,我正在通过ANTLR构造一个简单的语法,并使用ANTLRWorks解释器进行调试。

以下是目前的初步语法。沿着这条路线走下去,我假设我能够将某些 JVM 字节码简化为下面的语法可以检测到的表达式。

您认为这种方法存在哪些问题? 格林威治标准时间 6 月 29 日 2:36 1

更新了 Ira 的 cmets 语法
    grammar JVM;

options {k=3;}

WS  :   (' '|'\r'|'\n'|'\t')+ {$channel=HIDDEN;}
    ;
INT :   ('0'..'9')+ ;
UINT    :   ('_' INT)?;
IFEQ    :   'ifeq';
IFGE    :   'ifge';
IFGT    :   'ifgt';
IFLE    :   'ifle';
IFLT    :   'iflt';
IFNE    :   'ifne';
IFACMP_CONDTYPE :   'if_acmp' ('eq'|'ne'|'lt'|'ge'|'gt'|'le');
// THIS :   'aload_0';
LDC :   'ldc2_w'|'ldc_w'|'ldc';
LOADREFERENCE
//  :   THIS
    : 'aload' UINT;
//  | 'aload_2'
//  | 'aload_3';
DLOAD   :   'dload' UINT;
LOADINT :   'iload_0'
    |   'iload_1'
    | 'iload_2'
    | 'iload_3'
    ;
DCONST  :   'dconst' UINT;  
ICONST  :   'iconst' UINT;

goal    :   jvmStatement2+ ;

//fragment
//jvmStatement1
//  :   returnStatement
//  | newArrayStatement
//  | storeStatement
//  | assignmentStatement
//  | assertStatement
//  | invokeStatement
//  | ifStatement
//  | gotoStatement
//  ;

fragment // to test assert
jvmStatement2
    : returnStatement     // 2
    | newArrayStatement   // 3
    | storeStatement      // 4
    | invokeStatement     // 5
    | assignmentStatement // 6
    | assertStatement     // 7
    | ifStatement         // 8  
    | gotoStatement
    ;

fragment
setAssertionStatus
    :   ifStatement pushIntegerConstant
    gotoStatement pushIntegerConstant setStaticFieldInClass;

fragment
fetchFieldFromObject
    :   LOADREFERENCE 'getfield' INT;

fragment
loadDoubleFromLocalVariable
    :   DLOAD;

fragment
loadFloatFromLocalVariable
    :   'fload' UINT;

fragment
loadIntFromLocalVariable
    :   LOADINT;

fragment
loadLongFromLocalVariable
    :   'lload' UINT;   

fragment
loadReferenceFromLocalVariable
    :   'aload' UINT;

fragment
loadReferenceFromArray
    :   'aaload';

fragment
storeReference
    : storeIntoByteOrBooleanArray;  

fragment
storeReferenceIntoLocalVariable
    :   'astore' UINT;

fragment
storeDoubleIntoLocalVariable
    :   'dstore' INT;

fragment
storeFloatIntoLocalVariable
    :   'fstore' UINT;

fragment
storeIntIntoLocalVariable
    :   'istore' (INT|UINT);

fragment
storeLongIntoLocalVariable
    :   'lstore' UINT;  

fragment
storeIntoByteOrBooleanArray
    :   'bastore';

fragment
storeIntoReferenceArray
    :   'aastore';

fragment
pushNull:   'aconst_null';

fragment
pushByte:   'bipush' INT;

fragment
pushIntegerConstant
    :   ICONST;

fragment
pushDoubleConstant
    :   DCONST;

fragment
pushLongConstant
    :   'lconst' UINT;

fragment
pushFloatConstant
    :   'fconst' UINT;

fragment
pushItemFromRuntimeConstantPool
    :   LDC INT;


fragment invokeStatementArgument: constantExpr
    | createAnonymousClass;

fragment createAnonymousClass
    :   createNewObject dup thisInstance;

fragment invokeStatementArguments: invokeStatementArgument*;

fragment invokeStatement: getStaticField? invokeStatementArguments invokeMethod;    

fragment
invokeMethod
    : invokeInstanceMethod
    | invokeVirtualMethod
    | invokeStaticMethod
    ;

fragment
invokeInstanceMethod
    :   'invokespecial' INT;

fragment
invokeVirtualMethod
    :   'invokevirtual' INT;    

fragment
invokeStaticMethod
    :   'invokestatic' INT;

fragment
newArrayStatement
    :   'newarray' simpleType;

fragment
setFieldInObject
    :   'putfield' INT;

fragment setStaticFieldInClass
    :   'putstatic' INT;

fragment
simpleType
    :   ('boolean'|'byte'|'char'|'double'|'float'|'int'|'long'|'short');

fragment
returnVoid
    :    'return';
fragment
returnSimpleType
    :   returnReference
    | returnDouble
    | returnFloat
    | returnInteger
    | returnLong;

fragment
returnReference
    :    'areturn';
fragment
returnDouble
    :   'dreturn';
fragment returnFloat
    :   'freturn';
fragment returnInteger
    :   'ireturn';
fragment returnLong
    :   'lreturn';  

fragment
returnStatement
    :   returnVoid 
    | constantExpr returnSimpleType;    

fragment
dupX1
    :   'dup_x1';

fragment
dup
    :   'dup';  

fragment
storeStatement
    : storeReferenceIntoLocalVariable 
    | storeIntIntoLocalVariable
    | setStaticFieldInClass
    | storeIntoReferenceArray
    | setFieldInObject;

fragment
convertDouble
    :   convertDoubleToFloat | convertDoubleToInt | convertDoubleToLong;

fragment
convertDoubleToFloat
    :   'd2f';

fragment
convertDoubleToInt
    :   'd2i';

fragment
convertDoubleToLong
    :   'd2l';

fragment
convertFloat
    :   convertFloatToDouble|convertFloatToInt|convertFloatToLong;

fragment
convertFloatToDouble
    :   'f2d';
fragment
convertFloatToInt
    :   'f2i';
fragment
convertFloatToLong
    :   'f2l';  

fragment
convertInt
    :   convertIntToByte
    |convertIntToChar
    |convertIntToDouble
    |convertIntToFloat
    |convertIntToLong
    |convertIntToShort;

fragment
convertIntToByte
    :   'i2b';

fragment
convertIntToChar
    :   'i2c';

fragment
convertIntToDouble
    :   'i2d';

fragment
convertIntToFloat
    :   'i2f';

fragment
convertIntToLong
    :   'i2l';

fragment
convertIntToShort
    :   'i2s';

fragment
branchComparison
    :branchIfReferenceComparison
    |branchIfIntComparison
    |branchIfIntComparisonWithZero
    |branchIfReferenceNotNull
    |branchIfReferenceNull; 

fragment
branchIfReferenceComparison
    :   'if_acmp' condType;

fragment
branchIfIntComparison
    :   'if_icmp' condType INT;

fragment
branchIfIntComparisonWithZero
    :   (IFEQ|IFGE|IFGT|IFLE|IFLT|IFNE) INT;

fragment
gotoStatement
    :   'goto' INT;

fragment
ifStatementCompare
    :   (IFEQ INT)
    |   (IFNE INT);

fragment
ifStatement
    :   booleanExpression ifStatementCompare;

fragment
ifType  : 'ifeq'
 |'ifne'
 |'iflt'
 |'ifge'
 |'ifgt'
 |'ifle';

fragment
branchIfReferenceNotNull
    :   'ifnonnull' ;

fragment
branchIfReferenceNull
    :   'ifnull';

fragment
condType:   'eq'
 |'ne'
 |'lt'
 |'ge'
 |'gt'
 |'le';

fragment
checkCast
    :   'checkcast' INT;

fragment
createNewArrayOfReference
    :   constantExpr 'anewarray' INT;

fragment
createNewObject
    :   'new' INT;

fragment
assignmentStatement
//  : pushItemFromRuntimeConstantPool storeStatement
    : (constantExpr)+ storeStatement
    | invokeInheritedConstructor
    | expressionStatement
//  | setAssertionStatus
    ;

fragment
invokeInheritedConstructor
    :   loadReferenceFromLocalVariable invokeInstanceMethod;

fragment
throwExceptionOrError
    :   'athrow';

fragment
getStaticField
    :   'getstatic' INT;

fragment
newInstance
    :   'new' INT;

fragment // this needs to be extended to recognize more patterns
booleanExpression
    :   integerComparison
    | loadIntFromLocalVariable
    | invokeMethod;

fragment
integerComparison
    : loadIntFromLocalVariable loadIntFromLocalVariable branchIfIntComparison;  

fragment assertIfAssertEnabled: getStaticField branchIfIntComparisonWithZero;

fragment assertCondition:booleanExpression branchIfIntComparisonWithZero;

fragment assertThrow:createNewObject dup assertMessage throwExceptionOrError;

fragment assertMessage:pushItemFromRuntimeConstantPool invokeMethod;

fragment assertStatement:assertIfAssertEnabled assertCondition assertThrow;


fragment
stringPlusNumber
    :pushItemFromRuntimeConstantPool invokeMethod 
 loadReferenceFromLocalVariable invokeMethod invokeMethod invokeMethod;

fragment expressionStatement:   statementExpression;

fragment
statementExpression 
    :   preIncrementExpression
    | preDecrementExpression
//  | postIncrementExpression
//  | postDecrementExpression
    | newByteArray
    | ternaryExpression
    | createAndStoreObject // assignment expression
    | createNewArrayStatement
    | fetchFieldFromObject
    ;

fragment
createNewArrayStatement // with elements
    :   createNewArrayOfReference createNewArrayInitElement+;

createNewArrayInitElement
    : (dup constantExpr getStaticField storeStatement);

fragment
createAndStoreObject
    :   createNewObject dup invokeStatement storeStatement;

fragment ternaryExpression // doesn't cover all situations yet
    : loadIntFromLocalVariable ifStatementCompare loadIntFromLocalVariable gotoStatement
    loadIntFromLocalVariable storeStatement;    

fragment preIncrementExpression: preIncrementInteger;

fragment preDecrementExpression: preDecrementFloat|preDecrementLong|preDecrementDouble; 

fragment doubleExpression: pushDoubleConstant;

fragment integerExpression: pushIntegerConstant;

fragment longExpression: pushLongConstant;

fragment floatExpression: pushFloatConstant;

fragment preIncrementInteger: loadReferenceFromLocalVariable dup fetchFieldFromObject integerExpression 
    iAdd dupX1? setFieldInObject;

fragment preDecrementDouble: loadDoubleFromLocalVariable doubleExpression dSub storeDoubleIntoLocalVariable;

fragment preDecrementLong: loadLongFromLocalVariable longExpression lSub storeLongIntoLocalVariable;

fragment preDecrementFloat: loadFloatFromLocalVariable floatExpression fSub storeFloatIntoLocalVariable;

fragment newByteArray: newByteArrayWithNull|newByteArrayWithData;

// byte[] b = {'c', 'h', 'u', 'a'};
fragment newByteArrayWithData:  constantExpr newArrayStatement byteArrayElements;

fragment byteArrayElements: constantExpr constantExpr storeIntoByteOrBooleanArray;  

fragment constantExpr: 
    //loadReferenceFromLocalVariable
    LOADREFERENCE
    |loadDoubleFromLocalVariable
    |loadFloatFromLocalVariable
    |loadIntFromLocalVariable
    |loadLongFromLocalVariable
    |pushByte
    |pushDoubleConstant
    |pushFloatConstant
    |pushIntegerConstant
    |pushItemFromRuntimeConstantPool
    |pushLongConstant
    |pushNull
    |fetchFieldFromObject
    ;

// byte[] c = null;
// String s = null;
fragment newByteArrayWithNull: pushNull (checkCast)? storeReference;

fragment thisInstance:  LOADREFERENCE invokeMethod;

fragment ternaryOperator
    :   ifStatementCompare pushIntegerConstant gotoStatement pushIntegerConstant setStaticFieldInClass;

fragment floatMultiply
    :   constantExpr constantExpr dMul;

fragment iAdd: 'iadd';      
fragment dSub: 'dsub';
fragment fSub: 'fsub';
fragment lSub: 'lsub';
fragment lAdd: 'ladd';  
fragment dMul: 'dmul';

比如现在的语法(上面的进一步演进)可以转

getstatic 25
ifne 25
iload_1
iload_2
if_icmpgt 25
new 25
dup
invokespecial 44
athrow
return

进入

【问题讨论】:

  • 小建议。关于so​​的哪些问题与这个有关?
  • 这是一个有趣的问题。这会是一个开源项目吗?您可能想看看 jreversepro。
  • “碎片”业务怎么办?我不是 ANTLR 专家;但我希望规则看起来像“lhs : rhs1 | rhs2 | ... | rhsn ;”不是“片段 lhs : ...”
  • Ira,片段是子规则-antlr.org/wiki/display/ANTLR3/1.+Lexer你可以忽略“片段”关键字,假装片段XXX与XXX相同。
  • carlspring,谈论它是否开源还为时过早。

标签: java jvm antlr3 decompiling decompiler


【解决方案1】:

如果您只想识别单个 JVM 指令,那么语法可能没问题。您可能会花时间摆弄语法以获取正确的细节。这可能是简单的矫枉过正。字节操作码驱动的有限状态自动机 (FSA) 实现为巨型案例语句可能更容易;毕竟,JVM 指令应该易于解码,以便半快速解释器可以执行这些指令。

根据模糊的回忆,类文件中还有其他部分(表格,例如文字)。您可能也可以使用解析器识别它们,但也可能会矫枉过正。

你有第二个问题是你识别后收集指令/表格信息;解析器生成器倾向于帮助您构建某种 AST。这些指令不是 AST;它们至少是一个线性链,如果包含跳转目标,它们会形成一个引用表格的图表。因此,我怀疑您最终将难以获得语义操作以按照您想要的方式收集数据。

还有您可能想要捕获的图表。如果图具有某种层次结构(源自结构化编程语言),您可能希望发现该层次结构。解析器方法在这里没有任何作用。

【讨论】:

  • Ira,你能扩展 FSA 吗?你是说有限状态自动机吗?
  • @chuacw:是的,我就是这个意思。相应地修改了答案。
  • 解析器的方法其实是为了让我快速测试出字节码识别(使用文本),而不是编写自己识别字节码的高级代码。一旦语法正确,我会将其重写为高级代码。
  • @chuacw:我认为它的工作量大于它的价值。我怀疑你只需要一个大的 case 语句,而且很难有一个比这更简单的代码框架。如果您将跟踪信息放在 case 语句的顶部(“在偏移 N 处,我正在解码字节码 M”)和每个臂(“我已经解码了文字值为 17 的 PUSH LITERAL”),我认为您的调试过程会非常快。
  • 谢谢,艾拉。使用 case 语句方法遇到的问题是我不知道如何将每个字节码组织成可识别的模式。另一方面,通过语法方法,当我将字节码(例如,0x62、0x30)转换为它们的文本等价物(例如 fadd、faload)时,语法可以告诉我它识别了哪些模式。
【解决方案2】:

这种方法存在识别参数嵌套的问题。

例如,给定声明,

int func1(int x, int y, int z) {
    return 0;
}

int func0() {
    return 0;
}

和电话

Object[] x = new Object[func1(2, 3, 4)];
x = new Object[func0()];
x = new Object[func1(func1(func1(0, 1, 2), 3, 4), 5, 6)];

生成以下字节码:

Offset  Instruction       Comments (Method: none)
0       aload_0           (cheewee.helloworld.test000031_applet this)
1       iconst_2
2       iconst_3
3       iconst_4
4       invokevirtual 79  (cheewee.helloworld.test000031_applet.func1)
7       anewarray 81      (java.lang.Object)
10      astore_1          (java.lang.Object[] x)
11      aload_0           (cheewee.helloworld.test000031_applet this)
12      invokevirtual 83  (cheewee.helloworld.test000031_applet.func0)
15      anewarray 81      (java.lang.Object)
18      astore_1          (java.lang.Object[] x)
19      aload_0           (cheewee.helloworld.test000031_applet this)
20      aload_0           (cheewee.helloworld.test000031_applet this)
21      aload_0           (cheewee.helloworld.test000031_applet this)
22      iconst_0
23      iconst_1
24      iconst_2
25      invokevirtual 79  (cheewee.helloworld.test000031_applet.func1)
28      iconst_3
29      iconst_4
30      invokevirtual 79  (cheewee.helloworld.test000031_applet.func1)
33      iconst_5
34      bipush 6
36      invokevirtual 79  (cheewee.helloworld.test000031_applet.func1)
39      anewarray 81      (java.lang.Object)
42      astore_1          (java.lang.Object[] x)
43      return

无法检测到涉及嵌套。我不确定这是否是 ANTLR 的限制,或者这是否是我学习如何编写 ANTLR 语法的限制。

下一步将使用混合方法,首先将字节码组简化为标记(以便将它们识别为更简单的模式),然后将其传递给解析器以检测更高级别的模式。

【讨论】:

  • 您在问题中提出的语法似乎侧重于识别单个操作码,尽管有非终结符之类的“表达式”。如果您只想要单个操作码,我认为 ANTLR 完全是矫枉过正;坚持使用操作码 FSA。但是,如果您要识别“表达式”,那么您需要语法规则,即表达式可以是一系列表达式,后跟一个调用虚拟操作码。抽象地,我希望会选择参数,但您可能需要无限前瞻以确保解析器可以“猜测”每次调用的正确参数数量
  • Ira,我有的语法的更高版本,有表达式后跟一个invokexxx操作码。不幸的是,同样的问题发生了。可能会出现表达式嵌套。
  • 对。表达式的嵌套也出现在常规语言的语法中,并且它们可以被常规解析器识别。 如果 JVM代码总是以结构化块的形式生成,那么你大概可以通过语法识别出这些结构;正如我之前观察到的,如果它被组织为一个任意图(如汇编代码),那么上下文无关语法将很难。坚持前一种情况,你需要这样的产品: stmt = exp; stmt = exp jlt_opcode 目标; stmt = exp store_instruction ;以及 exp = push_literal 文字; exp = push_variable var_name
  • exp = exp exp add_opcode; exp = 表达式列表调用虚拟;表达式列表 = ;表达式列表 = 表达式列表表达式;最后一个应该处理嵌套表达式。你现在的语法是这样组织的吗?
  • Ira,我更新了语法以响应您上面的评论。调用语句。语法在 Offset 19 到 Offset 42 处的代码有问题,即 x = new Object[func1(func1(func1(0, 1, 2), 3, 4), 5, 6)]
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-17
  • 2022-06-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多