在 Java 中伪造堆栈跟踪

发布于 2024-11-14 11:30:11 字数 782 浏览 0 评论 0原文

当您在 Java 中使用 RMI 时,异常的远程堆栈跟踪将在您收到异常时预先添加,有点像这样:

ERROR Client received error when doing stuff:
myapp.FooBarException: bla
 at server.myMethod()
 at rmi.callHandler() // and now, on the next line comes the client
 at rmi.sendCall();
 at client.doServerMethod()
 at Thread.run()

这种堆栈跟踪“伪造”是如何完成的?


我想要它做什么(除了被迭代之外)?好吧,如果我能做到这一点,它将对我有所帮助:

outer() {

  thread = new Thread(...
      inner();
      // inner() throws
      // RuntimeException
      //  at inner();
      //  at Runnable.run();
      //  at Thread.run();
      //  at outer();
      //  at lalalala();
      //  ...

  ).start();

  thread.join();

}

并使其在 inner() 中抛出的异常将具有 outer() (以及链下游的方法) 也在堆栈跟踪中,用于记录目的。

When you use RMI in Java the remote stack trace of an exception will be prepended when you receive it, somewhat like this:

ERROR Client received error when doing stuff:
myapp.FooBarException: bla
 at server.myMethod()
 at rmi.callHandler() // and now, on the next line comes the client
 at rmi.sendCall();
 at client.doServerMethod()
 at Thread.run()

How is that kind of stacktrace "forgery" done?


What do I want it for (apart from just being iterested)? Well, it would help me if I could do this:

outer() {

  thread = new Thread(...
      inner();
      // inner() throws
      // RuntimeException
      //  at inner();
      //  at Runnable.run();
      //  at Thread.run();
      //  at outer();
      //  at lalalala();
      //  ...

  ).start();

  thread.join();

}

And make it so that an exception thrown in inner() would have outer() (and methods lower down the chain) in the stacktrace as well, for logging purposes.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

清风不识月 2024-11-21 11:30:11

这很简单:

Throwable 有方法 getStackTrace()setStackTrace()

来自我的一个项目(非开源,但也许有一天我会打开远程调用引擎):

    /**
     * Setzt den Stack-Trace zusammen. Das untere Ende (tiefer in der
     * Aufrufhierarchie, am Anfang des Arrays/der Ausgabe) ist das,
     * welches im Throwable schon drin ist, das obere Ende wird aus
     * dem aktuellen Stack genommen. Dazwischen
     * kommt ein "Remote-Aufruf-Markierer".
     */

为方便起见,翻译为:

合并堆栈跟踪。低端(在调用层次结构的更深处,在
数组的末尾/输出)是堆栈中已有的内容,即上端
将从当前堆栈中获取。在它们之间我们将放置一个
远程调用标记

    private void mergeStackTraces(Throwable error)
    {
        StackTraceElement[] currentStack =
            new Throwable().getStackTrace();
        int currentStackLimit = 5; // TODO: raussuchen
        StackTraceElement[] oldStack =
            error.getStackTrace();
        StackTraceElement[] zusammen =
            new StackTraceElement[currentStack.length - currentStackLimit +
                                  oldStack.length + 1];
        System.arraycopy(oldStack, 0, zusammen, 0, oldStack.length);
        zusammen[oldStack.length] =
            new StackTraceElement("══════════════════════════",
                                  "<remote call %" +callID+ ">",
                                  "", -3);
        System.arraycopy(currentStack, currentStackLimit,
                         zusammen, oldStack.length+1,
                         currentStack.length - currentStackLimit);
        error.setStackTrace(zusammen);
    }

(在服务器端,我已经切断了与方法调用本身无关的堆栈跟踪部分,即与消息处理相关的所有内容。)

这会产生如下组合堆栈跟踪:

