0
|
1 /*
|
|
2 * Copyright 2000-2002 Sun Microsystems, Inc. All Rights Reserved.
|
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
4 *
|
|
5 * This code is free software; you can redistribute it and/or modify it
|
|
6 * under the terms of the GNU General Public License version 2 only, as
|
|
7 * published by the Free Software Foundation.
|
|
8 *
|
|
9 * This code is distributed in the hope that it will be useful, but WITHOUT
|
|
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
12 * version 2 for more details (a copy is included in the LICENSE file that
|
|
13 * accompanied this code).
|
|
14 *
|
|
15 * You should have received a copy of the GNU General Public License version
|
|
16 * 2 along with this work; if not, write to the Free Software Foundation,
|
|
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
18 *
|
|
19 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
|
|
20 * CA 95054 USA or visit www.sun.com if you need additional information or
|
|
21 * have any questions.
|
|
22 *
|
|
23 */
|
|
24
|
|
25 // This is the implementation of a very simple dbx import module which
|
|
26 // handles requests from the VM which come in over a socket. The
|
|
27 // higher-level Java wrapper for dbx starts the debugger, attaches to
|
|
28 // the process, imports this command, and runs it. After that, the SA
|
|
29 // writes commands to this agent via its own private communications
|
|
30 // channel. The intent is to move away from the text-based front-end
|
|
31 // completely in the near future (no more calling "debug" by printing
|
|
32 // text to dbx's stdin).
|
|
33
|
|
34 #include <stdio.h>
|
|
35 #include <errno.h>
|
|
36 #include <ctype.h>
|
|
37 #include <sys/types.h>
|
|
38 #include <sys/socket.h>
|
|
39 #include <unistd.h>
|
|
40 #include <string.h>
|
|
41 #include <stropts.h>
|
|
42 #include <netinet/in.h>
|
|
43 #include <netinet/tcp.h>
|
|
44
|
|
45 #include <proc_service.h>
|
|
46 #include <sys/procfs_isa.h>
|
|
47 #include <rtld_db.h>
|
|
48 #include "proc_service_2.h"
|
|
49 #include "svc_agent_dbx.hpp"
|
|
50
|
|
51 static ServiceabilityAgentDbxModule* module = NULL;
|
|
52 #define NEEDS_CLEANUP
|
|
53
|
|
54 // Useful for debugging
|
|
55 #define VERBOSE_DEBUGGING
|
|
56
|
|
57 #ifdef VERBOSE_DEBUGGING
|
|
58 # define debug_only(x) x
|
|
59 #else
|
|
60 # define debug_only(x)
|
|
61 #endif
|
|
62
|
|
63 // For profiling
|
|
64 //#define PROFILING
|
|
65
|
|
66 #ifdef PROFILING
|
|
67 #define PROFILE_COUNT 200
|
|
68 static Timer scanTimer;
|
|
69 static Timer workTimer;
|
|
70 static Timer writeTimer;
|
|
71 static int numRequests = 0;
|
|
72 #endif /* PROFILING */
|
|
73
|
|
74 const char* ServiceabilityAgentDbxModule::CMD_ADDRESS_SIZE = "address_size";
|
|
75 const char* ServiceabilityAgentDbxModule::CMD_PEEK_FAIL_FAST = "peek_fail_fast";
|
|
76 const char* ServiceabilityAgentDbxModule::CMD_PEEK = "peek";
|
|
77 const char* ServiceabilityAgentDbxModule::CMD_POKE = "poke";
|
|
78 const char* ServiceabilityAgentDbxModule::CMD_MAPPED = "mapped";
|
|
79 const char* ServiceabilityAgentDbxModule::CMD_LOOKUP = "lookup";
|
|
80 const char* ServiceabilityAgentDbxModule::CMD_THR_GREGS = "thr_gregs";
|
|
81 const char* ServiceabilityAgentDbxModule::CMD_EXIT = "exit";
|
|
82
|
|
83 // The initialization routines must not have C++ name mangling
|
|
84 extern "C" {
|
|
85
|
|
86 /** This is the initialization routine called by dbx upon importing of
|
|
87 this module. Returns 0 upon successful initialization, -1 upon
|
|
88 failure. */
|
|
89 int shell_imp_init(int major, int minor,
|
|
90 shell_imp_interp_t interp, int argc, char *argv[])
|
|
91 {
|
|
92 // Ensure shell interpreter data structure is laid out the way we
|
|
93 // expect
|
|
94 if (major != SHELL_IMP_MAJOR) {
|
|
95 debug_only(fprintf(stderr, "Serviceability agent: unexpected value for SHELL_IMP_MAJOR (got %d, expected %d)\n", major, SHELL_IMP_MAJOR);)
|
|
96 return -1;
|
|
97 }
|
|
98 if (minor < SHELL_IMP_MINOR) {
|
|
99 debug_only(fprintf(stderr, "Serviceability agent: unexpected value for SHELL_IMP_MINOR (got %d, expected >= %d)\n", minor, SHELL_IMP_MINOR);)
|
|
100 return -1;
|
|
101 }
|
|
102
|
|
103 if (module != NULL) {
|
|
104 debug_only(fprintf(stderr, "Serviceability agent: module appears to already be initialized (should not happen)\n");)
|
|
105 // Already initialized. Should not happen.
|
|
106 return -1;
|
|
107 }
|
|
108
|
|
109 module = new ServiceabilityAgentDbxModule(major, minor, interp, argc, argv);
|
|
110 if (!module->install()) {
|
|
111 debug_only(fprintf(stderr, "Serviceability agent: error installing import module\n");)
|
|
112 delete module;
|
|
113 module = NULL;
|
|
114 return -1;
|
|
115 }
|
|
116
|
|
117 // Installation was successful. Next step will be for the user to
|
|
118 // enter the appropriate command on the command line, which will
|
|
119 // make the SA's dbx module wait for commands to come in over the
|
|
120 // socket.
|
|
121 return 0;
|
|
122 }
|
|
123
|
|
124 /** This is the routine called by dbx upon unloading of this module.
|
|
125 Returns 0 upon success, -1 upon failure. */
|
|
126 int
|
|
127 shell_imp_fini(shell_imp_interp_t)
|
|
128 {
|
|
129 if (module == NULL) {
|
|
130 return -1;
|
|
131 }
|
|
132
|
|
133 bool res = module->uninstall();
|
|
134 delete module;
|
|
135 module = NULL;
|
|
136 if (!res) {
|
|
137 return -1;
|
|
138 }
|
|
139 return 0;
|
|
140 }
|
|
141
|
|
142 } // extern "C"
|
|
143
|
|
144 /** This is the routine which is called by the dbx shell when the user
|
|
145 requests the serviceability agent module to run. This delegates to
|
|
146 ServiceabilityAgentDbxModule::run. This routine's signature must
|
|
147 match that of shell_imp_fun_t. */
|
|
148 extern "C" {
|
|
149 static int
|
|
150 svc_agent_run(shell_imp_interp_t, int, char **, void *) {
|
|
151 if (module == NULL) {
|
|
152 return -1;
|
|
153 }
|
|
154
|
|
155 module->run();
|
|
156 return 0;
|
|
157 }
|
|
158 }
|
|
159
|
|
160 /*
|
|
161 * Implementation of ServiceabilityAgentDbxModule class
|
|
162 */
|
|
163
|
|
164 // NOTE: we need to forward declare the special "ps_get_prochandle2"
|
|
165 // function which allows examination of core files as well. It isn't
|
|
166 // currently in proc_service_2.h. Note also that it has name mangling
|
|
167 // because it isn't declared extern "C".
|
|
168 //const struct ps_prochandle *ps_get_prochandle2(int cores_too);
|
|
169
|
|
170 ServiceabilityAgentDbxModule::ServiceabilityAgentDbxModule(int, int, shell_imp_interp_t interp,
|
|
171 int argc, char *argv[])
|
|
172 :myComm(32768, 131072)
|
|
173 {
|
|
174 _interp = interp;
|
|
175 _argc = argc;
|
|
176 _argv = argv;
|
|
177 _tdb_agent = NULL;
|
|
178 peek_fail_fast = false;
|
|
179 libThreadName = NULL;
|
|
180 }
|
|
181
|
|
182 ServiceabilityAgentDbxModule::~ServiceabilityAgentDbxModule() {
|
|
183 if (_command != NULL) {
|
|
184 uninstall();
|
|
185 }
|
|
186 }
|
|
187
|
|
188 char*
|
|
189 readCStringFromProcess(psaddr_t addr) {
|
|
190 char c;
|
|
191 int num = 0;
|
|
192 ps_prochandle* cur_proc = (ps_prochandle*) ps_get_prochandle2(1);
|
|
193
|
|
194 // Search for null terminator
|
|
195 do {
|
|
196 if (ps_pread(cur_proc, addr + num, &c, 1) != PS_OK) {
|
|
197 return NULL;
|
|
198 }
|
|
199 ++num;
|
|
200 } while (c != 0);
|
|
201
|
|
202 // Allocate string
|
|
203 char* res = new char[num];
|
|
204 if (ps_pread(cur_proc, addr, res, num) != PS_OK) {
|
|
205 delete[] res;
|
|
206 return NULL;
|
|
207 }
|
|
208 return res;
|
|
209 }
|
|
210
|
|
211 int
|
|
212 findLibThreadCB(const rd_loadobj_t* lo, void* data) {
|
|
213 ServiceabilityAgentDbxModule* module = (ServiceabilityAgentDbxModule*) data;
|
|
214 char* name = readCStringFromProcess(lo->rl_nameaddr);
|
|
215 if (strstr(name, "libthread.so") != NULL) {
|
|
216 module->libThreadName = name;
|
|
217 return 0;
|
|
218 } else {
|
|
219 delete[] name;
|
|
220 return 1;
|
|
221 }
|
|
222 }
|
|
223
|
|
224 bool
|
|
225 ServiceabilityAgentDbxModule::install() {
|
|
226 // NOTE interdependency between here and Java side wrapper
|
|
227 // FIXME: casts of string literal to char * to match prototype
|
|
228 _command = shell_imp_define_command((char *) "svc_agent_run",
|
|
229 &svc_agent_run,
|
|
230 0,
|
|
231 NULL,
|
|
232 (char *) "Run the serviceability agent's dbx module.\n"
|
|
233 "This routine causes the module to listen on a socket for requests.\n"
|
|
234 "It does not return until the Java-side code tells it to exit, at\n"
|
|
235 "which point control is returned to the dbx shell.");
|
|
236 if (_command == NULL) {
|
|
237 debug_only(fprintf(stderr, "Serviceability agent: Failed to install svc_agent_run command\n"));
|
|
238 return false;
|
|
239 }
|
|
240
|
|
241 // This is fairly painful. Since dbx doesn't currently load
|
|
242 // libthread_db with RTLD_GLOBAL, we can't just use RTLD_DEFAULT for
|
|
243 // the argument to dlsym. Instead, we have to use rtld_db to search
|
|
244 // through the loaded objects in the target process for libthread.so and
|
|
245
|
|
246 // Try rtld_db
|
|
247 if (rd_init(RD_VERSION) != RD_OK) {
|
|
248 debug_only(fprintf(stderr, "Serviceability agent: Unable to init rtld_db\n"));
|
|
249 return false;
|
|
250 }
|
|
251
|
|
252 rd_agent_t* rda = rd_new((struct ps_prochandle*) ps_get_prochandle2(1));
|
|
253 if (rda == NULL) {
|
|
254 debug_only(fprintf(stderr, "Serviceability agent: Unable to allocate rtld_db agent\n"));
|
|
255 return false;
|
|
256 }
|
|
257
|
|
258 if (rd_loadobj_iter(rda, (rl_iter_f*) findLibThreadCB, this) != RD_OK) {
|
|
259 debug_only(fprintf(stderr, "Serviceability agent: Loadobject iteration failed\n"));
|
|
260 return false;
|
|
261 }
|
|
262
|
|
263 if (libThreadName == NULL) {
|
|
264 debug_only(fprintf(stderr, "Serviceability agent: Failed to find pathname to libthread.so in target process\n"));
|
|
265 return false;
|
|
266 }
|
|
267
|
|
268 // Find and open libthread_db.so
|
|
269 char* slash = strrchr(libThreadName, '/');
|
|
270 if (slash == NULL) {
|
|
271 debug_only(fprintf(stderr, "Serviceability agent: can't parse path to libthread.so \"%s\"\n"));
|
|
272 return false;
|
|
273 }
|
|
274
|
|
275 int slashPos = slash - libThreadName;
|
|
276 char* buf = new char[slashPos + strlen("libthread_db.so") + 20]; // slop
|
|
277 if (buf == NULL) {
|
|
278 debug_only(fprintf(stderr, "Serviceability agent: error allocating libthread_db.so pathname\n"));
|
|
279 return false;
|
|
280 }
|
|
281 strncpy(buf, libThreadName, slashPos + 1);
|
|
282
|
|
283 // Check dbx's data model; use sparcv9/ subdirectory if 64-bit and
|
|
284 // if target process is 32-bit
|
|
285 if ((sizeof(void*) == 8) &&
|
|
286 (strstr(libThreadName, "sparcv9") == NULL)) {
|
|
287 strcpy(buf + slashPos + 1, "sparcv9/");
|
|
288 slashPos += strlen("sparcv9/");
|
|
289 }
|
|
290
|
|
291 strcpy(buf + slashPos + 1, "libthread_db.so");
|
|
292
|
|
293 libThreadDB = dlopen(buf, RTLD_LAZY);
|
|
294 void* tmpDB = libThreadDB;
|
|
295 if (libThreadDB == NULL) {
|
|
296 debug_only(fprintf(stderr, "Serviceability agent: Warning: unable to find libthread_db.so at \"%s\"\n", buf));
|
|
297 // Would like to handle this case as well. Maybe dbx has a better
|
|
298 // idea of where libthread_db.so lies. If the problem with dbx
|
|
299 // loading libthread_db without RTLD_GLOBAL specified ever gets
|
|
300 // fixed, we could run this code all the time.
|
|
301 tmpDB = RTLD_DEFAULT;
|
|
302 }
|
|
303
|
|
304 delete[] buf;
|
|
305
|
|
306 // Initialize access to libthread_db
|
|
307 td_init_fn = (td_init_fn_t*) dlsym(tmpDB, "td_init");
|
|
308 td_ta_new_fn = (td_ta_new_fn_t*) dlsym(tmpDB, "td_ta_new");
|
|
309 td_ta_delete_fn = (td_ta_delete_fn_t*) dlsym(tmpDB, "td_ta_delete");
|
|
310 td_ta_map_id2thr_fn = (td_ta_map_id2thr_fn_t*) dlsym(tmpDB, "td_ta_map_id2thr");
|
|
311 td_thr_getgregs_fn = (td_thr_getgregs_fn_t*) dlsym(tmpDB, "td_thr_getgregs");
|
|
312
|
|
313 if (td_init_fn == NULL ||
|
|
314 td_ta_new_fn == NULL ||
|
|
315 td_ta_delete_fn == NULL ||
|
|
316 td_ta_map_id2thr_fn == NULL ||
|
|
317 td_thr_getgregs_fn == NULL) {
|
|
318 debug_only(fprintf(stderr, "Serviceability agent: Failed to find one or more libthread_db symbols:\n"));
|
|
319 debug_only(if (td_init_fn == NULL) fprintf(stderr, " td_init\n"));
|
|
320 debug_only(if (td_ta_new_fn == NULL) fprintf(stderr, " td_ta_new\n"));
|
|
321 debug_only(if (td_ta_delete_fn == NULL) fprintf(stderr, " td_ta_delete\n"));
|
|
322 debug_only(if (td_ta_map_id2thr_fn == NULL) fprintf(stderr, " td_ta_map_id2thr\n"));
|
|
323 debug_only(if (td_thr_getgregs_fn == NULL) fprintf(stderr, " td_thr_getgregs\n"));
|
|
324 return false;
|
|
325 }
|
|
326
|
|
327 if ((*td_init_fn)() != TD_OK) {
|
|
328 debug_only(fprintf(stderr, "Serviceability agent: Failed to initialize libthread_db\n"));
|
|
329 return false;
|
|
330 }
|
|
331
|
|
332 return true;
|
|
333 }
|
|
334
|
|
335 bool
|
|
336 ServiceabilityAgentDbxModule::uninstall() {
|
|
337 if (_command == NULL) {
|
|
338 return false;
|
|
339 }
|
|
340
|
|
341 if (libThreadDB != NULL) {
|
|
342 dlclose(libThreadDB);
|
|
343 libThreadDB = NULL;
|
|
344 }
|
|
345
|
|
346 int res = shell_imp_undefine_command(_command);
|
|
347
|
|
348 if (res != 0) {
|
|
349 return false;
|
|
350 }
|
|
351
|
|
352 return true;
|
|
353 }
|
|
354
|
|
355 bool
|
|
356 ServiceabilityAgentDbxModule::run() {
|
|
357 // This is where most of the work gets done.
|
|
358 // The command processor loop looks like the following:
|
|
359 // - create listening socket
|
|
360 // - accept a connection (only one for now)
|
|
361 // - while that connection is open and the "exit" command has not
|
|
362 // been received:
|
|
363 // - read command
|
|
364 // - if it's the exit command, cleanup and return
|
|
365 // - otherwise, process command and write result
|
|
366
|
|
367 int listening_socket = socket(AF_INET, SOCK_STREAM, 0);
|
|
368 if (listening_socket < 0) {
|
|
369 return false;
|
|
370 }
|
|
371
|
|
372 // Set the SO_REUSEADDR property on the listening socket. This
|
|
373 // prevents problems with calls to bind() to the same port failing
|
|
374 // after this process exits. This seems to work on all platforms.
|
|
375 int reuse_address = 1;
|
|
376 if (setsockopt(listening_socket, SOL_SOCKET, SO_REUSEADDR,
|
|
377 (char *)&reuse_address, sizeof(reuse_address)) < 0) {
|
|
378 close(listening_socket);
|
|
379 return false;
|
|
380 }
|
|
381
|
|
382 sockaddr_in server_address;
|
|
383 // Build the server address. We can bind the listening socket to the
|
|
384 // INADDR_ANY internet address.
|
|
385 memset((char*)&server_address, 0, sizeof(server_address));
|
|
386 server_address.sin_family = AF_INET;
|
|
387 server_address.sin_addr.s_addr = (unsigned long)htonl(INADDR_ANY);
|
|
388 server_address.sin_port = htons((short)PORT);
|
|
389
|
|
390 // Bind socket to port
|
|
391 if (bind(listening_socket, (sockaddr*) &server_address,
|
|
392 sizeof(server_address)) < 0) {
|
|
393 close(listening_socket);
|
|
394 return false;
|
|
395 }
|
|
396
|
|
397 // Arbitrarily chosen backlog of 5 (shouldn't matter since we expect
|
|
398 // at most one connection)
|
|
399 if (listen(listening_socket, 5) < 0) {
|
|
400 close(listening_socket);
|
|
401 return false;
|
|
402 }
|
|
403
|
|
404 // OK, now ready to wait for a data connection. This call to
|
|
405 // accept() will block.
|
|
406 struct sockaddr_in client_address;
|
|
407 int address_len = sizeof(client_address);
|
|
408 int client_socket = accept(listening_socket, (sockaddr*) &client_address,
|
|
409 &address_len);
|
|
410 // Close listening socket regardless of whether accept() succeeded.
|
|
411 // (FIXME: this may be annoying, especially during debugging, but I
|
|
412 // really feel that robustness and multiple connections should be
|
|
413 // handled higher up, e.g., at the Java level -- multiple clients
|
|
414 // could conceivably connect to the SA via RMI, and that would be a
|
|
415 // more robust solution than implementing multiple connections at
|
|
416 // this level)
|
|
417 NEEDS_CLEANUP;
|
|
418
|
|
419 // NOTE: the call to shutdown() usually fails, so don't panic if this happens
|
|
420 shutdown(listening_socket, 2);
|
|
421
|
|
422 if (close(listening_socket) < 0) {
|
|
423 debug_only(fprintf(stderr, "Serviceability agent: Error closing listening socket\n"));
|
|
424 return false;
|
|
425 }
|
|
426
|
|
427 if (client_socket < 0) {
|
|
428 debug_only(fprintf(stderr, "Serviceability agent: Failed to open client socket\n"));
|
|
429 // No more cleanup necessary
|
|
430 return false;
|
|
431 }
|
|
432
|
|
433 // Attempt to disable TCP buffering on this socket. We send small
|
|
434 // amounts of data back and forth and don't want buffering.
|
|
435 int buffer_val = 1;
|
|
436 if (setsockopt(client_socket, IPPROTO_IP, TCP_NODELAY, (char *) &buffer_val, sizeof(buffer_val)) < 0) {
|
|
437 debug_only(fprintf(stderr, "Serviceability agent: Failed to set TCP_NODELAY option on client socket\n"));
|
|
438 cleanup(client_socket);
|
|
439 return false;
|
|
440 }
|
|
441
|
|
442 // OK, we have the data socket through which we will communicate
|
|
443 // with the Java side. Wait for commands or until reading or writing
|
|
444 // caused an error.
|
|
445
|
|
446 bool should_continue = true;
|
|
447
|
|
448 myComm.setSocket(client_socket);
|
|
449
|
|
450 #ifdef PROFILING
|
|
451 scanTimer.reset();
|
|
452 workTimer.reset();
|
|
453 writeTimer.reset();
|
|
454 #endif
|
|
455
|
|
456 // Allocate a new thread agent for libthread_db
|
|
457 if ((*td_ta_new_fn)((ps_prochandle*) ps_get_prochandle2(1), &_tdb_agent) !=
|
|
458 TD_OK) {
|
|
459 debug_only(fprintf(stderr, "Serviceability agent: Failed to allocate thread agent\n"));
|
|
460 cleanup(client_socket);
|
|
461 return false;
|
|
462 }
|
|
463
|
|
464 do {
|
|
465 // Decided to use text to communicate between these processes.
|
|
466 // Probably will make debugging easier -- could telnet in if
|
|
467 // necessary. Will make scanning harder, but probably doesn't
|
|
468 // matter.
|
|
469
|
|
470 // Why not just do what workshop does and parse dbx's console?
|
|
471 // Probably could do that, but at least this way we are in control
|
|
472 // of the text format on both ends.
|
|
473
|
|
474 // FIXME: should have some way of synchronizing these commands
|
|
475 // between the C and Java sources.
|
|
476
|
|
477 NEEDS_CLEANUP;
|
|
478
|
|
479 // Do a blocking read of a line from the socket.
|
|
480 char *input_buffer = myComm.readLine();
|
|
481 if (input_buffer == NULL) {
|
|
482 debug_only(fprintf(stderr, "Serviceability agent: error during read: errno = %d\n", errno));
|
|
483 debug_only(perror("Serviceability agent"));
|
|
484 // Error occurred during read.
|
|
485 // FIXME: should guard against SIGPIPE
|
|
486 cleanup(client_socket);
|
|
487 return false;
|
|
488 }
|
|
489
|
|
490 // OK, now ready to scan. See README-commands.txt for syntax
|
|
491 // descriptions.
|
|
492
|
|
493 bool res = false;
|
|
494 if (!strncmp(input_buffer, CMD_ADDRESS_SIZE, strlen(CMD_ADDRESS_SIZE))) {
|
|
495 res = handleAddressSize(input_buffer + strlen(CMD_ADDRESS_SIZE));
|
|
496 } else if (!strncmp(input_buffer, CMD_PEEK_FAIL_FAST, strlen(CMD_PEEK_FAIL_FAST))) {
|
|
497 res = handlePeekFailFast(input_buffer + strlen(CMD_PEEK_FAIL_FAST));
|
|
498 } else if (!strncmp(input_buffer, CMD_PEEK, strlen(CMD_PEEK))) {
|
|
499 res = handlePeek(input_buffer + strlen(CMD_PEEK));
|
|
500 } else if (!strncmp(input_buffer, CMD_POKE, strlen(CMD_POKE))) {
|
|
501 res = handlePoke(input_buffer + strlen(CMD_POKE));
|
|
502 } else if (!strncmp(input_buffer, CMD_MAPPED, strlen(CMD_MAPPED))) {
|
|
503 res = handleMapped(input_buffer + strlen(CMD_MAPPED));
|
|
504 } else if (!strncmp(input_buffer, CMD_LOOKUP, strlen(CMD_LOOKUP))) {
|
|
505 res = handleLookup(input_buffer + strlen(CMD_LOOKUP));
|
|
506 } else if (!strncmp(input_buffer, CMD_THR_GREGS, strlen(CMD_THR_GREGS))) {
|
|
507 res = handleThrGRegs(input_buffer + strlen(CMD_THR_GREGS));
|
|
508 } else if (!strncmp(input_buffer, CMD_EXIT, strlen(CMD_EXIT))) {
|
|
509 should_continue = false;
|
|
510 }
|
|
511
|
|
512 if (should_continue) {
|
|
513 if (!res) {
|
|
514 cleanup(client_socket);
|
|
515 return false;
|
|
516 }
|
|
517 }
|
|
518
|
|
519 #ifdef PROFILING
|
|
520 if (++numRequests == PROFILE_COUNT) {
|
|
521 fprintf(stderr, "%d requests: %d ms scanning, %d ms work, %d ms writing\n",
|
|
522 PROFILE_COUNT, scanTimer.total(), workTimer.total(), writeTimer.total());
|
|
523 fflush(stderr);
|
|
524 scanTimer.reset();
|
|
525 workTimer.reset();
|
|
526 writeTimer.reset();
|
|
527 numRequests = 0;
|
|
528 }
|
|
529 #endif
|
|
530
|
|
531 } while (should_continue);
|
|
532
|
|
533 // Successful exit
|
|
534 cleanup(client_socket);
|
|
535 return true;
|
|
536 }
|
|
537
|
|
538 void
|
|
539 ServiceabilityAgentDbxModule::cleanup(int client_socket) {
|
|
540 shutdown(client_socket, 2);
|
|
541 close(client_socket);
|
|
542 if (_tdb_agent != NULL) {
|
|
543 (*td_ta_delete_fn)(_tdb_agent);
|
|
544 }
|
|
545 }
|
|
546
|
|
547 bool
|
|
548 ServiceabilityAgentDbxModule::handleAddressSize(char* data) {
|
|
549 int data_model;
|
|
550 ps_err_e result = ps_pdmodel((ps_prochandle*) ps_get_prochandle2(1),
|
|
551 &data_model);
|
|
552 if (result != PS_OK) {
|
|
553 myComm.writeString("0");
|
|
554 myComm.flush();
|
|
555 return false;
|
|
556 }
|
|
557
|
|
558 int val;
|
|
559 switch (data_model) {
|
|
560 case PR_MODEL_ILP32:
|
|
561 val = 32;
|
|
562 break;
|
|
563 case PR_MODEL_LP64:
|
|
564 val = 64;
|
|
565 break;
|
|
566 default:
|
|
567 val = 0;
|
|
568 break;
|
|
569 }
|
|
570
|
|
571 if (!myComm.writeInt(val)) {
|
|
572 return false;
|
|
573 }
|
|
574 if (!myComm.writeEOL()) {
|
|
575 return false;
|
|
576 }
|
|
577 return myComm.flush();
|
|
578 }
|
|
579
|
|
580 bool
|
|
581 ServiceabilityAgentDbxModule::handlePeekFailFast(char* data) {
|
|
582 unsigned int val;
|
|
583 if (!scanUnsignedInt(&data, &val)) {
|
|
584 return false;
|
|
585 }
|
|
586 peek_fail_fast = (val ? true : false);
|
|
587 return true;
|
|
588 }
|
|
589
|
|
590 bool
|
|
591 ServiceabilityAgentDbxModule::handlePeek(char* data) {
|
|
592 // Scan hex address, return false if failed
|
|
593 psaddr_t addr;
|
|
594 #ifdef PROFILING
|
|
595 scanTimer.start();
|
|
596 #endif /* PROFILING */
|
|
597 if (!scanAddress(&data, &addr)) {
|
|
598 return false;
|
|
599 }
|
|
600 unsigned int num;
|
|
601 if (!scanUnsignedInt(&data, &num)) {
|
|
602 return false;
|
|
603 }
|
|
604 if (num == 0) {
|
|
605 #ifdef PROFILING
|
|
606 writeTimer.start();
|
|
607 #endif /* PROFILING */
|
|
608 myComm.writeBinChar('B');
|
|
609 myComm.writeBinChar(1);
|
|
610 myComm.writeBinUnsignedInt(0);
|
|
611 myComm.writeBinChar(0);
|
|
612 #ifdef PROFILING
|
|
613 writeTimer.stop();
|
|
614 #endif /* PROFILING */
|
|
615 return true;
|
|
616 }
|
|
617 #ifdef PROFILING
|
|
618 scanTimer.stop();
|
|
619 workTimer.start();
|
|
620 #endif /* PROFILING */
|
|
621 char* buf = new char[num];
|
|
622 ps_prochandle* cur_proc = (ps_prochandle*) ps_get_prochandle2(1);
|
|
623 ps_err_e result = ps_pread(cur_proc, addr, buf, num);
|
|
624 if (result == PS_OK) {
|
|
625 // Fast case; entire read succeeded.
|
|
626 #ifdef PROFILING
|
|
627 workTimer.stop();
|
|
628 writeTimer.start();
|
|
629 #endif /* PROFILING */
|
|
630 myComm.writeBinChar('B');
|
|
631 myComm.writeBinChar(1);
|
|
632 myComm.writeBinUnsignedInt(num);
|
|
633 myComm.writeBinChar(1);
|
|
634 myComm.writeBinBuf(buf, num);
|
|
635 #ifdef PROFILING
|
|
636 writeTimer.stop();
|
|
637 #endif /* PROFILING */
|
|
638 } else {
|
|
639 #ifdef PROFILING
|
|
640 workTimer.stop();
|
|
641 #endif /* PROFILING */
|
|
642
|
|
643 if (peek_fail_fast) {
|
|
644 #ifdef PROFILING
|
|
645 writeTimer.start();
|
|
646 #endif /* PROFILING */
|
|
647 // Fail fast
|
|
648 myComm.writeBinChar('B');
|
|
649 myComm.writeBinChar(1);
|
|
650 myComm.writeBinUnsignedInt(num);
|
|
651 myComm.writeBinChar(0);
|
|
652 #ifdef PROFILING
|
|
653 writeTimer.stop();
|
|
654 #endif /* PROFILING */
|
|
655 } else {
|
|
656 // Slow case: try to read one byte at a time
|
|
657 // FIXME: need better way of handling this, a la VirtualQuery
|
|
658
|
|
659 unsigned int strideLen = 0;
|
|
660 int bufIdx = 0;
|
|
661 bool lastByteMapped = (ps_pread(cur_proc, addr, buf, 1) == PS_OK ? true : false);
|
|
662
|
|
663 #ifdef PROFILING
|
|
664 writeTimer.start();
|
|
665 #endif /* PROFILING */
|
|
666 myComm.writeBinChar('B');
|
|
667 myComm.writeBinChar(1);
|
|
668 #ifdef PROFILING
|
|
669 writeTimer.stop();
|
|
670 #endif /* PROFILING */
|
|
671
|
|
672 for (int i = 0; i < num; ++i, ++addr) {
|
|
673 #ifdef PROFILING
|
|
674 workTimer.start();
|
|
675 #endif /* PROFILING */
|
|
676 result = ps_pread(cur_proc, addr, &buf[bufIdx], 1);
|
|
677 #ifdef PROFILING
|
|
678 workTimer.stop();
|
|
679 #endif /* PROFILING */
|
|
680 bool tmpMapped = (result == PS_OK ? true : false);
|
|
681 #ifdef PROFILING
|
|
682 writeTimer.start();
|
|
683 #endif /* PROFILING */
|
|
684 if (tmpMapped != lastByteMapped) {
|
|
685 // State change. Write the length of the last stride.
|
|
686 myComm.writeBinUnsignedInt(strideLen);
|
|
687 if (lastByteMapped) {
|
|
688 // Stop gathering data. Write the data of the last stride.
|
|
689 myComm.writeBinChar(1);
|
|
690 myComm.writeBinBuf(buf, strideLen);
|
|
691 bufIdx = 0;
|
|
692 } else {
|
|
693 // Start gathering data to write.
|
|
694 myComm.writeBinChar(0);
|
|
695 }
|
|
696 strideLen = 0;
|
|
697 lastByteMapped = tmpMapped;
|
|
698 }
|
|
699 #ifdef PROFILING
|
|
700 writeTimer.stop();
|
|
701 #endif /* PROFILING */
|
|
702 if (lastByteMapped) {
|
|
703 ++bufIdx;
|
|
704 }
|
|
705 ++strideLen;
|
|
706 }
|
|
707
|
|
708 // Write last stride (must be at least one byte long by definition)
|
|
709 #ifdef PROFILING
|
|
710 writeTimer.start();
|
|
711 #endif /* PROFILING */
|
|
712 myComm.writeBinUnsignedInt(strideLen);
|
|
713 if (lastByteMapped) {
|
|
714 myComm.writeBinChar(1);
|
|
715 myComm.writeBinBuf(buf, strideLen);
|
|
716 } else {
|
|
717 myComm.writeBinChar(0);
|
|
718 }
|
|
719 #ifdef PROFILING
|
|
720 writeTimer.stop();
|
|
721 #endif /* PROFILING */
|
|
722 }
|
|
723 }
|
|
724 delete[] buf;
|
|
725 myComm.flush();
|
|
726 return true;
|
|
727 }
|
|
728
|
|
729 bool
|
|
730 ServiceabilityAgentDbxModule::handlePoke(char* data) {
|
|
731 // FIXME: not yet implemented
|
|
732 NEEDS_CLEANUP;
|
|
733 bool res = myComm.writeBoolAsInt(false);
|
|
734 myComm.flush();
|
|
735 return res;
|
|
736 }
|
|
737
|
|
738 bool
|
|
739 ServiceabilityAgentDbxModule::handleMapped(char* data) {
|
|
740 // Scan address
|
|
741 psaddr_t addr;
|
|
742 if (!scanAddress(&data, &addr)) {
|
|
743 return false;
|
|
744 }
|
|
745 unsigned int num;
|
|
746 if (!scanUnsignedInt(&data, &num)) {
|
|
747 return false;
|
|
748 }
|
|
749 unsigned char val;
|
|
750 ps_prochandle* cur_proc = (ps_prochandle*) ps_get_prochandle2(1);
|
|
751 char* buf = new char[num];
|
|
752 if (ps_pread(cur_proc, addr, buf, num) == PS_OK) {
|
|
753 myComm.writeBoolAsInt(true);
|
|
754 } else {
|
|
755 myComm.writeBoolAsInt(false);
|
|
756 }
|
|
757 delete[] buf;
|
|
758 myComm.writeEOL();
|
|
759 myComm.flush();
|
|
760 return true;
|
|
761 }
|
|
762
|
|
763 extern "C"
|
|
764 int loadobj_iterator(const rd_loadobj_t* loadobj, void *) {
|
|
765 if (loadobj != NULL) {
|
|
766 fprintf(stderr, "loadobj_iterator: visited loadobj \"%p\"\n", (void*) loadobj->rl_nameaddr);
|
|
767 return 1;
|
|
768 }
|
|
769
|
|
770 fprintf(stderr, "loadobj_iterator: NULL loadobj\n");
|
|
771 return 0;
|
|
772 }
|
|
773
|
|
774 bool
|
|
775 ServiceabilityAgentDbxModule::handleLookup(char* data) {
|
|
776 // Debugging: iterate over loadobjs
|
|
777 /*
|
|
778 rd_agent_t* rld_agent = rd_new((ps_prochandle*) ps_get_prochandle2(1));
|
|
779 rd_loadobj_iter(rld_agent, &loadobj_iterator, NULL);
|
|
780 rd_delete(rld_agent);
|
|
781 */
|
|
782
|
|
783 #ifdef PROFILING
|
|
784 scanTimer.start();
|
|
785 #endif /* PROFILING */
|
|
786
|
|
787 char* object_name = scanSymbol(&data);
|
|
788 if (object_name == NULL) {
|
|
789 return false;
|
|
790 }
|
|
791 char* symbol_name = scanSymbol(&data);
|
|
792 if (symbol_name == NULL) {
|
|
793 delete[] object_name;
|
|
794 return false;
|
|
795 }
|
|
796
|
|
797 #ifdef PROFILING
|
|
798 scanTimer.stop();
|
|
799 workTimer.start();
|
|
800 #endif /* PROFILING */
|
|
801
|
|
802 ps_sym_t sym;
|
|
803 // FIXME: check return values from write routines
|
|
804 ps_prochandle* process = (ps_prochandle*) ps_get_prochandle2(1);
|
|
805 ps_err_e lookup_res = ps_pglobal_sym(process,
|
|
806 object_name, symbol_name, &sym);
|
|
807 #ifdef PROFILING
|
|
808 workTimer.stop();
|
|
809 writeTimer.start();
|
|
810 #endif /* PROFILING */
|
|
811
|
|
812 delete[] object_name;
|
|
813 delete[] symbol_name;
|
|
814 if (lookup_res != PS_OK) {
|
|
815 // This is too noisy
|
|
816 // debug_only(fprintf(stderr, "ServiceabilityAgentDbxModule::handleLookup: error %d\n", lookup_res));
|
|
817 myComm.writeString("0x0");
|
|
818 } else {
|
|
819 myComm.writeAddress((void *)sym.st_value);
|
|
820 }
|
|
821 myComm.writeEOL();
|
|
822 myComm.flush();
|
|
823
|
|
824 #ifdef PROFILING
|
|
825 writeTimer.stop();
|
|
826 #endif /* PROFILING */
|
|
827
|
|
828 return true;
|
|
829 }
|
|
830
|
|
831 bool
|
|
832 ServiceabilityAgentDbxModule::handleThrGRegs(char* data) {
|
|
833 #ifdef PROFILING
|
|
834 scanTimer.start();
|
|
835 #endif /* PROFILING */
|
|
836
|
|
837 unsigned int num;
|
|
838 // Get the thread ID
|
|
839 if (!scanUnsignedInt(&data, &num)) {
|
|
840 return false;
|
|
841 }
|
|
842
|
|
843 #ifdef PROFILING
|
|
844 scanTimer.stop();
|
|
845 workTimer.start();
|
|
846 #endif /* PROFILING */
|
|
847
|
|
848 // Map tid to thread handle
|
|
849 td_thrhandle_t thread_handle;
|
|
850 if ((*td_ta_map_id2thr_fn)(_tdb_agent, num, &thread_handle) != TD_OK) {
|
|
851 // fprintf(stderr, "Error mapping thread ID %d to thread handle\n", num);
|
|
852 return false;
|
|
853 }
|
|
854
|
|
855 // Fetch register set
|
|
856 prgregset_t reg_set;
|
|
857 memset(reg_set, 0, sizeof(reg_set));
|
|
858 td_err_e result = (*td_thr_getgregs_fn)(&thread_handle, reg_set);
|
|
859 if ((result != TD_OK) && (result != TD_PARTIALREG)) {
|
|
860 // fprintf(stderr, "Error fetching registers for thread handle %d: error = %d\n", num, result);
|
|
861 return false;
|
|
862 }
|
|
863
|
|
864 #ifdef PROFILING
|
|
865 workTimer.stop();
|
|
866 writeTimer.start();
|
|
867 #endif /* PROFILING */
|
|
868
|
|
869 #if (defined(__sparc) || defined(__i386))
|
|
870 myComm.writeInt(NPRGREG);
|
|
871 myComm.writeSpace();
|
|
872 for (int i = 0; i < NPRGREG; i++) {
|
|
873 myComm.writeAddress((void *)reg_set[i]);
|
|
874 if (i == NPRGREG - 1) {
|
|
875 myComm.writeEOL();
|
|
876 } else {
|
|
877 myComm.writeSpace();
|
|
878 }
|
|
879 }
|
|
880 #else
|
|
881 #error Please port ServiceabilityAgentDbxModule::handleThrGRegs to your current platform
|
|
882 #endif
|
|
883
|
|
884 myComm.flush();
|
|
885
|
|
886 #ifdef PROFILING
|
|
887 writeTimer.stop();
|
|
888 #endif /* PROFILING */
|
|
889
|
|
890 return true;
|
|
891 }
|
|
892
|
|
893 //
|
|
894 // Input routines
|
|
895 //
|
|
896
|
|
897 bool
|
|
898 ServiceabilityAgentDbxModule::scanAddress(char** data, psaddr_t* addr) {
|
|
899 *addr = 0;
|
|
900
|
|
901 // Skip whitespace
|
|
902 while ((**data != 0) && (isspace(**data))) {
|
|
903 ++*data;
|
|
904 }
|
|
905
|
|
906 if (**data == 0) {
|
|
907 return false;
|
|
908 }
|
|
909
|
|
910 if (strncmp(*data, "0x", 2) != 0) {
|
|
911 return false;
|
|
912 }
|
|
913
|
|
914 *data += 2;
|
|
915
|
|
916 while ((**data != 0) && (!isspace(**data))) {
|
|
917 int val;
|
|
918 bool res = charToNibble(**data, &val);
|
|
919 if (!res) {
|
|
920 return false;
|
|
921 }
|
|
922 *addr <<= 4;
|
|
923 *addr |= val;
|
|
924 ++*data;
|
|
925 }
|
|
926
|
|
927 return true;
|
|
928 }
|
|
929
|
|
930 bool
|
|
931 ServiceabilityAgentDbxModule::scanUnsignedInt(char** data, unsigned int* num) {
|
|
932 *num = 0;
|
|
933
|
|
934 // Skip whitespace
|
|
935 while ((**data != 0) && (isspace(**data))) {
|
|
936 ++*data;
|
|
937 }
|
|
938
|
|
939 if (**data == 0) {
|
|
940 return false;
|
|
941 }
|
|
942
|
|
943 while ((**data != 0) && (!isspace(**data))) {
|
|
944 char cur = **data;
|
|
945 if ((cur < '0') || (cur > '9')) {
|
|
946 return false;
|
|
947 }
|
|
948 *num *= 10;
|
|
949 *num += cur - '0';
|
|
950 ++*data;
|
|
951 }
|
|
952
|
|
953 return true;
|
|
954 }
|
|
955
|
|
956 char*
|
|
957 ServiceabilityAgentDbxModule::scanSymbol(char** data) {
|
|
958 // Skip whitespace
|
|
959 while ((**data != 0) && (isspace(**data))) {
|
|
960 ++*data;
|
|
961 }
|
|
962
|
|
963 if (**data == 0) {
|
|
964 return NULL;
|
|
965 }
|
|
966
|
|
967 // First count length
|
|
968 int len = 1; // Null terminator
|
|
969 char* tmpData = *data;
|
|
970 while ((*tmpData != 0) && (!isspace(*tmpData))) {
|
|
971 ++tmpData;
|
|
972 ++len;
|
|
973 }
|
|
974 char* buf = new char[len];
|
|
975 strncpy(buf, *data, len - 1);
|
|
976 buf[len - 1] = 0;
|
|
977 *data += len - 1;
|
|
978 return buf;
|
|
979 }
|
|
980
|
|
981 bool
|
|
982 ServiceabilityAgentDbxModule::charToNibble(char ascii, int* value) {
|
|
983 if (ascii >= '0' && ascii <= '9') {
|
|
984 *value = ascii - '0';
|
|
985 return true;
|
|
986 } else if (ascii >= 'A' && ascii <= 'F') {
|
|
987 *value = 10 + ascii - 'A';
|
|
988 return true;
|
|
989 } else if (ascii >= 'a' && ascii <= 'f') {
|
|
990 *value = 10 + ascii - 'a';
|
|
991 return true;
|
|
992 }
|
|
993
|
|
994 return false;
|
|
995 }
|
|
996
|
|
997
|
|
998 char*
|
|
999 ServiceabilityAgentDbxModule::readCStringFromProcess(psaddr_t addr) {
|
|
1000 char c;
|
|
1001 int num = 0;
|
|
1002 ps_prochandle* cur_proc = (ps_prochandle*) ps_get_prochandle2(1);
|
|
1003
|
|
1004 // Search for null terminator
|
|
1005 do {
|
|
1006 if (ps_pread(cur_proc, addr + num, &c, 1) != PS_OK) {
|
|
1007 return NULL;
|
|
1008 }
|
|
1009 ++num;
|
|
1010 } while (c != 0);
|
|
1011
|
|
1012 // Allocate string
|
|
1013 char* res = new char[num];
|
|
1014 if (ps_pread(cur_proc, addr, res, num) != PS_OK) {
|
|
1015 delete[] res;
|
|
1016 return NULL;
|
|
1017 }
|
|
1018 return res;
|
|
1019 }
|
|
1020
|
|
1021
|
|
1022 //--------------------------------------------------------------------------------
|
|
1023 // Class Timer
|
|
1024 //
|
|
1025
|
|
1026 Timer::Timer() {
|
|
1027 reset();
|
|
1028 }
|
|
1029
|
|
1030 Timer::~Timer() {
|
|
1031 }
|
|
1032
|
|
1033 void
|
|
1034 Timer::start() {
|
|
1035 gettimeofday(&startTime, NULL);
|
|
1036 }
|
|
1037
|
|
1038 void
|
|
1039 Timer::stop() {
|
|
1040 struct timeval endTime;
|
|
1041 gettimeofday(&endTime, NULL);
|
|
1042 totalMicroseconds += timevalDiff(&startTime, &endTime);
|
|
1043 ++counter;
|
|
1044 }
|
|
1045
|
|
1046 long
|
|
1047 Timer::total() {
|
|
1048 return (totalMicroseconds / 1000);
|
|
1049 }
|
|
1050
|
|
1051 long
|
|
1052 Timer::average() {
|
|
1053 return (long) ((double) total() / (double) counter);
|
|
1054 }
|
|
1055
|
|
1056 void
|
|
1057 Timer::reset() {
|
|
1058 totalMicroseconds = 0;
|
|
1059 counter = 0;
|
|
1060 }
|
|
1061
|
|
1062 long long
|
|
1063 Timer::timevalDiff(struct timeval* start, struct timeval* end) {
|
|
1064 long long secs = end->tv_sec - start->tv_sec;
|
|
1065 secs *= 1000000;
|
|
1066 long long usecs = end->tv_usec - start->tv_usec;
|
|
1067 return (secs + usecs);
|
|
1068 }
|