Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 #include <linux/compiler.h>
0003 #include <linux/string.h>
0004 #include <sys/types.h>
0005 #include <stdio.h>
0006 #include <string.h>
0007 #include <stdlib.h>
0008 #include <err.h>
0009 #include <jvmti.h>
0010 #ifdef HAVE_JVMTI_CMLR
0011 #include <jvmticmlr.h>
0012 #endif
0013 #include <limits.h>
0014 
0015 #include "jvmti_agent.h"
0016 
0017 static int has_line_numbers;
0018 void *jvmti_agent;
0019 
0020 static void print_error(jvmtiEnv *jvmti, const char *msg, jvmtiError ret)
0021 {
0022     char *err_msg = NULL;
0023     jvmtiError err;
0024     err = (*jvmti)->GetErrorName(jvmti, ret, &err_msg);
0025     if (err == JVMTI_ERROR_NONE) {
0026         warnx("%s failed with %s", msg, err_msg);
0027         (*jvmti)->Deallocate(jvmti, (unsigned char *)err_msg);
0028     } else {
0029         warnx("%s failed with an unknown error %d", msg, ret);
0030     }
0031 }
0032 
0033 #ifdef HAVE_JVMTI_CMLR
0034 static jvmtiError
0035 do_get_line_number(jvmtiEnv *jvmti, void *pc, jmethodID m, jint bci,
0036            jvmti_line_info_t *tab)
0037 {
0038     jint i, nr_lines = 0;
0039     jvmtiLineNumberEntry *loc_tab = NULL;
0040     jvmtiError ret;
0041     jint src_line = -1;
0042 
0043     ret = (*jvmti)->GetLineNumberTable(jvmti, m, &nr_lines, &loc_tab);
0044     if (ret == JVMTI_ERROR_ABSENT_INFORMATION || ret == JVMTI_ERROR_NATIVE_METHOD) {
0045         /* No debug information for this method */
0046         return ret;
0047     } else if (ret != JVMTI_ERROR_NONE) {
0048         print_error(jvmti, "GetLineNumberTable", ret);
0049         return ret;
0050     }
0051 
0052     for (i = 0; i < nr_lines && loc_tab[i].start_location <= bci; i++) {
0053         src_line = i;
0054     }
0055 
0056     if (src_line != -1) {
0057         tab->pc = (unsigned long)pc;
0058         tab->line_number = loc_tab[src_line].line_number;
0059         tab->discrim = 0; /* not yet used */
0060         tab->methodID = m;
0061 
0062         ret = JVMTI_ERROR_NONE;
0063     } else {
0064         ret = JVMTI_ERROR_ABSENT_INFORMATION;
0065     }
0066 
0067     (*jvmti)->Deallocate(jvmti, (unsigned char *)loc_tab);
0068 
0069     return ret;
0070 }
0071 
0072 static jvmtiError
0073 get_line_numbers(jvmtiEnv *jvmti, const void *compile_info, jvmti_line_info_t **tab, int *nr_lines)
0074 {
0075     const jvmtiCompiledMethodLoadRecordHeader *hdr;
0076     jvmtiCompiledMethodLoadInlineRecord *rec;
0077     PCStackInfo *c;
0078     jint ret;
0079     int nr_total = 0;
0080     int i, lines_total = 0;
0081 
0082     if (!(tab && nr_lines))
0083         return JVMTI_ERROR_NULL_POINTER;
0084 
0085     /*
0086      * Phase 1 -- get the number of lines necessary
0087      */
0088     for (hdr = compile_info; hdr != NULL; hdr = hdr->next) {
0089         if (hdr->kind == JVMTI_CMLR_INLINE_INFO) {
0090             rec = (jvmtiCompiledMethodLoadInlineRecord *)hdr;
0091             nr_total += rec->numpcs;
0092         }
0093     }
0094 
0095     if (nr_total == 0)
0096         return JVMTI_ERROR_NOT_FOUND;
0097 
0098     /*
0099      * Phase 2 -- allocate big enough line table
0100      */
0101     *tab = malloc(nr_total * sizeof(**tab));
0102     if (!*tab)
0103         return JVMTI_ERROR_OUT_OF_MEMORY;
0104 
0105     for (hdr = compile_info; hdr != NULL; hdr = hdr->next) {
0106         if (hdr->kind == JVMTI_CMLR_INLINE_INFO) {
0107             rec = (jvmtiCompiledMethodLoadInlineRecord *)hdr;
0108             for (i = 0; i < rec->numpcs; i++) {
0109                 c = rec->pcinfo + i;
0110                                 /*
0111                                  * c->methods is the stack of inlined method calls
0112                                  * at c->pc. [0] is the leaf method. Caller frames
0113                                  * are ignored at the moment.
0114                                  */
0115                 ret = do_get_line_number(jvmti, c->pc,
0116                              c->methods[0],
0117                              c->bcis[0],
0118                              *tab + lines_total);
0119                 if (ret == JVMTI_ERROR_NONE)
0120                     lines_total++;
0121             }
0122         }
0123     }
0124     *nr_lines = lines_total;
0125     return JVMTI_ERROR_NONE;
0126 }
0127 #else /* HAVE_JVMTI_CMLR */
0128 
0129 static jvmtiError
0130 get_line_numbers(jvmtiEnv *jvmti __maybe_unused, const void *compile_info __maybe_unused,
0131          jvmti_line_info_t **tab __maybe_unused, int *nr_lines __maybe_unused)
0132 {
0133     return JVMTI_ERROR_NONE;
0134 }
0135 #endif /* HAVE_JVMTI_CMLR */
0136 
0137 static void
0138 copy_class_filename(const char * class_sign, const char * file_name, char * result, size_t max_length)
0139 {
0140     /*
0141     * Assume path name is class hierarchy, this is a common practice with Java programs
0142     */
0143     if (*class_sign == 'L') {
0144         int j, i = 0;
0145         char *p = strrchr(class_sign, '/');
0146         if (p) {
0147             /* drop the 'L' prefix and copy up to the final '/' */
0148             for (i = 0; i < (p - class_sign); i++)
0149                 result[i] = class_sign[i+1];
0150         }
0151         /*
0152         * append file name, we use loops and not string ops to avoid modifying
0153         * class_sign which is used later for the symbol name
0154         */
0155         for (j = 0; i < (max_length - 1) && file_name && j < strlen(file_name); j++, i++)
0156             result[i] = file_name[j];
0157 
0158         result[i] = '\0';
0159     } else {
0160         /* fallback case */
0161         strlcpy(result, file_name, max_length);
0162     }
0163 }
0164 
0165 static jvmtiError
0166 get_source_filename(jvmtiEnv *jvmti, jmethodID methodID, char ** buffer)
0167 {
0168     jvmtiError ret;
0169     jclass decl_class;
0170     char *file_name = NULL;
0171     char *class_sign = NULL;
0172     char fn[PATH_MAX];
0173     size_t len;
0174 
0175     ret = (*jvmti)->GetMethodDeclaringClass(jvmti, methodID, &decl_class);
0176     if (ret != JVMTI_ERROR_NONE) {
0177         print_error(jvmti, "GetMethodDeclaringClass", ret);
0178         return ret;
0179     }
0180 
0181     ret = (*jvmti)->GetSourceFileName(jvmti, decl_class, &file_name);
0182     if (ret != JVMTI_ERROR_NONE) {
0183         print_error(jvmti, "GetSourceFileName", ret);
0184         return ret;
0185     }
0186 
0187     ret = (*jvmti)->GetClassSignature(jvmti, decl_class, &class_sign, NULL);
0188     if (ret != JVMTI_ERROR_NONE) {
0189         print_error(jvmti, "GetClassSignature", ret);
0190         goto free_file_name_error;
0191     }
0192 
0193     copy_class_filename(class_sign, file_name, fn, PATH_MAX);
0194     len = strlen(fn);
0195     *buffer = malloc((len + 1) * sizeof(char));
0196     if (!*buffer) {
0197         print_error(jvmti, "GetClassSignature", ret);
0198         ret = JVMTI_ERROR_OUT_OF_MEMORY;
0199         goto free_class_sign_error;
0200     }
0201     strcpy(*buffer, fn);
0202     ret = JVMTI_ERROR_NONE;
0203 
0204 free_class_sign_error:
0205     (*jvmti)->Deallocate(jvmti, (unsigned char *)class_sign);
0206 free_file_name_error:
0207     (*jvmti)->Deallocate(jvmti, (unsigned char *)file_name);
0208 
0209     return ret;
0210 }
0211 
0212 static jvmtiError
0213 fill_source_filenames(jvmtiEnv *jvmti, int nr_lines,
0214               const jvmti_line_info_t * line_tab,
0215               char ** file_names)
0216 {
0217     int index;
0218     jvmtiError ret;
0219 
0220     for (index = 0; index < nr_lines; ++index) {
0221         ret = get_source_filename(jvmti, line_tab[index].methodID, &(file_names[index]));
0222         if (ret != JVMTI_ERROR_NONE)
0223             return ret;
0224     }
0225 
0226     return JVMTI_ERROR_NONE;
0227 }
0228 
0229 static void JNICALL
0230 compiled_method_load_cb(jvmtiEnv *jvmti,
0231             jmethodID method,
0232             jint code_size,
0233             void const *code_addr,
0234             jint map_length,
0235             jvmtiAddrLocationMap const *map,
0236             const void *compile_info)
0237 {
0238     jvmti_line_info_t *line_tab = NULL;
0239     char ** line_file_names = NULL;
0240     jclass decl_class;
0241     char *class_sign = NULL;
0242     char *func_name = NULL;
0243     char *func_sign = NULL;
0244     uint64_t addr = (uint64_t)(uintptr_t)code_addr;
0245     jvmtiError ret;
0246     int nr_lines = 0; /* in line_tab[] */
0247     size_t len;
0248     int output_debug_info = 0;
0249 
0250     ret = (*jvmti)->GetMethodDeclaringClass(jvmti, method,
0251                         &decl_class);
0252     if (ret != JVMTI_ERROR_NONE) {
0253         print_error(jvmti, "GetMethodDeclaringClass", ret);
0254         return;
0255     }
0256 
0257     if (has_line_numbers && map && map_length) {
0258         ret = get_line_numbers(jvmti, compile_info, &line_tab, &nr_lines);
0259         if (ret != JVMTI_ERROR_NONE) {
0260             if (ret != JVMTI_ERROR_NOT_FOUND) {
0261                 warnx("jvmti: cannot get line table for method");
0262             }
0263             nr_lines = 0;
0264         } else if (nr_lines > 0) {
0265             line_file_names = malloc(sizeof(char*) * nr_lines);
0266             if (!line_file_names) {
0267                 warnx("jvmti: cannot allocate space for line table method names");
0268             } else {
0269                 memset(line_file_names, 0, sizeof(char*) * nr_lines);
0270                 ret = fill_source_filenames(jvmti, nr_lines, line_tab, line_file_names);
0271                 if (ret != JVMTI_ERROR_NONE) {
0272                     warnx("jvmti: fill_source_filenames failed");
0273                 } else {
0274                     output_debug_info = 1;
0275                 }
0276             }
0277         }
0278     }
0279 
0280     ret = (*jvmti)->GetClassSignature(jvmti, decl_class,
0281                       &class_sign, NULL);
0282     if (ret != JVMTI_ERROR_NONE) {
0283         print_error(jvmti, "GetClassSignature", ret);
0284         goto error;
0285     }
0286 
0287     ret = (*jvmti)->GetMethodName(jvmti, method, &func_name,
0288                       &func_sign, NULL);
0289     if (ret != JVMTI_ERROR_NONE) {
0290         print_error(jvmti, "GetMethodName", ret);
0291         goto error;
0292     }
0293 
0294     /*
0295      * write source line info record if we have it
0296      */
0297     if (output_debug_info)
0298         if (jvmti_write_debug_info(jvmti_agent, addr, nr_lines, line_tab, (const char * const *) line_file_names))
0299             warnx("jvmti: write_debug_info() failed");
0300 
0301     len = strlen(func_name) + strlen(class_sign) + strlen(func_sign) + 2;
0302     {
0303         char str[len];
0304         snprintf(str, len, "%s%s%s", class_sign, func_name, func_sign);
0305 
0306         if (jvmti_write_code(jvmti_agent, str, addr, code_addr, code_size))
0307             warnx("jvmti: write_code() failed");
0308     }
0309 error:
0310     (*jvmti)->Deallocate(jvmti, (unsigned char *)func_name);
0311     (*jvmti)->Deallocate(jvmti, (unsigned char *)func_sign);
0312     (*jvmti)->Deallocate(jvmti, (unsigned char *)class_sign);
0313     free(line_tab);
0314     while (line_file_names && (nr_lines > 0)) {
0315         if (line_file_names[nr_lines - 1]) {
0316             free(line_file_names[nr_lines - 1]);
0317         }
0318         nr_lines -= 1;
0319     }
0320     free(line_file_names);
0321 }
0322 
0323 static void JNICALL
0324 code_generated_cb(jvmtiEnv *jvmti,
0325           char const *name,
0326           void const *code_addr,
0327           jint code_size)
0328 {
0329     uint64_t addr = (uint64_t)(unsigned long)code_addr;
0330     int ret;
0331 
0332     ret = jvmti_write_code(jvmti_agent, name, addr, code_addr, code_size);
0333     if (ret)
0334         warnx("jvmti: write_code() failed for code_generated");
0335 }
0336 
0337 JNIEXPORT jint JNICALL
0338 Agent_OnLoad(JavaVM *jvm, char *options, void *reserved __maybe_unused)
0339 {
0340     jvmtiEventCallbacks cb;
0341     jvmtiCapabilities caps1;
0342     jvmtiJlocationFormat format;
0343     jvmtiEnv *jvmti = NULL;
0344     jint ret;
0345 
0346     jvmti_agent = jvmti_open();
0347     if (!jvmti_agent) {
0348         warnx("jvmti: open_agent failed");
0349         return -1;
0350     }
0351 
0352     /*
0353      * Request a JVMTI interface version 1 environment
0354      */
0355     ret = (*jvm)->GetEnv(jvm, (void *)&jvmti, JVMTI_VERSION_1);
0356     if (ret != JNI_OK) {
0357         warnx("jvmti: jvmti version 1 not supported");
0358         return -1;
0359     }
0360 
0361     /*
0362      * acquire method_load capability, we require it
0363      * request line numbers (optional)
0364      */
0365     memset(&caps1, 0, sizeof(caps1));
0366     caps1.can_generate_compiled_method_load_events = 1;
0367 
0368     ret = (*jvmti)->AddCapabilities(jvmti, &caps1);
0369     if (ret != JVMTI_ERROR_NONE) {
0370         print_error(jvmti, "AddCapabilities", ret);
0371         return -1;
0372     }
0373     ret = (*jvmti)->GetJLocationFormat(jvmti, &format);
0374         if (ret == JVMTI_ERROR_NONE && format == JVMTI_JLOCATION_JVMBCI) {
0375                 memset(&caps1, 0, sizeof(caps1));
0376                 caps1.can_get_line_numbers = 1;
0377                 caps1.can_get_source_file_name = 1;
0378         ret = (*jvmti)->AddCapabilities(jvmti, &caps1);
0379                 if (ret == JVMTI_ERROR_NONE)
0380                         has_line_numbers = 1;
0381         } else if (ret != JVMTI_ERROR_NONE)
0382         print_error(jvmti, "GetJLocationFormat", ret);
0383 
0384 
0385     memset(&cb, 0, sizeof(cb));
0386 
0387     cb.CompiledMethodLoad   = compiled_method_load_cb;
0388     cb.DynamicCodeGenerated = code_generated_cb;
0389 
0390     ret = (*jvmti)->SetEventCallbacks(jvmti, &cb, sizeof(cb));
0391     if (ret != JVMTI_ERROR_NONE) {
0392         print_error(jvmti, "SetEventCallbacks", ret);
0393         return -1;
0394     }
0395 
0396     ret = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE,
0397             JVMTI_EVENT_COMPILED_METHOD_LOAD, NULL);
0398     if (ret != JVMTI_ERROR_NONE) {
0399         print_error(jvmti, "SetEventNotificationMode(METHOD_LOAD)", ret);
0400         return -1;
0401     }
0402 
0403     ret = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE,
0404             JVMTI_EVENT_DYNAMIC_CODE_GENERATED, NULL);
0405     if (ret != JVMTI_ERROR_NONE) {
0406         print_error(jvmti, "SetEventNotificationMode(CODE_GENERATED)", ret);
0407         return -1;
0408     }
0409     return 0;
0410 }
0411 
0412 JNIEXPORT void JNICALL
0413 Agent_OnUnload(JavaVM *jvm __maybe_unused)
0414 {
0415     int ret;
0416 
0417     ret = jvmti_close(jvmti_agent);
0418     if (ret)
0419         errx(1, "Error: op_close_agent()");
0420 }