使用 GDB 更改 JVM 中的变量值
Change Variable Value in JVM with GDB
目前我有一个简单的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 可以从它的控制台获取创建的虚拟机 (JNI_GetCreatedVMs),env 对象也可以通过 GetEnv 的 API 获得。 如何在JVM中找到test
变量地址并设置为false(可选)让程序正常退出? 不确定 API 像 AttachCurrentThread,class 像 HotSpotVirtualMachine,jmap 或 jstack 之类的工具是否可以提供帮助?
并且没有调试选项,假设生产中的简单程序 运行 java -cp . Test
。
在此先感谢您的指导。 :)
附加信息(赛道状态)
jmap -dump:file=hex <pid> && jhat hex
并浏览 http://localhost:7000;找不到对 test
的任何引用(它不是一个对象,只是 class Z
的一个实例)
jstack <pid>
可以得到主线程(0x7fa412002000)的tid,jhat hex
有主线程java.lang.Thread的对象(0x76ab05c40)
java.lang.Thread
有一个本地方法 start0
调用 JVM_StartThread
(hotspot/src/share/vm/prims/jvm.cpp) 的热点方法,还有一个 class JavaThread
可能包含线程堆栈中局部变量的内存结构。
- 如果
private static boolean test = true;
;然后 JNI_GetCreatedJavaVMs ==> jvm
、jvm->jvm_api->AttachCurrentThread ==> env
、env->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中反汇编和检查它。
目前我有一个简单的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 可以从它的控制台获取创建的虚拟机 (JNI_GetCreatedVMs),env 对象也可以通过 GetEnv 的 API 获得。 如何在JVM中找到test
变量地址并设置为false(可选)让程序正常退出? 不确定 API 像 AttachCurrentThread,class 像 HotSpotVirtualMachine,jmap 或 jstack 之类的工具是否可以提供帮助?
并且没有调试选项,假设生产中的简单程序 运行 java -cp . Test
。
在此先感谢您的指导。 :)
附加信息(赛道状态)
jmap -dump:file=hex <pid> && jhat hex
并浏览 http://localhost:7000;找不到对test
的任何引用(它不是一个对象,只是class Z
的一个实例)jstack <pid>
可以得到主线程(0x7fa412002000)的tid,jhat hex
有主线程java.lang.Thread的对象(0x76ab05c40)java.lang.Thread
有一个本地方法start0
调用JVM_StartThread
(hotspot/src/share/vm/prims/jvm.cpp) 的热点方法,还有一个 classJavaThread
可能包含线程堆栈中局部变量的内存结构。- 如果
private static boolean test = true;
;然后JNI_GetCreatedJavaVMs ==> jvm
、jvm->jvm_api->AttachCurrentThread ==> env
、env->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中反汇编和检查它。