对于跟踪 Java 应用程序的文件系统访问,本机跟踪工具始终是首选。在 Windows 上,使用 Process Monitor 跟踪 I/O。在 Linux 上,使用 strace。其他平台也提供类似的设施。
通过直接在 Java 中跟踪文件系统访问,您可以绕过环境限制。例如,strace 在缺少CAP_SYS_PTRACE 能力的容器中不可用,并且容器主机并不总是可访问的。
要走 Java 路线,您可以通过扩展 java.lang.SecurityManager 来实现自己的安全管理器。此类提供checkRead、checkWrite 和checkDelete 方法,只要代码尝试相应的访问,就会调用这些方法。
一个示例实现:
public class TraceSecurityManager extends SecurityManager {
public void checkRead(String file) {
System.out.println("Read: " + file);
}
public void checkRead(String file, Object context) {
System.out.println("Read: " + file);
}
public void checkWrite(String file) {
System.out.println("Write: " + file);
}
public void checkDelete(String file) {
System.out.println("Delete: " + file);
}
}
为了测试示例,我们使用 Java 编译器作为测试对象。要启用跟踪安全管理器,我们设置适当的系统属性并使用有效的 Java 源文件 Test.java 执行命令:
$ java -Djava.security.manager=TraceSecurityManager com.sun.tools.javac.Main Test.java
Read: /home/user/com/sun/tools/javac/resources/spi/compilerProvider.class
Read: /home/user/com/sun/tools/javac/resources/compiler_en.properties
Read: /home/user/com/sun/tools/javac/resources/compiler_en_US.properties
Exception in thread "main" java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "getenv.JDK_JAVAC_OPTIONS")
at java.base/java.security.AccessControlContext.checkPermission(AccessControlContext.java:472)
at java.base/java.security.AccessController.checkPermission(AccessController.java:897)
at java.base/java.lang.SecurityManager.checkPermission(SecurityManager.java:322)
at java.base/java.lang.System.getenv(System.java:999)
at jdk.compiler/com.sun.tools.javac.main.CommandLine.appendParsedEnvVariables(CommandLine.java:252)
at jdk.compiler/com.sun.tools.javac.main.CommandLine.parse(CommandLine.java:99)
at jdk.compiler/com.sun.tools.javac.main.CommandLine.parse(CommandLine.java:123)
at jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:215)
at jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:170)
at jdk.compiler/com.sun.tools.javac.Main.compile(Main.java:57)
at jdk.compiler/com.sun.tools.javac.Main.main(Main.java:43)
跟踪实现有效。我们甚至可以看到类加载尝试。但是,javac 失败,因为缺少权限。原因是,通过系统属性安装安全管理器时,默认的 Java 安全策略是活动的,并且不授予所需的权限。要解决此问题,您可以提供最小的自定义策略,也可以使用空实现覆盖 checkPermission 方法。在这种情况下,我选择了最小策略:
grant {
permission java.security.AllPermission "", "";
};
政策到位后,我们可以重新测试:
$ java -Djava.security.policy=test.policy -Djava.security.manager=TraceSecurityManager com.sun.tools.javac.Main Test.java
Read: /home/user/com/sun/tools/javac/resources/spi/compilerProvider.class
Read: /home/user/com/sun/tools/javac/resources/compiler_en.properties
Read: /home/user/com/sun/tools/javac/resources/compiler_en_US.properties
Read: Test.java
Read: Test.java
Read: /usr/lib/jvm/java-11-openjdk-11.0.10.0.9-1.el7_9.x86_64/lib/modules
Read: /usr/lib/jvm/java-11-openjdk-11.0.10.0.9-1.el7_9.x86_64/lib/modules
Read: /usr/lib/jvm/java-11-openjdk-11.0.10.0.9-1.el7_9.x86_64/lib/jfxrt.jar
Read: /home/user/META-INF/services/java.nio.file.spi.FileSystemProvider
Read: /usr/lib/jvm/java-11-openjdk-11.0.10.0.9-1.el7_9.x86_64/lib/modules
Read: /usr/lib/jvm/java-11-openjdk-11.0.10.0.9-1.el7_9.x86_64/lib/modules
Read: Test.java
Read: /home/user/Test.java
Read: ./.bash_logout
Read: /home/user/.bash_logout
Read: ./.bash_profile
Read: /home/user/.bash_profile
Read: ./.bashrc
Read: /home/user/.bashrc
[...]
Read: /home/user/com/sun/tools/javac/resources/spi/ctProvider.class
Read: /home/user/com/sun/tools/javac/resources/ct_en.properties
Read: /home/user/com/sun/tools/javac/resources/ct_en_US.properties
Read: /home/user/TraceSecurityManager.class
Read: /home/user/Test.class
Read: /home/user
Write: /home/user/Test.class
Read: Test.java
这一次,我们得到了 javac 的完整文件系统访问跟踪。安全管理器也可以在运行时启用,如果您由于某种原因无法控制 Java 命令行,这将非常有用:
System.setSecurityManager(new TraceSecurityManager());
在这种特殊情况下,不需要自定义策略,因为默认策略未激活。
使用安全管理器来跟踪文件系统访问肯定不是最佳选择,因为缺少可能与您的调试方案相关的细节,但如果您没有其他选择并且需要完成工作,这是一个很好的折衷方案。