4006
|
1 /*
|
|
2 * Copyright (c) 2006, 2010, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
20 * or visit www.oracle.com if you need additional information or have any
|
|
21 * questions.
|
|
22 *
|
|
23 */
|
|
24
|
|
25 #include <door.h>
|
|
26 #include <errno.h>
|
|
27 #include <fcntl.h>
|
|
28 #include <limits.h>
|
|
29 #include <poll.h>
|
|
30 #include <signal.h>
|
|
31 #include <stdarg.h>
|
|
32 #include <stdio.h>
|
|
33 #include <stdlib.h>
|
|
34 #include <string.h>
|
|
35 #include <sys/types.h>
|
|
36 #include <sys/stat.h>
|
|
37 #include <thread.h>
|
|
38 #include <unistd.h>
|
|
39 #include "jvm_dtrace.h"
|
|
40
|
|
41 // NOTE: These constants are used in JVM code as well.
|
|
42 // KEEP JVM CODE IN SYNC if you are going to change these...
|
|
43
|
|
44 #define DTRACE_ALLOC_PROBES 0x1
|
|
45 #define DTRACE_METHOD_PROBES 0x2
|
|
46 #define DTRACE_MONITOR_PROBES 0x4
|
|
47 #define DTRACE_ALL_PROBES -1
|
|
48
|
|
49 // generic error messages
|
|
50 #define JVM_ERR_OUT_OF_MEMORY "out of memory (native heap)"
|
|
51 #define JVM_ERR_INVALID_PARAM "invalid input parameter(s)"
|
|
52 #define JVM_ERR_NULL_PARAM "input paramater is NULL"
|
|
53
|
|
54 // error messages for attach
|
|
55 #define JVM_ERR_CANT_OPEN_DOOR "cannot open door file"
|
|
56 #define JVM_ERR_CANT_CREATE_ATTACH_FILE "cannot create attach file"
|
|
57 #define JVM_ERR_DOOR_FILE_PERMISSION "door file is not secure"
|
|
58 #define JVM_ERR_CANT_SIGNAL "cannot send SIGQUIT to target"
|
|
59
|
|
60 // error messages for enable probe
|
|
61 #define JVM_ERR_DOOR_CMD_SEND "door command send failed"
|
|
62 #define JVM_ERR_DOOR_CANT_READ_STATUS "cannot read door command status"
|
|
63 #define JVM_ERR_DOOR_CMD_STATUS "door command error status"
|
|
64
|
|
65 // error message for detach
|
|
66 #define JVM_ERR_CANT_CLOSE_DOOR "cannot close door file"
|
|
67
|
|
68 #define RESTARTABLE(_cmd, _result) do { \
|
|
69 do { \
|
|
70 _result = _cmd; \
|
|
71 } while((_result == -1) && (errno == EINTR)); \
|
|
72 } while(0)
|
|
73
|
|
74 struct _jvm_t {
|
|
75 pid_t pid;
|
|
76 int door_fd;
|
|
77 };
|
|
78
|
|
79 static int libjvm_dtrace_debug;
|
|
80 static void print_debug(const char* fmt,...) {
|
|
81 if (libjvm_dtrace_debug) {
|
|
82 va_list alist;
|
|
83 va_start(alist, fmt);
|
|
84 fputs("libjvm_dtrace DEBUG: ", stderr);
|
|
85 vfprintf(stderr, fmt, alist);
|
|
86 va_end(alist);
|
|
87 }
|
|
88 }
|
|
89
|
|
90 /* Key for thread local error message */
|
|
91 static thread_key_t jvm_error_key;
|
|
92
|
|
93 /* init function for this library */
|
|
94 static void init_jvm_dtrace() {
|
|
95 /* check for env. var for debug mode */
|
|
96 libjvm_dtrace_debug = getenv("LIBJVM_DTRACE_DEBUG") != NULL;
|
|
97 /* create key for thread local error message */
|
|
98 if (thr_keycreate(&jvm_error_key, NULL) != 0) {
|
|
99 print_debug("can't create thread_key_t for jvm error key\n");
|
|
100 // exit(1); ?
|
|
101 }
|
|
102 }
|
|
103
|
|
104 #pragma init(init_jvm_dtrace)
|
|
105
|
|
106 /* set thread local error message */
|
|
107 static void set_jvm_error(const char* msg) {
|
|
108 thr_setspecific(jvm_error_key, (void*)msg);
|
|
109 }
|
|
110
|
|
111 /* clear thread local error message */
|
|
112 static void clear_jvm_error() {
|
|
113 thr_setspecific(jvm_error_key, NULL);
|
|
114 }
|
|
115
|
|
116 /* file handling functions that can handle interrupt */
|
|
117
|
|
118 static int file_open(const char* path, int flag) {
|
|
119 int ret;
|
|
120 RESTARTABLE(open(path, flag), ret);
|
|
121 return ret;
|
|
122 }
|
|
123
|
|
124 static int file_close(int fd) {
|
|
125 int ret;
|
|
126 RESTARTABLE(close(fd), ret);
|
|
127 return ret;
|
|
128 }
|
|
129
|
|
130 static int file_read(int fd, char* buf, int len) {
|
|
131 int ret;
|
|
132 RESTARTABLE(read(fd, buf, len), ret);
|
|
133 return ret;
|
|
134 }
|
|
135
|
|
136 /* send SIGQUIT signal to given process */
|
|
137 static int send_sigquit(pid_t pid) {
|
|
138 int ret;
|
|
139 RESTARTABLE(kill(pid, SIGQUIT), ret);
|
|
140 return ret;
|
|
141 }
|
|
142
|
|
143 /* called to check permissions on attach file */
|
|
144 static int check_permission(const char* path) {
|
|
145 struct stat64 sb;
|
|
146 uid_t uid, gid;
|
|
147 int res;
|
|
148
|
|
149 /*
|
|
150 * Check that the path is owned by the effective uid/gid of this
|
|
151 * process. Also check that group/other access is not allowed.
|
|
152 */
|
|
153 uid = geteuid();
|
|
154 gid = getegid();
|
|
155
|
|
156 res = stat64(path, &sb);
|
|
157 if (res != 0) {
|
|
158 print_debug("stat failed for %s\n", path);
|
|
159 return -1;
|
|
160 }
|
|
161
|
|
162 if ((sb.st_uid != uid) || (sb.st_gid != gid) ||
|
|
163 ((sb.st_mode & (S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) != 0)) {
|
|
164 print_debug("well-known file %s is not secure\n", path);
|
|
165 return -1;
|
|
166 }
|
|
167 return 0;
|
|
168 }
|
|
169
|
|
170 #define ATTACH_FILE_PATTERN "/tmp/.attach_pid%d"
|
|
171
|
|
172 /* fill-in the name of attach file name in given buffer */
|
|
173 static void fill_attach_file_name(char* path, int len, pid_t pid) {
|
|
174 memset(path, 0, len);
|
|
175 sprintf(path, ATTACH_FILE_PATTERN, pid);
|
|
176 }
|
|
177
|
|
178 #define DOOR_FILE_PATTERN "/tmp/.java_pid%d"
|
|
179
|
|
180 /* open door file for the given JVM */
|
|
181 static int open_door(pid_t pid) {
|
|
182 char path[PATH_MAX + 1];
|
|
183 int fd;
|
|
184
|
|
185 sprintf(path, DOOR_FILE_PATTERN, pid);
|
|
186 fd = file_open(path, O_RDONLY);
|
|
187 if (fd < 0) {
|
|
188 set_jvm_error(JVM_ERR_CANT_OPEN_DOOR);
|
|
189 print_debug("cannot open door file %s\n", path);
|
|
190 return -1;
|
|
191 }
|
|
192 print_debug("opened door file %s\n", path);
|
|
193 if (check_permission(path) != 0) {
|
|
194 set_jvm_error(JVM_ERR_DOOR_FILE_PERMISSION);
|
|
195 print_debug("check permission failed for %s\n", path);
|
|
196 file_close(fd);
|
|
197 fd = -1;
|
|
198 }
|
|
199 return fd;
|
|
200 }
|
|
201
|
|
202 /* create attach file for given process */
|
|
203 static int create_attach_file(pid_t pid) {
|
|
204 char path[PATH_MAX + 1];
|
|
205 int fd;
|
|
206 fill_attach_file_name(path, sizeof(path), pid);
|
|
207 fd = file_open(path, O_CREAT | O_RDWR);
|
|
208 if (fd < 0) {
|
|
209 set_jvm_error(JVM_ERR_CANT_CREATE_ATTACH_FILE);
|
|
210 print_debug("cannot create file %s\n", path);
|
|
211 } else {
|
|
212 print_debug("created attach file %s\n", path);
|
|
213 }
|
|
214 return fd;
|
|
215 }
|
|
216
|
|
217 /* delete attach file for given process */
|
|
218 static void delete_attach_file(pid_t pid) {
|
|
219 char path[PATH_MAX + 1];
|
|
220 fill_attach_file_name(path, sizeof(path), pid);
|
|
221 int res = unlink(path);
|
|
222 if (res) {
|
|
223 print_debug("cannot delete attach file %s\n", path);
|
|
224 } else {
|
|
225 print_debug("deleted attach file %s\n", path);
|
|
226 }
|
|
227 }
|
|
228
|
|
229 /* attach to given JVM */
|
|
230 jvm_t* jvm_attach(pid_t pid) {
|
|
231 jvm_t* jvm;
|
|
232 int door_fd, attach_fd, i;
|
|
233
|
|
234 jvm = (jvm_t*) calloc(1, sizeof(jvm_t));
|
|
235 if (jvm == NULL) {
|
|
236 set_jvm_error(JVM_ERR_OUT_OF_MEMORY);
|
|
237 print_debug("calloc failed in %s at %d\n", __FILE__, __LINE__);
|
|
238 return NULL;
|
|
239 }
|
|
240 jvm->pid = pid;
|
|
241 attach_fd = -1;
|
|
242
|
|
243 door_fd = open_door(pid);
|
|
244 if (door_fd < 0) {
|
|
245 print_debug("trying to create attach file\n");
|
|
246 if ((attach_fd = create_attach_file(pid)) < 0) {
|
|
247 goto quit;
|
|
248 }
|
|
249
|
|
250 /* send QUIT signal to the target so that it will
|
|
251 * check for the attach file.
|
|
252 */
|
|
253 if (send_sigquit(pid) != 0) {
|
|
254 set_jvm_error(JVM_ERR_CANT_SIGNAL);
|
|
255 print_debug("sending SIGQUIT failed\n");
|
|
256 goto quit;
|
|
257 }
|
|
258
|
|
259 /* give the target VM time to start the attach mechanism */
|
|
260 do {
|
|
261 int res;
|
|
262 RESTARTABLE(poll(0, 0, 200), res);
|
|
263 door_fd = open_door(pid);
|
|
264 i++;
|
|
265 } while (i <= 50 && door_fd == -1);
|
|
266 if (door_fd < 0) {
|
|
267 print_debug("Unable to open door to process %d\n", pid);
|
|
268 goto quit;
|
|
269 }
|
|
270 }
|
|
271
|
|
272 quit:
|
|
273 if (attach_fd >= 0) {
|
|
274 file_close(attach_fd);
|
|
275 delete_attach_file(jvm->pid);
|
|
276 }
|
|
277 if (door_fd >= 0) {
|
|
278 jvm->door_fd = door_fd;
|
|
279 clear_jvm_error();
|
|
280 } else {
|
|
281 free(jvm);
|
|
282 jvm = NULL;
|
|
283 }
|
|
284 return jvm;
|
|
285 }
|
|
286
|
|
287 /* return the last thread local error message */
|
|
288 const char* jvm_get_last_error() {
|
|
289 const char* res = NULL;
|
|
290 thr_getspecific(jvm_error_key, (void**)&res);
|
|
291 return res;
|
|
292 }
|
|
293
|
|
294 /* detach the givenb JVM */
|
|
295 int jvm_detach(jvm_t* jvm) {
|
|
296 if (jvm) {
|
|
297 int res;
|
|
298 if (jvm->door_fd != -1) {
|
|
299 if (file_close(jvm->door_fd) != 0) {
|
|
300 set_jvm_error(JVM_ERR_CANT_CLOSE_DOOR);
|
|
301 res = -1;
|
|
302 } else {
|
|
303 clear_jvm_error();
|
|
304 res = 0;
|
|
305 }
|
|
306 }
|
|
307 free(jvm);
|
|
308 return res;
|
|
309 } else {
|
|
310 set_jvm_error(JVM_ERR_NULL_PARAM);
|
|
311 print_debug("jvm_t* is NULL\n");
|
|
312 return -1;
|
|
313 }
|
|
314 }
|
|
315
|
|
316 /*
|
|
317 * A simple table to translate some known errors into reasonable
|
|
318 * error messages
|
|
319 */
|
|
320 static struct {
|
|
321 int err;
|
|
322 const char* msg;
|
|
323 } const error_messages[] = {
|
|
324 { 100, "Bad request" },
|
|
325 { 101, "Protocol mismatch" },
|
|
326 { 102, "Resource failure" },
|
|
327 { 103, "Internal error" },
|
|
328 { 104, "Permission denied" },
|
|
329 };
|
|
330
|
|
331 /*
|
|
332 * Lookup the given error code and return the appropriate
|
|
333 * message. If not found return NULL.
|
|
334 */
|
|
335 static const char* translate_error(int err) {
|
|
336 int table_size = sizeof(error_messages) / sizeof(error_messages[0]);
|
|
337 int i;
|
|
338
|
|
339 for (i=0; i<table_size; i++) {
|
|
340 if (err == error_messages[i].err) {
|
|
341 return error_messages[i].msg;
|
|
342 }
|
|
343 }
|
|
344 return NULL;
|
|
345 }
|
|
346
|
|
347 /*
|
|
348 * Current protocol version
|
|
349 */
|
|
350 static const char* PROTOCOL_VERSION = "1";
|
|
351
|
|
352 #define RES_BUF_SIZE 128
|
|
353
|
|
354 /*
|
|
355 * Enqueue attach-on-demand command to the given JVM
|
|
356 */
|
|
357 static
|
|
358 int enqueue_command(jvm_t* jvm, const char* cstr, int arg_count, const char** args) {
|
|
359 size_t size;
|
|
360 door_arg_t door_args;
|
|
361 char res_buffer[RES_BUF_SIZE];
|
|
362 int rc, i;
|
|
363 char* buf = NULL;
|
|
364 int result = -1;
|
|
365
|
|
366 /*
|
|
367 * First we get the command string and create the start of the
|
|
368 * argument string to send to the target VM:
|
|
369 * <ver>\0<cmd>\0
|
|
370 */
|
|
371 if (cstr == NULL) {
|
|
372 print_debug("command name is NULL\n");
|
|
373 goto quit;
|
|
374 }
|
|
375 size = strlen(PROTOCOL_VERSION) + strlen(cstr) + 2;
|
|
376 buf = (char*)malloc(size);
|
|
377 if (buf != NULL) {
|
|
378 char* pos = buf;
|
|
379 strcpy(buf, PROTOCOL_VERSION);
|
|
380 pos += strlen(PROTOCOL_VERSION)+1;
|
|
381 strcpy(pos, cstr);
|
|
382 } else {
|
|
383 set_jvm_error(JVM_ERR_OUT_OF_MEMORY);
|
|
384 print_debug("malloc failed at %d in %s\n", __LINE__, __FILE__);
|
|
385 goto quit;
|
|
386 }
|
|
387
|
|
388 /*
|
|
389 * Next we iterate over the arguments and extend the buffer
|
|
390 * to include them.
|
|
391 */
|
|
392 for (i=0; i<arg_count; i++) {
|
|
393 cstr = args[i];
|
|
394 if (cstr != NULL) {
|
|
395 size_t len = strlen(cstr);
|
|
396 char* newbuf = (char*)realloc(buf, size+len+1);
|
|
397 if (newbuf == NULL) {
|
|
398 set_jvm_error(JVM_ERR_OUT_OF_MEMORY);
|
|
399 print_debug("realloc failed in %s at %d\n", __FILE__, __LINE__);
|
|
400 goto quit;
|
|
401 }
|
|
402 buf = newbuf;
|
|
403 strcpy(buf+size, cstr);
|
|
404 size += len+1;
|
|
405 }
|
|
406 }
|
|
407
|
|
408 /*
|
|
409 * The arguments to the door function are in 'buf' so we now
|
|
410 * do the door call
|
|
411 */
|
|
412 door_args.data_ptr = buf;
|
|
413 door_args.data_size = size;
|
|
414 door_args.desc_ptr = NULL;
|
|
415 door_args.desc_num = 0;
|
|
416 door_args.rbuf = (char*)&res_buffer;
|
|
417 door_args.rsize = sizeof(res_buffer);
|
|
418
|
|
419 RESTARTABLE(door_call(jvm->door_fd, &door_args), rc);
|
|
420
|
|
421 /*
|
|
422 * door_call failed
|
|
423 */
|
|
424 if (rc == -1) {
|
|
425 print_debug("door_call failed\n");
|
|
426 } else {
|
|
427 /*
|
|
428 * door_call succeeded but the call didn't return the the expected jint.
|
|
429 */
|
|
430 if (door_args.data_size < sizeof(int)) {
|
|
431 print_debug("Enqueue error - reason unknown as result is truncated!");
|
|
432 } else {
|
|
433 int* res = (int*)(door_args.data_ptr);
|
|
434 if (*res != 0) {
|
|
435 const char* msg = translate_error(*res);
|
|
436 if (msg == NULL) {
|
|
437 print_debug("Unable to enqueue command to target VM: %d\n", *res);
|
|
438 } else {
|
|
439 print_debug("Unable to enqueue command to target VM: %s\n", msg);
|
|
440 }
|
|
441 } else {
|
|
442 /*
|
|
443 * The door call should return a file descriptor to one end of
|
|
444 * a socket pair
|
|
445 */
|
|
446 if ((door_args.desc_ptr != NULL) &&
|
|
447 (door_args.desc_num == 1) &&
|
|
448 (door_args.desc_ptr->d_attributes & DOOR_DESCRIPTOR)) {
|
|
449 result = door_args.desc_ptr->d_data.d_desc.d_descriptor;
|
|
450 } else {
|
|
451 print_debug("Reply from enqueue missing descriptor!\n");
|
|
452 }
|
|
453 }
|
|
454 }
|
|
455 }
|
|
456
|
|
457 quit:
|
|
458 if (buf) free(buf);
|
|
459 return result;
|
|
460 }
|
|
461
|
|
462 /* read status code for a door command */
|
|
463 static int read_status(int fd) {
|
|
464 char ch, buf[16];
|
|
465 int index = 0;
|
|
466
|
|
467 while (1) {
|
|
468 if (file_read(fd, &ch, sizeof(ch)) != sizeof(ch)) {
|
|
469 set_jvm_error(JVM_ERR_DOOR_CANT_READ_STATUS);
|
|
470 print_debug("door cmd status: read status failed\n");
|
|
471 return -1;
|
|
472 }
|
|
473 buf[index++] = ch;
|
|
474 if (ch == '\n') {
|
|
475 buf[index - 1] = '\0';
|
|
476 return atoi(buf);
|
|
477 }
|
|
478 if (index == sizeof(buf)) {
|
|
479 set_jvm_error(JVM_ERR_DOOR_CANT_READ_STATUS);
|
|
480 print_debug("door cmd status: read status overflow\n");
|
|
481 return -1;
|
|
482 }
|
|
483 }
|
|
484 }
|
|
485
|
|
486 static const char* ENABLE_DPROBES_CMD = "enabledprobes";
|
|
487
|
|
488 /* enable one or more DTrace probes for a given JVM */
|
|
489 int jvm_enable_dtprobes(jvm_t* jvm, int num_probe_types, const char** probe_types) {
|
|
490 int fd, status = 0;
|
|
491 char ch;
|
|
492 const char* args[1];
|
|
493 char buf[16];
|
|
494 int probe_type = 0, index;
|
|
495 int count = 0;
|
|
496
|
|
497 if (jvm == NULL) {
|
|
498 set_jvm_error(JVM_ERR_NULL_PARAM);
|
|
499 print_debug("jvm_t* is NULL\n");
|
|
500 return -1;
|
|
501 }
|
|
502
|
|
503 if (num_probe_types == 0 || probe_types == NULL ||
|
|
504 probe_types[0] == NULL) {
|
|
505 set_jvm_error(JVM_ERR_INVALID_PARAM);
|
|
506 print_debug("invalid probe type argument(s)\n");
|
|
507 return -1;
|
|
508 }
|
|
509
|
|
510 for (index = 0; index < num_probe_types; index++) {
|
|
511 const char* p = probe_types[index];
|
|
512 if (strcmp(p, JVM_DTPROBE_OBJECT_ALLOC) == 0) {
|
|
513 probe_type |= DTRACE_ALLOC_PROBES;
|
|
514 count++;
|
|
515 } else if (strcmp(p, JVM_DTPROBE_METHOD_ENTRY) == 0 ||
|
|
516 strcmp(p, JVM_DTPROBE_METHOD_RETURN) == 0) {
|
|
517 probe_type |= DTRACE_METHOD_PROBES;
|
|
518 count++;
|
|
519 } else if (strcmp(p, JVM_DTPROBE_MONITOR_ENTER) == 0 ||
|
|
520 strcmp(p, JVM_DTPROBE_MONITOR_ENTERED) == 0 ||
|
|
521 strcmp(p, JVM_DTPROBE_MONITOR_EXIT) == 0 ||
|
|
522 strcmp(p, JVM_DTPROBE_MONITOR_WAIT) == 0 ||
|
|
523 strcmp(p, JVM_DTPROBE_MONITOR_WAITED) == 0 ||
|
|
524 strcmp(p, JVM_DTPROBE_MONITOR_NOTIFY) == 0 ||
|
|
525 strcmp(p, JVM_DTPROBE_MONITOR_NOTIFYALL) == 0) {
|
|
526 probe_type |= DTRACE_MONITOR_PROBES;
|
|
527 count++;
|
|
528 } else if (strcmp(p, JVM_DTPROBE_ALL) == 0) {
|
|
529 probe_type |= DTRACE_ALL_PROBES;
|
|
530 count++;
|
|
531 }
|
|
532 }
|
|
533
|
|
534 if (count == 0) {
|
|
535 return count;
|
|
536 }
|
|
537 sprintf(buf, "%d", probe_type);
|
|
538 args[0] = buf;
|
|
539
|
|
540 fd = enqueue_command(jvm, ENABLE_DPROBES_CMD, 1, args);
|
|
541 if (fd < 0) {
|
|
542 set_jvm_error(JVM_ERR_DOOR_CMD_SEND);
|
|
543 return -1;
|
|
544 }
|
|
545
|
|
546 status = read_status(fd);
|
|
547 // non-zero status is error
|
|
548 if (status) {
|
|
549 set_jvm_error(JVM_ERR_DOOR_CMD_STATUS);
|
|
550 print_debug("%s command failed (status: %d) in target JVM\n",
|
|
551 ENABLE_DPROBES_CMD, status);
|
|
552 file_close(fd);
|
|
553 return -1;
|
|
554 }
|
|
555 // read from stream until EOF
|
|
556 while (file_read(fd, &ch, sizeof(ch)) == sizeof(ch)) {
|
|
557 if (libjvm_dtrace_debug) {
|
|
558 printf("%c", ch);
|
|
559 }
|
|
560 }
|
|
561
|
|
562 file_close(fd);
|
|
563 clear_jvm_error();
|
|
564 return count;
|
|
565 }
|