8205992: jhsdb cannot attach to Java processes running in Docker containers
authorysuenaga
Fri, 27 Jul 2018 00:54:39 +0900
changeset 51203 220c9188db4f
parent 51202 59b0d8afc831
child 51204 b257e2c3bc8d
8205992: jhsdb cannot attach to Java processes running in Docker containers Reviewed-by: cjplummer, jgeorge
src/jdk.hotspot.agent/linux/native/libsaproc/LinuxDebuggerLocal.c
src/jdk.hotspot.agent/linux/native/libsaproc/libproc.h
src/jdk.hotspot.agent/linux/native/libsaproc/ps_proc.c
src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/LinuxDebuggerLocal.java
src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/LinuxThread.java
--- 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) {