blob: 8f8efde5d242ee6f7d30f2bd1a8ea6661d63ea8e [file] [log] [blame]
/*
* Copyright 2000-2002 Sun Microsystems, Inc. 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*
*/
// This is the implementation of a very simple dbx import module which
// handles requests from the VM which come in over a socket. The
// higher-level Java wrapper for dbx starts the debugger, attaches to
// the process, imports this command, and runs it. After that, the SA
// writes commands to this agent via its own private communications
// channel. The intent is to move away from the text-based front-end
// completely in the near future (no more calling "debug" by printing
// text to dbx's stdin).
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <stropts.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <proc_service.h>
#include <sys/procfs_isa.h>
#include <rtld_db.h>
#include "proc_service_2.h"
#include "svc_agent_dbx.hpp"
static ServiceabilityAgentDbxModule* module = NULL;
#define NEEDS_CLEANUP
// Useful for debugging
#define VERBOSE_DEBUGGING
#ifdef VERBOSE_DEBUGGING
# define debug_only(x) x
#else
# define debug_only(x)
#endif
// For profiling
//#define PROFILING
#ifdef PROFILING
#define PROFILE_COUNT 200
static Timer scanTimer;
static Timer workTimer;
static Timer writeTimer;
static int numRequests = 0;
#endif /* PROFILING */
const char* ServiceabilityAgentDbxModule::CMD_ADDRESS_SIZE = "address_size";
const char* ServiceabilityAgentDbxModule::CMD_PEEK_FAIL_FAST = "peek_fail_fast";
const char* ServiceabilityAgentDbxModule::CMD_PEEK = "peek";
const char* ServiceabilityAgentDbxModule::CMD_POKE = "poke";
const char* ServiceabilityAgentDbxModule::CMD_MAPPED = "mapped";
const char* ServiceabilityAgentDbxModule::CMD_LOOKUP = "lookup";
const char* ServiceabilityAgentDbxModule::CMD_THR_GREGS = "thr_gregs";
const char* ServiceabilityAgentDbxModule::CMD_EXIT = "exit";
// The initialization routines must not have C++ name mangling
extern "C" {
/** This is the initialization routine called by dbx upon importing of
this module. Returns 0 upon successful initialization, -1 upon
failure. */
int shell_imp_init(int major, int minor,
shell_imp_interp_t interp, int argc, char *argv[])
{
// Ensure shell interpreter data structure is laid out the way we
// expect
if (major != SHELL_IMP_MAJOR) {
debug_only(fprintf(stderr, "Serviceability agent: unexpected value for SHELL_IMP_MAJOR (got %d, expected %d)\n", major, SHELL_IMP_MAJOR);)
return -1;
}
if (minor < SHELL_IMP_MINOR) {
debug_only(fprintf(stderr, "Serviceability agent: unexpected value for SHELL_IMP_MINOR (got %d, expected >= %d)\n", minor, SHELL_IMP_MINOR);)
return -1;
}
if (module != NULL) {
debug_only(fprintf(stderr, "Serviceability agent: module appears to already be initialized (should not happen)\n");)
// Already initialized. Should not happen.
return -1;
}
module = new ServiceabilityAgentDbxModule(major, minor, interp, argc, argv);
if (!module->install()) {
debug_only(fprintf(stderr, "Serviceability agent: error installing import module\n");)
delete module;
module = NULL;
return -1;
}
// Installation was successful. Next step will be for the user to
// enter the appropriate command on the command line, which will
// make the SA's dbx module wait for commands to come in over the
// socket.
return 0;
}
/** This is the routine called by dbx upon unloading of this module.
Returns 0 upon success, -1 upon failure. */
int
shell_imp_fini(shell_imp_interp_t)
{
if (module == NULL) {
return -1;
}
bool res = module->uninstall();
delete module;
module = NULL;
if (!res) {
return -1;
}
return 0;
}
} // extern "C"
/** This is the routine which is called by the dbx shell when the user
requests the serviceability agent module to run. This delegates to
ServiceabilityAgentDbxModule::run. This routine's signature must
match that of shell_imp_fun_t. */
extern "C" {
static int
svc_agent_run(shell_imp_interp_t, int, char **, void *) {
if (module == NULL) {
return -1;
}
module->run();
return 0;
}
}
/*
* Implementation of ServiceabilityAgentDbxModule class
*/
// NOTE: we need to forward declare the special "ps_get_prochandle2"
// function which allows examination of core files as well. It isn't
// currently in proc_service_2.h. Note also that it has name mangling
// because it isn't declared extern "C".
//const struct ps_prochandle *ps_get_prochandle2(int cores_too);
ServiceabilityAgentDbxModule::ServiceabilityAgentDbxModule(int, int, shell_imp_interp_t interp,
int argc, char *argv[])
:myComm(32768, 131072)
{
_interp = interp;
_argc = argc;
_argv = argv;
_tdb_agent = NULL;
peek_fail_fast = false;
libThreadName = NULL;
}
ServiceabilityAgentDbxModule::~ServiceabilityAgentDbxModule() {
if (_command != NULL) {
uninstall();
}
}
char*
readCStringFromProcess(psaddr_t addr) {
char c;
int num = 0;
ps_prochandle* cur_proc = (ps_prochandle*) ps_get_prochandle2(1);
// Search for null terminator
do {
if (ps_pread(cur_proc, addr + num, &c, 1) != PS_OK) {
return NULL;
}
++num;
} while (c != 0);
// Allocate string
char* res = new char[num];
if (ps_pread(cur_proc, addr, res, num) != PS_OK) {
delete[] res;
return NULL;
}
return res;
}
int
findLibThreadCB(const rd_loadobj_t* lo, void* data) {
ServiceabilityAgentDbxModule* module = (ServiceabilityAgentDbxModule*) data;
char* name = readCStringFromProcess(lo->rl_nameaddr);
if (strstr(name, "libthread.so") != NULL) {
module->libThreadName = name;
return 0;
} else {
delete[] name;
return 1;
}
}
bool
ServiceabilityAgentDbxModule::install() {
// NOTE interdependency between here and Java side wrapper
// FIXME: casts of string literal to char * to match prototype
_command = shell_imp_define_command((char *) "svc_agent_run",
&svc_agent_run,
0,
NULL,
(char *) "Run the serviceability agent's dbx module.\n"
"This routine causes the module to listen on a socket for requests.\n"
"It does not return until the Java-side code tells it to exit, at\n"
"which point control is returned to the dbx shell.");
if (_command == NULL) {
debug_only(fprintf(stderr, "Serviceability agent: Failed to install svc_agent_run command\n"));
return false;
}
// This is fairly painful. Since dbx doesn't currently load
// libthread_db with RTLD_GLOBAL, we can't just use RTLD_DEFAULT for
// the argument to dlsym. Instead, we have to use rtld_db to search
// through the loaded objects in the target process for libthread.so and
// Try rtld_db
if (rd_init(RD_VERSION) != RD_OK) {
debug_only(fprintf(stderr, "Serviceability agent: Unable to init rtld_db\n"));
return false;
}
rd_agent_t* rda = rd_new((struct ps_prochandle*) ps_get_prochandle2(1));
if (rda == NULL) {
debug_only(fprintf(stderr, "Serviceability agent: Unable to allocate rtld_db agent\n"));
return false;
}
if (rd_loadobj_iter(rda, (rl_iter_f*) findLibThreadCB, this) != RD_OK) {
debug_only(fprintf(stderr, "Serviceability agent: Loadobject iteration failed\n"));
return false;
}
if (libThreadName == NULL) {
debug_only(fprintf(stderr, "Serviceability agent: Failed to find pathname to libthread.so in target process\n"));
return false;
}
// Find and open libthread_db.so
char* slash = strrchr(libThreadName, '/');
if (slash == NULL) {
debug_only(fprintf(stderr, "Serviceability agent: can't parse path to libthread.so \"%s\"\n"));
return false;
}
int slashPos = slash - libThreadName;
char* buf = new char[slashPos + strlen("libthread_db.so") + 20]; // slop
if (buf == NULL) {
debug_only(fprintf(stderr, "Serviceability agent: error allocating libthread_db.so pathname\n"));
return false;
}
strncpy(buf, libThreadName, slashPos + 1);
// Check dbx's data model; use sparcv9/ subdirectory if 64-bit and
// if target process is 32-bit
if ((sizeof(void*) == 8) &&
(strstr(libThreadName, "sparcv9") == NULL)) {
strcpy(buf + slashPos + 1, "sparcv9/");
slashPos += strlen("sparcv9/");
}
strcpy(buf + slashPos + 1, "libthread_db.so");
libThreadDB = dlopen(buf, RTLD_LAZY);
void* tmpDB = libThreadDB;
if (libThreadDB == NULL) {
debug_only(fprintf(stderr, "Serviceability agent: Warning: unable to find libthread_db.so at \"%s\"\n", buf));
// Would like to handle this case as well. Maybe dbx has a better
// idea of where libthread_db.so lies. If the problem with dbx
// loading libthread_db without RTLD_GLOBAL specified ever gets
// fixed, we could run this code all the time.
tmpDB = RTLD_DEFAULT;
}
delete[] buf;
// Initialize access to libthread_db
td_init_fn = (td_init_fn_t*) dlsym(tmpDB, "td_init");
td_ta_new_fn = (td_ta_new_fn_t*) dlsym(tmpDB, "td_ta_new");
td_ta_delete_fn = (td_ta_delete_fn_t*) dlsym(tmpDB, "td_ta_delete");
td_ta_map_id2thr_fn = (td_ta_map_id2thr_fn_t*) dlsym(tmpDB, "td_ta_map_id2thr");
td_thr_getgregs_fn = (td_thr_getgregs_fn_t*) dlsym(tmpDB, "td_thr_getgregs");
if (td_init_fn == NULL ||
td_ta_new_fn == NULL ||
td_ta_delete_fn == NULL ||
td_ta_map_id2thr_fn == NULL ||
td_thr_getgregs_fn == NULL) {
debug_only(fprintf(stderr, "Serviceability agent: Failed to find one or more libthread_db symbols:\n"));
debug_only(if (td_init_fn == NULL) fprintf(stderr, " td_init\n"));
debug_only(if (td_ta_new_fn == NULL) fprintf(stderr, " td_ta_new\n"));
debug_only(if (td_ta_delete_fn == NULL) fprintf(stderr, " td_ta_delete\n"));
debug_only(if (td_ta_map_id2thr_fn == NULL) fprintf(stderr, " td_ta_map_id2thr\n"));
debug_only(if (td_thr_getgregs_fn == NULL) fprintf(stderr, " td_thr_getgregs\n"));
return false;
}
if ((*td_init_fn)() != TD_OK) {
debug_only(fprintf(stderr, "Serviceability agent: Failed to initialize libthread_db\n"));
return false;
}
return true;
}
bool
ServiceabilityAgentDbxModule::uninstall() {
if (_command == NULL) {
return false;
}
if (libThreadDB != NULL) {
dlclose(libThreadDB);
libThreadDB = NULL;
}
int res = shell_imp_undefine_command(_command);
if (res != 0) {
return false;
}
return true;
}
bool
ServiceabilityAgentDbxModule::run() {
// This is where most of the work gets done.
// The command processor loop looks like the following:
// - create listening socket
// - accept a connection (only one for now)
// - while that connection is open and the "exit" command has not
// been received:
// - read command
// - if it's the exit command, cleanup and return
// - otherwise, process command and write result
int listening_socket = socket(AF_INET, SOCK_STREAM, 0);
if (listening_socket < 0) {
return false;
}
// Set the SO_REUSEADDR property on the listening socket. This
// prevents problems with calls to bind() to the same port failing
// after this process exits. This seems to work on all platforms.
int reuse_address = 1;
if (setsockopt(listening_socket, SOL_SOCKET, SO_REUSEADDR,
(char *)&reuse_address, sizeof(reuse_address)) < 0) {
close(listening_socket);
return false;
}
sockaddr_in server_address;
// Build the server address. We can bind the listening socket to the
// INADDR_ANY internet address.
memset((char*)&server_address, 0, sizeof(server_address));
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = (unsigned long)htonl(INADDR_ANY);
server_address.sin_port = htons((short)PORT);
// Bind socket to port
if (bind(listening_socket, (sockaddr*) &server_address,
sizeof(server_address)) < 0) {
close(listening_socket);
return false;
}
// Arbitrarily chosen backlog of 5 (shouldn't matter since we expect
// at most one connection)
if (listen(listening_socket, 5) < 0) {
close(listening_socket);
return false;
}
// OK, now ready to wait for a data connection. This call to
// accept() will block.
struct sockaddr_in client_address;
int address_len = sizeof(client_address);
int client_socket = accept(listening_socket, (sockaddr*) &client_address,
&address_len);
// Close listening socket regardless of whether accept() succeeded.
// (FIXME: this may be annoying, especially during debugging, but I
// really feel that robustness and multiple connections should be
// handled higher up, e.g., at the Java level -- multiple clients
// could conceivably connect to the SA via RMI, and that would be a
// more robust solution than implementing multiple connections at
// this level)
NEEDS_CLEANUP;
// NOTE: the call to shutdown() usually fails, so don't panic if this happens
shutdown(listening_socket, 2);
if (close(listening_socket) < 0) {
debug_only(fprintf(stderr, "Serviceability agent: Error closing listening socket\n"));
return false;
}
if (client_socket < 0) {
debug_only(fprintf(stderr, "Serviceability agent: Failed to open client socket\n"));
// No more cleanup necessary
return false;
}
// Attempt to disable TCP buffering on this socket. We send small
// amounts of data back and forth and don't want buffering.
int buffer_val = 1;
if (setsockopt(client_socket, IPPROTO_IP, TCP_NODELAY, (char *) &buffer_val, sizeof(buffer_val)) < 0) {
debug_only(fprintf(stderr, "Serviceability agent: Failed to set TCP_NODELAY option on client socket\n"));
cleanup(client_socket);
return false;
}
// OK, we have the data socket through which we will communicate
// with the Java side. Wait for commands or until reading or writing
// caused an error.
bool should_continue = true;
myComm.setSocket(client_socket);
#ifdef PROFILING
scanTimer.reset();
workTimer.reset();
writeTimer.reset();
#endif
// Allocate a new thread agent for libthread_db
if ((*td_ta_new_fn)((ps_prochandle*) ps_get_prochandle2(1), &_tdb_agent) !=
TD_OK) {
debug_only(fprintf(stderr, "Serviceability agent: Failed to allocate thread agent\n"));
cleanup(client_socket);
return false;
}
do {
// Decided to use text to communicate between these processes.
// Probably will make debugging easier -- could telnet in if
// necessary. Will make scanning harder, but probably doesn't
// matter.
// Why not just do what workshop does and parse dbx's console?
// Probably could do that, but at least this way we are in control
// of the text format on both ends.
// FIXME: should have some way of synchronizing these commands
// between the C and Java sources.
NEEDS_CLEANUP;
// Do a blocking read of a line from the socket.
char *input_buffer = myComm.readLine();
if (input_buffer == NULL) {
debug_only(fprintf(stderr, "Serviceability agent: error during read: errno = %d\n", errno));
debug_only(perror("Serviceability agent"));
// Error occurred during read.
// FIXME: should guard against SIGPIPE
cleanup(client_socket);
return false;
}
// OK, now ready to scan. See README-commands.txt for syntax
// descriptions.
bool res = false;
if (!strncmp(input_buffer, CMD_ADDRESS_SIZE, strlen(CMD_ADDRESS_SIZE))) {
res = handleAddressSize(input_buffer + strlen(CMD_ADDRESS_SIZE));
} else if (!strncmp(input_buffer, CMD_PEEK_FAIL_FAST, strlen(CMD_PEEK_FAIL_FAST))) {
res = handlePeekFailFast(input_buffer + strlen(CMD_PEEK_FAIL_FAST));
} else if (!strncmp(input_buffer, CMD_PEEK, strlen(CMD_PEEK))) {
res = handlePeek(input_buffer + strlen(CMD_PEEK));
} else if (!strncmp(input_buffer, CMD_POKE, strlen(CMD_POKE))) {
res = handlePoke(input_buffer + strlen(CMD_POKE));
} else if (!strncmp(input_buffer, CMD_MAPPED, strlen(CMD_MAPPED))) {
res = handleMapped(input_buffer + strlen(CMD_MAPPED));
} else if (!strncmp(input_buffer, CMD_LOOKUP, strlen(CMD_LOOKUP))) {
res = handleLookup(input_buffer + strlen(CMD_LOOKUP));
} else if (!strncmp(input_buffer, CMD_THR_GREGS, strlen(CMD_THR_GREGS))) {
res = handleThrGRegs(input_buffer + strlen(CMD_THR_GREGS));
} else if (!strncmp(input_buffer, CMD_EXIT, strlen(CMD_EXIT))) {
should_continue = false;
}
if (should_continue) {
if (!res) {
cleanup(client_socket);
return false;
}
}
#ifdef PROFILING
if (++numRequests == PROFILE_COUNT) {
fprintf(stderr, "%d requests: %d ms scanning, %d ms work, %d ms writing\n",
PROFILE_COUNT, scanTimer.total(), workTimer.total(), writeTimer.total());
fflush(stderr);
scanTimer.reset();
workTimer.reset();
writeTimer.reset();
numRequests = 0;
}
#endif
} while (should_continue);
// Successful exit
cleanup(client_socket);
return true;
}
void
ServiceabilityAgentDbxModule::cleanup(int client_socket) {
shutdown(client_socket, 2);
close(client_socket);
if (_tdb_agent != NULL) {
(*td_ta_delete_fn)(_tdb_agent);
}
}
bool
ServiceabilityAgentDbxModule::handleAddressSize(char* data) {
int data_model;
ps_err_e result = ps_pdmodel((ps_prochandle*) ps_get_prochandle2(1),
&data_model);
if (result != PS_OK) {
myComm.writeString("0");
myComm.flush();
return false;
}
int val;
switch (data_model) {
case PR_MODEL_ILP32:
val = 32;
break;
case PR_MODEL_LP64:
val = 64;
break;
default:
val = 0;
break;
}
if (!myComm.writeInt(val)) {
return false;
}
if (!myComm.writeEOL()) {
return false;
}
return myComm.flush();
}
bool
ServiceabilityAgentDbxModule::handlePeekFailFast(char* data) {
unsigned int val;
if (!scanUnsignedInt(&data, &val)) {
return false;
}
peek_fail_fast = (val ? true : false);
return true;
}
bool
ServiceabilityAgentDbxModule::handlePeek(char* data) {
// Scan hex address, return false if failed
psaddr_t addr;
#ifdef PROFILING
scanTimer.start();
#endif /* PROFILING */
if (!scanAddress(&data, &addr)) {
return false;
}
unsigned int num;
if (!scanUnsignedInt(&data, &num)) {
return false;
}
if (num == 0) {
#ifdef PROFILING
writeTimer.start();
#endif /* PROFILING */
myComm.writeBinChar('B');
myComm.writeBinChar(1);
myComm.writeBinUnsignedInt(0);
myComm.writeBinChar(0);
#ifdef PROFILING
writeTimer.stop();
#endif /* PROFILING */
return true;
}
#ifdef PROFILING
scanTimer.stop();
workTimer.start();
#endif /* PROFILING */
char* buf = new char[num];
ps_prochandle* cur_proc = (ps_prochandle*) ps_get_prochandle2(1);
ps_err_e result = ps_pread(cur_proc, addr, buf, num);
if (result == PS_OK) {
// Fast case; entire read succeeded.
#ifdef PROFILING
workTimer.stop();
writeTimer.start();
#endif /* PROFILING */
myComm.writeBinChar('B');
myComm.writeBinChar(1);
myComm.writeBinUnsignedInt(num);
myComm.writeBinChar(1);
myComm.writeBinBuf(buf, num);
#ifdef PROFILING
writeTimer.stop();
#endif /* PROFILING */
} else {
#ifdef PROFILING
workTimer.stop();
#endif /* PROFILING */
if (peek_fail_fast) {
#ifdef PROFILING
writeTimer.start();
#endif /* PROFILING */
// Fail fast
myComm.writeBinChar('B');
myComm.writeBinChar(1);
myComm.writeBinUnsignedInt(num);
myComm.writeBinChar(0);
#ifdef PROFILING
writeTimer.stop();
#endif /* PROFILING */
} else {
// Slow case: try to read one byte at a time
// FIXME: need better way of handling this, a la VirtualQuery
unsigned int strideLen = 0;
int bufIdx = 0;
bool lastByteMapped = (ps_pread(cur_proc, addr, buf, 1) == PS_OK ? true : false);
#ifdef PROFILING
writeTimer.start();
#endif /* PROFILING */
myComm.writeBinChar('B');
myComm.writeBinChar(1);
#ifdef PROFILING
writeTimer.stop();
#endif /* PROFILING */
for (int i = 0; i < num; ++i, ++addr) {
#ifdef PROFILING
workTimer.start();
#endif /* PROFILING */
result = ps_pread(cur_proc, addr, &buf[bufIdx], 1);
#ifdef PROFILING
workTimer.stop();
#endif /* PROFILING */
bool tmpMapped = (result == PS_OK ? true : false);
#ifdef PROFILING
writeTimer.start();
#endif /* PROFILING */
if (tmpMapped != lastByteMapped) {
// State change. Write the length of the last stride.
myComm.writeBinUnsignedInt(strideLen);
if (lastByteMapped) {
// Stop gathering data. Write the data of the last stride.
myComm.writeBinChar(1);
myComm.writeBinBuf(buf, strideLen);
bufIdx = 0;
} else {
// Start gathering data to write.
myComm.writeBinChar(0);
}
strideLen = 0;
lastByteMapped = tmpMapped;
}
#ifdef PROFILING
writeTimer.stop();
#endif /* PROFILING */
if (lastByteMapped) {
++bufIdx;
}
++strideLen;
}
// Write last stride (must be at least one byte long by definition)
#ifdef PROFILING
writeTimer.start();
#endif /* PROFILING */
myComm.writeBinUnsignedInt(strideLen);
if (lastByteMapped) {
myComm.writeBinChar(1);
myComm.writeBinBuf(buf, strideLen);
} else {
myComm.writeBinChar(0);
}
#ifdef PROFILING
writeTimer.stop();
#endif /* PROFILING */
}
}
delete[] buf;
myComm.flush();
return true;
}
bool
ServiceabilityAgentDbxModule::handlePoke(char* data) {
// FIXME: not yet implemented
NEEDS_CLEANUP;
bool res = myComm.writeBoolAsInt(false);
myComm.flush();
return res;
}
bool
ServiceabilityAgentDbxModule::handleMapped(char* data) {
// Scan address
psaddr_t addr;
if (!scanAddress(&data, &addr)) {
return false;
}
unsigned int num;
if (!scanUnsignedInt(&data, &num)) {
return false;
}
unsigned char val;
ps_prochandle* cur_proc = (ps_prochandle*) ps_get_prochandle2(1);
char* buf = new char[num];
if (ps_pread(cur_proc, addr, buf, num) == PS_OK) {
myComm.writeBoolAsInt(true);
} else {
myComm.writeBoolAsInt(false);
}
delete[] buf;
myComm.writeEOL();
myComm.flush();
return true;
}
extern "C"
int loadobj_iterator(const rd_loadobj_t* loadobj, void *) {
if (loadobj != NULL) {
fprintf(stderr, "loadobj_iterator: visited loadobj \"%p\"\n", (void*) loadobj->rl_nameaddr);
return 1;
}
fprintf(stderr, "loadobj_iterator: NULL loadobj\n");
return 0;
}
bool
ServiceabilityAgentDbxModule::handleLookup(char* data) {
// Debugging: iterate over loadobjs
/*
rd_agent_t* rld_agent = rd_new((ps_prochandle*) ps_get_prochandle2(1));
rd_loadobj_iter(rld_agent, &loadobj_iterator, NULL);
rd_delete(rld_agent);
*/
#ifdef PROFILING
scanTimer.start();
#endif /* PROFILING */
char* object_name = scanSymbol(&data);
if (object_name == NULL) {
return false;
}
char* symbol_name = scanSymbol(&data);
if (symbol_name == NULL) {
delete[] object_name;
return false;
}
#ifdef PROFILING
scanTimer.stop();
workTimer.start();
#endif /* PROFILING */
ps_sym_t sym;
// FIXME: check return values from write routines
ps_prochandle* process = (ps_prochandle*) ps_get_prochandle2(1);
ps_err_e lookup_res = ps_pglobal_sym(process,
object_name, symbol_name, &sym);
#ifdef PROFILING
workTimer.stop();
writeTimer.start();
#endif /* PROFILING */
delete[] object_name;
delete[] symbol_name;
if (lookup_res != PS_OK) {
// This is too noisy
// debug_only(fprintf(stderr, "ServiceabilityAgentDbxModule::handleLookup: error %d\n", lookup_res));
myComm.writeString("0x0");
} else {
myComm.writeAddress((void *)sym.st_value);
}
myComm.writeEOL();
myComm.flush();
#ifdef PROFILING
writeTimer.stop();
#endif /* PROFILING */
return true;
}
bool
ServiceabilityAgentDbxModule::handleThrGRegs(char* data) {
#ifdef PROFILING
scanTimer.start();
#endif /* PROFILING */
unsigned int num;
// Get the thread ID
if (!scanUnsignedInt(&data, &num)) {
return false;
}
#ifdef PROFILING
scanTimer.stop();
workTimer.start();
#endif /* PROFILING */
// Map tid to thread handle
td_thrhandle_t thread_handle;
if ((*td_ta_map_id2thr_fn)(_tdb_agent, num, &thread_handle) != TD_OK) {
// fprintf(stderr, "Error mapping thread ID %d to thread handle\n", num);
return false;
}
// Fetch register set
prgregset_t reg_set;
memset(reg_set, 0, sizeof(reg_set));
td_err_e result = (*td_thr_getgregs_fn)(&thread_handle, reg_set);
if ((result != TD_OK) && (result != TD_PARTIALREG)) {
// fprintf(stderr, "Error fetching registers for thread handle %d: error = %d\n", num, result);
return false;
}
#ifdef PROFILING
workTimer.stop();
writeTimer.start();
#endif /* PROFILING */
#if (defined(__sparc) || defined(__i386))
myComm.writeInt(NPRGREG);
myComm.writeSpace();
for (int i = 0; i < NPRGREG; i++) {
myComm.writeAddress((void *)reg_set[i]);
if (i == NPRGREG - 1) {
myComm.writeEOL();
} else {
myComm.writeSpace();
}
}
#else
#error Please port ServiceabilityAgentDbxModule::handleThrGRegs to your current platform
#endif
myComm.flush();
#ifdef PROFILING
writeTimer.stop();
#endif /* PROFILING */
return true;
}
//
// Input routines
//
bool
ServiceabilityAgentDbxModule::scanAddress(char** data, psaddr_t* addr) {
*addr = 0;
// Skip whitespace
while ((**data != 0) && (isspace(**data))) {
++*data;
}
if (**data == 0) {
return false;
}
if (strncmp(*data, "0x", 2) != 0) {
return false;
}
*data += 2;
while ((**data != 0) && (!isspace(**data))) {
int val;
bool res = charToNibble(**data, &val);
if (!res) {
return false;
}
*addr <<= 4;
*addr |= val;
++*data;
}
return true;
}
bool
ServiceabilityAgentDbxModule::scanUnsignedInt(char** data, unsigned int* num) {
*num = 0;
// Skip whitespace
while ((**data != 0) && (isspace(**data))) {
++*data;
}
if (**data == 0) {
return false;
}
while ((**data != 0) && (!isspace(**data))) {
char cur = **data;
if ((cur < '0') || (cur > '9')) {
return false;
}
*num *= 10;
*num += cur - '0';
++*data;
}
return true;
}
char*
ServiceabilityAgentDbxModule::scanSymbol(char** data) {
// Skip whitespace
while ((**data != 0) && (isspace(**data))) {
++*data;
}
if (**data == 0) {
return NULL;
}
// First count length
int len = 1; // Null terminator
char* tmpData = *data;
while ((*tmpData != 0) && (!isspace(*tmpData))) {
++tmpData;
++len;
}
char* buf = new char[len];
strncpy(buf, *data, len - 1);
buf[len - 1] = 0;
*data += len - 1;
return buf;
}
bool
ServiceabilityAgentDbxModule::charToNibble(char ascii, int* value) {
if (ascii >= '0' && ascii <= '9') {
*value = ascii - '0';
return true;
} else if (ascii >= 'A' && ascii <= 'F') {
*value = 10 + ascii - 'A';
return true;
} else if (ascii >= 'a' && ascii <= 'f') {
*value = 10 + ascii - 'a';
return true;
}
return false;
}
char*
ServiceabilityAgentDbxModule::readCStringFromProcess(psaddr_t addr) {
char c;
int num = 0;
ps_prochandle* cur_proc = (ps_prochandle*) ps_get_prochandle2(1);
// Search for null terminator
do {
if (ps_pread(cur_proc, addr + num, &c, 1) != PS_OK) {
return NULL;
}
++num;
} while (c != 0);
// Allocate string
char* res = new char[num];
if (ps_pread(cur_proc, addr, res, num) != PS_OK) {
delete[] res;
return NULL;
}
return res;
}
//--------------------------------------------------------------------------------
// Class Timer
//
Timer::Timer() {
reset();
}
Timer::~Timer() {
}
void
Timer::start() {
gettimeofday(&startTime, NULL);
}
void
Timer::stop() {
struct timeval endTime;
gettimeofday(&endTime, NULL);
totalMicroseconds += timevalDiff(&startTime, &endTime);
++counter;
}
long
Timer::total() {
return (totalMicroseconds / 1000);
}
long
Timer::average() {
return (long) ((double) total() / (double) counter);
}
void
Timer::reset() {
totalMicroseconds = 0;
counter = 0;
}
long long
Timer::timevalDiff(struct timeval* start, struct timeval* end) {
long long secs = end->tv_sec - start->tv_sec;
secs *= 1000000;
long long usecs = end->tv_usec - start->tv_usec;
return (secs + usecs);
}