“看到一个沙粒中的世界”,我们很可能会看到最简单的“ Hello World”中的世界,所以我们开始吧,再一次向世界问好。
我猜所有的Java课程,教程都是从这个著名的Hello World程序开始的,这是我可以在没有IDE的帮助下编写的非常罕见的程序之一:)
public class HelloWorld {public static void main(String[] args) {System.out.println("Hello World");}
}
1.您知道这些javac选项吗?
编写第一个程序后,您将首先执行以下命令进行编译,否则将无法运行。
javac HelloWorld.java
您可能会发现不必将文件命名为“ HelloWorld.java”,“ Hello.java”也可以使用。 public class HelloWorld
也可以降级为class HelloWorld
。
如果您好奇地按下javac --help
,将会看到很多有关Java编译器的选项,例如,我们要打印中文版“ Hello World”,并希望它完全适用于JDK8语言级别,元数据为包含的参数名称,它看起来像这样:
javac -encoding UTF-8 -source 8 -target 8 -parameters Hello.java
您已经安装了JDK11,但是使用上面的命令仅使用1.8功能发布了类文件。 如果您编写了一些仅可从JDK9获得的内容,则会发现它无法按预期进行编译。
2.类文件的基础
关于Java虚拟机规范中的类文件格式的整章内容,您是否需要对其进行一些探讨?
您会看到字节码(与JDK11一起编译)以一个神奇的,神秘的“ cafe babe”开头,随后为55,很多东西会伤害您的大脑。 其中,“ cafe babe”是魔力,指向次要版本的55分,映射到JDK11。 与读取超赞的类文件格式相比,您还可以使用javap
检索该类文件的信息:
# You would use javap -h to see how many options you have
javap -p -l -c -s -constants HelloWorld
您将获得如下内容:
class HelloWorld {HelloWorld(); descriptor: ()V Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hello World 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: return LineNumberTable: line 4: 0 line 5: 8
}
您会发现这里的指令与源代码有些相似,带有源代码的行号和指令号的映射,您可能想知道,我可以从这些东西中恢复源代码吗?
3.反编译器
是的你可以。 反编译器有很多,但是其中一些反编译器已经过时,例如JD-GUI ,JAD等,它们在使用最新JDK编译的类文件上不能很好地工作。 您仍然可以使用它们,但CFR更合适。
# java -jar cfr-0.139.jar HelloWorld.class
/* * Decompiled with CFR 0.139.*/
import java.io.PrintStream; class HelloWorld { HelloWorld() { } public static void main(String[] arrstring) {System.out.println("Hello World"); }
}
您可能已经发现源代码和反编译的代码(添加了构造方法)略有不同,实际上,您可能会惊讶地发现有时似乎对源代码进行了修改,从而使您感到惊讶。 但是,其中许多是通过JVM进行的优化,通常可以提高性能,比较它们之间的差异实际上很有趣,并且可以为您提供很多见识。
4.如何再次初始化具有空值的最终变量?
System.out.println("Hello World")
,System是一个类,out是其最终属性的静态属性之一:
public final static PrintStream out = null;
然后问题来了,为什么hack System.out.println("Hello World")
不会抛出著名的NullPointerException
,根据语言规范,似乎最终的静态变量out不可能分配给有效值再次吧?
是的,在大多数情况下,如果您不使用肮脏的反射技巧并且不引入native
好友,那是对的。
如果您只是想玩转,可以这样做:
Field f = clazz.getDeclaredField("out");
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);
但是,这对于System
无效,实际的秘密隐藏在System.java
以下代码行中:
private static native void registerNatives();
static {registerNatives();
}
按照方法上方写的注释,“ VM将调用initializeSystemClass方法来完成此类的初始化”,转到initializeSystemClass
方法,您将看到以下行:
FileInputStream fdIn = new FileInputStream(FileDescriptor.in);
FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err);
setIn0(new BufferedInputStream(fdIn));
setOut0(newPrintStream(fdOut, props.getProperty("sun.stdout.encoding")));
setErr0(newPrintStream(fdErr, props.getProperty("sun.stderr.encoding")));
而且你还可以看到这3种本地方法设置in
与out
:
private static native void setIn0(InputStream in);
private static native void setOut0(PrintStream out);
private static native void setErr0(PrintStream err);
因此,现在您知道JVM在OS级别上做了这些工作并“绕过”了final
限制,您可能会问,JVM将适应的OS级别代码在哪里被破解?
所以这里是System.c
(JDK11版本) 。
JNIEXPORT void JNICALL
Java_java_lang_System_registerNatives(JNIEnv *env, jclass cls)
{(*env)->RegisterNatives(env, cls,methods, sizeof(methods)/sizeof(methods[0]));
}
/** The following three functions implement setter methods for* java.lang.System.{in, out, err}. They are natively implemented* because they violate the semantics of the language (i.e. set final* variable).*/
JNIEXPORT void JNICALL
Java_java_lang_System_setIn0(JNIEnv *env, jclass cla, jobject stream)
{jfieldID fid =(*env)->GetStaticFieldID(env,cla,"in","Ljava/io/InputStream;");if (fid == 0)return;(*env)->SetStaticObjectField(env,cla,fid,stream);
}
在这里,您可以在注释中找到后门, “它们是本机实现的,因为它们违反了语言的语义(即,设置最终变量)” 。
然后,您会发现这是一条漫长的道路。 旅程将永远不会停止。
结束:停一会儿
“用沙粒看世界
还有野花中的天堂
将Infinity握在手中 一小时的永恒”
如果最简单的HelloWorld
只是一片沙粒,那么里面肯定有一个世界,也许您对它说过很多次“ Hello”,但这并不意味着您已经探索了一点世界,也许现在时间和探索世界,虽然沙子会使您的手变脏,但花朵却不会。
翻译自: https://www.javacodegeeks.com/2019/02/world-grain-sand-world.html