8205992: jhsdb cannot attach to Java processes running in Docker containers
Reviewed-by: cjplummer, jgeorge
This commit is contained in:
parent
a3e7f01f33
commit
f45dc7748e
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -66,6 +66,12 @@ static jmethodID createLoadObject_ID = 0;
|
||||
static jmethodID getThreadForThreadId_ID = 0;
|
||||
static jmethodID listAdd_ID = 0;
|
||||
|
||||
/*
|
||||
* SA_ALTROOT environment variable.
|
||||
* This memory holds env string for putenv(3).
|
||||
*/
|
||||
static char *saaltroot = NULL;
|
||||
|
||||
#define CHECK_EXCEPTION_(value) if ((*env)->ExceptionOccurred(env)) { return value; }
|
||||
#define CHECK_EXCEPTION if ((*env)->ExceptionOccurred(env)) { return;}
|
||||
#define THROW_NEW_DEBUGGER_EXCEPTION_(str, value) { throw_new_debugger_exception(env, str); return value; }
|
||||
@ -211,11 +217,34 @@ void verifyBitness(JNIEnv *env, const char *binaryName) {
|
||||
|
||||
/*
|
||||
* Class: sun_jvm_hotspot_debugger_linux_LinuxDebuggerLocal
|
||||
* Method: attach0
|
||||
* Signature: (I)V
|
||||
* Method: setSAAltRoot0
|
||||
* Signature: (Ljava/lang/String;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_sun_jvm_hotspot_debugger_linux_LinuxDebuggerLocal_attach0__I
|
||||
(JNIEnv *env, jobject this_obj, jint jpid) {
|
||||
JNIEXPORT void JNICALL Java_sun_jvm_hotspot_debugger_linux_LinuxDebuggerLocal_setSAAltRoot0
|
||||
(JNIEnv *env, jobject this_obj, jstring altroot) {
|
||||
if (saaltroot != NULL) {
|
||||
free(saaltroot);
|
||||
}
|
||||
const char *path = (*env)->GetStringUTFChars(env, altroot, JNI_FALSE);
|
||||
/*
|
||||
* `saaltroot` is used for putenv().
|
||||
* So we need to keep this memory.
|
||||
*/
|
||||
static const char *PREFIX = "SA_ALTROOT=";
|
||||
size_t len = strlen(PREFIX) + strlen(path) + 1;
|
||||
saaltroot = (char *)malloc(len);
|
||||
snprintf(saaltroot, len, "%s%s", PREFIX, path);
|
||||
putenv(saaltroot);
|
||||
(*env)->ReleaseStringUTFChars(env, altroot, path);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: sun_jvm_hotspot_debugger_linux_LinuxDebuggerLocal
|
||||
* Method: attach0
|
||||
* Signature: (IZ)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_sun_jvm_hotspot_debugger_linux_LinuxDebuggerLocal_attach0__IZ
|
||||
(JNIEnv *env, jobject this_obj, jint jpid, jboolean is_in_container) {
|
||||
|
||||
// For bitness checking, locate binary at /proc/jpid/exe
|
||||
char buf[PATH_MAX];
|
||||
@ -225,7 +254,7 @@ JNIEXPORT void JNICALL Java_sun_jvm_hotspot_debugger_linux_LinuxDebuggerLocal_at
|
||||
|
||||
char err_buf[200];
|
||||
struct ps_prochandle* ph;
|
||||
if ( (ph = Pgrab(jpid, err_buf, sizeof(err_buf))) == NULL) {
|
||||
if ((ph = Pgrab(jpid, err_buf, sizeof(err_buf), is_in_container)) == NULL) {
|
||||
char msg[230];
|
||||
snprintf(msg, sizeof(msg), "Can't attach to the process: %s", err_buf);
|
||||
THROW_NEW_DEBUGGER_EXCEPTION(msg);
|
||||
@ -276,6 +305,10 @@ JNIEXPORT void JNICALL Java_sun_jvm_hotspot_debugger_linux_LinuxDebuggerLocal_de
|
||||
if (ph != NULL) {
|
||||
Prelease(ph);
|
||||
}
|
||||
if (saaltroot != NULL) {
|
||||
free(saaltroot);
|
||||
saaltroot = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -87,7 +87,7 @@ struct ps_prochandle;
|
||||
|
||||
// attach to a process
|
||||
JNIEXPORT struct ps_prochandle* JNICALL
|
||||
Pgrab(pid_t pid, char* err_buf, size_t err_buf_len);
|
||||
Pgrab(pid_t pid, char* err_buf, size_t err_buf_len, bool is_in_container);
|
||||
|
||||
// attach to a core dump
|
||||
JNIEXPORT struct ps_prochandle* JNICALL
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include <signal.h>
|
||||
#include <errno.h>
|
||||
#include <elf.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/ptrace.h>
|
||||
@ -374,7 +375,7 @@ static ps_prochandle_ops process_ops = {
|
||||
|
||||
// attach to the process. One and only one exposed stuff
|
||||
JNIEXPORT struct ps_prochandle* JNICALL
|
||||
Pgrab(pid_t pid, char* err_buf, size_t err_buf_len) {
|
||||
Pgrab(pid_t pid, char* err_buf, size_t err_buf_len, bool is_in_container) {
|
||||
struct ps_prochandle* ph = NULL;
|
||||
thread_info* thr = NULL;
|
||||
|
||||
@ -401,7 +402,32 @@ Pgrab(pid_t pid, char* err_buf, size_t err_buf_len) {
|
||||
read_lib_info(ph);
|
||||
|
||||
// read thread info
|
||||
read_thread_info(ph, add_new_thread);
|
||||
if (is_in_container) {
|
||||
/*
|
||||
* If the process is running in the container, SA scans all tasks in
|
||||
* /proc/<PID>/task to read all threads info.
|
||||
*/
|
||||
char taskpath[PATH_MAX];
|
||||
DIR *dirp;
|
||||
struct dirent *entry;
|
||||
|
||||
snprintf(taskpath, PATH_MAX, "/proc/%d/task", ph->pid);
|
||||
dirp = opendir(taskpath);
|
||||
int lwp_id;
|
||||
while ((entry = readdir(dirp)) != NULL) {
|
||||
if (*entry->d_name == '.') {
|
||||
continue;
|
||||
}
|
||||
lwp_id = atoi(entry->d_name);
|
||||
if (lwp_id == ph->pid) {
|
||||
continue;
|
||||
}
|
||||
add_new_thread(ph, -1, lwp_id);
|
||||
}
|
||||
closedir(dirp);
|
||||
} else {
|
||||
read_thread_info(ph, add_new_thread);
|
||||
}
|
||||
|
||||
// attach to the threads
|
||||
thr = ph->threads;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2002, 2017, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -25,8 +25,16 @@
|
||||
package sun.jvm.hotspot.debugger.linux;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import sun.jvm.hotspot.debugger.Address;
|
||||
import sun.jvm.hotspot.debugger.DebuggerBase;
|
||||
@ -72,6 +80,10 @@ public class LinuxDebuggerLocal extends DebuggerBase implements LinuxDebugger {
|
||||
private List threadList;
|
||||
private List loadObjectList;
|
||||
|
||||
// PID namespace support
|
||||
// It maps the LWPID in the host to the LWPID in the container.
|
||||
private Map<Integer, Integer> nspidMap;
|
||||
|
||||
// called by native method lookupByAddress0
|
||||
private ClosestSymbol createClosestSymbol(String name, long offset) {
|
||||
return new ClosestSymbol(name, offset);
|
||||
@ -89,7 +101,8 @@ public class LinuxDebuggerLocal extends DebuggerBase implements LinuxDebugger {
|
||||
|
||||
private native static void init0()
|
||||
throws DebuggerException;
|
||||
private native void attach0(int pid)
|
||||
private native void setSAAltRoot0(String altroot);
|
||||
private native void attach0(int pid, boolean isInContainer)
|
||||
throws DebuggerException;
|
||||
private native void attach0(String execName, String coreName)
|
||||
throws DebuggerException;
|
||||
@ -254,15 +267,63 @@ public class LinuxDebuggerLocal extends DebuggerBase implements LinuxDebugger {
|
||||
}
|
||||
}
|
||||
|
||||
// Get namespace PID from /proc/<PID>/status.
|
||||
private int getNamespacePID(Path statusPath) {
|
||||
try (var lines = Files.lines(statusPath)) {
|
||||
return lines.map(s -> s.split("\\s+"))
|
||||
.filter(a -> a.length == 3)
|
||||
.filter(a -> a[0].equals("NSpid:"))
|
||||
.mapToInt(a -> Integer.valueOf(a[2]))
|
||||
.findFirst()
|
||||
.getAsInt();
|
||||
} catch (IOException | NoSuchElementException e) {
|
||||
return Integer.valueOf(statusPath.getParent()
|
||||
.toFile()
|
||||
.getName());
|
||||
}
|
||||
}
|
||||
|
||||
// Get LWPID in the host from the container's LWPID.
|
||||
// Returns -1 if the process is running in the host.
|
||||
public int getHostPID(int id) {
|
||||
return (nspidMap == null) ? -1 : nspidMap.get(id);
|
||||
}
|
||||
|
||||
// Fill namespace PID map from procfs.
|
||||
// This method scans all tasks (/proc/<PID>/task) in the process.
|
||||
private void fillNSpidMap(Path proc) {
|
||||
Path task = Paths.get(proc.toString(), "task");
|
||||
try (var tasks = Files.list(task)) {
|
||||
nspidMap = tasks.filter(p -> !p.toString().startsWith("."))
|
||||
.collect(Collectors.toMap(p -> Integer.valueOf(getNamespacePID(Paths.get(p.toString(), "status"))),
|
||||
p -> Integer.valueOf(p.toFile().getName())));
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** From the Debugger interface via JVMDebugger */
|
||||
public synchronized void attach(int processID) throws DebuggerException {
|
||||
checkAttached();
|
||||
threadList = new ArrayList();
|
||||
loadObjectList = new ArrayList();
|
||||
|
||||
Path proc = Paths.get("/proc", Integer.toString(processID));
|
||||
int NSpid = getNamespacePID(Paths.get(proc.toString(), "status"));
|
||||
if (NSpid != processID) {
|
||||
// If PID different from namespace PID, we can assume the process
|
||||
// is running in the container.
|
||||
// So we need to set SA_ALTROOT environment variable that SA reads
|
||||
// binaries in the container.
|
||||
setSAAltRoot0(Paths.get(proc.toString(), "root").toString());
|
||||
fillNSpidMap(proc);
|
||||
}
|
||||
|
||||
class AttachTask implements WorkerThreadTask {
|
||||
int pid;
|
||||
boolean isInContainer;
|
||||
public void doit(LinuxDebuggerLocal debugger) {
|
||||
debugger.attach0(pid);
|
||||
debugger.attach0(pid, isInContainer);
|
||||
debugger.attached = true;
|
||||
debugger.isCore = false;
|
||||
findABIVersion();
|
||||
@ -271,6 +332,7 @@ public class LinuxDebuggerLocal extends DebuggerBase implements LinuxDebugger {
|
||||
|
||||
AttachTask task = new AttachTask();
|
||||
task.pid = processID;
|
||||
task.isInContainer = (processID != NSpid);
|
||||
workerThread.execute(task);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2002, 2003, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -37,7 +37,16 @@ class LinuxThread implements ThreadProxy {
|
||||
// FIXME: size of data fetched here should be configurable.
|
||||
// However, making it so would produce a dependency on the "types"
|
||||
// package from the debugger package, which is not desired.
|
||||
this.lwp_id = (int) addr.getCIntegerAt(0, 4, true);
|
||||
int pid = (int)addr.getCIntegerAt(0, 4, true);
|
||||
if (debugger instanceof LinuxDebuggerLocal) {
|
||||
int hostPID = ((LinuxDebuggerLocal)debugger).getHostPID(pid);
|
||||
// Debuggee is not running in the container
|
||||
if (hostPID != -1) {
|
||||
pid = hostPID;
|
||||
}
|
||||
}
|
||||
this.lwp_id = pid;
|
||||
|
||||
}
|
||||
|
||||
LinuxThread(LinuxDebugger debugger, long id) {
|
||||
|
Loading…
Reference in New Issue
Block a user