8332488: Add JVMTI DataDumpRequest to the debug agent
Reviewed-by: sspitsyn, lmesnik
This commit is contained in:
parent
c0384b6f35
commit
723ac5763a
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1998, 2024, 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
|
||||
@ -995,6 +995,8 @@ parseOptions(char *options)
|
||||
gdata->includeVThreads = JNI_FALSE;
|
||||
gdata->rememberVThreadsWhenDisconnected = JNI_FALSE;
|
||||
|
||||
gdata->jvmti_data_dump = JNI_FALSE;
|
||||
|
||||
/* Options being NULL will end up being an error. */
|
||||
if (options == NULL) {
|
||||
options = "";
|
||||
@ -1159,6 +1161,13 @@ parseOptions(char *options)
|
||||
if ( dopause ) {
|
||||
do_pause();
|
||||
}
|
||||
} else if (strcmp(buf, "datadump") == 0) {
|
||||
// Enable JVMTI DATA_DUMP_REQUEST support.
|
||||
// This is not a documented flag. This feature is experimental and is only intended
|
||||
// to be used by debug agent developers. See comment for cbDataDump() for more details.
|
||||
if ( !get_boolean(&str, &(gdata->jvmti_data_dump)) ) {
|
||||
goto syntax_error;
|
||||
}
|
||||
} else if (strcmp(buf, "coredump") == 0) {
|
||||
if ( !get_boolean(&str, &docoredump) ) {
|
||||
goto syntax_error;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2001, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2001, 2024, 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
|
||||
@ -1387,9 +1387,7 @@ eventFilterRestricted_deinstall(HandlerNode *node)
|
||||
return error1 != JVMTI_ERROR_NONE? error1 : error2;
|
||||
}
|
||||
|
||||
/***** debugging *****/
|
||||
|
||||
#ifdef DEBUG
|
||||
/***** APIs for debugging the debug agent *****/
|
||||
|
||||
void
|
||||
eventFilter_dumpHandlerFilters(HandlerNode *node)
|
||||
@ -1476,5 +1474,3 @@ eventFilter_dumpHandlerFilters(HandlerNode *node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* DEBUG */
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2001, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2001, 2024, 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
|
||||
@ -76,10 +76,8 @@ jvmtiError eventFilter_setPlatformThreadsOnlyFilter(HandlerNode *node, jint inde
|
||||
jboolean eventFilter_predictFiltering(HandlerNode *node, jclass clazz, char *classname);
|
||||
jboolean isBreakpointSet(jclass clazz, jmethodID method, jlocation location);
|
||||
|
||||
/***** debugging *****/
|
||||
/***** APIs for debugging the debug agent *****/
|
||||
|
||||
#ifdef DEBUG
|
||||
void eventFilter_dumpHandlerFilters(HandlerNode *node);
|
||||
#endif
|
||||
|
||||
#endif /* _EVENT_FILTER_H */
|
||||
|
@ -1330,6 +1330,44 @@ cbVMDeath(jvmtiEnv *jvmti_env, JNIEnv *env)
|
||||
LOG_MISC(("END cbVMDeath"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Event callback for JVMTI_EVENT_DATA_DUMP_REQUEST
|
||||
*
|
||||
* This callback is made when a JVMTI data dump is requested. The common way of doing
|
||||
* this is with "jcmd <pid> JVMTI.data_dump".
|
||||
*
|
||||
* Debug agent data dumps are experimental and only intended to be used by debug agent
|
||||
* developers. Data dumps are disabled by default.
|
||||
*
|
||||
* This callback is enabled by launching the debug agent with datadump=y. The easiest
|
||||
* way to enabled data dumps with debugger tests or when using jdb is to use the
|
||||
* _JAVA_JDWP_OPTIONS export. The following works well when running tests:
|
||||
*
|
||||
* make test TEST=<test> \
|
||||
* JTREG='JAVA_OPTIONS=-XX:+StartAttachListener;OPTIONS=-e:_JAVA_JDWP_OPTIONS=datadump=y'
|
||||
*
|
||||
* Data dumps may fail to happen due to the debug agent suspending all threads.
|
||||
* This causes the Signal Dispatcher and Attach Listener threads to be suspended,
|
||||
* which can cause issues with jcmd attaching. Running with -XX:+StartAttachListener can
|
||||
* help, but in general it is best not to try a datadump when all threads are suspended.
|
||||
*
|
||||
* Data dumps are also risky when the debug agent is handling events or commands from
|
||||
* the debugger, due to dumping data that is not lock protected. This can cause a
|
||||
* crash.
|
||||
*
|
||||
* Data dumps are meant to aid with post mortem debugging (debugging after a
|
||||
* problem has been detected), not for ongoing periodic data gathering.
|
||||
*/
|
||||
static void JNICALL
|
||||
cbDataDump(jvmtiEnv *jvmti_env)
|
||||
{
|
||||
tty_message("Debug Agent Data Dump");
|
||||
tty_message("=== START DUMP ===");
|
||||
threadControl_dumpAllThreads();
|
||||
eventHandler_dumpAllHandlers(JNI_TRUE);
|
||||
tty_message("=== END DUMP ===");
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete this handler (do not delete permanent handlers):
|
||||
* Deinsert handler from active list,
|
||||
@ -1518,6 +1556,19 @@ eventHandler_initialize(jbyte sessionID)
|
||||
if (error != JVMTI_ERROR_NONE) {
|
||||
EXIT_ERROR(error,"Can't enable garbage collection finish events");
|
||||
}
|
||||
|
||||
/*
|
||||
* DATA_DUMP_REQUEST is special since it is not tied to any handlers or an EI,
|
||||
* so it cannot be setup using threadControl_setEventMode(). Use JVMTI API directly.
|
||||
*/
|
||||
if (gdata->jvmti_data_dump) {
|
||||
error = JVMTI_FUNC_PTR(gdata->jvmti,SetEventNotificationMode)
|
||||
(gdata->jvmti, JVMTI_ENABLE, JVMTI_EVENT_DATA_DUMP_REQUEST, NULL);
|
||||
if (error != JVMTI_ERROR_NONE) {
|
||||
EXIT_ERROR(error,"Can't enable data dump request events");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Only enable vthread START and END events if we want to remember
|
||||
* vthreads when no debugger is connected.
|
||||
@ -1580,6 +1631,8 @@ eventHandler_initialize(jbyte sessionID)
|
||||
gdata->callbacks.VirtualThreadStart = &cbVThreadStart;
|
||||
/* Event callback for JVMTI_EVENT_VIRTUAL_THREAD_END */
|
||||
gdata->callbacks.VirtualThreadEnd = &cbVThreadEnd;
|
||||
/* Event callback for JVMTI_EVENT_DATA_DUMP_REQUEST */
|
||||
gdata->callbacks.DataDumpRequest = &cbDataDump;
|
||||
|
||||
error = JVMTI_FUNC_PTR(gdata->jvmti,SetEventCallbacks)
|
||||
(gdata->jvmti, &(gdata->callbacks), sizeof(gdata->callbacks));
|
||||
@ -1851,9 +1904,7 @@ eventHandler_installExternal(HandlerNode *node)
|
||||
JNI_TRUE);
|
||||
}
|
||||
|
||||
/***** debugging *****/
|
||||
|
||||
#ifdef DEBUG
|
||||
/***** APIs for debugging the debug agent *****/
|
||||
|
||||
void
|
||||
eventHandler_dumpAllHandlers(jboolean dumpPermanent)
|
||||
@ -1892,5 +1943,3 @@ eventHandler_dumpHandler(HandlerNode *node)
|
||||
tty_message("Handler for %s(%d)\n", eventIndex2EventName(node->ei), node->ei);
|
||||
eventFilter_dumpHandlerFilters(node);
|
||||
}
|
||||
|
||||
#endif /* DEBUG */
|
||||
|
@ -85,12 +85,10 @@ jboolean eventHandler_synthesizeUnloadEvent(char *signature, JNIEnv *env);
|
||||
|
||||
jclass getMethodClass(jvmtiEnv *jvmti_env, jmethodID method);
|
||||
|
||||
/***** debugging *****/
|
||||
/***** APIs for debugging the debug agent *****/
|
||||
|
||||
#ifdef DEBUG
|
||||
void eventHandler_dumpAllHandlers(jboolean dumpPermanent);
|
||||
void eventHandler_dumpHandlers(EventIndex ei, jboolean dumpPermanent);
|
||||
void eventHandler_dumpHandler(HandlerNode *node);
|
||||
#endif
|
||||
|
||||
#endif /* _EVENTHANDLER_H */
|
||||
|
@ -137,11 +137,6 @@ typedef struct {
|
||||
|
||||
static DeferredEventModeList deferredEventModes;
|
||||
|
||||
#ifdef DEBUG
|
||||
static void dumpThreadList(ThreadList *list);
|
||||
static void dumpThread(ThreadNode *node);
|
||||
#endif
|
||||
|
||||
/* Get the state of the thread direct from JVMTI */
|
||||
static jvmtiError
|
||||
threadState(jthread thread, jint *pstate)
|
||||
@ -2561,13 +2556,15 @@ threadControl_allVThreads(jint *numVThreads)
|
||||
return vthreads;
|
||||
}
|
||||
|
||||
/***** debugging *****/
|
||||
/***** APIs for debugging the debug agent *****/
|
||||
|
||||
#ifdef DEBUG
|
||||
static void dumpThreadList(ThreadList *list);
|
||||
static void dumpThread(ThreadNode *node);
|
||||
|
||||
void
|
||||
threadControl_dumpAllThreads()
|
||||
{
|
||||
tty_message("suspendAllCount: %d", suspendAllCount);
|
||||
tty_message("Dumping runningThreads:");
|
||||
dumpThreadList(&runningThreads);
|
||||
tty_message("\nDumping runningVThreads:");
|
||||
@ -2652,5 +2649,3 @@ dumpThread(ThreadNode *node) {
|
||||
tty_message("\tobjID: %d", commonRef_refToID(getEnv(), node->thread));
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* DEBUG */
|
||||
|
@ -76,11 +76,9 @@ jlong threadControl_getFrameGeneration(jthread thread);
|
||||
|
||||
jthread *threadControl_allVThreads(jint *numVThreads);
|
||||
|
||||
/***** debugging *****/
|
||||
/***** APIs for debugging the debug agent *****/
|
||||
|
||||
#ifdef DEBUG
|
||||
void threadControl_dumpAllThreads();
|
||||
void threadControl_dumpThread(jthread thread);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@ -1984,8 +1984,6 @@ eventIndex2jvmti(EventIndex ei)
|
||||
return event;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
char*
|
||||
eventIndex2EventName(EventIndex ei)
|
||||
{
|
||||
@ -2040,8 +2038,6 @@ eventIndex2EventName(EventIndex ei)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
EventIndex
|
||||
jdwp2EventIndex(jdwpEvent eventType)
|
||||
{
|
||||
|
@ -90,6 +90,7 @@ typedef struct {
|
||||
jboolean doerrorexit;
|
||||
jboolean modifiedUtf8;
|
||||
jboolean quiet;
|
||||
jboolean jvmti_data_dump; /* If true, then support JVMTI DATA_DUMP_REQUEST events. */
|
||||
|
||||
/* Debug flags (bit mask) */
|
||||
int debugflags;
|
||||
@ -389,9 +390,7 @@ void *jvmtiAllocate(jint numBytes);
|
||||
void jvmtiDeallocate(void *buffer);
|
||||
|
||||
void eventIndexInit(void);
|
||||
#ifdef DEBUG
|
||||
char* eventIndex2EventName(EventIndex ei);
|
||||
#endif
|
||||
jdwpEvent eventIndex2jdwp(EventIndex i);
|
||||
jvmtiEvent eventIndex2jvmti(EventIndex i);
|
||||
EventIndex jdwp2EventIndex(jdwpEvent eventType);
|
||||
|
176
test/jdk/com/sun/jdi/DataDumpTest.java
Normal file
176
test/jdk/com/sun/jdi/DataDumpTest.java
Normal file
@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright (c) 2024, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
|
||||
import jdk.test.lib.dcmd.PidJcmdExecutor;
|
||||
import jdk.test.lib.process.OutputAnalyzer;
|
||||
import jdk.test.lib.process.ProcessTools;
|
||||
|
||||
import com.sun.jdi.Bootstrap;
|
||||
import com.sun.jdi.ClassType;
|
||||
import com.sun.jdi.VirtualMachine;
|
||||
import com.sun.jdi.VMDisconnectedException;
|
||||
import com.sun.jdi.connect.AttachingConnector;
|
||||
import com.sun.jdi.connect.Connector;
|
||||
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
|
||||
import com.sun.jdi.event.ClassPrepareEvent;
|
||||
import com.sun.jdi.event.Event;
|
||||
import com.sun.jdi.event.EventSet;
|
||||
import com.sun.jdi.request.EventRequest;
|
||||
import com.sun.jdi.request.ClassPrepareRequest;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8332488
|
||||
* @summary Unit test for testing debug agent support for JVMTI.data_dump jcmd.
|
||||
*
|
||||
* @library /test/lib
|
||||
* @modules jdk.jdi
|
||||
* @run driver DataDumpTest
|
||||
*/
|
||||
|
||||
class DataDumpTestTarg {
|
||||
public static void main(String args[]) throws Exception {
|
||||
// Write something that can be read by the driver
|
||||
System.out.println("Debuggee started");
|
||||
}
|
||||
}
|
||||
|
||||
public class DataDumpTest {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
System.out.println("Test 1: Debuggee start with datadump=y");
|
||||
runTest("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,datadump=y");
|
||||
}
|
||||
|
||||
private static void sleep(long ms) {
|
||||
try {
|
||||
Thread.sleep(ms);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
|
||||
private static void runTest(String jdwpArg) throws Exception {
|
||||
ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(
|
||||
jdwpArg,
|
||||
// Probably not required by this test, but best to include when using datadump
|
||||
"-XX:+StartAttachListener",
|
||||
"DataDumpTestTarg");
|
||||
Process p = null;
|
||||
OutputAnalyzer out = null;
|
||||
try {
|
||||
p = pb.start();
|
||||
InputStream is = p.getInputStream();
|
||||
out = new OutputAnalyzer(p);
|
||||
|
||||
// Attach a debugger and do the data dump. The data dump output will appear
|
||||
// in the debuggee output.
|
||||
attachAndDump(p.pid());
|
||||
|
||||
out.waitFor(); // Wait for the debuggee to exit
|
||||
|
||||
System.out.println("Deuggee output:");
|
||||
System.out.println(out.getOutput());
|
||||
|
||||
// All these strings are part of the debug agent data dump output.
|
||||
out.shouldHaveExitValue(0);
|
||||
out.shouldContain("Debuggee started");
|
||||
out.shouldContain("Debug Agent Data Dump");
|
||||
out.shouldContain("suspendAllCount: 0");
|
||||
out.shouldContain("ClassMatch: classPattern(DataDumpTestTarg)");
|
||||
out.shouldContain("Handlers for EI_VM_DEATH");
|
||||
} finally {
|
||||
if (p != null) {
|
||||
p.destroyForcibly();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void attachAndDump(long pid) throws IOException,
|
||||
IllegalConnectorArgumentsException {
|
||||
// Get the ProcessAttachingConnector, which can attach using the pid of the debuggee.
|
||||
AttachingConnector ac = Bootstrap.virtualMachineManager().attachingConnectors()
|
||||
.stream()
|
||||
.filter(c -> c.name().equals("com.sun.jdi.ProcessAttach"))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new RuntimeException("Unable to locate ProcessAttachingConnector"));
|
||||
|
||||
// Set the connector's "pid" argument to the pid of the debuggee.
|
||||
Map<String, Connector.Argument> args = ac.defaultArguments();
|
||||
Connector.StringArgument arg = (Connector.StringArgument)args.get("pid");
|
||||
arg.setValue("" + pid);
|
||||
|
||||
// Attach to the debuggee.
|
||||
System.out.println("Debugger is attaching to: " + pid + " ...");
|
||||
VirtualMachine vm = ac.attach(args);
|
||||
|
||||
// List all threads as a sanity check.
|
||||
System.out.println("Attached! Now listing threads ...");
|
||||
vm.allThreads().stream().forEach(System.out::println);
|
||||
|
||||
// Request VM to trigger ClassPrepareRequest when DataDumpTestTarg class is prepared.
|
||||
ClassPrepareRequest classPrepareRequest = vm.eventRequestManager().createClassPrepareRequest();
|
||||
classPrepareRequest.addClassFilter("DataDumpTestTarg");
|
||||
// Don't use SUSPEND_ALL here. That might prevent the data dump because the
|
||||
// Signal Dispatcher and Attach Listener threads will be suspended, and they
|
||||
// may be needed by the jcmd support.
|
||||
classPrepareRequest.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
|
||||
classPrepareRequest.enable();
|
||||
|
||||
try {
|
||||
while (true) { // Exit when we get VMDisconnectedException
|
||||
EventSet eventSet = vm.eventQueue().remove();
|
||||
if (eventSet == null) {
|
||||
continue;
|
||||
}
|
||||
for (Event event : eventSet) {
|
||||
System.out.println("Received event: " + event);
|
||||
if (event instanceof ClassPrepareEvent) {
|
||||
ClassPrepareEvent evt = (ClassPrepareEvent) event;
|
||||
ClassType classType = (ClassType) evt.referenceType();
|
||||
|
||||
// Run JVMTI.data_dump jcmd.
|
||||
OutputAnalyzer out = new PidJcmdExecutor("" + pid).execute("JVMTI.data_dump");
|
||||
out.waitFor();
|
||||
|
||||
// Verify the output of the jcmd. Note the actual dump is in the debuggee
|
||||
// output, not in the jcmd output, so we don't check it here.
|
||||
System.out.println("JVMTI.data_dump output:");
|
||||
System.out.println(out.getOutput());
|
||||
out.shouldContain("Command executed successfully");
|
||||
out.shouldHaveExitValue(0);
|
||||
}
|
||||
}
|
||||
eventSet.resume();
|
||||
}
|
||||
} catch (VMDisconnectedException e) {
|
||||
System.out.println("VM is now disconnected.");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user