|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | #include <linux/compiler.h> | 
|  | #include <linux/string.h> | 
|  | #include <sys/types.h> | 
|  | #include <stdio.h> | 
|  | #include <string.h> | 
|  | #include <stdlib.h> | 
|  | #include <err.h> | 
|  | #include <jvmti.h> | 
|  | #include <jvmticmlr.h> | 
|  | #include <limits.h> | 
|  |  | 
|  | #include "jvmti_agent.h" | 
|  |  | 
|  | static int has_line_numbers; | 
|  | void *jvmti_agent; | 
|  |  | 
|  | static void print_error(jvmtiEnv *jvmti, const char *msg, jvmtiError ret) | 
|  | { | 
|  | char *err_msg = NULL; | 
|  | jvmtiError err; | 
|  | err = (*jvmti)->GetErrorName(jvmti, ret, &err_msg); | 
|  | if (err == JVMTI_ERROR_NONE) { | 
|  | warnx("%s failed with %s", msg, err_msg); | 
|  | (*jvmti)->Deallocate(jvmti, (unsigned char *)err_msg); | 
|  | } else { | 
|  | warnx("%s failed with an unknown error %d", msg, ret); | 
|  | } | 
|  | } | 
|  |  | 
|  | static jvmtiError | 
|  | do_get_line_numbers(jvmtiEnv *jvmti, void *pc, jmethodID m, jint bci, | 
|  | jvmti_line_info_t *tab, jint *nr) | 
|  | { | 
|  | jint i, lines = 0; | 
|  | jint nr_lines = 0; | 
|  | jvmtiLineNumberEntry *loc_tab = NULL; | 
|  | jvmtiError ret; | 
|  |  | 
|  | ret = (*jvmti)->GetLineNumberTable(jvmti, m, &nr_lines, &loc_tab); | 
|  | if (ret != JVMTI_ERROR_NONE) { | 
|  | print_error(jvmti, "GetLineNumberTable", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < nr_lines; i++) { | 
|  | if (loc_tab[i].start_location < bci) { | 
|  | tab[lines].pc = (unsigned long)pc; | 
|  | tab[lines].line_number = loc_tab[i].line_number; | 
|  | tab[lines].discrim = 0; /* not yet used */ | 
|  | tab[lines].methodID = m; | 
|  | lines++; | 
|  | } else { | 
|  | break; | 
|  | } | 
|  | } | 
|  | (*jvmti)->Deallocate(jvmti, (unsigned char *)loc_tab); | 
|  | *nr = lines; | 
|  | return JVMTI_ERROR_NONE; | 
|  | } | 
|  |  | 
|  | static jvmtiError | 
|  | get_line_numbers(jvmtiEnv *jvmti, const void *compile_info, jvmti_line_info_t **tab, int *nr_lines) | 
|  | { | 
|  | const jvmtiCompiledMethodLoadRecordHeader *hdr; | 
|  | jvmtiCompiledMethodLoadInlineRecord *rec; | 
|  | jvmtiLineNumberEntry *lne = NULL; | 
|  | PCStackInfo *c; | 
|  | jint nr, ret; | 
|  | int nr_total = 0; | 
|  | int i, lines_total = 0; | 
|  |  | 
|  | if (!(tab && nr_lines)) | 
|  | return JVMTI_ERROR_NULL_POINTER; | 
|  |  | 
|  | /* | 
|  | * Phase 1 -- get the number of lines necessary | 
|  | */ | 
|  | for (hdr = compile_info; hdr != NULL; hdr = hdr->next) { | 
|  | if (hdr->kind == JVMTI_CMLR_INLINE_INFO) { | 
|  | rec = (jvmtiCompiledMethodLoadInlineRecord *)hdr; | 
|  | for (i = 0; i < rec->numpcs; i++) { | 
|  | c = rec->pcinfo + i; | 
|  | nr = 0; | 
|  | /* | 
|  | * unfortunately, need a tab to get the number of lines! | 
|  | */ | 
|  | ret = (*jvmti)->GetLineNumberTable(jvmti, c->methods[0], &nr, &lne); | 
|  | if (ret == JVMTI_ERROR_NONE) { | 
|  | /* free what was allocated for nothing */ | 
|  | (*jvmti)->Deallocate(jvmti, (unsigned char *)lne); | 
|  | nr_total += (int)nr; | 
|  | } else { | 
|  | print_error(jvmti, "GetLineNumberTable", ret); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (nr_total == 0) | 
|  | return JVMTI_ERROR_NOT_FOUND; | 
|  |  | 
|  | /* | 
|  | * Phase 2 -- allocate big enough line table | 
|  | */ | 
|  | *tab = malloc(nr_total * sizeof(**tab)); | 
|  | if (!*tab) | 
|  | return JVMTI_ERROR_OUT_OF_MEMORY; | 
|  |  | 
|  | for (hdr = compile_info; hdr != NULL; hdr = hdr->next) { | 
|  | if (hdr->kind == JVMTI_CMLR_INLINE_INFO) { | 
|  | rec = (jvmtiCompiledMethodLoadInlineRecord *)hdr; | 
|  | for (i = 0; i < rec->numpcs; i++) { | 
|  | c = rec->pcinfo + i; | 
|  | nr = 0; | 
|  | ret = do_get_line_numbers(jvmti, c->pc, | 
|  | c->methods[0], | 
|  | c->bcis[0], | 
|  | *tab + lines_total, | 
|  | &nr); | 
|  | if (ret == JVMTI_ERROR_NONE) | 
|  | lines_total += nr; | 
|  | } | 
|  | } | 
|  | } | 
|  | *nr_lines = lines_total; | 
|  | return JVMTI_ERROR_NONE; | 
|  | } | 
|  |  | 
|  | static void | 
|  | copy_class_filename(const char * class_sign, const char * file_name, char * result, size_t max_length) | 
|  | { | 
|  | /* | 
|  | * Assume path name is class hierarchy, this is a common practice with Java programs | 
|  | */ | 
|  | if (*class_sign == 'L') { | 
|  | int j, i = 0; | 
|  | char *p = strrchr(class_sign, '/'); | 
|  | if (p) { | 
|  | /* drop the 'L' prefix and copy up to the final '/' */ | 
|  | for (i = 0; i < (p - class_sign); i++) | 
|  | result[i] = class_sign[i+1]; | 
|  | } | 
|  | /* | 
|  | * append file name, we use loops and not string ops to avoid modifying | 
|  | * class_sign which is used later for the symbol name | 
|  | */ | 
|  | for (j = 0; i < (max_length - 1) && file_name && j < strlen(file_name); j++, i++) | 
|  | result[i] = file_name[j]; | 
|  |  | 
|  | result[i] = '\0'; | 
|  | } else { | 
|  | /* fallback case */ | 
|  | strlcpy(result, file_name, max_length); | 
|  | } | 
|  | } | 
|  |  | 
|  | static jvmtiError | 
|  | get_source_filename(jvmtiEnv *jvmti, jmethodID methodID, char ** buffer) | 
|  | { | 
|  | jvmtiError ret; | 
|  | jclass decl_class; | 
|  | char *file_name = NULL; | 
|  | char *class_sign = NULL; | 
|  | char fn[PATH_MAX]; | 
|  | size_t len; | 
|  |  | 
|  | ret = (*jvmti)->GetMethodDeclaringClass(jvmti, methodID, &decl_class); | 
|  | if (ret != JVMTI_ERROR_NONE) { | 
|  | print_error(jvmti, "GetMethodDeclaringClass", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | ret = (*jvmti)->GetSourceFileName(jvmti, decl_class, &file_name); | 
|  | if (ret != JVMTI_ERROR_NONE) { | 
|  | print_error(jvmti, "GetSourceFileName", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | ret = (*jvmti)->GetClassSignature(jvmti, decl_class, &class_sign, NULL); | 
|  | if (ret != JVMTI_ERROR_NONE) { | 
|  | print_error(jvmti, "GetClassSignature", ret); | 
|  | goto free_file_name_error; | 
|  | } | 
|  |  | 
|  | copy_class_filename(class_sign, file_name, fn, PATH_MAX); | 
|  | len = strlen(fn); | 
|  | *buffer = malloc((len + 1) * sizeof(char)); | 
|  | if (!*buffer) { | 
|  | print_error(jvmti, "GetClassSignature", ret); | 
|  | ret = JVMTI_ERROR_OUT_OF_MEMORY; | 
|  | goto free_class_sign_error; | 
|  | } | 
|  | strcpy(*buffer, fn); | 
|  | ret = JVMTI_ERROR_NONE; | 
|  |  | 
|  | free_class_sign_error: | 
|  | (*jvmti)->Deallocate(jvmti, (unsigned char *)class_sign); | 
|  | free_file_name_error: | 
|  | (*jvmti)->Deallocate(jvmti, (unsigned char *)file_name); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static jvmtiError | 
|  | fill_source_filenames(jvmtiEnv *jvmti, int nr_lines, | 
|  | const jvmti_line_info_t * line_tab, | 
|  | char ** file_names) | 
|  | { | 
|  | int index; | 
|  | jvmtiError ret; | 
|  |  | 
|  | for (index = 0; index < nr_lines; ++index) { | 
|  | ret = get_source_filename(jvmti, line_tab[index].methodID, &(file_names[index])); | 
|  | if (ret != JVMTI_ERROR_NONE) | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | return JVMTI_ERROR_NONE; | 
|  | } | 
|  |  | 
|  | static void JNICALL | 
|  | compiled_method_load_cb(jvmtiEnv *jvmti, | 
|  | jmethodID method, | 
|  | jint code_size, | 
|  | void const *code_addr, | 
|  | jint map_length, | 
|  | jvmtiAddrLocationMap const *map, | 
|  | const void *compile_info) | 
|  | { | 
|  | jvmti_line_info_t *line_tab = NULL; | 
|  | char ** line_file_names = NULL; | 
|  | jclass decl_class; | 
|  | char *class_sign = NULL; | 
|  | char *func_name = NULL; | 
|  | char *func_sign = NULL; | 
|  | char *file_name = NULL; | 
|  | char fn[PATH_MAX]; | 
|  | uint64_t addr = (uint64_t)(uintptr_t)code_addr; | 
|  | jvmtiError ret; | 
|  | int nr_lines = 0; /* in line_tab[] */ | 
|  | size_t len; | 
|  | int output_debug_info = 0; | 
|  |  | 
|  | ret = (*jvmti)->GetMethodDeclaringClass(jvmti, method, | 
|  | &decl_class); | 
|  | if (ret != JVMTI_ERROR_NONE) { | 
|  | print_error(jvmti, "GetMethodDeclaringClass", ret); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (has_line_numbers && map && map_length) { | 
|  | ret = get_line_numbers(jvmti, compile_info, &line_tab, &nr_lines); | 
|  | if (ret != JVMTI_ERROR_NONE) { | 
|  | warnx("jvmti: cannot get line table for method"); | 
|  | nr_lines = 0; | 
|  | } else if (nr_lines > 0) { | 
|  | line_file_names = malloc(sizeof(char*) * nr_lines); | 
|  | if (!line_file_names) { | 
|  | warnx("jvmti: cannot allocate space for line table method names"); | 
|  | } else { | 
|  | memset(line_file_names, 0, sizeof(char*) * nr_lines); | 
|  | ret = fill_source_filenames(jvmti, nr_lines, line_tab, line_file_names); | 
|  | if (ret != JVMTI_ERROR_NONE) { | 
|  | warnx("jvmti: fill_source_filenames failed"); | 
|  | } else { | 
|  | output_debug_info = 1; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | ret = (*jvmti)->GetSourceFileName(jvmti, decl_class, &file_name); | 
|  | if (ret != JVMTI_ERROR_NONE) { | 
|  | print_error(jvmti, "GetSourceFileName", ret); | 
|  | goto error; | 
|  | } | 
|  |  | 
|  | ret = (*jvmti)->GetClassSignature(jvmti, decl_class, | 
|  | &class_sign, NULL); | 
|  | if (ret != JVMTI_ERROR_NONE) { | 
|  | print_error(jvmti, "GetClassSignature", ret); | 
|  | goto error; | 
|  | } | 
|  |  | 
|  | ret = (*jvmti)->GetMethodName(jvmti, method, &func_name, | 
|  | &func_sign, NULL); | 
|  | if (ret != JVMTI_ERROR_NONE) { | 
|  | print_error(jvmti, "GetMethodName", ret); | 
|  | goto error; | 
|  | } | 
|  |  | 
|  | copy_class_filename(class_sign, file_name, fn, PATH_MAX); | 
|  |  | 
|  | /* | 
|  | * write source line info record if we have it | 
|  | */ | 
|  | if (output_debug_info) | 
|  | if (jvmti_write_debug_info(jvmti_agent, addr, nr_lines, line_tab, (const char * const *) line_file_names)) | 
|  | warnx("jvmti: write_debug_info() failed"); | 
|  |  | 
|  | len = strlen(func_name) + strlen(class_sign) + strlen(func_sign) + 2; | 
|  | { | 
|  | char str[len]; | 
|  | snprintf(str, len, "%s%s%s", class_sign, func_name, func_sign); | 
|  |  | 
|  | if (jvmti_write_code(jvmti_agent, str, addr, code_addr, code_size)) | 
|  | warnx("jvmti: write_code() failed"); | 
|  | } | 
|  | error: | 
|  | (*jvmti)->Deallocate(jvmti, (unsigned char *)func_name); | 
|  | (*jvmti)->Deallocate(jvmti, (unsigned char *)func_sign); | 
|  | (*jvmti)->Deallocate(jvmti, (unsigned char *)class_sign); | 
|  | (*jvmti)->Deallocate(jvmti, (unsigned char *)file_name); | 
|  | free(line_tab); | 
|  | while (line_file_names && (nr_lines > 0)) { | 
|  | if (line_file_names[nr_lines - 1]) { | 
|  | free(line_file_names[nr_lines - 1]); | 
|  | } | 
|  | nr_lines -= 1; | 
|  | } | 
|  | free(line_file_names); | 
|  | } | 
|  |  | 
|  | static void JNICALL | 
|  | code_generated_cb(jvmtiEnv *jvmti, | 
|  | char const *name, | 
|  | void const *code_addr, | 
|  | jint code_size) | 
|  | { | 
|  | uint64_t addr = (uint64_t)(unsigned long)code_addr; | 
|  | int ret; | 
|  |  | 
|  | ret = jvmti_write_code(jvmti_agent, name, addr, code_addr, code_size); | 
|  | if (ret) | 
|  | warnx("jvmti: write_code() failed for code_generated"); | 
|  | } | 
|  |  | 
|  | JNIEXPORT jint JNICALL | 
|  | Agent_OnLoad(JavaVM *jvm, char *options, void *reserved __maybe_unused) | 
|  | { | 
|  | jvmtiEventCallbacks cb; | 
|  | jvmtiCapabilities caps1; | 
|  | jvmtiJlocationFormat format; | 
|  | jvmtiEnv *jvmti = NULL; | 
|  | jint ret; | 
|  |  | 
|  | jvmti_agent = jvmti_open(); | 
|  | if (!jvmti_agent) { | 
|  | warnx("jvmti: open_agent failed"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Request a JVMTI interface version 1 environment | 
|  | */ | 
|  | ret = (*jvm)->GetEnv(jvm, (void *)&jvmti, JVMTI_VERSION_1); | 
|  | if (ret != JNI_OK) { | 
|  | warnx("jvmti: jvmti version 1 not supported"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * acquire method_load capability, we require it | 
|  | * request line numbers (optional) | 
|  | */ | 
|  | memset(&caps1, 0, sizeof(caps1)); | 
|  | caps1.can_generate_compiled_method_load_events = 1; | 
|  |  | 
|  | ret = (*jvmti)->AddCapabilities(jvmti, &caps1); | 
|  | if (ret != JVMTI_ERROR_NONE) { | 
|  | print_error(jvmti, "AddCapabilities", ret); | 
|  | return -1; | 
|  | } | 
|  | ret = (*jvmti)->GetJLocationFormat(jvmti, &format); | 
|  | if (ret == JVMTI_ERROR_NONE && format == JVMTI_JLOCATION_JVMBCI) { | 
|  | memset(&caps1, 0, sizeof(caps1)); | 
|  | caps1.can_get_line_numbers = 1; | 
|  | caps1.can_get_source_file_name = 1; | 
|  | ret = (*jvmti)->AddCapabilities(jvmti, &caps1); | 
|  | if (ret == JVMTI_ERROR_NONE) | 
|  | has_line_numbers = 1; | 
|  | } else if (ret != JVMTI_ERROR_NONE) | 
|  | print_error(jvmti, "GetJLocationFormat", ret); | 
|  |  | 
|  |  | 
|  | memset(&cb, 0, sizeof(cb)); | 
|  |  | 
|  | cb.CompiledMethodLoad   = compiled_method_load_cb; | 
|  | cb.DynamicCodeGenerated = code_generated_cb; | 
|  |  | 
|  | ret = (*jvmti)->SetEventCallbacks(jvmti, &cb, sizeof(cb)); | 
|  | if (ret != JVMTI_ERROR_NONE) { | 
|  | print_error(jvmti, "SetEventCallbacks", ret); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | ret = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, | 
|  | JVMTI_EVENT_COMPILED_METHOD_LOAD, NULL); | 
|  | if (ret != JVMTI_ERROR_NONE) { | 
|  | print_error(jvmti, "SetEventNotificationMode(METHOD_LOAD)", ret); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | ret = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, | 
|  | JVMTI_EVENT_DYNAMIC_CODE_GENERATED, NULL); | 
|  | if (ret != JVMTI_ERROR_NONE) { | 
|  | print_error(jvmti, "SetEventNotificationMode(CODE_GENERATED)", ret); | 
|  | return -1; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | JNIEXPORT void JNICALL | 
|  | Agent_OnUnload(JavaVM *jvm __maybe_unused) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | ret = jvmti_close(jvmti_agent); | 
|  | if (ret) | 
|  | errx(1, "Error: op_close_agent()"); | 
|  | } |