Skip to content

jcmd jmap_attach

ceragon edited this page Jun 17, 2022 · 2 revisions

attach vm

jdk 中的 attach 流程

public class HotSpotAgent {
    private JVMDebugger debugger;
    private TypeDataBase db;
    public synchronized void attach(int processID)
            throws DebuggerException {
        pid = processID;
        startupMode = PROCESS_MODE;
        go();
    }

    private void go() {
        setupDebugger();
        setupVM();
    }

    private void setupDebugger() {
        setupDebuggerLinux();
    }

    private void setupDebuggerLinux() {
        LinuxDebuggerLocal dbg = new LinuxDebuggerLocal(machDesc, !isServer);
        debugger = dbg;
        attachDebugger();
    }

    private void attachDebugger() {
        debugger.attach(pid);
    }
    
    private void setupVM() {
        if (os.equals("linux")) {
            db = new HotSpotTypeDataBase(machDesc,
                    new LinuxVtblAccess(debugger, jvmLibNames),
                    debugger, jvmLibNames);
        }
        VM.initialize(db, debugger);
    }
}
public class VM {
    private static VM    soleInstance;
    public static void initialize(TypeDataBase db, JVMDebugger debugger) {
        soleInstance = new VM(db, debugger, 
                debugger.getMachineDescription().isBigEndian());
        
    }
}
public class LinuxDebuggerLocal extends DebuggerBase implements LinuxDebugger {
    private LinuxDebuggerLocalWorkerThread workerThread = null;
    public synchronized void attach(int processID) throws DebuggerException {
        class AttachTask implements WorkerThreadTask {
            int pid;
            public void doit(LinuxDebuggerLocal debugger) {
                debugger.attach0(pid);
                debugger.attached = true;
                debugger.isCore = false;
                findABIVersion();
            }
        }
        AttachTask task = new AttachTask();
        task.pid = processID;
        workerThread.execute(task);
    }

    private native void attach0(int pid) throws DebuggerException;
}

jvm 中的 attach0

void Java_sun_jvm_hotspot_debugger_linux_LinuxDebuggerLocal_attach0__I 
        (JNIEnv *env, jobject this_obj, jint jpid) {
    char err_buf[200];
    struct ps_prochandle* ph;
    if ((ph = Pgrab(jpid, err_buf, sizeof(err_buf))) == NULL) {
        // 失败
    }
}
struct ps_prochandle* Pgrab(pid_t pid, char* err_buf, size_t err_buf_len) {
    struct ps_prochandle* ph = NULL;
    attach_state_t attach_status = ATTACH_SUCCESS;
    // 先 attach 主线程
    if ((attach_status = ptrace_attach(pid, err_buf, err_buf_len)) != ATTACH_SUCCESS) {
        return NULL;
    }
    ph->pid = pid;
    
    // 从 /proc/pid/task 目录下检索所有线程
    char taskpath[PATH_MAX];
    struct dirent *entry;
    snprintf(taskpath, PATH_MAX, "/proc/%d/task", ph->pid);
    dirp = opendir(taskpath);
    int lwp_id;
    while ((entry = readdir(dirp)) != NULL) {
        lwp_id = atoi(entry->d_name);
        if (lwp_id == ph->pid) {
            continue;
        }
        add_thread_info(ph, lwp_id);
    }
    
    // 遍历所有线程,并依次 attach
    thr = ph->threads;
    while (thr) {
        thread_info* current_thr = thr;
        thr = thr->next;
        // don't attach to the main thread again
        if (ph->pid != current_thr->lwp_id) {
            if ((attach_status = ptrace_attach(current_thr->lwp_id, err_buf, err_buf_len)) != ATTACH_SUCCESS) {
                return NULL:
            }
        }
    }
    // 最终所有 jvm 的线程都被 attach 方法给挂起了
    return ph;
}

ptrace_attach

static attach_state_t ptrace_attach(pid_t pid, char* err_buf, size_t err_buf_len) {
    // 先调用 attach,这样 pid 线程就会挂起
    if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) {
        return ATTACH_FAIL;
    } else {
        // 线程挂起了,此时 pid 所有的 signal(除了 KILL)都会被当前线程拦截
        // 调用 waitpid 可以在线程停止后让线程正常退出
        attach_state_t wait_ret = ptrace_waitpid(pid);
        return wait_ret;
    }
}

ptrace_waitpid

static attach_state_t ptrace_waitpid(pid_t pid) {
    int ret;
    int status;
    while (true) {
        ret = waitpid(pid, &status, 0);
        if (ret >= 0) {
            if (WIFSTOPPED(status)) {
                if (WSTOPSIG(status) == SIGSTOP) {
                    // Debuggee stopped by SIGSTOP.
                    return ATTACH_SUCCESS;
                }
            }
            return ATTACH_FAIL;
        }
    }
}

jvm 中的 deattach

void Java_sun_jvm_hotspot_debugger_linux_LinuxDebuggerLocal_detach0
        (JNIEnv *env, jobject this_obj) {
    struct ps_prochandle* ph = get_proc_handle(env, this_obj);
    if (ph != NULL) {
        Prelease(ph);
    }
}
void Prelease(struct ps_prochandle* ph) {
    ph->ops->release(ph);
    destroy_lib_info(ph);
    destroy_thread_info(ph);
    free(ph);
}

release

static void process_cleanup(struct ps_prochandle* ph) {
    detach_all_pids(ph);
}
static void detach_all_pids(struct ps_prochandle* ph) {
    thread_info* thr = ph->threads;
    while (thr) {
        ptrace_detach(thr->lwp_id);
        thr = thr->next;
    }
}
static bool ptrace_detach(pid_t pid) {
    // detach
    if (pid && ptrace(PTRACE_DETACH, pid, NULL, NULL) < 0) {
        return false;
    } else {
        return true;
    }
}

Clone this wiki locally