Change Variable Value in JVM with GDB

怎甘沉沦 提交于 2019-11-30 23:11:40

In some cases it is possible to get local variable addresses using HotSpot Serviceability Agent. I've made a sample agent that prints extended stack traces with local variable info:

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);
    }
}

To run it:

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

This will attach to Java process PID and print the stacktraces like

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

Here main is Java thread name; 30920 is native thread ID; @ 15 is bytecode index.

The line [1] 0x00007f075a857910 = (int) 0x1 means that the local variable #1 is located at address 0x00007f075a857910 and has the value 1. This is exactly the variable you are interested in.

The local variable information is reliable for interpreted methods, but not always for compiled methods. However, compiled methods will have an extra line with an address of the code, so you can disassemble and inspect it in gdb.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!