在Java中伪造堆栈跟踪
当您在Java中使用RMI时,exception的远程堆栈跟踪将在您收到时添加,有点像这样:
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()
抛出的exception也会在stacktrace中具有outer()
(以及链中的方法)。
这很容易:
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("══════════════════════════", "", "", -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 ══════════════════════════.() 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方法中捕获exception并将外部堆栈跟踪附加到内部exception的堆栈跟踪。 不过,我真的建议放一些类型的分隔符。
您创建一个自定义exception,从一个exception中提取堆栈跟踪 ,并通过setStackTrace()将其添加到另一个exception。
当你不想保持对exception引起的硬引用时,它可以用于执行这样的操作或维护堆栈跟踪。 将exception信息从服务器传递到客户端时,这很方便,因为根本原因exception类可能不存在,从而导致序列化问题。
我想提出一个替代解决方案,这不是OP所要求的,但对于一些有类似问题的人来说可能更好。 像我这样的。
我建议在外部创建一个throwable并从内部添加throwable作为throwable从外部的原因。 这也将捕获进行线程切换的点…这可能有助于避免混淆堆栈跟踪。 此外,线程信息可以存储在外部创建的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(); }
- 如何映射Java流中的RuntimeExceptions以从无效流元素中“恢复”
- 如何检查数组元素是否为null以避免Java中的NullPointerException
- 在Java中的Rethrowexception
- 是否应将例外情况放在单独的包裹中?
- 如何捕获java.lang.NoClassDefFoundError?
- JavaFx 2.x – Swing:不在FX应用程序线程上
- 如何在我的自定义exception中设置我自己的消息,可以检索我的getMessage()但是没有使用构造函数,有什么办法吗?
- 使用Spring Security标记库时将exception映射到404页面
- 链式exception的优点是什么?