java.lang.SecurityException: Das Passwort für Nutzer »Paul« ist falsch.
        at de.fencing_game.db.userdb.Db4oUserDB.login(Db4oUserDB.java:304)
        at de.fencing_game.server.impl.StandardServers$SSServer$1.run(StandardServers.java:316)
        at de.fencing_game.server.impl.StandardServers$SSServer$1.run(StandardServers.java:314)
        at java.security.AccessController.doPrivileged(Native Method)
        at de.fencing_game.server.impl.StandardServers$SSServer.login(StandardServers.java:313)
        at de.fencing_game.transport.server.ServerTransport$ConnectionInfo$4.login(ServerTransport.java:460)
        at ══════════════════════════.<remote call %2>()
        at $Proxy1.login(Unknown Source)
        at de.fencing_game.gui.basics.LoginUtils.login(LoginUtils.java:80)
        at de.fencing_game.gui.Lobby.connectTo(Lobby.java:302)
        at de.fencing_game.gui.Lobby$20.run(Lobby.java:849)
        at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:226)
        at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:647)
        at java.awt.EventQueue.access$000(EventQueue.java:96)
        at java.awt.EventQueue$1.run(EventQueue.java:608)
        at java.awt.EventQueue$1.run(EventQueue.java:606)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:105)
        at java.awt.EventQueue.dispatchEvent(EventQueue.java:617)
        at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:275)
        at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:200)
        at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:190)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:185)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:177)
        at java.awt.EventDispatchThread.run(EventDispatchThread.java:138)

我想RMI 系统做的事情非常相似(只是没有<代码>══════════════════════════)。


编辑:
对于您的用例,您必须在启动内部线程时保存外部线程的堆栈跟踪,然后在 run 方法中捕获异常并将外部堆栈跟踪附加到内部异常的堆栈跟踪。不过,我真的建议放置某种类型的分隔符。

It is kind of easy:

Throwable has methods getStackTrace() and setStackTrace().

From one of my projects (non open-source, but maybe I'll some day open the remote call engine):

    /**
     * Setzt den Stack-Trace zusammen. Das untere Ende (tiefer in der
     * Aufrufhierarchie, am Anfang des Arrays/der Ausgabe) ist das,
     * welches im Throwable schon drin ist, das obere Ende wird aus
     * dem aktuellen Stack genommen. Dazwischen
     * kommt ein "Remote-Aufruf-Markierer".
     */

Translated for your convenience:

Merges the stack trace. The lower end (deeper in the call hierarchy, at the
end of the array/the output) is what already is in the stack, the upper end
will be taken from the current stack. Between them we will put an
Remote call marker.

    private void mergeStackTraces(Throwable error)
    {
        StackTraceElement[] currentStack =
            new Throwable().getStackTrace();
        int currentStackLimit = 5; // TODO: raussuchen
        StackTraceElement[] oldStack =
            error.getStackTrace();
        StackTraceElement[] zusammen =
            new StackTraceElement[currentStack.length - currentStackLimit +
                                  oldStack.length + 1];
        System.arraycopy(oldStack, 0, zusammen, 0, oldStack.length);
        zusammen[oldStack.length] =
            new StackTraceElement("══════════════════════════",
                                  "<remote call %" +callID+ ">",
                                  "", -3);
        System.arraycopy(currentStack, currentStackLimit,
                         zusammen, oldStack.length+1,
                         currentStack.length - currentStackLimit);
        error.setStackTrace(zusammen);
    }

(On the server side, I'm already cutting off the parts of the stack trace which do not relate to the method call itself, i.e. everything related to the message handling.)

This results in a combined stack trace like this:

java.lang.SecurityException: Das Passwort für Nutzer »Paul« ist falsch.
        at de.fencing_game.db.userdb.Db4oUserDB.login(Db4oUserDB.java:304)
        at de.fencing_game.server.impl.StandardServers$SSServer$1.run(StandardServers.java:316)
        at de.fencing_game.server.impl.StandardServers$SSServer$1.run(StandardServers.java:314)
        at java.security.AccessController.doPrivileged(Native Method)
        at de.fencing_game.server.impl.StandardServers$SSServer.login(StandardServers.java:313)
        at de.fencing_game.transport.server.ServerTransport$ConnectionInfo$4.login(ServerTransport.java:460)
        at ══════════════════════════.<remote call %2>()
        at $Proxy1.login(Unknown Source)
        at de.fencing_game.gui.basics.LoginUtils.login(LoginUtils.java:80)
        at de.fencing_game.gui.Lobby.connectTo(Lobby.java:302)
        at de.fencing_game.gui.Lobby$20.run(Lobby.java:849)
        at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:226)
        at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:647)
        at java.awt.EventQueue.access$000(EventQueue.java:96)
        at java.awt.EventQueue$1.run(EventQueue.java:608)
        at java.awt.EventQueue$1.run(EventQueue.java:606)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:105)
        at java.awt.EventQueue.dispatchEvent(EventQueue.java:617)
        at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:275)
        at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:200)
        at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:190)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:185)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:177)
        at java.awt.EventDispatchThread.run(EventDispatchThread.java:138)

