8205992: jhsdb cannot attach to Java processes running in Docker containers
Reviewed-by: cjplummer, jgeorge
--- a/src/jdk.hotspot.agent/linux/native/libsaproc/LinuxDebuggerLocal.c Thu Jul 26 10:56:58 2018 -0400
+++ b/src/jdk.hotspot.agent/linux/native/libsaproc/LinuxDebuggerLocal.c Fri Jul 27 00:54:39 2018 +0900
@@ -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 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 @@
/*
* 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 @@
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 @@
if (ph != NULL) {
Prelease(ph);
}
+ if (saaltroot != NULL) {
+ free(saaltroot);
+ saaltroot = NULL;
+ }
}
/*
--- a/src/jdk.hotspot.agent/linux/native/libsaproc/libproc.h Thu Jul 26 10:56:58 2018 -0400
+++ b/src/jdk.hotspot.agent/linux/native/libsaproc/libproc.h Fri Jul 27 00:54:39 2018 +0900
@@ -87,7 +87,7 @@
// 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
--- a/src/jdk.hotspot.agent/linux/native/libsaproc/ps_proc.c Thu Jul 26 10:56:58 2018 -0400
+++ b/src/jdk.hotspot.agent/linux/native/libsaproc/ps_proc.c Fri Jul 27 00:54:39 2018 +0900
@@ -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 @@
// 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 @@
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;
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/LinuxDebuggerLocal.java Thu Jul 26 10:56:58 2018 -0400
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/LinuxDebuggerLocal.java Fri Jul 27 00:54:39 2018 +0900
@@ -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 @@
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 @@
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 @@
}
}
+ // 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 @@
AttachTask task = new AttachTask();
task.pid = processID;
+ task.isInContainer = (processID != NSpid);
workerThread.execute(task);
}
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/LinuxThread.java Thu Jul 26 10:56:58 2018 -0400
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/LinuxThread.java Fri Jul 27 00:54:39 2018 +0900
@@ -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 @@
// 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) {