【问题标题】:java.lang.System.currentTimeMillis() replace methodjava.lang.System.currentTimeMillis() 替换方法
【发布时间】:2013-08-14 18:48:12
【问题描述】:

除了重新编译 rt.jar 之外,还有什么方法可以用我自己的调用替换 currentTimeMillis() 调用?

1#正确的做法是使用Clock对象和抽象时间。

我知道,但我们将运行由无数尚未实现 Clock 或自己实现的开发人员开发的代码。


2# 使用 JMockit 之类的模拟工具来模拟该类。

尽管这仅适用于禁用 Hotspot -Xint 并且我们使用下面的代码取得了成功,但它不会“持续”在外部库上。这意味着您必须在任何地方模拟它,因为代码超出了我们的控制范围,这是不可行的。 main() 下的所有代码都返回 0 milis(如示例所示),但 new DateTime() 将返回实际的系统 milis。

    @MockClass(realClass = System.class)
    public class SystemMock extends MockUp<System> { 
        // returns 1970-01-01   
        @Mock public static long currentTimeMillis() { return 0; }
    }

3# 在启动时使用-Xbootclasspath/p重新声明System(已编辑)

虽然可能,并且您可以创建/更改方法,但有问题的方法被声明为public static native long currentTimeMillis();。如果不深入研究 Sun 的专有和 本机 代码,就无法更改它的声明,这将使其成为逆向工程的练习,并且几乎不是一种稳定的方法。 所有最近的 SUN JVM 崩溃并出现以下错误:

    EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00000, pid=4668, tid=5736  

4# 使用自定义 ClassLoader(在 cmets 上建议的新测试)

虽然使用-Djava.system.class.loader JVM 替换系统 CL 很简单,但实际上使用默认的 classLoader 加载了自定义的 classLoader,并且系统甚至没有通过自定义的 CL 推送。

    public class SimpleClassLoader extends ClassLoader {
        public SimpleClassLoader(ClassLoader classLoader) {
            super(classLoader);
        }

        @Override 
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            return super.loadClass(name);
        }   
    }

我们可以看到java.lang.System是从rt.jar使用java -verbose:class加载的

Line 15: [Loaded java.lang.System from C:\jdk1.7.0_25\jre\lib\rt.jar]

我的选择已经不多了。
我缺少什么方法吗?

【问题讨论】:

  • AspectJ 可能是一个选项。
  • 标志不足,但可能与 stackoverflow.com/questions/2001671/… 重复。
  • 好吧,我想你总是可以使用 CGLIB 并使用他们的方法拦截器返回你自己的值。
  • 好吧,使用-Djava.system.class.loader选项替换系统类加载器?
  • 同样对于ClassLoader方式,可以在JVM命令行中指定自定义系统类加载器; 应该影响该 JVM 加载的所有库:java -Djava.system.class.loader=your.package.CustomClassLoader ...

标签: java time


【解决方案1】:

您可以使用 AspectJ 编译器/编织器来编译/编织有问题的用户代码,用您自己的代码替换对 java.lang.System.currentTimeMillis() 的调用。以下方面将做到这一点:

public aspect CurrentTimeInMillisMethodCallChanger {

    long around(): 
       call(public static native long java.lang.System.currentTimeMillis()) 
       && within(user.code.base.pckg.*) {
         return 0; //provide your own implementation returning a long
    }
}

【讨论】:

  • 虽然这看起来很有希望,但在用户从外部库捕获日期的情况下仍然失败。说乔达被使用了。 new DateTime() 会返回正确的系统时间,对吗?我之所以问,是因为我以前从未使用过 AspectJ,虽然我能够运行和编译您提供的代码(顺便说一句,谢谢),但我无法让 Joda 将 milis 报告为 0。 Joda 不是由编译/编织的AspectJ 并因此反馈系统时间。我的假设是否正确?
  • 你也可以编织 Joda 库。 org.joda.time.DateTimeUtils.SystemMillisProvider 正在使用 System.currentTimeMillis(),所以如果它被替换(通过类路径顺序、类加载或编织),它会做任何你想做的事情。 Joda 中还有 org.joda.time.DateTimeUtils.MillisProvider 的其他子类,所以我想它可以配置为使用另一个提供程序,然后您甚至不必更改 SystemMillisProvider。如果涉及其他库,也可以通过编织来更改它们,尽管您最终会得到这些库的多个特殊编织版本。
  • 那里的每个库,甚至是Date(),都回退到System.currentTimeMilis(),所以如果我们可以更改它,它们应该都能按预期工作。为了运行您的示例,我阅读了文档并使用ajc 编译了代码。从这个回复我推断你可以在运行时“编织”。我将阅读文档以尝试去做,如果您可以扩展答案或在正确的轨道上给我一些观点,我将不胜感激。谢谢!
  • 您可以使用加载时间编织,但设置起来可能有点困难。尽管我从未尝试过,但我强烈建议您不要尝试在加载时编织 JDK 类,因为 LTW 代理本身是由 JDK 在引导时加载的,因此您最终会编织一些 JDK 类,而另一些则没有。我看到这条路线有更多潜在的问题。我选择的路线是在编译/构建时编织,并在用户代码和 JDK 代码之间的边界处编织所有代码点,以便最终调用 System.currentTimeMillis() 的所有调用都被您的自定义代码替换
  • 我都尝试过,但选择了 LTW(加载时间编织),因为它为各种可能的用户加载类(包括那些使用自定义类加载器加载的类)提供了出色的适应性。重新定义currentTimeMillisDateCalendar。当 LTW 编织他们的电话时,Joda 和所有类似的工作立即开箱即用。还能够捕获 waitsleep 必须重新定义才能使事情完全正常运行(因为计时器依赖等待),但是到目前为止,这条路线已经产生了很好的结果。非常感谢您的帮助并提供相关且非常有用的代码。