I suppose the RMI system does something quite similar (just without the ══════════════════════════).


Edit:
For your usecase, you would have to save the stack trace of the outer thread when the inner thread is started, then in the run method catch the exception and append the outer stack trace to the stack trace of the inner exception. I would really recommend putting some type of separator, though.

情感失落者 2024-11-21 11:30:11

您创建一个自定义异常,从一个异常中提取堆栈跟踪,并通过 setStackTrace() 将其添加到另一个异常中。

当您不想维护对异常引起的硬引用时,它对于执行此类操作或维护堆栈跟踪非常有用。当将异常信息从服务器传递到客户端时,这很方便,其中根本原因异常类可能不存在,从而导致序列化问题。

You create a customized exception that extracts the stacktrace from one exception and adds it to another via setStackTrace().

It's useful for doing things like this or maintaining a stacktrace when you don't want to maintain a hard reference to the caused by exception. This is handy when passing exception info from server to client where the root cause exception classes may not be present, thus causing serialization issues.

自由如风 2024-11-21 11:30:11

我想建议一个替代解决方案,这不是OP所要求的,但对于一些有非常相似问题的人来说可能更好。像我这样的。

我建议在外部创建一个可抛出的对象,并添加内部可抛出的对象作为外部可抛出对象的原因。这还将捕获进行线程切换的点...这可能有助于避免堆栈跟踪的混乱。此外,线程信息可以存储在外部创建的 Throwable 中,这可能会有更多帮助。

这是一些代码。

public class StackCaptor {
    public static Runnable capture(Runnable runnable) {
        // Capture the stack
        final Throwable prison = new Throwable();
        // Wrap run method to create a new throwable representing the creator of the original Runnable.
        return new Runnable() {
            @Override
            public void run() {
                try {
                    runnable.run();
                } catch (Throwable originalThrowable) {
                    RuntimeException callingThreadsException = new RuntimeException(originalThrowable);
                    callingThreadsException.setStackTrace(prison.getStackTrace());
                    throw callingThreadsException;
                }
            }
        };
    }
}

然后使用这样的代码:

// This code has not be compiled or tested... You may need to use your
// smarts to get it working, but it should give you an idea.
public void outer() {
    Thread thread = new Thread(StackCaptor.capture(new Runnable() {
        public void run() { throw new RuntimeException("my ex"); }
    }));
    thread.start();
}

I would like to suggest an alternative solution, which isn't what the OP asked for, but may be better for some people that have a very similar question. Like me.

I suggest creating a throwable in outer and add the throwable from inner as the cause of the throwable from outer. This will also capture the point at which a thread switch was made... which may be useful to avoid confusion about the stack trace. Furthermore, thread information can be stored in this throwable created in outer that may help even more.

Here's some code.

public class StackCaptor {
    public static Runnable capture(Runnable runnable) {
        // Capture the stack
        final Throwable prison = new Throwable();
        // Wrap run method to create a new throwable representing the creator of the original Runnable.
        return new Runnable() {
            @Override
            public void run() {
                try {
                    runnable.run();
                } catch (Throwable originalThrowable) {
                    RuntimeException callingThreadsException = new RuntimeException(originalThrowable);
                    callingThreadsException.setStackTrace(prison.getStackTrace());
                    throw callingThreadsException;
                }
            }
        };
    }
}

Then use the code like this:

// This code has not be compiled or tested... You may need to use your
// smarts to get it working, but it should give you an idea.
public void outer() {
    Thread thread = new Thread(StackCaptor.capture(new Runnable() {
        public void run() { throw new RuntimeException("my ex"); }
    }));
    thread.start();
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文