Java Agent(四)OpenJdk/JDK Attach部分源码分析

总的来说,JDK端的代码,目的就是:

  • 给JVM发送SIGBREAK信号,触发Attach Listener线程的创建;
  • 给JVM发送load命令,让JVM执行加载目标agent的操作;
  • 接收JVM返回的执行结果,判断是否加载agent成功;

流程图:
JVM+JDK源码流程
我们在外部程序里只需要两行编码:

VirtualMachine virtualMachine = VirtualMachine.attach(String.valueOf(pid));
virtualMachine.loadAgent("agent.jar=args");
  1. attach方法
public static VirtualMachine attach(String id)
    throws AttachNotSupportedException, IOException
{
    if (id == null) {
        throw new NullPointerException("id cannot be null");
    }
    List<AttachProvider> providers = AttachProvider.providers();
    if (providers.size() == 0) {
        throw new AttachNotSupportedException("no providers installed");
    }
    AttachNotSupportedException lastExc = null;
    for (AttachProvider provider: providers) {
        try {
            return provider.attachVirtualMachine(id);
        } catch (AttachNotSupportedException x) {
            lastExc = x;
        }
    }
    throw lastExc;
}

此方法通过AttachProvider.providers()得到所有的provider,并逐个调用它们的attachVirtualMachine方法,只要有一个成功就返回。
provider.attachVirtualMachine是个抽象方法,以Linux中的实现为例:

public VirtualMachine attachVirtualMachine(String vmid)
    throws AttachNotSupportedException, IOException
{
    checkAttachPermission();

    // AttachNotSupportedException will be thrown if the target VM can be determined
    // to be not attachable.
    testAttachable(vmid);

    return new LinuxVirtualMachine(this, vmid);
}

/**
 * Attaches to the target VM
 */
LinuxVirtualMachine(AttachProvider provider, String vmid)
    throws AttachNotSupportedException, IOException
{
    super(provider, vmid);

    // This provider only understands pids
    int pid;
    try {
        pid = Integer.parseInt(vmid);
    } catch (NumberFormatException x) {
        throw new AttachNotSupportedException("Invalid process identifier");
    }

    // Find the socket file. If not found then we attempt to start the
    // attach mechanism in the target VM by sending it a QUIT signal.
    // Then we attempt to find the socket file again.
    path = findSocketFile(pid);
    if (path == null) {
        File f = createAttachFile(pid);
        try {
            // On LinuxThreads each thread is a process and we don't have the
            // pid of the VMThread which has SIGQUIT unblocked. To workaround
            // this we get the pid of the "manager thread" that is created
            // by the first call to pthread_create. This is parent of all
            // threads (except the initial thread).
            if (isLinuxThreads) {
                int mpid;
                try {
                    mpid = getLinuxThreadsManager(pid);
                } catch (IOException x) {
                    throw new AttachNotSupportedException(x.getMessage());
                }
                assert(mpid >= 1);
                sendQuitToChildrenOf(mpid);
            } else {
                sendQuitTo(pid);
            }

            // give the target VM time to start the attach mechanism
            int i = 0;
            long delay = 200;
            int retries = (int)(attachTimeout() / delay);
            do {
                try {
                    Thread.sleep(delay);
                } catch (InterruptedException x) { }
                path = findSocketFile(pid);
                i++;
            } while (i <= retries && path == null);
            if (path == null) {
                throw new AttachNotSupportedException(
                    "Unable to open socket file: target process not responding " +
                    "or HotSpot VM not loaded");
            }
        } finally {
            f.delete();
        }
    }

    // Check that the file owner/permission to avoid attaching to
    // bogus process
    checkPermissions(path);

    // Check that we can connect to the process
    // - this ensures we throw the permission denied error now rather than
    // later when we attempt to enqueue a command.
    int s = socket();
    try {
        connect(s, path);
    } finally {
        close(s);
    }
}

在此方法中,首先找/tmp/.java_pid文件,如果没有找到,则创建/tmp/.attach_pid文件,再发送QUIT信号给目标pid;之后循环等待.java_pid文件。
(目的是为了给上文中提到的Signal Dispatcher线程一个Sigbreak信号,使之创建Attach Listener线程,当JVM创建了.java_pid文件,也就说明了目标JVM与外部进程间的通信socket已建立,用户可以与之建立连接)

  1. loadAgent方法
