1 /*
2 * Copyright (c) 2003, 2019, 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
parseArgumentTail(char * tail,char ** name,char ** options)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
getBooleanAttribute(const jarAttribute * attributes,const char * name)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
convertCapabilityAttributes(const jarAttribute * attributes,JPLISAgent * agent)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
DEF_Agent_OnLoad(JavaVM * vm,char * tail,void * reserved)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 /*
204 * According to JVMS class name is represented as CONSTANT_Utf8_info,
205 * so its length is u2 (i.e. must be <= 0xFFFF).
206 * Negative oldLen or newLen means we got signed integer overflow
207 * (modifiedUtf8LengthOfUtf8 returns negative value if oldLen is negative).
208 */
209 if (oldLen < 0 || newLen < 0 || newLen > 0xFFFF) {
210 fprintf(stderr, "-javaagent: Premain-Class value is too big\n");
211 free(jarfile);
212 if (options != NULL) free(options);
213 freeAttributes(attributes);
214 return JNI_ERR;
215 }
216 if (newLen == oldLen) {
217 premainClass = strdup(premainClass);
218 } else {
219 char* str = (char*)malloc( newLen+1 );
220 if (str != NULL) {
221 convertUtf8ToModifiedUtf8(premainClass, oldLen, str, newLen);
222 }
223 premainClass = str;
224 }
225 if (premainClass == NULL) {
226 fprintf(stderr, "-javaagent: memory allocation failed\n");
227 free(jarfile);
228 if (options != NULL) free(options);
229 freeAttributes(attributes);
230 return JNI_ERR;
231 }
232
233 /*
234 * If the Boot-Class-Path attribute is specified then we process
235 * each relative URL and add it to the bootclasspath.
236 */
237 bootClassPath = getAttribute(attributes, "Boot-Class-Path");
238 if (bootClassPath != NULL) {
239 appendBootClassPath(agent, jarfile, bootClassPath);
240 }
241
242 /*
243 * Convert JAR attributes into agent capabilities
244 */
245 convertCapabilityAttributes(attributes, agent);
246
247 /*
248 * Track (record) the agent class name and options data
249 */
250 initerror = recordCommandLineData(agent, premainClass, options);
251
252 /*
253 * Clean-up
254 */
255 if (options != NULL) free(options);
256 freeAttributes(attributes);
257 free(premainClass);
258 }
259
260 switch (initerror) {
261 case JPLIS_INIT_ERROR_NONE:
262 result = JNI_OK;
263 break;
264 case JPLIS_INIT_ERROR_CANNOT_CREATE_NATIVE_AGENT:
265 result = JNI_ERR;
266 fprintf(stderr, "java.lang.instrument/-javaagent: cannot create native agent.\n");
267 break;
268 case JPLIS_INIT_ERROR_FAILURE:
269 result = JNI_ERR;
270 fprintf(stderr, "java.lang.instrument/-javaagent: initialization of native agent failed.\n");
271 break;
272 case JPLIS_INIT_ERROR_ALLOCATION_FAILURE:
273 result = JNI_ERR;
274 fprintf(stderr, "java.lang.instrument/-javaagent: allocation failure.\n");
275 break;
276 case JPLIS_INIT_ERROR_AGENT_CLASS_NOT_SPECIFIED:
277 result = JNI_ERR;
278 fprintf(stderr, "-javaagent: agent class not specified.\n");
279 break;
280 default:
281 result = JNI_ERR;
282 fprintf(stderr, "java.lang.instrument/-javaagent: unknown error\n");
283 break;
284 }
285 return result;
286 }
287
288 /*
289 * Agent_OnAttach returns a jint. 0/JNI_OK indicates success and non-0
290 * indicates an error. To allow the attach mechanism throw an
291 * AgentInitializationException with a reasonable exception message we define
292 * a few specific errors here.
293 */
294 #define AGENT_ERROR_BADJAR ((jint)100) /* Agent JAR not found or no Agent-Class attribute */
295 #define AGENT_ERROR_NOTONCP ((jint)101) /* Unable to add JAR file to system class path */
296 #define AGENT_ERROR_STARTFAIL ((jint)102) /* No agentmain method or agentmain failed */
297
298 /*
299 * This will be called once each time a tool attaches to the VM and loads
300 * the JPLIS library.
301 */
302 JNIEXPORT jint JNICALL
DEF_Agent_OnAttach(JavaVM * vm,char * args,void * reserved)303 DEF_Agent_OnAttach(JavaVM* vm, char *args, void * reserved) {
304 JPLISInitializationError initerror = JPLIS_INIT_ERROR_NONE;
305 jint result = JNI_OK;
306 JPLISAgent * agent = NULL;
307 JNIEnv * jni_env = NULL;
308
309 /*
310 * Need JNIEnv - guaranteed to be called from thread that is already
311 * attached to VM
312 */
313 result = (*vm)->GetEnv(vm, (void**)&jni_env, JNI_VERSION_1_2);
314 jplis_assert(result==JNI_OK);
315
316 initerror = createNewJPLISAgent(vm, &agent);
317 if ( initerror == JPLIS_INIT_ERROR_NONE ) {
318 int oldLen, newLen;
319 char * jarfile;
320 char * options;
321 jarAttribute* attributes;
322 char * agentClass;
323 char * bootClassPath;
324 jboolean success;
325
326 /*
327 * Parse <jarfile>[=options] into jarfile and options
328 */
329 if (parseArgumentTail(args, &jarfile, &options) != 0) {
330 return JNI_ENOMEM;
331 }
332
333 /*
334 * Open the JAR file and parse the manifest
335 */
336 attributes = readAttributes( jarfile );
337 if (attributes == NULL) {
338 fprintf(stderr, "Error opening zip file or JAR manifest missing: %s\n", jarfile);
339 free(jarfile);
340 if (options != NULL) free(options);
341 return AGENT_ERROR_BADJAR;
342 }
343
344 agentClass = getAttribute(attributes, "Agent-Class");
345 if (agentClass == NULL) {
346 fprintf(stderr, "Failed to find Agent-Class manifest attribute from %s\n",
347 jarfile);
348 free(jarfile);
349 if (options != NULL) free(options);
350 freeAttributes(attributes);
351 return AGENT_ERROR_BADJAR;
352 }
353
354 /*
355 * Add the jarfile to the system class path
356 */
357 if (appendClassPath(agent, jarfile)) {
358 fprintf(stderr, "Unable to add %s to system class path "
359 "- not supported by system class loader or configuration error!\n",
360 jarfile);
361 free(jarfile);
362 if (options != NULL) free(options);
363 freeAttributes(attributes);
364 return AGENT_ERROR_NOTONCP;
365 }
366
367 /*
368 * The value of the Agent-Class attribute becomes the agent
369 * class name. The manifest is in UTF8 so need to convert to
370 * modified UTF8 (see JNI spec).
371 */
372 oldLen = (int)strlen(agentClass);
373 newLen = modifiedUtf8LengthOfUtf8(agentClass, oldLen);
374 /*
375 * According to JVMS class name is represented as CONSTANT_Utf8_info,
376 * so its length is u2 (i.e. must be <= 0xFFFF).
377 * Negative oldLen or newLen means we got signed integer overflow
378 * (modifiedUtf8LengthOfUtf8 returns negative value if oldLen is negative).
379 */
380 if (oldLen < 0 || newLen < 0 || newLen > 0xFFFF) {
381 fprintf(stderr, "Agent-Class value is too big\n");
382 free(jarfile);
383 if (options != NULL) free(options);
384 freeAttributes(attributes);
385 return AGENT_ERROR_BADJAR;
386 }
387 if (newLen == oldLen) {
388 agentClass = strdup(agentClass);
389 } else {
390 char* str = (char*)malloc( newLen+1 );
391 if (str != NULL) {
392 convertUtf8ToModifiedUtf8(agentClass, oldLen, str, newLen);
393 }
394 agentClass = str;
395 }
396 if (agentClass == NULL) {
397 free(jarfile);
398 if (options != NULL) free(options);
399 freeAttributes(attributes);
400 return JNI_ENOMEM;
401 }
402
403 /*
404 * If the Boot-Class-Path attribute is specified then we process
405 * each URL - in the live phase only JAR files will be added.
406 */
407 bootClassPath = getAttribute(attributes, "Boot-Class-Path");
408 if (bootClassPath != NULL) {
409 appendBootClassPath(agent, jarfile, bootClassPath);
410 }
411
412 /*
413 * Convert JAR attributes into agent capabilities
414 */
415 convertCapabilityAttributes(attributes, agent);
416
417 /*
418 * Create the java.lang.instrument.Instrumentation instance
419 */
420 success = createInstrumentationImpl(jni_env, agent);
421 jplis_assert(success);
422
423 /*
424 * Setup ClassFileLoadHook handler.
425 */
426 if (success) {
427 success = setLivePhaseEventHandlers(agent);
428 jplis_assert(success);
429 }
430
431 /*
432 * Start the agent
433 */
434 if (success) {
435 success = startJavaAgent(agent,
436 jni_env,
437 agentClass,
438 options,
439 agent->mAgentmainCaller);
440 }
441
442 if (!success) {
443 fprintf(stderr, "Agent failed to start!\n");
444 result = AGENT_ERROR_STARTFAIL;
445 }
446
447 /*
448 * Clean-up
449 */
450 free(jarfile);
451 if (options != NULL) free(options);
452 free(agentClass);
453 freeAttributes(attributes);
454 }
455
456 return result;
457 }
458
459
460 JNIEXPORT void JNICALL
DEF_Agent_OnUnload(JavaVM * vm)461 DEF_Agent_OnUnload(JavaVM *vm) {
462 }
463
464 /**
465 * Invoked by the java launcher to load an agent in the main executable JAR.
466 * The Launcher-Agent-Class attribute in the main manifest of the JAR file
467 * is the agent class.
468 *
469 * Returns JNI_OK if the agent is loaded and initialized; JNI_ERR if this
470 * function fails, possibly with a pending exception.
471 */
loadAgent(JNIEnv * env,jstring path)472 jint loadAgent(JNIEnv* env, jstring path) {
473 JavaVM* vm;
474 JPLISAgent* agent;
475 const char* jarfile = NULL;
476 jarAttribute* attributes = NULL;
477 char* agentClass = NULL;
478 char* bootClassPath;
479 int oldLen, newLen;
480 jint result = JNI_ERR;
481
482 if ((*env)->GetJavaVM(env, &vm) < 0) {
483 return JNI_ERR;
484 }
485
486 // create JPLISAgent with JVMTI environment
487 if (createNewJPLISAgent(vm, &agent) != JPLIS_INIT_ERROR_NONE) {
488 return JNI_ERR;
489 }
490
491 // get path to JAR file as UTF-8 string
492 jarfile = (*env)->GetStringUTFChars(env, path, NULL);
493 if (jarfile == NULL) {
494 return JNI_ERR;
495 }
496
497 // read the attributes in the main section of JAR manifest
498 attributes = readAttributes(jarfile);
499 if (attributes == NULL) {
500 goto releaseAndReturn;
501 }
502
503 // Launcher-Agent-Class is required
504 agentClass = getAttribute(attributes, "Launcher-Agent-Class");
505 if (agentClass == NULL) {
506 goto releaseAndReturn;
507 }
508
509 // The value of Launcher-Agent-Class is in UTF-8, convert it to modified UTF-8
510 oldLen = (int) strlen(agentClass);
511 newLen = modifiedUtf8LengthOfUtf8(agentClass, oldLen);
512 /*
513 * According to JVMS class name is represented as CONSTANT_Utf8_info,
514 * so its length is u2 (i.e. must be <= 0xFFFF).
515 * Negative oldLen or newLen means we got signed integer overflow
516 * (modifiedUtf8LengthOfUtf8 returns negative value if oldLen is negative).
517 */
518 if (oldLen < 0 || newLen < 0 || newLen > 0xFFFF) {
519 goto releaseAndReturn;
520 }
521 if (newLen == oldLen) {
522 agentClass = strdup(agentClass);
523 } else {
524 char* str = (char*) malloc(newLen + 1);
525 if (str != NULL) {
526 convertUtf8ToModifiedUtf8(agentClass, oldLen, str, newLen);
527 }
528 agentClass = str;
529 }
530 if (agentClass == NULL) {
531 jthrowable oome = createThrowable(env, "java/lang/OutOfMemoryError", NULL);
532 if (oome != NULL) (*env)->Throw(env, oome);
533 goto releaseAndReturn;
534 }
535
536 // Boot-Class-Path
537 bootClassPath = getAttribute(attributes, "Boot-Class-Path");
538 if (bootClassPath != NULL) {
539 appendBootClassPath(agent, jarfile, bootClassPath);
540 }
541
542 // Can-XXXX capabilities
543 convertCapabilityAttributes(attributes, agent);
544
545 // Create the java.lang.instrument.Instrumentation object
546 if (!createInstrumentationImpl(env, agent)) {
547 goto releaseAndReturn;
548 }
549
550 // Enable the ClassFileLoadHook
551 if (!setLivePhaseEventHandlers(agent)) {
552 goto releaseAndReturn;
553 }
554
555 // invoke the agentmain method
556 if (!startJavaAgent(agent, env, agentClass, "", agent->mAgentmainCaller)) {
557 goto releaseAndReturn;
558 }
559
560 // initialization complete
561 result = JNI_OK;
562
563 releaseAndReturn:
564 if (agentClass != NULL) {
565 free(agentClass);
566 }
567 if (attributes != NULL) {
568 freeAttributes(attributes);
569 }
570 if (jarfile != NULL) {
571 (*env)->ReleaseStringUTFChars(env, path, jarfile);
572 }
573
574 return result;
575 }
576
577 /*
578 * JVMTI callback support
579 *
580 * We have two "stages" of callback support.
581 * At OnLoad time, we install a VMInit handler.
582 * When the VMInit handler runs, we remove the VMInit handler and install a
583 * ClassFileLoadHook handler.
584 */
585
586 void JNICALL
eventHandlerVMInit(jvmtiEnv * jvmtienv,JNIEnv * jnienv,jthread thread)587 eventHandlerVMInit( jvmtiEnv * jvmtienv,
588 JNIEnv * jnienv,
589 jthread thread) {
590 JPLISEnvironment * environment = NULL;
591 jboolean success = JNI_FALSE;
592
593 environment = getJPLISEnvironment(jvmtienv);
594
595 /* process the premain calls on the all the JPL agents */
596 if (environment == NULL) {
597 abortJVM(jnienv, JPLIS_ERRORMESSAGE_CANNOTSTART ", getting JPLIS environment failed");
598 }
599 jthrowable outstandingException = NULL;
600 /*
601 * Add the jarfile to the system class path
602 */
603 JPLISAgent * agent = environment->mAgent;
604 if (appendClassPath(agent, agent->mJarfile)) {
605 fprintf(stderr, "Unable to add %s to system class path - "
606 "the system class loader does not define the "
607 "appendToClassPathForInstrumentation method or the method failed\n",
608 agent->mJarfile);
609 free((void *)agent->mJarfile);
610 abortJVM(jnienv, JPLIS_ERRORMESSAGE_CANNOTSTART ", appending to system class path failed");
611 }
612 free((void *)agent->mJarfile);
613 agent->mJarfile = NULL;
614
615 outstandingException = preserveThrowable(jnienv);
616 success = processJavaStart( environment->mAgent, jnienv);
617 restoreThrowable(jnienv, outstandingException);
618
619 /* if we fail to start cleanly, bring down the JVM */
620 if ( !success ) {
621 abortJVM(jnienv, JPLIS_ERRORMESSAGE_CANNOTSTART ", processJavaStart failed");
622 }
623 }
624
625 void JNICALL
eventHandlerClassFileLoadHook(jvmtiEnv * jvmtienv,JNIEnv * jnienv,jclass class_being_redefined,jobject loader,const char * name,jobject protectionDomain,jint class_data_len,const unsigned char * class_data,jint * new_class_data_len,unsigned char ** new_class_data)626 eventHandlerClassFileLoadHook( jvmtiEnv * jvmtienv,
627 JNIEnv * jnienv,
628 jclass class_being_redefined,
629 jobject loader,
630 const char* name,
631 jobject protectionDomain,
632 jint class_data_len,
633 const unsigned char* class_data,
634 jint* new_class_data_len,
635 unsigned char** new_class_data) {
636 JPLISEnvironment * environment = NULL;
637
638 environment = getJPLISEnvironment(jvmtienv);
639
640 /* if something is internally inconsistent (no agent), just silently return without touching the buffer */
641 if ( environment != NULL ) {
642 jthrowable outstandingException = preserveThrowable(jnienv);
643 transformClassFile( environment->mAgent,
644 jnienv,
645 loader,
646 name,
647 class_being_redefined,
648 protectionDomain,
649 class_data_len,
650 class_data,
651 new_class_data_len,
652 new_class_data,
653 environment->mIsRetransformer);
654 restoreThrowable(jnienv, outstandingException);
655 }
656 }
657
658
659
660
661 /*
662 * URLs in Boot-Class-Path attributes are separated by one or more spaces.
663 * This function splits the attribute value into a list of path segments.
664 * The attribute value is in UTF8 but cannot contain NUL. Also non US-ASCII
665 * characters must be escaped (URI syntax) so safe to iterate through the
666 * value as a C string.
667 */
668 static void
splitPathList(const char * str,int * pathCount,char *** paths)669 splitPathList(const char* str, int* pathCount, char*** paths) {
670 int count = 0;
671 char** segments = NULL;
672 char** new_segments;
673 char* c = (char*) str;
674 while (*c != '\0') {
675 while (*c == ' ') c++; /* skip leading spaces */
676 if (*c == '\0') {
677 break;
678 }
679 new_segments = (char**)realloc(segments, (count+1)*sizeof(char*));
680 if (new_segments == NULL) {
681 jplis_assert(0);
682 free(segments);
683 count = 0;
684 segments = NULL;
685 break;
686 }
687 segments = new_segments;
688 segments[count++] = c;
689 c = strchr(c, ' ');
690 if (c == NULL) {
691 break;
692 }
693 *c = '\0';
694 c++;
695 }
696 *pathCount = count;
697 *paths = segments;
698 }
699
700
701 /* URI path decoding - ported from src/share/classes/java/net/URI.java */
702
703 static int
decodeNibble(char c)704 decodeNibble(char c) {
705 if ((c >= '0') && (c <= '9'))
706 return c - '0';
707 if ((c >= 'a') && (c <= 'f'))
708 return c - 'a' + 10;
709 if ((c >= 'A') && (c <= 'F'))
710 return c - 'A' + 10;
711 return -1;
712 }
713
714 static int
decodeByte(char c1,char c2)715 decodeByte(char c1, char c2) {
716 return (((decodeNibble(c1) & 0xf) << 4) | ((decodeNibble(c2) & 0xf) << 0));
717 }
718
719 /*
720 * Evaluates all escapes in s. Assumes that escapes are well-formed
721 * syntactically, i.e., of the form %XX.
722 * If the path does not require decoding the original path is
723 * returned. Otherwise the decoded path (heap allocated) is returned,
724 * along with the length of the decoded path. Note that the return
725 * string will not be null terminated after decoding.
726 */
727 static
decodePath(const char * s,int * decodedLen)728 char *decodePath(const char *s, int* decodedLen) {
729 int n;
730 char *result;
731 char *resultp;
732 int c;
733 int i;
734
735 n = (int)strlen(s);
736 if (n == 0) {
737 *decodedLen = 0;
738 return (char*)s;
739 }
740 if (strchr(s, '%') == NULL) {
741 *decodedLen = n;
742 return (char*)s; /* no escapes, we are done */
743 }
744
745 resultp = result = calloc(n+1, 1);
746 if (result == NULL) {
747 *decodedLen = 0;
748 return NULL;
749 }
750 c = s[0];
751 for (i = 0; i < n;) {
752 if (c != '%') {
753 *resultp++ = c;
754 if (++i >= n)
755 break;
756 c = s[i];
757 continue;
758 }
759 for (;;) {
760 char b1 = s[++i];
761 char b2 = s[++i];
762 int decoded = decodeByte(b1, b2);
763 *resultp++ = decoded;
764 if (++i >= n)
765 break;
766 c = s[i];
767 if (c != '%')
768 break;
769 }
770 }
771 *decodedLen = (int)(resultp - result);
772 return result; // not null terminated.
773 }
774
775 /*
776 * Append the given jar file to the system class path. This should succeed in the
777 * onload phase but may fail in the live phase if the system class loader doesn't
778 * support appending to the class path.
779 */
780 static int
appendClassPath(JPLISAgent * agent,const char * jarfile)781 appendClassPath( JPLISAgent* agent,
782 const char* jarfile ) {
783 jvmtiEnv* jvmtienv = jvmti(agent);
784 jvmtiError jvmtierr;
785
786 jvmtierr = (*jvmtienv)->AddToSystemClassLoaderSearch(jvmtienv, jarfile);
787 check_phase_ret_1(jvmtierr);
788
789 switch (jvmtierr) {
790 case JVMTI_ERROR_NONE :
791 return 0;
792 case JVMTI_ERROR_CLASS_LOADER_UNSUPPORTED :
793 fprintf(stderr, "System class loader does not define "
794 "the appendToClassPathForInstrumentation method\n");
795 break;
796 default:
797 fprintf(stderr, "Unexpected error (%d) returned by "
798 "AddToSystemClassLoaderSearch\n", jvmtierr);
799 break;
800 }
801 return -1;
802 }
803
804
805 /*
806 * res = func, free'ing the previous value of 'res' if function
807 * returns a new result.
808 */
809 #define TRANSFORM(res,func) { \
810 char* tmp = func; \
811 if (tmp != res) { \
812 free(res); \
813 res = tmp; \
814 } \
815 jplis_assert((void*)res != (void*)NULL); \
816 }
817
818 /**
819 * Convert a pathname to canonical form.
820 * This method is exported from libjava.
821 */
822 extern int
823 Canonicalize(JNIEnv *unused, char *orig, char *out, int len);
824
825
826 /*
827 * This function takes the value of the Boot-Class-Path attribute,
828 * splits it into the individual path segments, and then combines it
829 * with the path to the jar file to create the path to be added
830 * to the bootclasspath.
831 *
832 * Each individual path segment starts out as a UTF8 string. Additionally
833 * as the path is specified to use URI path syntax all non US-ASCII
834 * characters are escaped. Once the URI path is decoded we get a UTF8
835 * string which must then be converted to the platform encoding (as it
836 * will be combined with the platform path of the jar file). Once
837 * converted it is then normalized (remove duplicate slashes, etc.).
838 * If the resulting path is an absolute path (starts with a slash for
839 * example) then the path will be added to the bootclasspath. Otherwise
840 * if it's not absolute then we get the canoncial path of the agent jar
841 * file and then resolve the path in the context of the base path of
842 * the agent jar.
843 */
844 static void
appendBootClassPath(JPLISAgent * agent,const char * jarfile,const char * pathList)845 appendBootClassPath( JPLISAgent* agent,
846 const char* jarfile,
847 const char* pathList ) {
848 char canonicalPath[MAXPATHLEN];
849 char *parent = NULL;
850 int haveBasePath = 0;
851
852 int count, i;
853 char **paths;
854 jvmtiEnv* jvmtienv = jvmti(agent);
855 jvmtiError jvmtierr;
856
857 /*
858 * Split the attribute value into the individual path segments
859 * and process each in sequence
860 */
861 splitPathList(pathList, &count, &paths);
862
863 for (i=0; i<count; i++) {
864 int len;
865 char* path;
866 char* pos;
867
868 /*
869 * The path segment at this point is a pointer into the attribute
870 * value. As it will go through a number of transformation (tossing away
871 * the previous results as we go along) it make it easier if the path
872 * starts out as a heap allocated string.
873 */
874 path = strdup(paths[i]);
875 jplis_assert(path != (char*)NULL);
876
877 /*
878 * The attribute is specified to be a list of relative URIs so in theory
879 * there could be a query component - if so, get rid of it.
880 */
881 pos = strchr(path, '?');
882 if (pos != NULL) {
883 *pos = '\0';
884 }
885
886 /*
887 * Check for characters that are not allowed in the path component of
888 * a URI.
889 */
890 if (validatePathChars(path)) {
891 fprintf(stderr, "WARNING: illegal character in Boot-Class-Path value: %s\n",
892 path);
893 free(path);
894 continue;
895 }
896
897
898 /*
899 * Next decode any escaped characters. The result is a UTF8 string.
900 */
901 TRANSFORM(path, decodePath(path,&len));
902
903 /*
904 * Convert to the platform encoding
905 */
906 {
907 char platform[MAXPATHLEN];
908 int new_len = convertUft8ToPlatformString(path, len, platform, MAXPATHLEN);
909 free(path);
910 if (new_len < 0) {
911 /* bogus value - exceeds maximum path size or unable to convert */
912 continue;
913 }
914 path = strdup(platform);
915 jplis_assert(path != (char*)NULL);
916 }
917
918 /*
919 * Post-process the URI path - needed on Windows to transform
920 * /c:/foo to c:/foo.
921 */
922 TRANSFORM(path, fromURIPath(path));
923
924 /*
925 * Normalize the path - no duplicate slashes (except UNCs on Windows), trailing
926 * slash removed.
927 */
928 TRANSFORM(path, normalize(path));
929
930 /*
931 * If the path is an absolute path then add to the bootclassloader
932 * search path. Otherwise we get the canonical path of the agent jar
933 * and then use its base path (directory) to resolve the given path
934 * segment.
935 *
936 * NOTE: JVMTI is specified to use modified UTF8 strings (like JNI).
937 * In 1.5.0 the AddToBootstrapClassLoaderSearch takes a platform string
938 * - see 5049313.
939 */
940 if (isAbsolute(path)) {
941 jvmtierr = (*jvmtienv)->AddToBootstrapClassLoaderSearch(jvmtienv, path);
942 } else {
943 char* resolved;
944
945 if (!haveBasePath) {
946 /* Use NULL as the JNIEnv since we know that Canonicalize does not use it. */
947 if (Canonicalize(NULL, (char*)jarfile, canonicalPath, sizeof(canonicalPath)) != 0) {
948 fprintf(stderr, "WARNING: unable to canonicalize %s\n", jarfile);
949 free(path);
950 continue;
951 }
952 parent = basePath(canonicalPath);
953 jplis_assert(parent != (char*)NULL);
954 haveBasePath = 1;
955 }
956
957 resolved = resolve(parent, path);
958 jvmtierr = (*jvmtienv)->AddToBootstrapClassLoaderSearch(jvmtienv, resolved);
959 }
960
961 /* print warning if boot class path not updated */
962 if (jvmtierr != JVMTI_ERROR_NONE) {
963 check_phase_blob_ret(jvmtierr, free(path));
964
965 fprintf(stderr, "WARNING: %s not added to bootstrap class loader search: ", path);
966 switch (jvmtierr) {
967 case JVMTI_ERROR_ILLEGAL_ARGUMENT :
968 fprintf(stderr, "Illegal argument or not JAR file\n");
969 break;
970 default:
971 fprintf(stderr, "Unexpected error: %d\n", jvmtierr);
972 }
973 }
974
975 /* finished with the path */
976 free(path);
977 }
978
979
980 /* clean-up */
981 if (haveBasePath && parent != canonicalPath) {
982 free(parent);
983 }
984 }
985