GraalVM - 可配置的类初始化

配置

上一节整完了,虽然这个破东西编译屎慢,对吧?嗯...emmmm,他就是编译这么慢,但是他花活多啊!

package org.example;

public class Main {
    static class Father {
        static {
            System.out.println("Your father is here!");
        }

        public static void greet() {
            System.out.println("Hello, Father!");
        }
    }

    public static void main(String[] args) {
        Father.greet();
    }
}

这段代码将会在运行的时候打印出两行字符串,但是呢GraalVM有个花活,可以在编译的时候就把System.out.println("Your father is here!"); 给打印出来,

    <build>
        <plugins>
            <plugin>
                <groupId>org.graalvm.buildtools</groupId>
                <artifactId>native-maven-plugin</artifactId>
                <version>${native.maven.plugin.version}</version>
                <extensions>true</extensions>
                <executions>
                    <execution>
                        <id>build-native</id>
                        <goals>
                            <goal>compile-no-fork</goal>
                        </goals>
                        <phase>package</phase>
                    </execution>
                </executions>
                <configuration>
                    <verbose>true</verbose>
                    <buildArgs>
                        <arg>--initialize-at-build-time=org.example</arg>
                        <arg>-H:+ReportExceptionStackTraces</arg>
                    </buildArgs>
                </configuration>
            </plugin>
        </plugins>
    </build>

这里的<arg>--initialize-at-build-time=org.example</arg>就是关键点,其实这句话的意思是在编译期初始化org.example下的所有类,这个时候就会在编译的时候得到以下输出

  • 初始化某个包下所有类的时候:<arg>--initialize-at-build-time=org.example</arg>

  • 初始化指定类:<arg>--initialize-at-build-time=org.example.Main\$Father</arg>

  • 初始化多个规则:<arg>--initialize-at-build-time=org.example,kotlin,io,ktor</arg>

投毒

public class Main {
    static class Father {
        static {
            var f = new File("test");
            if (!f.exists()) {
                try {
                    f.createNewFile();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("Success");
            } else {
                System.out.println("exists");
            }
            System.out.println("MMMMMM");
        }

        public static void greet() {
            System.out.println("Hello, Father?");
        }
    }

    public static void main(String[] args) {
        Father.greet();
    }
}

上面代码正常运行!那么这个玩意挺离谱的,整点奇怪的东西,可以实现远程投毒。

知识点

镜像堆(image heap)在镜像构建期间初始化的类的静态字段存储在镜像堆中

class Example {
    private static final String message;
    
    static {
        message = System.getProperty("message");
    }

    public static void main(String[] args) {
        System.out.println("Hello, World! My message is: " + message);
    }
}

现在我们在 JVM 上编译并运行该应用程序:

javac Example.java
java -Dmessage=hi Example
Hello, World! My message is: hi
java -Dmessage=hello Example 
Hello, World! My message is: hello
java Example
Hello, World! My message is: null

Example现在检查当我们构建一个在构建时初始化类的本机映像时会发生什么:

native-image Example --initialize-at-build-time=Example -Dmessage=native
================================================================================
GraalVM Native Image: Generating 'example' (executable)...
================================================================================
...
Finished generating 'example' in 19.0s.
./example 
Hello, World! My message is: native
./example -Dmessage=aNewMessage
Hello, World! My message is: native

该类的类初始化程序Example在图像构建时执行。这会String为该message字段创建一个对象并将其存储在镜像堆中。