public void loadAgent(String agent)
    throws AgentLoadException, AgentInitializationException, IOException
{
    loadAgent(agent, null);
}

loadAgent在 HotSpotVirtualMachine中的实现:

public void loadAgent(String agent, String options)
    throws AgentLoadException, AgentInitializationException, IOException
{
    String args = agent;
    if (options != null) {
        args = args + "=" + options;
    }
    try {
        loadAgentLibrary("instrument", args);
    } catch (AgentLoadException x) {
        throw new InternalError("instrument library is missing in target VM", x);
    } catch (AgentInitializationException x) {
        /*
         * Translate interesting errors into the right exception and
         * message (FIXME: create a better interface to the instrument
         * implementation so this isn't necessary)
         */
        int rc = x.returnValue();
        switch (rc) {
            case JNI_ENOMEM:
                throw new AgentLoadException("Insuffient memory");
            case ATTACH_ERROR_BADJAR:
                throw new AgentLoadException("Agent JAR not found or no Agent-Class attribute");
            case ATTACH_ERROR_NOTONCP:
                throw new AgentLoadException("Unable to add JAR file to system class path");
            case ATTACH_ERROR_STARTFAIL:
                throw new AgentInitializationException("Agent JAR loaded but agent failed to initialize");
            default :
                throw new AgentLoadException("Failed to load agent - unknown reason: " + rc);
        }
    }
}

public void loadAgentLibrary(String agentLibrary, String options)
    throws AgentLoadException, AgentInitializationException, IOException
{
    loadAgentLibrary(agentLibrary, false, options);
}

private void loadAgentLibrary(String agentLibrary, boolean isAbsolute, String options)
    throws AgentLoadException, AgentInitializationException, IOException
{
    InputStream in = execute("load",
                             agentLibrary,
                             isAbsolute ? "true" : "false",
                             options);
    try {
        int result = readInt(in);
        if (result != 0) {
            throw new AgentInitializationException("Agent_OnAttach failed", result);
        }
    } finally {
        in.close();

    }
}

其中的execute方法,在Linux中的实现:

InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException {
    assert args.length <= 3;                // includes null

    // did we detach?
    String p;
    synchronized (this) {
        if (this.path == null) {
            throw new IOException("Detached from target VM");
        }
        p = this.path;
    }

    // create UNIX socket
    int s = socket();

    // connect to target VM
    try {
        connect(s, p);
    } catch (IOException x) {
        close(s);
        throw x;
    }

    IOException ioe = null;

    // connected - write request
    // <ver> <cmd> <args...>
    try {
        writeString(s, PROTOCOL_VERSION);
        writeString(s, cmd);

        for (int i=0; i<3; i++) {
            if (i < args.length && args[i] != null) {
                writeString(s, (String)args[i]);
            } else {
                writeString(s, "");
            }
        }
    } catch (IOException x) {
        ioe = x;
    }


    // Create an input stream to read reply
    SocketInputStream sis = new SocketInputStream(s);

    // Read the command completion status
    int completionStatus;
    try {
        completionStatus = readInt(sis);
    } catch (IOException x) {
        sis.close();
        if (ioe != null) {
            throw ioe;
        } else {
            throw x;
        }
    }

    if (completionStatus != 0) {
        sis.close();

        // In the event of a protocol mismatch then the target VM
        // returns a known error so that we can throw a reasonable
        // error.
        if (completionStatus == ATTACH_ERROR_BADVERSION) {
            throw new IOException("Protocol mismatch with target VM");
        }

        // Special-case the "load" command so that the right exception is
        // thrown.
        if (cmd.equals("load")) {
            throw new AgentLoadException("Failed to load agent library");
        } else {
            throw new IOException("Command failed in target VM");
        }
    }

    // Return the input stream so that the command output can be read
    return sis;
}

在此方法中,首先创建socket(通信文件为调用attach时所找到的/tmp/.java_pid文件)通过connect连接到目标JVM。
向socket写命令(load),写参数;并返回结果。如果返回的结果不为0 ,报错Agent_OnAttach failed。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值