8205992: jhsdb cannot attach to Java processes running in Docker containers

Reviewed-by: cjplummer, jgeorge
This commit is contained in:
Yasumasa Suenaga 2018-07-27 00:54:39 +09:00
parent a3e7f01f33
commit f45dc7748e
5 changed files with 144 additions and 14 deletions

View File

@ -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;
}
}
/*

View File

@ -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

View File

@ -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;

View File

@ -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);
}

View File

@ -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) {