sa.cpp revision 1472
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* 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.
*
*/
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <vector>
#include "sa.hpp"
#include "jni.h"
#include "jvmdi.h"
#ifndef WIN32
#include <inttypes.h>
#else
typedef int int32_t;
#endif
#ifdef WIN32
#include <windows.h>
#define vsnprintf _vsnprintf
#else
#endif
using namespace std;
//////////////////////////////////////////////////////////////////////
// //
// Exported "interface" for Java language-level interaction between //
// the SA and the VM. Note that the SA knows about the layout of //
// certain VM data structures and that knowledge is taken advantage //
// of in this code, although this interfaces with the VM via JVMDI. //
// //
//////////////////////////////////////////////////////////////////////
extern "C" {
/////////////////////////////////////
// //
// Events sent by the VM to the SA //
// //
/////////////////////////////////////
// Set by the SA when it attaches. Indicates that events should be
// posted via these exported variables, and that the VM should wait
// for those events to be acknowledged by the SA (via its setting
// saEventPending to 0).
// Set to nonzero value by the VM when an event has been posted; set
// back to 0 by the SA when it has processed that event.
// Kind of the event (from jvmdi.h)
//
// Exception events
//
//
// Breakpoint events
//
///////////////////////////////////////
// //
// Commands sent by the SA to the VM //
// //
///////////////////////////////////////
// SA sets this to a nonzero value when it is requesting a command
// to be processed; VM sets it back to 0 when the command has been
// executed
// SA sets this to one of the manifest constants above to indicate
// the kind of command to be executed
// VM sets this to 0 if the last command succeeded or a nonzero
// value if it failed
// If last command failed, this buffer will contain a descriptive
// error message
//
// Toggling of breakpoint command arguments.
//
// taking a class name, method name and signature, and the iteration
// through the debug information was done in the SA. It turns out
// that doing this work in the target VM is significantly faster,
// and since interactivity when setting and clearing breakpoints is
// important, the solution which resulted in more C/C++ code was used.
//
// Source file name
// Package name ('/' as separator instead of '.')
// Line number
// Output back to SA: indicator whether the last failure of a
// breakpoint toggle command was really an error or just a lack of
// debug information covering the requested line. 0 if not error.
// Valid only if saCmdResult != 0.
// Output back to SA: resulting line number at which the breakpoint
// was set or cleared (valid only if saCmdResult == 0)
// Output back to SA: resulting byte code index at which the
// breakpoint was set or cleared (valid only if saCmdResult == 0)
// Output back to SA: indicator whether the breakpoint operation
// resulted in a set or cleared breakpoint; nonzero if set, zero if
// cleared (valid only if saCmdResult == 0)
// Output back to SA: method name the breakpoint was set in (valid
// only if saCmdResult == 0)
// Output back to SA: method signature (JNI style) the breakpoint
// was set in (valid only if saCmdResult == 0)
}
// Internal state
static bool suspended = false;
class MonitorLocker {
private:
public:
}
}
~MonitorLocker() {
}
}
};
class JvmdiDeallocator {
private:
void* ptr;
public:
JvmdiDeallocator(void* ptr) {
}
~JvmdiDeallocator() {
}
};
class JvmdiRefListDeallocator {
private:
public:
}
for (int i = 0; i < refCount; i++) {
}
}
};
static void
exit(1);
}
// This fills in the command result error message, sets the command
// result to -1, and clears the pending command flag
static void
reportErrorToSA(const char* str, ...) {
saCmdResult = -1;
saCmdPending = 0;
}
static bool
return false;
}
return false;
}
// Ensure that '/' is the next character if non-empty package name
int l = pkgLen;
if (l > 0) {
if (clazzName[l] != '/') {
return false;
}
l++;
}
// Ensure that there are no more trailing slashes
while (l < clazzNameLen) {
if (clazzName[l++] == '/') {
return false;
}
}
return true;
}
static void
switch (saCmdType) {
case SA_CMD_SUSPEND_ALL: {
if (suspended) {
reportErrorToSA("Target process already suspended");
return;
}
// We implement this by getting all of the threads and calling
// SuspendThread on each one, except for the thread object
// corresponding to this thread. Each thread for which the call
// succeeded (i.e., did not return JVMDI_ERROR_INVALID_THREAD)
// is added to a list which is remembered for later resumption.
// Note that this currently has race conditions since a thread
// might be started after we call GetAllThreads and since a
// thread for which we got an error earlier might be resumed by
// the VM while we are busy suspending other threads. We could
// solve this by looping until there are no more threads we can
// suspend, but a more robust and scalable solution is to add
// this functionality to the JVMDI interface (i.e.,
// "suspendAll"). Probably need to provide an exclude list for
// such a routine.
reportErrorToSA("Error while getting thread list");
return;
}
for (int i = 0; i < threadCount; i++) {
if (err == JVMDI_ERROR_NONE) {
// Remember this thread and do not free it
continue;
} else {
// FIXME: stop, resume all threads, report error
}
}
}
// Free up threads
// Suspension is complete
suspended = true;
break;
}
case SA_CMD_RESUME_ALL: {
if (!suspended) {
reportErrorToSA("Target process already suspended");
return;
}
saCmdResult = 0;
bool errorOccurred = false;
for (int i = 0; i < suspendedThreads.size(); i++) {
if (err != JVMDI_ERROR_NONE) {
if (!errorOccurred) {
errorOccurred = true;
firstError = err;
}
}
}
suspended = false;
if (errorOccurred) {
return;
}
break;
}
case SA_CMD_TOGGLE_BREAKPOINT: {
saCmdBkptResWasError = 1;
// Search line number info for all loaded classes
if (glcRes != JVMDI_ERROR_NONE) {
return;
}
bool done = false;
bool gotOne = false;
for (int i = 0; i < classCount && !done; i++) {
char* srcName;
if (sfnRes == JVMDI_ERROR_NONE) {
// Got a match. Now see whether the package name of the class also matches
char* clazzName;
if (sigRes != JVMDI_ERROR_NONE) {
return;
}
// Iterate through all methods
return;
}
for (int j = 0; j < methodCount && !done; j++) {
if (lnRes == JVMDI_ERROR_NONE) {
// Look for line number greater than or equal to requested line
for (int k = 0; k < entryCount && !done; k++) {
gotOne = true;
targetClass = clazz;
targetMethod = m;
}
}
} else if (lnRes != JVMDI_ERROR_ABSENT_INFORMATION) {
return;
}
}
}
}
} else if (sfnRes != JVMDI_ERROR_ABSENT_INFORMATION) {
return;
}
}
bool wasSet = true;
if (gotOne) {
// Really toggle this breakpoint
if (bpRes == JVMDI_ERROR_DUPLICATE) {
wasSet = false;
}
if (bpRes != JVMDI_ERROR_NONE) {
reportErrorToSA("Unexpected error %d while setting or clearing breakpoint at bci %d, line %d",
return;
}
} else {
saCmdBkptResWasError = 0;
reportErrorToSA("No debug information found covering this line");
return;
}
// Provide result
{
char* methodName;
char* methodSig;
== JVMDI_ERROR_NONE) {
} else {
}
}
break;
}
default:
return;
}
// Successful command execution
saCmdResult = 0;
saCmdPending = 0;
}
static void
saCommandThread(void *arg) {
stop("Error while starting Serviceability Agent "
"command thread: could not get JNI environment");
}
while (1) {
// Wait for command
while (!saCmdPending) {
SLEEP();
}
}
}
static void
{
// Create event lock
!= JVMDI_ERROR_NONE) {
stop("Unable to create Serviceability Agent's event lock");
}
// Start thread which receives commands from the SA.
// Allocate thread object
// Remember which thread this is
// Start thread
!= JVMDI_ERROR_NONE) {
char buf[256];
}
// OK, initialization is done
return;
}
if (!saAttached) {
return;
}
case JVMDI_EVENT_EXCEPTION: {
// saEventPending = 1;
break;
}
case JVMDI_EVENT_BREAKPOINT: {
saEventPending = 1;
break;
}
default:
break;
}
while (saAttached && saEventPending) {
SLEEP();
}
}
extern "C" {
{
return -1;
}
return -1;
}
return 0;
}
};