使用GDB更改JVM中的变量值

目前我有一个简单的Java程序:

public class Test { public static void main(String[] args) { boolean test = true; while (test) { System.out.println("Hello World"); try { Thread.sleep(1000); } catch (Exception e) {} } System.out.println("Bye-bye"); } } 

它每秒打印“Hello World”。 我想使用gdb附加到进程并制作一个内存补丁,以打印“Bye-bye”来阻止它。

我知道GDB可以从其控制台获取创建的VM(JNI_GetCreatedVMs),也可以通过GetEnv的API获得env对象。 如何在JVM中找到test变量地址并将其设置为false(这是可选的)以使程序正常退出? 不确定像AttachCurrentThread这样的API,像HotSpotVirtualMachine这样的类,像jmap或jstack这样的工具会有帮助吗?

并且没有调试选项,假设使用java -cp . Test在生产中运行简单程序java -cp . Test java -cp . Test

提前感谢任何指导。 🙂


附加信息(跟踪状态)

  • jmap -dump:file=hex && jhat hex并浏览http:// localhost:7000 ; 找不到任何test引用(它不是一个对象,只是一个class Z的实例)
  • jstack 可以获取主线程的tid(0x7fa412002000),而jhat hex具有main的java.lang.Thread的对象(0x76ab05c40)
  • java.lang.Thread有一个本机方法start0 ,它调用start0热点方法(hotspot / src / share / vm / prims / jvm.cpp),有一个类JavaThread可能包含线程堆栈中局部变量的内存结构。
  • 如果private static boolean test = true; ; 然后JNI_GetCreatedJavaVMs ==> jvmjvm->jvm_api->AttachCurrentThread ==> envenv->env_api->(FindClass, GetStaticFieldID, SetStaticBooleanField) ==> test[true ==> false]

在某些情况下,可以使用HotSpot Serviceability Agent获取本地变量地址。 我已经制作了一个样本代理,它使用局部变量信息打印扩展堆栈跟踪:

 import sun.jvm.hotspot.code.Location; import sun.jvm.hotspot.code.LocationValue; import sun.jvm.hotspot.code.NMethod; import sun.jvm.hotspot.code.ScopeValue; import sun.jvm.hotspot.code.VMRegImpl; import sun.jvm.hotspot.debugger.Address; import sun.jvm.hotspot.debugger.OopHandle; import sun.jvm.hotspot.interpreter.OopMapCacheEntry; import sun.jvm.hotspot.oops.Method; import sun.jvm.hotspot.oops.Oop; import sun.jvm.hotspot.runtime.CompiledVFrame; import sun.jvm.hotspot.runtime.InterpretedVFrame; import sun.jvm.hotspot.runtime.JavaThread; import sun.jvm.hotspot.runtime.JavaVFrame; import sun.jvm.hotspot.runtime.VM; import sun.jvm.hotspot.runtime.VMReg; import sun.jvm.hotspot.tools.Tool; import java.util.List; public class Frames extends Tool { @Override public void run() { for (JavaThread thread = VM.getVM().getThreads().first(); thread != null; thread = thread.next()) { System.out.println(thread.getThreadName() + ", id = " + thread.getOSThread().threadId()); for (JavaVFrame vf = thread.getLastJavaVFrameDbg(); vf != null; vf = vf.javaSender()) { dumpFrame(vf); } System.out.println(); } } private void dumpFrame(JavaVFrame vf) { Method method = vf.getMethod(); String className = method.getMethodHolder().getName().asString().replace('/', '.'); String methodName = method.getName().asString() + method.getSignature().asString(); System.out.println(" # " + className + '.' + methodName + " @ " + vf.getBCI()); if (vf.isCompiledFrame()) { dumpCompiledFrame(((CompiledVFrame) vf)); } else { dumpInterpretedFrame(((InterpretedVFrame) vf)); } } private void dumpCompiledFrame(CompiledVFrame vf) { if (vf.getScope() == null) { return; } NMethod nm = vf.getCode(); System.out.println(" * code=[" + nm.codeBegin() + "-" + nm.codeEnd() + "], pc=" + vf.getFrame().getPC()); List locals = vf.getScope().getLocals(); for (int i = 0; i < locals.size(); i++) { ScopeValue sv = (ScopeValue) locals.get(i); if (!sv.isLocation()) continue; Location loc = ((LocationValue) sv).getLocation(); Address addr = null; String regName = ""; if (loc.isRegister()) { int reg = loc.getRegisterNumber(); addr = vf.getRegisterMap().getLocation(new VMReg(reg)); regName = ":" + VMRegImpl.getRegisterName(reg); } else if (loc.isStack() && !loc.isIllegal()) { addr = vf.getFrame().getUnextendedSP().addOffsetTo(loc.getStackOffset()); } String value = getValue(addr, loc.getType()); System.out.println(" [" + i + "] " + addr + regName + " = " + value); } } private void dumpInterpretedFrame(InterpretedVFrame vf) { Method method = vf.getMethod(); int locals = (int) (method.isNative() ? method.getSizeOfParameters() : method.getMaxLocals()); OopMapCacheEntry oopMask = method.getMaskFor(vf.getBCI()); for (int i = 0; i < locals; i++) { Address addr = vf.getFrame().addressOfInterpreterFrameLocal(i); String value = getValue(addr, oopMask.isOop(i) ? Location.Type.OOP : Location.Type.NORMAL); System.out.println(" [" + i + "] " + addr + " = " + value); } } private String getValue(Address addr, Location.Type type) { if (type == Location.Type.INVALID || addr == null) { return "(invalid)"; } else if (type == Location.Type.OOP) { return "(oop) " + getOopName(addr.getOopHandleAt(0)); } else if (type == Location.Type.NARROWOOP) { return "(narrow_oop) " + getOopName(addr.getCompOopHandleAt(0)); } else if (type == Location.Type.NORMAL) { return "(int) 0x" + Integer.toHexString(addr.getJIntAt(0)); } else { return "(" + type + ") 0x" + Long.toHexString(addr.getJLongAt(0)); } } private String getOopName(OopHandle hadle) { if (hadle == null) { return "null"; } Oop oop = VM.getVM().getObjectHeap().newOop(hadle); return oop.getKlass().getName().asString(); } public static void main(String[] args) throws Exception { new Frames().execute(args); } } 

要运行它:

 java -cp $JAVA_HOME/lib/sa-jdi.jar:. Frames PID 

这将附加到Java进程PID并打印堆栈跟踪之类的

 main, id = 30920 # java.lang.Thread.sleep(J)V @ 0 # Test.main([Ljava/lang/String;)V @ 15 [0] 0x00007f075a857918 = (oop) [Ljava/lang/String; [1] 0x00007f075a857910 = (int) 0x1 [2] 0x00007f075a857908 = (int) 0x0 

这里main是Java线程名称; 30920是本机线程ID; @ 15是字节码索引。

[1] 0x00007f075a857910 = (int) 0x1表示局部变量#1位于地址0x00007f075a857910并且值为1.这正是您感兴趣的变量。

局部变量信息对于解释方法是可靠的,但对于编译方法并不总是如此。 但是,编译后的方法将有一个带有代码地址的额外行,因此您可以在gdb中对其进行反汇编和检查。