我正在使用ProGuard混淆我的.jar程序。一切正常,除了ProGuard不会混淆方法主体中的局部变量。这是一个例子:
原始:
混淆:
以黄色突出显示的变量名称应该被混淆,但不是。我如何也对它们进行混淆(将它们重命名为a,b,c等?)
这是我的ProGuard配置:http://pastebin.com/sb3DMRcC(以上方法不是来自排除的类之一)。
最佳答案
Why proguard does not obfuscate method body?
因为它不能。
方法参数和局部变量的名称在编译时根本不会存储。
您看到的名称是由反编译器生成的。
对于已编译的代码,有两种方法可以在本地(即在方法内)存储数据:
操作数堆栈实际上只是一个堆栈。
有关堆栈运算符,请参见Java VM规范中的Table 7.2。
您可以弹出值(
pop
),复制最上面的值(dup
),交换最上面的两个值(swap
),并且行为略有不同(行为pop2
,dup_x1
,dup_x2
,dup2
,dup2_x1
,dup2_x2
)。而且,如果不是所有产生返回值的指令,大多数都会将其返回到堆栈中。
对于此问题,重要的是如何引用堆栈中的内容,就像其他任何堆栈一样:
相对于最高位置,并基于所使用的说明。
没有分配的编号或名称,仅是当前存在的编号或名称。
现在,对于所谓的“局部变量”:
将它们更多地看作是
ArrayList
,而不是Java中的变量。因为这正是您访问它们的方式:按索引。
对于变量0到3,有特殊的指令(即单字节),因为它们经常使用,所有其他变量只能通过两字节指令访问,其中第二个字节是索引。
再次参见Table 7.2,“加载”和“存储”。
两个表中的前五个条目是每种数据类型的宽(两字节)存储/加载指令(请注意,对于单个值,
boolean
,char
,byte
和short
都转换为int
,仅保留int
,float
和Object
是单槽值,long
和double
是双槽值),接下来的20条指令是用于直接访问寄存器0到3的指令,最后八条指令是用于访问数组索引的(请注意,内部数组boolean
,byte
,char
和short
都是而不是转换为int
,以不浪费空间,这就是为什么还要多三个指令(而不是四个指令,因为byte
和char
具有相同的大小))。最大堆栈大小和局部变量数量都受到限制,并且必须在每种方法的
Code
属性的 header 中给出,如Section 4.7.3(max_stack
和max_locals
)中所定义。但是,有关局部变量的有趣之处在于它们兼用作方法参数,这意味着局部变量的数量永远不能少于方法参数的数量。
请注意,在为Java VM计数值时,类型
long
和double
的变量被视为两个值,因此需要两个“槽”。还要注意,对于非静态方法,参数0将是
this
,它本身需要另一个“槽”。话虽如此,让我们看一些代码!
例子:
class Test
{
public static void main(String[] myArgs) throws NumberFormatException
{
String myString = "42";
int myInt = Integer.parseInt(myString);
double myDouble = (double)myInt * 42.0d;
System.out.println(myDouble);
}
}
在这里,我们有三个局部变量
myString
,myInt
和myDouble
,还有一个参数myArgs
。另外,我们有两个常量
"42"
和42.0d
,还有很多外部引用:java.lang.String[]
-类java.lang.NumberFormatException
-类java.lang.String
-类java.lang.Integer.parseInt
-方法java.lang.System.out
-字段java.io.PrintStream.println
-方法以及一些导出:
Test
和main
,以及编译器将为我们生成的默认构造函数。所有常量,引用和导出都将导出到Constant Pool-本地变量和参数名称将不会导出。
编译和反汇编该类(使用
javap -c Test
)将产生:Compiled from "Test.java"
class Test {
Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.NumberFormatException;
Code:
0: ldc #2 // String 42
2: astore_1
3: aload_1
4: invokestatic #3 // Method java/lang/Integer.parseInt:(Ljava/lang/String;)I
7: istore_2
8: iload_2
9: i2d
10: ldc2_w #4 // double 42.0d
13: dmul
14: dstore_3
15: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
18: dload_3
19: invokevirtual #7 // Method java/io/PrintStream.println:(D)V
22: return
}
除了默认的构造函数,我们可以逐步看到
main
方法。注意如何使用
myString
和astore_1
访问aload_1
,如何使用myInt
和istore_2
访问iload_2
以及使用myDouble
和dstore_3
访问dload_3
。myArgs
在任何地方都无法访问,因此也没有字节码可以处理它,但是在方法开始时,对String数组的引用将在局部变量1中,该引用很快就会被对"42"
的引用所覆盖。如果您向其传递
javap
标志,则-v
还将向您显示常量池,但是它实际上并没有为输出添加任何值,因为常量池中的所有相关信息始终会显示在注释中。但是现在,让我们看一下反编译器产生的结果!
JD-GUI 0.3.5(JD-Core 0.6.2):
import java.io.PrintStream;
class Test
{
public static void main(String[] paramArrayOfString)
throws NumberFormatException
{
String str = "42";
int i = Integer.parseInt(str);
double d = i * 42.0D;
System.out.println(d);
}
}
Procyon 0.5.28:
class Test
{
public static void main(final String[] array) throws NumberFormatException {
System.out.println(Integer.parseInt("42") * 42.0);
}
}
请注意,导出到常量池的所有内容如何保持不变,而JD-GUI只是为局部变量选择了一些名称,而Procyon则对其进行了完全优化。
但是,自变量的名称
paramArrayOfString
vs array
(与原始myArgs
相比)是一个完美的例子,它表明不再有“正确的”名称,并且反编译器只需要依靠某种选择名称的方式即可。我不知道反编译代码中的“真实”名称是从哪里来的,但是我很确定它们不包含在jar文件中。
您的IDE的功能可能是?
关于java - 为什么proguard不混淆方法主体?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31508230/