|
1 /* |
|
2 * Copyright (c) 2003, 2017, 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. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
24 */ |
|
25 |
|
26 /* |
|
27 * Copyright 2003 Wily Technology, Inc. |
|
28 */ |
|
29 |
|
30 #include <string.h> |
|
31 #include <stdlib.h> |
|
32 |
|
33 #include "jni.h" |
|
34 |
|
35 #include "Utilities.h" |
|
36 #include "JPLISAssert.h" |
|
37 #include "JPLISAgent.h" |
|
38 #include "JavaExceptions.h" |
|
39 |
|
40 #include "EncodingSupport.h" |
|
41 #include "FileSystemSupport.h" |
|
42 #include "JarFacade.h" |
|
43 #include "PathCharsValidator.h" |
|
44 |
|
45 /** |
|
46 * This module contains the direct interface points with the JVMTI. |
|
47 * The OnLoad handler is here, along with the various event handlers. |
|
48 */ |
|
49 |
|
50 static int |
|
51 appendClassPath(JPLISAgent* agent, |
|
52 const char* jarfile); |
|
53 |
|
54 static void |
|
55 appendBootClassPath(JPLISAgent* agent, |
|
56 const char* jarfile, |
|
57 const char* pathList); |
|
58 |
|
59 |
|
60 /* |
|
61 * Parse -javaagent tail, of the form name[=options], into name |
|
62 * and options. Returned values are heap allocated and options maybe |
|
63 * NULL. Returns 0 if parse succeeds, -1 if allocation fails. |
|
64 */ |
|
65 static int |
|
66 parseArgumentTail(char* tail, char** name, char** options) { |
|
67 int len; |
|
68 char* pos; |
|
69 |
|
70 pos = strchr(tail, '='); |
|
71 len = (pos == NULL) ? (int)strlen(tail) : (int)(pos - tail); |
|
72 |
|
73 *name = (char*)malloc(len+1); |
|
74 if (*name == NULL) { |
|
75 return -1; |
|
76 } |
|
77 memcpy(*name, tail, len); |
|
78 (*name)[len] = '\0'; |
|
79 |
|
80 if (pos == NULL) { |
|
81 *options = NULL; |
|
82 } else { |
|
83 char * str = (char*)malloc( (int)strlen(pos + 1) + 1 ); |
|
84 if (str == NULL) { |
|
85 free(*name); |
|
86 return -1; |
|
87 } |
|
88 strcpy(str, pos +1); |
|
89 *options = str; |
|
90 } |
|
91 return 0; |
|
92 } |
|
93 |
|
94 /* |
|
95 * Get the value of an attribute in an attribute list. Returns NULL |
|
96 * if attribute not found. |
|
97 */ |
|
98 jboolean |
|
99 getBooleanAttribute(const jarAttribute* attributes, const char* name) { |
|
100 char* attributeValue = getAttribute(attributes, name); |
|
101 return attributeValue != NULL && strcasecmp(attributeValue, "true") == 0; |
|
102 } |
|
103 |
|
104 /* |
|
105 * Parse any capability settings in the JAR manifest and |
|
106 * convert them to JVM TI capabilities. |
|
107 */ |
|
108 void |
|
109 convertCapabilityAttributes(const jarAttribute* attributes, JPLISAgent* agent) { |
|
110 /* set redefineClasses capability */ |
|
111 if (getBooleanAttribute(attributes, "Can-Redefine-Classes")) { |
|
112 addRedefineClassesCapability(agent); |
|
113 } |
|
114 |
|
115 /* create an environment which has the retransformClasses capability */ |
|
116 if (getBooleanAttribute(attributes, "Can-Retransform-Classes")) { |
|
117 retransformableEnvironment(agent); |
|
118 } |
|
119 |
|
120 /* set setNativeMethodPrefix capability */ |
|
121 if (getBooleanAttribute(attributes, "Can-Set-Native-Method-Prefix")) { |
|
122 addNativeMethodPrefixCapability(agent); |
|
123 } |
|
124 |
|
125 /* for retransformClasses testing, set capability to use original method order */ |
|
126 if (getBooleanAttribute(attributes, "Can-Maintain-Original-Method-Order")) { |
|
127 addOriginalMethodOrderCapability(agent); |
|
128 } |
|
129 } |
|
130 |
|
131 /* |
|
132 * This will be called once for every -javaagent on the command line. |
|
133 * Each call to Agent_OnLoad will create its own agent and agent data. |
|
134 * |
|
135 * The argument tail string provided to Agent_OnLoad will be of form |
|
136 * <jarfile>[=<options>]. The tail string is split into the jarfile and |
|
137 * options components. The jarfile manifest is parsed and the value of the |
|
138 * Premain-Class attribute will become the agent's premain class. The jar |
|
139 * file is then added to the system class path, and if the Boot-Class-Path |
|
140 * attribute is present then all relative URLs in the value are processed |
|
141 * to create boot class path segments to append to the boot class path. |
|
142 */ |
|
143 JNIEXPORT jint JNICALL |
|
144 DEF_Agent_OnLoad(JavaVM *vm, char *tail, void * reserved) { |
|
145 JPLISInitializationError initerror = JPLIS_INIT_ERROR_NONE; |
|
146 jint result = JNI_OK; |
|
147 JPLISAgent * agent = NULL; |
|
148 |
|
149 initerror = createNewJPLISAgent(vm, &agent); |
|
150 if ( initerror == JPLIS_INIT_ERROR_NONE ) { |
|
151 int oldLen, newLen; |
|
152 char * jarfile; |
|
153 char * options; |
|
154 jarAttribute* attributes; |
|
155 char * premainClass; |
|
156 char * bootClassPath; |
|
157 |
|
158 /* |
|
159 * Parse <jarfile>[=options] into jarfile and options |
|
160 */ |
|
161 if (parseArgumentTail(tail, &jarfile, &options) != 0) { |
|
162 fprintf(stderr, "-javaagent: memory allocation failure.\n"); |
|
163 return JNI_ERR; |
|
164 } |
|
165 |
|
166 /* |
|
167 * Agent_OnLoad is specified to provide the agent options |
|
168 * argument tail in modified UTF8. However for 1.5.0 this is |
|
169 * actually in the platform encoding - see 5049313. |
|
170 * |
|
171 * Open zip/jar file and parse archive. If can't be opened or |
|
172 * not a zip file return error. Also if Premain-Class attribute |
|
173 * isn't present we return an error. |
|
174 */ |
|
175 attributes = readAttributes(jarfile); |
|
176 if (attributes == NULL) { |
|
177 fprintf(stderr, "Error opening zip file or JAR manifest missing : %s\n", jarfile); |
|
178 free(jarfile); |
|
179 if (options != NULL) free(options); |
|
180 return JNI_ERR; |
|
181 } |
|
182 |
|
183 premainClass = getAttribute(attributes, "Premain-Class"); |
|
184 if (premainClass == NULL) { |
|
185 fprintf(stderr, "Failed to find Premain-Class manifest attribute in %s\n", |
|
186 jarfile); |
|
187 free(jarfile); |
|
188 if (options != NULL) free(options); |
|
189 freeAttributes(attributes); |
|
190 return JNI_ERR; |
|
191 } |
|
192 |
|
193 /* Save the jarfile name */ |
|
194 agent->mJarfile = jarfile; |
|
195 |
|
196 /* |
|
197 * The value of the Premain-Class attribute becomes the agent |
|
198 * class name. The manifest is in UTF8 so need to convert to |
|
199 * modified UTF8 (see JNI spec). |
|
200 */ |
|
201 oldLen = (int)strlen(premainClass); |
|
202 newLen = modifiedUtf8LengthOfUtf8(premainClass, oldLen); |
|
203 if (newLen == oldLen) { |
|
204 premainClass = strdup(premainClass); |
|
205 } else { |
|
206 char* str = (char*)malloc( newLen+1 ); |
|
207 if (str != NULL) { |
|
208 convertUtf8ToModifiedUtf8(premainClass, oldLen, str, newLen); |
|
209 } |
|
210 premainClass = str; |
|
211 } |
|
212 if (premainClass == NULL) { |
|
213 fprintf(stderr, "-javaagent: memory allocation failed\n"); |
|
214 free(jarfile); |
|
215 if (options != NULL) free(options); |
|
216 freeAttributes(attributes); |
|
217 return JNI_ERR; |
|
218 } |
|
219 |
|
220 /* |
|
221 * If the Boot-Class-Path attribute is specified then we process |
|
222 * each relative URL and add it to the bootclasspath. |
|
223 */ |
|
224 bootClassPath = getAttribute(attributes, "Boot-Class-Path"); |
|
225 if (bootClassPath != NULL) { |
|
226 appendBootClassPath(agent, jarfile, bootClassPath); |
|
227 } |
|
228 |
|
229 /* |
|
230 * Convert JAR attributes into agent capabilities |
|
231 */ |
|
232 convertCapabilityAttributes(attributes, agent); |
|
233 |
|
234 /* |
|
235 * Track (record) the agent class name and options data |
|
236 */ |
|
237 initerror = recordCommandLineData(agent, premainClass, options); |
|
238 |
|
239 /* |
|
240 * Clean-up |
|
241 */ |
|
242 if (options != NULL) free(options); |
|
243 freeAttributes(attributes); |
|
244 free(premainClass); |
|
245 } |
|
246 |
|
247 switch (initerror) { |
|
248 case JPLIS_INIT_ERROR_NONE: |
|
249 result = JNI_OK; |
|
250 break; |
|
251 case JPLIS_INIT_ERROR_CANNOT_CREATE_NATIVE_AGENT: |
|
252 result = JNI_ERR; |
|
253 fprintf(stderr, "java.lang.instrument/-javaagent: cannot create native agent.\n"); |
|
254 break; |
|
255 case JPLIS_INIT_ERROR_FAILURE: |
|
256 result = JNI_ERR; |
|
257 fprintf(stderr, "java.lang.instrument/-javaagent: initialization of native agent failed.\n"); |
|
258 break; |
|
259 case JPLIS_INIT_ERROR_ALLOCATION_FAILURE: |
|
260 result = JNI_ERR; |
|
261 fprintf(stderr, "java.lang.instrument/-javaagent: allocation failure.\n"); |
|
262 break; |
|
263 case JPLIS_INIT_ERROR_AGENT_CLASS_NOT_SPECIFIED: |
|
264 result = JNI_ERR; |
|
265 fprintf(stderr, "-javaagent: agent class not specified.\n"); |
|
266 break; |
|
267 default: |
|
268 result = JNI_ERR; |
|
269 fprintf(stderr, "java.lang.instrument/-javaagent: unknown error\n"); |
|
270 break; |
|
271 } |
|
272 return result; |
|
273 } |
|
274 |
|
275 /* |
|
276 * Agent_OnAttach returns a jint. 0/JNI_OK indicates success and non-0 |
|
277 * indicates an error. To allow the attach mechanism throw an |
|
278 * AgentInitializationException with a reasonable exception message we define |
|
279 * a few specific errors here. |
|
280 */ |
|
281 #define AGENT_ERROR_BADJAR ((jint)100) /* Agent JAR not found or no Agent-Class attribute */ |
|
282 #define AGENT_ERROR_NOTONCP ((jint)101) /* Unable to add JAR file to system class path */ |
|
283 #define AGENT_ERROR_STARTFAIL ((jint)102) /* No agentmain method or agentmain failed */ |
|
284 |
|
285 /* |
|
286 * This will be called once each time a tool attaches to the VM and loads |
|
287 * the JPLIS library. |
|
288 */ |
|
289 JNIEXPORT jint JNICALL |
|
290 DEF_Agent_OnAttach(JavaVM* vm, char *args, void * reserved) { |
|
291 JPLISInitializationError initerror = JPLIS_INIT_ERROR_NONE; |
|
292 jint result = JNI_OK; |
|
293 JPLISAgent * agent = NULL; |
|
294 JNIEnv * jni_env = NULL; |
|
295 |
|
296 /* |
|
297 * Need JNIEnv - guaranteed to be called from thread that is already |
|
298 * attached to VM |
|
299 */ |
|
300 result = (*vm)->GetEnv(vm, (void**)&jni_env, JNI_VERSION_1_2); |
|
301 jplis_assert(result==JNI_OK); |
|
302 |
|
303 initerror = createNewJPLISAgent(vm, &agent); |
|
304 if ( initerror == JPLIS_INIT_ERROR_NONE ) { |
|
305 int oldLen, newLen; |
|
306 char * jarfile; |
|
307 char * options; |
|
308 jarAttribute* attributes; |
|
309 char * agentClass; |
|
310 char * bootClassPath; |
|
311 jboolean success; |
|
312 |
|
313 /* |
|
314 * Parse <jarfile>[=options] into jarfile and options |
|
315 */ |
|
316 if (parseArgumentTail(args, &jarfile, &options) != 0) { |
|
317 return JNI_ENOMEM; |
|
318 } |
|
319 |
|
320 /* |
|
321 * Open the JAR file and parse the manifest |
|
322 */ |
|
323 attributes = readAttributes( jarfile ); |
|
324 if (attributes == NULL) { |
|
325 fprintf(stderr, "Error opening zip file or JAR manifest missing: %s\n", jarfile); |
|
326 free(jarfile); |
|
327 if (options != NULL) free(options); |
|
328 return AGENT_ERROR_BADJAR; |
|
329 } |
|
330 |
|
331 agentClass = getAttribute(attributes, "Agent-Class"); |
|
332 if (agentClass == NULL) { |
|
333 fprintf(stderr, "Failed to find Agent-Class manifest attribute from %s\n", |
|
334 jarfile); |
|
335 free(jarfile); |
|
336 if (options != NULL) free(options); |
|
337 freeAttributes(attributes); |
|
338 return AGENT_ERROR_BADJAR; |
|
339 } |
|
340 |
|
341 /* |
|
342 * Add the jarfile to the system class path |
|
343 */ |
|
344 if (appendClassPath(agent, jarfile)) { |
|
345 fprintf(stderr, "Unable to add %s to system class path " |
|
346 "- not supported by system class loader or configuration error!\n", |
|
347 jarfile); |
|
348 free(jarfile); |
|
349 if (options != NULL) free(options); |
|
350 freeAttributes(attributes); |
|
351 return AGENT_ERROR_NOTONCP; |
|
352 } |
|
353 |
|
354 /* |
|
355 * The value of the Agent-Class attribute becomes the agent |
|
356 * class name. The manifest is in UTF8 so need to convert to |
|
357 * modified UTF8 (see JNI spec). |
|
358 */ |
|
359 oldLen = (int)strlen(agentClass); |
|
360 newLen = modifiedUtf8LengthOfUtf8(agentClass, oldLen); |
|
361 if (newLen == oldLen) { |
|
362 agentClass = strdup(agentClass); |
|
363 } else { |
|
364 char* str = (char*)malloc( newLen+1 ); |
|
365 if (str != NULL) { |
|
366 convertUtf8ToModifiedUtf8(agentClass, oldLen, str, newLen); |
|
367 } |
|
368 agentClass = str; |
|
369 } |
|
370 if (agentClass == NULL) { |
|
371 free(jarfile); |
|
372 if (options != NULL) free(options); |
|
373 freeAttributes(attributes); |
|
374 return JNI_ENOMEM; |
|
375 } |
|
376 |
|
377 /* |
|
378 * If the Boot-Class-Path attribute is specified then we process |
|
379 * each URL - in the live phase only JAR files will be added. |
|
380 */ |
|
381 bootClassPath = getAttribute(attributes, "Boot-Class-Path"); |
|
382 if (bootClassPath != NULL) { |
|
383 appendBootClassPath(agent, jarfile, bootClassPath); |
|
384 } |
|
385 |
|
386 /* |
|
387 * Convert JAR attributes into agent capabilities |
|
388 */ |
|
389 convertCapabilityAttributes(attributes, agent); |
|
390 |
|
391 /* |
|
392 * Create the java.lang.instrument.Instrumentation instance |
|
393 */ |
|
394 success = createInstrumentationImpl(jni_env, agent); |
|
395 jplis_assert(success); |
|
396 |
|
397 /* |
|
398 * Turn on the ClassFileLoadHook. |
|
399 */ |
|
400 if (success) { |
|
401 success = setLivePhaseEventHandlers(agent); |
|
402 jplis_assert(success); |
|
403 } |
|
404 |
|
405 /* |
|
406 * Start the agent |
|
407 */ |
|
408 if (success) { |
|
409 success = startJavaAgent(agent, |
|
410 jni_env, |
|
411 agentClass, |
|
412 options, |
|
413 agent->mAgentmainCaller); |
|
414 } |
|
415 |
|
416 if (!success) { |
|
417 fprintf(stderr, "Agent failed to start!\n"); |
|
418 result = AGENT_ERROR_STARTFAIL; |
|
419 } |
|
420 |
|
421 /* |
|
422 * Clean-up |
|
423 */ |
|
424 free(jarfile); |
|
425 if (options != NULL) free(options); |
|
426 free(agentClass); |
|
427 freeAttributes(attributes); |
|
428 } |
|
429 |
|
430 return result; |
|
431 } |
|
432 |
|
433 |
|
434 JNIEXPORT void JNICALL |
|
435 DEF_Agent_OnUnload(JavaVM *vm) { |
|
436 } |
|
437 |
|
438 /** |
|
439 * Invoked by the java launcher to load an agent in the main executable JAR. |
|
440 * The Launcher-Agent-Class attribute in the main manifest of the JAR file |
|
441 * is the agent class. |
|
442 * |
|
443 * Returns JNI_OK if the agent is loaded and initialized; JNI_ERR if this |
|
444 * function fails, possibly with a pending exception. |
|
445 */ |
|
446 jint loadAgent(JNIEnv* env, jstring path) { |
|
447 JavaVM* vm; |
|
448 JPLISAgent* agent; |
|
449 const char* jarfile = NULL; |
|
450 jarAttribute* attributes = NULL; |
|
451 char* agentClass = NULL; |
|
452 char* bootClassPath; |
|
453 int oldLen, newLen; |
|
454 jint result = JNI_ERR; |
|
455 |
|
456 if ((*env)->GetJavaVM(env, &vm) < 0) { |
|
457 return JNI_ERR; |
|
458 } |
|
459 |
|
460 // create JPLISAgent with JVMTI environment |
|
461 if (createNewJPLISAgent(vm, &agent) != JPLIS_INIT_ERROR_NONE) { |
|
462 return JNI_ERR; |
|
463 } |
|
464 |
|
465 // get path to JAR file as UTF-8 string |
|
466 jarfile = (*env)->GetStringUTFChars(env, path, NULL); |
|
467 if (jarfile == NULL) { |
|
468 return JNI_ERR; |
|
469 } |
|
470 |
|
471 // read the attributes in the main section of JAR manifest |
|
472 attributes = readAttributes(jarfile); |
|
473 if (attributes == NULL) { |
|
474 goto releaseAndReturn; |
|
475 } |
|
476 |
|
477 // Launcher-Agent-Class is required |
|
478 agentClass = getAttribute(attributes, "Launcher-Agent-Class"); |
|
479 if (agentClass == NULL) { |
|
480 goto releaseAndReturn; |
|
481 } |
|
482 |
|
483 // The value of Launcher-Agent-Class is in UTF-8, convert it to modified UTF-8 |
|
484 oldLen = (int) strlen(agentClass); |
|
485 newLen = modifiedUtf8LengthOfUtf8(agentClass, oldLen); |
|
486 if (newLen == oldLen) { |
|
487 agentClass = strdup(agentClass); |
|
488 } else { |
|
489 char* str = (char*) malloc(newLen + 1); |
|
490 if (str != NULL) { |
|
491 convertUtf8ToModifiedUtf8(agentClass, oldLen, str, newLen); |
|
492 } |
|
493 agentClass = str; |
|
494 } |
|
495 if (agentClass == NULL) { |
|
496 jthrowable oome = createThrowable(env, "java/lang/OutOfMemoryError", NULL); |
|
497 if (oome != NULL) (*env)->Throw(env, oome); |
|
498 goto releaseAndReturn; |
|
499 } |
|
500 |
|
501 // Boot-Class-Path |
|
502 bootClassPath = getAttribute(attributes, "Boot-Class-Path"); |
|
503 if (bootClassPath != NULL) { |
|
504 appendBootClassPath(agent, jarfile, bootClassPath); |
|
505 } |
|
506 |
|
507 // Can-XXXX capabilities |
|
508 convertCapabilityAttributes(attributes, agent); |
|
509 |
|
510 // Create the java.lang.instrument.Instrumentation object |
|
511 if (!createInstrumentationImpl(env, agent)) { |
|
512 goto releaseAndReturn; |
|
513 } |
|
514 |
|
515 // Enable the ClassFileLoadHook |
|
516 if (!setLivePhaseEventHandlers(agent)) { |
|
517 goto releaseAndReturn; |
|
518 } |
|
519 |
|
520 // invoke the agentmain method |
|
521 if (!startJavaAgent(agent, env, agentClass, "", agent->mAgentmainCaller)) { |
|
522 goto releaseAndReturn; |
|
523 } |
|
524 |
|
525 // initialization complete |
|
526 result = JNI_OK; |
|
527 |
|
528 releaseAndReturn: |
|
529 if (agentClass != NULL) { |
|
530 free(agentClass); |
|
531 } |
|
532 if (attributes != NULL) { |
|
533 freeAttributes(attributes); |
|
534 } |
|
535 if (jarfile != NULL) { |
|
536 (*env)->ReleaseStringUTFChars(env, path, jarfile); |
|
537 } |
|
538 |
|
539 return result; |
|
540 } |
|
541 |
|
542 /* |
|
543 * JVMTI callback support |
|
544 * |
|
545 * We have two "stages" of callback support. |
|
546 * At OnLoad time, we install a VMInit handler. |
|
547 * When the VMInit handler runs, we remove the VMInit handler and install a |
|
548 * ClassFileLoadHook handler. |
|
549 */ |
|
550 |
|
551 void JNICALL |
|
552 eventHandlerVMInit( jvmtiEnv * jvmtienv, |
|
553 JNIEnv * jnienv, |
|
554 jthread thread) { |
|
555 JPLISEnvironment * environment = NULL; |
|
556 jboolean success = JNI_FALSE; |
|
557 |
|
558 environment = getJPLISEnvironment(jvmtienv); |
|
559 |
|
560 /* process the premain calls on the all the JPL agents */ |
|
561 if ( environment != NULL ) { |
|
562 jthrowable outstandingException = NULL; |
|
563 /* |
|
564 * Add the jarfile to the system class path |
|
565 */ |
|
566 JPLISAgent * agent = environment->mAgent; |
|
567 if (appendClassPath(agent, agent->mJarfile)) { |
|
568 fprintf(stderr, "Unable to add %s to system class path - " |
|
569 "the system class loader does not define the " |
|
570 "appendToClassPathForInstrumentation method or the method failed\n", |
|
571 agent->mJarfile); |
|
572 free((void *)agent->mJarfile); |
|
573 abortJVM(jnienv, JPLIS_ERRORMESSAGE_CANNOTSTART); |
|
574 } |
|
575 free((void *)agent->mJarfile); |
|
576 agent->mJarfile = NULL; |
|
577 |
|
578 outstandingException = preserveThrowable(jnienv); |
|
579 success = processJavaStart( environment->mAgent, |
|
580 jnienv); |
|
581 restoreThrowable(jnienv, outstandingException); |
|
582 } |
|
583 |
|
584 /* if we fail to start cleanly, bring down the JVM */ |
|
585 if ( !success ) { |
|
586 abortJVM(jnienv, JPLIS_ERRORMESSAGE_CANNOTSTART); |
|
587 } |
|
588 } |
|
589 |
|
590 void JNICALL |
|
591 eventHandlerClassFileLoadHook( jvmtiEnv * jvmtienv, |
|
592 JNIEnv * jnienv, |
|
593 jclass class_being_redefined, |
|
594 jobject loader, |
|
595 const char* name, |
|
596 jobject protectionDomain, |
|
597 jint class_data_len, |
|
598 const unsigned char* class_data, |
|
599 jint* new_class_data_len, |
|
600 unsigned char** new_class_data) { |
|
601 JPLISEnvironment * environment = NULL; |
|
602 |
|
603 environment = getJPLISEnvironment(jvmtienv); |
|
604 |
|
605 /* if something is internally inconsistent (no agent), just silently return without touching the buffer */ |
|
606 if ( environment != NULL ) { |
|
607 jthrowable outstandingException = preserveThrowable(jnienv); |
|
608 transformClassFile( environment->mAgent, |
|
609 jnienv, |
|
610 loader, |
|
611 name, |
|
612 class_being_redefined, |
|
613 protectionDomain, |
|
614 class_data_len, |
|
615 class_data, |
|
616 new_class_data_len, |
|
617 new_class_data, |
|
618 environment->mIsRetransformer); |
|
619 restoreThrowable(jnienv, outstandingException); |
|
620 } |
|
621 } |
|
622 |
|
623 |
|
624 |
|
625 |
|
626 /* |
|
627 * URLs in Boot-Class-Path attributes are separated by one or more spaces. |
|
628 * This function splits the attribute value into a list of path segments. |
|
629 * The attribute value is in UTF8 but cannot contain NUL. Also non US-ASCII |
|
630 * characters must be escaped (URI syntax) so safe to iterate through the |
|
631 * value as a C string. |
|
632 */ |
|
633 static void |
|
634 splitPathList(const char* str, int* pathCount, char*** paths) { |
|
635 int count = 0; |
|
636 char** segments = NULL; |
|
637 char** new_segments; |
|
638 char* c = (char*) str; |
|
639 while (*c != '\0') { |
|
640 while (*c == ' ') c++; /* skip leading spaces */ |
|
641 if (*c == '\0') { |
|
642 break; |
|
643 } |
|
644 new_segments = (char**)realloc(segments, (count+1)*sizeof(char*)); |
|
645 if (new_segments == NULL) { |
|
646 jplis_assert(0); |
|
647 free(segments); |
|
648 count = 0; |
|
649 segments = NULL; |
|
650 break; |
|
651 } |
|
652 segments = new_segments; |
|
653 segments[count++] = c; |
|
654 c = strchr(c, ' '); |
|
655 if (c == NULL) { |
|
656 break; |
|
657 } |
|
658 *c = '\0'; |
|
659 c++; |
|
660 } |
|
661 *pathCount = count; |
|
662 *paths = segments; |
|
663 } |
|
664 |
|
665 |
|
666 /* URI path decoding - ported from src/share/classes/java/net/URI.java */ |
|
667 |
|
668 static int |
|
669 decodeNibble(char c) { |
|
670 if ((c >= '0') && (c <= '9')) |
|
671 return c - '0'; |
|
672 if ((c >= 'a') && (c <= 'f')) |
|
673 return c - 'a' + 10; |
|
674 if ((c >= 'A') && (c <= 'F')) |
|
675 return c - 'A' + 10; |
|
676 return -1; |
|
677 } |
|
678 |
|
679 static int |
|
680 decodeByte(char c1, char c2) { |
|
681 return (((decodeNibble(c1) & 0xf) << 4) | ((decodeNibble(c2) & 0xf) << 0)); |
|
682 } |
|
683 |
|
684 /* |
|
685 * Evaluates all escapes in s. Assumes that escapes are well-formed |
|
686 * syntactically, i.e., of the form %XX. |
|
687 * If the path does not require decoding the original path is |
|
688 * returned. Otherwise the decoded path (heap allocated) is returned, |
|
689 * along with the length of the decoded path. Note that the return |
|
690 * string will not be null terminated after decoding. |
|
691 */ |
|
692 static |
|
693 char *decodePath(const char *s, int* decodedLen) { |
|
694 int n; |
|
695 char *result; |
|
696 char *resultp; |
|
697 int c; |
|
698 int i; |
|
699 |
|
700 n = (int)strlen(s); |
|
701 if (n == 0) { |
|
702 *decodedLen = 0; |
|
703 return (char*)s; |
|
704 } |
|
705 if (strchr(s, '%') == NULL) { |
|
706 *decodedLen = n; |
|
707 return (char*)s; /* no escapes, we are done */ |
|
708 } |
|
709 |
|
710 resultp = result = calloc(n+1, 1); |
|
711 c = s[0]; |
|
712 for (i = 0; i < n;) { |
|
713 if (c != '%') { |
|
714 *resultp++ = c; |
|
715 if (++i >= n) |
|
716 break; |
|
717 c = s[i]; |
|
718 continue; |
|
719 } |
|
720 for (;;) { |
|
721 char b1 = s[++i]; |
|
722 char b2 = s[++i]; |
|
723 int decoded = decodeByte(b1, b2); |
|
724 *resultp++ = decoded; |
|
725 if (++i >= n) |
|
726 break; |
|
727 c = s[i]; |
|
728 if (c != '%') |
|
729 break; |
|
730 } |
|
731 } |
|
732 *decodedLen = (int)(resultp - result); |
|
733 return result; // not null terminated. |
|
734 } |
|
735 |
|
736 /* |
|
737 * Append the given jar file to the system class path. This should succeed in the |
|
738 * onload phase but may fail in the live phase if the system class loader doesn't |
|
739 * support appending to the class path. |
|
740 */ |
|
741 static int |
|
742 appendClassPath( JPLISAgent* agent, |
|
743 const char* jarfile ) { |
|
744 jvmtiEnv* jvmtienv = jvmti(agent); |
|
745 jvmtiError jvmtierr; |
|
746 |
|
747 jvmtierr = (*jvmtienv)->AddToSystemClassLoaderSearch(jvmtienv, jarfile); |
|
748 check_phase_ret_1(jvmtierr); |
|
749 |
|
750 switch (jvmtierr) { |
|
751 case JVMTI_ERROR_NONE : |
|
752 return 0; |
|
753 case JVMTI_ERROR_CLASS_LOADER_UNSUPPORTED : |
|
754 fprintf(stderr, "System class loader does not define " |
|
755 "the appendToClassPathForInstrumentation method\n"); |
|
756 break; |
|
757 default: |
|
758 fprintf(stderr, "Unexpected error (%d) returned by " |
|
759 "AddToSystemClassLoaderSearch\n", jvmtierr); |
|
760 break; |
|
761 } |
|
762 return -1; |
|
763 } |
|
764 |
|
765 |
|
766 /* |
|
767 * res = func, free'ing the previous value of 'res' if function |
|
768 * returns a new result. |
|
769 */ |
|
770 #define TRANSFORM(res,func) { \ |
|
771 char* tmp = func; \ |
|
772 if (tmp != res) { \ |
|
773 free(res); \ |
|
774 res = tmp; \ |
|
775 } \ |
|
776 jplis_assert((void*)res != (void*)NULL); \ |
|
777 } |
|
778 |
|
779 /** |
|
780 * Convert a pathname to canonical form. |
|
781 * This method is exported from libjava. |
|
782 */ |
|
783 extern int |
|
784 Canonicalize(JNIEnv *unused, char *orig, char *out, int len); |
|
785 |
|
786 |
|
787 /* |
|
788 * This function takes the value of the Boot-Class-Path attribute, |
|
789 * splits it into the individual path segments, and then combines it |
|
790 * with the path to the jar file to create the path to be added |
|
791 * to the bootclasspath. |
|
792 * |
|
793 * Each individual path segment starts out as a UTF8 string. Additionally |
|
794 * as the path is specified to use URI path syntax all non US-ASCII |
|
795 * characters are escaped. Once the URI path is decoded we get a UTF8 |
|
796 * string which must then be converted to the platform encoding (as it |
|
797 * will be combined with the platform path of the jar file). Once |
|
798 * converted it is then normalized (remove duplicate slashes, etc.). |
|
799 * If the resulting path is an absolute path (starts with a slash for |
|
800 * example) then the path will be added to the bootclasspath. Otherwise |
|
801 * if it's not absolute then we get the canoncial path of the agent jar |
|
802 * file and then resolve the path in the context of the base path of |
|
803 * the agent jar. |
|
804 */ |
|
805 static void |
|
806 appendBootClassPath( JPLISAgent* agent, |
|
807 const char* jarfile, |
|
808 const char* pathList ) { |
|
809 char canonicalPath[MAXPATHLEN]; |
|
810 char *parent = NULL; |
|
811 int haveBasePath = 0; |
|
812 |
|
813 int count, i; |
|
814 char **paths; |
|
815 jvmtiEnv* jvmtienv = jvmti(agent); |
|
816 jvmtiError jvmtierr; |
|
817 |
|
818 /* |
|
819 * Split the attribute value into the individual path segments |
|
820 * and process each in sequence |
|
821 */ |
|
822 splitPathList(pathList, &count, &paths); |
|
823 |
|
824 for (i=0; i<count; i++) { |
|
825 int len; |
|
826 char* path; |
|
827 char* pos; |
|
828 |
|
829 /* |
|
830 * The path segment at this point is a pointer into the attribute |
|
831 * value. As it will go through a number of transformation (tossing away |
|
832 * the previous results as we go along) it make it easier if the path |
|
833 * starts out as a heap allocated string. |
|
834 */ |
|
835 path = strdup(paths[i]); |
|
836 jplis_assert(path != (char*)NULL); |
|
837 |
|
838 /* |
|
839 * The attribute is specified to be a list of relative URIs so in theory |
|
840 * there could be a query component - if so, get rid of it. |
|
841 */ |
|
842 pos = strchr(path, '?'); |
|
843 if (pos != NULL) { |
|
844 *pos = '\0'; |
|
845 } |
|
846 |
|
847 /* |
|
848 * Check for characters that are not allowed in the path component of |
|
849 * a URI. |
|
850 */ |
|
851 if (validatePathChars(path)) { |
|
852 fprintf(stderr, "WARNING: illegal character in Boot-Class-Path value: %s\n", |
|
853 path); |
|
854 free(path); |
|
855 continue; |
|
856 } |
|
857 |
|
858 |
|
859 /* |
|
860 * Next decode any escaped characters. The result is a UTF8 string. |
|
861 */ |
|
862 TRANSFORM(path, decodePath(path,&len)); |
|
863 |
|
864 /* |
|
865 * Convert to the platform encoding |
|
866 */ |
|
867 { |
|
868 char platform[MAXPATHLEN]; |
|
869 int new_len = convertUft8ToPlatformString(path, len, platform, MAXPATHLEN); |
|
870 free(path); |
|
871 if (new_len < 0) { |
|
872 /* bogus value - exceeds maximum path size or unable to convert */ |
|
873 continue; |
|
874 } |
|
875 path = strdup(platform); |
|
876 jplis_assert(path != (char*)NULL); |
|
877 } |
|
878 |
|
879 /* |
|
880 * Post-process the URI path - needed on Windows to transform |
|
881 * /c:/foo to c:/foo. |
|
882 */ |
|
883 TRANSFORM(path, fromURIPath(path)); |
|
884 |
|
885 /* |
|
886 * Normalize the path - no duplicate slashes (except UNCs on Windows), trailing |
|
887 * slash removed. |
|
888 */ |
|
889 TRANSFORM(path, normalize(path)); |
|
890 |
|
891 /* |
|
892 * If the path is an absolute path then add to the bootclassloader |
|
893 * search path. Otherwise we get the canonical path of the agent jar |
|
894 * and then use its base path (directory) to resolve the given path |
|
895 * segment. |
|
896 * |
|
897 * NOTE: JVMTI is specified to use modified UTF8 strings (like JNI). |
|
898 * In 1.5.0 the AddToBootstrapClassLoaderSearch takes a platform string |
|
899 * - see 5049313. |
|
900 */ |
|
901 if (isAbsolute(path)) { |
|
902 jvmtierr = (*jvmtienv)->AddToBootstrapClassLoaderSearch(jvmtienv, path); |
|
903 } else { |
|
904 char* resolved; |
|
905 |
|
906 if (!haveBasePath) { |
|
907 /* Use NULL as the JNIEnv since we know that Canonicalize does not use it. */ |
|
908 if (Canonicalize(NULL, (char*)jarfile, canonicalPath, sizeof(canonicalPath)) != 0) { |
|
909 fprintf(stderr, "WARNING: unable to canonicalize %s\n", jarfile); |
|
910 free(path); |
|
911 continue; |
|
912 } |
|
913 parent = basePath(canonicalPath); |
|
914 jplis_assert(parent != (char*)NULL); |
|
915 haveBasePath = 1; |
|
916 } |
|
917 |
|
918 resolved = resolve(parent, path); |
|
919 jvmtierr = (*jvmtienv)->AddToBootstrapClassLoaderSearch(jvmtienv, resolved); |
|
920 } |
|
921 |
|
922 /* print warning if boot class path not updated */ |
|
923 if (jvmtierr != JVMTI_ERROR_NONE) { |
|
924 check_phase_blob_ret(jvmtierr, free(path)); |
|
925 |
|
926 fprintf(stderr, "WARNING: %s not added to bootstrap class loader search: ", path); |
|
927 switch (jvmtierr) { |
|
928 case JVMTI_ERROR_ILLEGAL_ARGUMENT : |
|
929 fprintf(stderr, "Illegal argument or not JAR file\n"); |
|
930 break; |
|
931 default: |
|
932 fprintf(stderr, "Unexpected error: %d\n", jvmtierr); |
|
933 } |
|
934 } |
|
935 |
|
936 /* finished with the path */ |
|
937 free(path); |
|
938 } |
|
939 |
|
940 |
|
941 /* clean-up */ |
|
942 if (haveBasePath && parent != canonicalPath) { |
|
943 free(parent); |
|
944 } |
|
945 } |