看到一个沙粒世界:再一次你好世界

“看到一个沙粒中的世界”,我们很可能会看到最简单的“ 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;)V
       8: 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种本地方法设置inout

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值