【解决方案2】:

我不能 100% 确定我是否在这里监督某些事情,但您可以像这样创建自己的 System 类:

public static class System {
    static PrintStream err = System.err;
    static InputStream in = System.in;
    static PrintStream out = System.out;

    static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) {
        System.arraycopy(src, srcPos, dest, destPos, length);
    }

    // ... and so on with all methods (currently 26) except `currentTimeMillis()`

    static long currentTimeMillis() {
        return 4711L; // Your application specific clock value
    }
}

而不是在每个 java 文件中导入您自己的 System 类。在 Eclipse 中重新组织导入应该可以解决问题。 而且所有的 java 文件都应该使用你的应用程序特定的System 类。

正如我所说,这不是一个好的解决方案,因为每当 Java 更改原始类时,您都需要维护您的 System 类。此外,您必须确保始终使用您的课程。

【讨论】:

  • 感谢您的输入,但在 #1 上失败了。我们无法替换代码,因为它是由无数不同的开发人员上传的。而且,如果可以的话,我们应该使用日期的抽象,比如Clock,并让它根据我们的需要返回一个真或假的时间,这比重新定义 System.我仍在慢慢研究其他反馈,并将报告任何发现。
【解决方案3】:

正如 cmets 中所讨论的,原问题中的选项 #3 可能确实有效,成功替换了默认的 System 类。

如果这是真的,那么调用currentTimeMillis() 的应用程序代码将调用替换,正如预期的那样。

也许出乎意料的是,像java.util.Timer 这样的核心类也会被替换!

如果以上所有都成立,那么崩溃的根本原因可能是成功替换了System 类。

要进行测试,您可以改为将 System 替换为功能与原始副本相同的副本,以查看崩溃是否消失。

不幸的是,如果这个答案被证明是正确的,我们似乎有一个新问题。 :) 可能是这样的:

“如何为应用程序类提供更改后的System.currentTimeMillis(),但为核心类保留默认实现?”

【讨论】:

  • 您认为我已经能够替换 System 但出现错误是正确的。如果我复制 Sun 的 System.java 代码,我可以弄乱它,创建自己的方法,更改已实现的方法,但不幸的是,currentTimeMillis() 被声明为 public static native long,我不能在 VM 崩溃时弄乱声明。我已经对其进行了一些研究,但最终我必须更改本机专有代码,虽然这可能是可行的,但它却是一个很长的目标。将尝试其他方法并继续报告。还将相应地编辑问题。谢谢。
【解决方案4】:

我尝试使用 javassist 删除本机 currentTimeMills,添加一个纯 java 并使用 bootclasspath/p 加载它,但我遇到了与您一样的异常访问冲突。我相信这可能是因为在静态块中调用了本机方法 registerNatives 但是反汇编本机库确实太多了。

那么,与其更改 System.currentTimeMills,不如更改用户代码?如果用户代码已经编译(你没有源代码),我们可以使用 findbugs 之类的工具来识别 currentTimeMillis 的使用并拒绝代码(也许我们甚至可以用你自己的实现替换对 currentTimeMills 的调用)。

【讨论】:

  • 如 #1 所述,我们无法更改用户代码。虽然我可以理解你的逻辑 - 反编译整个事情 - 将所有调用更改为 currentTimeMilis() - 再次编译;它看起来很乱。尽管我仍在调查中,但 ASM 和 BCEL 可能是更好的选择。非常感谢您的意见。
  • FWIW 我设法在 Java 代理 premain 中使用 Javassist 做到这一点,使用 instrumentation.retransformClasses(java.lang.System.class); 比我预期的要少得多。
猜你喜欢
  • 2011-03-15
  • 2021-11-08
  • 2020-11-11
  • 2012-02-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多