1 // SPDX-License-Identifier: GPL-2.0 2 #include <linux/compiler.h> 3 #include <sys/types.h> 4 #include <stdio.h> 5 #include <string.h> 6 #include <stdlib.h> 7 #include <err.h> 8 #include <jvmti.h> 9 #include <jvmticmlr.h> 10 #include <limits.h> 11 12 #include "jvmti_agent.h" 13 14 static int has_line_numbers; 15 void *jvmti_agent; 16 17 static void print_error(jvmtiEnv *jvmti, const char *msg, jvmtiError ret) 18 { 19 char *err_msg = NULL; 20 jvmtiError err; 21 err = (*jvmti)->GetErrorName(jvmti, ret, &err_msg); 22 if (err == JVMTI_ERROR_NONE) { 23 warnx("%s failed with %s", msg, err_msg); 24 (*jvmti)->Deallocate(jvmti, (unsigned char *)err_msg); 25 } else { 26 warnx("%s failed with an unknown error %d", msg, ret); 27 } 28 } 29 30 static jvmtiError 31 do_get_line_numbers(jvmtiEnv *jvmti, void *pc, jmethodID m, jint bci, 32 jvmti_line_info_t *tab, jint *nr) 33 { 34 jint i, lines = 0; 35 jint nr_lines = 0; 36 jvmtiLineNumberEntry *loc_tab = NULL; 37 jvmtiError ret; 38 39 ret = (*jvmti)->GetLineNumberTable(jvmti, m, &nr_lines, &loc_tab); 40 if (ret != JVMTI_ERROR_NONE) { 41 print_error(jvmti, "GetLineNumberTable", ret); 42 return ret; 43 } 44 45 for (i = 0; i < nr_lines; i++) { 46 if (loc_tab[i].start_location < bci) { 47 tab[lines].pc = (unsigned long)pc; 48 tab[lines].line_number = loc_tab[i].line_number; 49 tab[lines].discrim = 0; /* not yet used */ 50 lines++; 51 } else { 52 break; 53 } 54 } 55 (*jvmti)->Deallocate(jvmti, (unsigned char *)loc_tab); 56 *nr = lines; 57 return JVMTI_ERROR_NONE; 58 } 59 60 static jvmtiError 61 get_line_numbers(jvmtiEnv *jvmti, const void *compile_info, jvmti_line_info_t **tab, int *nr_lines) 62 { 63 const jvmtiCompiledMethodLoadRecordHeader *hdr; 64 jvmtiCompiledMethodLoadInlineRecord *rec; 65 jvmtiLineNumberEntry *lne = NULL; 66 PCStackInfo *c; 67 jint nr, ret; 68 int nr_total = 0; 69 int i, lines_total = 0; 70 71 if (!(tab && nr_lines)) 72 return JVMTI_ERROR_NULL_POINTER; 73 74 /* 75 * Phase 1 -- get the number of lines necessary 76 */ 77 for (hdr = compile_info; hdr != NULL; hdr = hdr->next) { 78 if (hdr->kind == JVMTI_CMLR_INLINE_INFO) { 79 rec = (jvmtiCompiledMethodLoadInlineRecord *)hdr; 80 for (i = 0; i < rec->numpcs; i++) { 81 c = rec->pcinfo + i; 82 nr = 0; 83 /* 84 * unfortunately, need a tab to get the number of lines! 85 */ 86 ret = (*jvmti)->GetLineNumberTable(jvmti, c->methods[0], &nr, &lne); 87 if (ret == JVMTI_ERROR_NONE) { 88 /* free what was allocated for nothing */ 89 (*jvmti)->Deallocate(jvmti, (unsigned char *)lne); 90 nr_total += (int)nr; 91 } else { 92 print_error(jvmti, "GetLineNumberTable", ret); 93 } 94 } 95 } 96 } 97 98 if (nr_total == 0) 99 return JVMTI_ERROR_NOT_FOUND; 100 101 /* 102 * Phase 2 -- allocate big enough line table 103 */ 104 *tab = malloc(nr_total * sizeof(**tab)); 105 if (!*tab) 106 return JVMTI_ERROR_OUT_OF_MEMORY; 107 108 for (hdr = compile_info; hdr != NULL; hdr = hdr->next) { 109 if (hdr->kind == JVMTI_CMLR_INLINE_INFO) { 110 rec = (jvmtiCompiledMethodLoadInlineRecord *)hdr; 111 for (i = 0; i < rec->numpcs; i++) { 112 c = rec->pcinfo + i; 113 nr = 0; 114 ret = do_get_line_numbers(jvmti, c->pc, 115 c->methods[0], 116 c->bcis[0], 117 *tab + lines_total, 118 &nr); 119 if (ret == JVMTI_ERROR_NONE) 120 lines_total += nr; 121 } 122 } 123 } 124 *nr_lines = lines_total; 125 return JVMTI_ERROR_NONE; 126 } 127 128 static void JNICALL 129 compiled_method_load_cb(jvmtiEnv *jvmti, 130 jmethodID method, 131 jint code_size, 132 void const *code_addr, 133 jint map_length, 134 jvmtiAddrLocationMap const *map, 135 const void *compile_info) 136 { 137 jvmti_line_info_t *line_tab = NULL; 138 jclass decl_class; 139 char *class_sign = NULL; 140 char *func_name = NULL; 141 char *func_sign = NULL; 142 char *file_name= NULL; 143 char fn[PATH_MAX]; 144 uint64_t addr = (uint64_t)(uintptr_t)code_addr; 145 jvmtiError ret; 146 int nr_lines = 0; /* in line_tab[] */ 147 size_t len; 148 149 ret = (*jvmti)->GetMethodDeclaringClass(jvmti, method, 150 &decl_class); 151 if (ret != JVMTI_ERROR_NONE) { 152 print_error(jvmti, "GetMethodDeclaringClass", ret); 153 return; 154 } 155 156 if (has_line_numbers && map && map_length) { 157 ret = get_line_numbers(jvmti, compile_info, &line_tab, &nr_lines); 158 if (ret != JVMTI_ERROR_NONE) { 159 warnx("jvmti: cannot get line table for method"); 160 nr_lines = 0; 161 } 162 } 163 164 ret = (*jvmti)->GetSourceFileName(jvmti, decl_class, &file_name); 165 if (ret != JVMTI_ERROR_NONE) { 166 print_error(jvmti, "GetSourceFileName", ret); 167 goto error; 168 } 169 170 ret = (*jvmti)->GetClassSignature(jvmti, decl_class, 171 &class_sign, NULL); 172 if (ret != JVMTI_ERROR_NONE) { 173 print_error(jvmti, "GetClassSignature", ret); 174 goto error; 175 } 176 177 ret = (*jvmti)->GetMethodName(jvmti, method, &func_name, 178 &func_sign, NULL); 179 if (ret != JVMTI_ERROR_NONE) { 180 print_error(jvmti, "GetMethodName", ret); 181 goto error; 182 } 183 184 /* 185 * Assume path name is class hierarchy, this is a common practice with Java programs 186 */ 187 if (*class_sign == 'L') { 188 int j, i = 0; 189 char *p = strrchr(class_sign, '/'); 190 if (p) { 191 /* drop the 'L' prefix and copy up to the final '/' */ 192 for (i = 0; i < (p - class_sign); i++) 193 fn[i] = class_sign[i+1]; 194 } 195 /* 196 * append file name, we use loops and not string ops to avoid modifying 197 * class_sign which is used later for the symbol name 198 */ 199 for (j = 0; i < (PATH_MAX - 1) && file_name && j < strlen(file_name); j++, i++) 200 fn[i] = file_name[j]; 201 fn[i] = '\0'; 202 } else { 203 /* fallback case */ 204 strcpy(fn, file_name); 205 } 206 /* 207 * write source line info record if we have it 208 */ 209 if (jvmti_write_debug_info(jvmti_agent, addr, fn, line_tab, nr_lines)) 210 warnx("jvmti: write_debug_info() failed"); 211 212 len = strlen(func_name) + strlen(class_sign) + strlen(func_sign) + 2; 213 { 214 char str[len]; 215 snprintf(str, len, "%s%s%s", class_sign, func_name, func_sign); 216 217 if (jvmti_write_code(jvmti_agent, str, addr, code_addr, code_size)) 218 warnx("jvmti: write_code() failed"); 219 } 220 error: 221 (*jvmti)->Deallocate(jvmti, (unsigned char *)func_name); 222 (*jvmti)->Deallocate(jvmti, (unsigned char *)func_sign); 223 (*jvmti)->Deallocate(jvmti, (unsigned char *)class_sign); 224 (*jvmti)->Deallocate(jvmti, (unsigned char *)file_name); 225 free(line_tab); 226 } 227 228 static void JNICALL 229 code_generated_cb(jvmtiEnv *jvmti, 230 char const *name, 231 void const *code_addr, 232 jint code_size) 233 { 234 uint64_t addr = (uint64_t)(unsigned long)code_addr; 235 int ret; 236 237 ret = jvmti_write_code(jvmti_agent, name, addr, code_addr, code_size); 238 if (ret) 239 warnx("jvmti: write_code() failed for code_generated"); 240 } 241 242 JNIEXPORT jint JNICALL 243 Agent_OnLoad(JavaVM *jvm, char *options, void *reserved __maybe_unused) 244 { 245 jvmtiEventCallbacks cb; 246 jvmtiCapabilities caps1; 247 jvmtiJlocationFormat format; 248 jvmtiEnv *jvmti = NULL; 249 jint ret; 250 251 jvmti_agent = jvmti_open(); 252 if (!jvmti_agent) { 253 warnx("jvmti: open_agent failed"); 254 return -1; 255 } 256 257 /* 258 * Request a JVMTI interface version 1 environment 259 */ 260 ret = (*jvm)->GetEnv(jvm, (void *)&jvmti, JVMTI_VERSION_1); 261 if (ret != JNI_OK) { 262 warnx("jvmti: jvmti version 1 not supported"); 263 return -1; 264 } 265 266 /* 267 * acquire method_load capability, we require it 268 * request line numbers (optional) 269 */ 270 memset(&caps1, 0, sizeof(caps1)); 271 caps1.can_generate_compiled_method_load_events = 1; 272 273 ret = (*jvmti)->AddCapabilities(jvmti, &caps1); 274 if (ret != JVMTI_ERROR_NONE) { 275 print_error(jvmti, "AddCapabilities", ret); 276 return -1; 277 } 278 ret = (*jvmti)->GetJLocationFormat(jvmti, &format); 279 if (ret == JVMTI_ERROR_NONE && format == JVMTI_JLOCATION_JVMBCI) { 280 memset(&caps1, 0, sizeof(caps1)); 281 caps1.can_get_line_numbers = 1; 282 caps1.can_get_source_file_name = 1; 283 ret = (*jvmti)->AddCapabilities(jvmti, &caps1); 284 if (ret == JVMTI_ERROR_NONE) 285 has_line_numbers = 1; 286 } else if (ret != JVMTI_ERROR_NONE) 287 print_error(jvmti, "GetJLocationFormat", ret); 288 289 290 memset(&cb, 0, sizeof(cb)); 291 292 cb.CompiledMethodLoad = compiled_method_load_cb; 293 cb.DynamicCodeGenerated = code_generated_cb; 294 295 ret = (*jvmti)->SetEventCallbacks(jvmti, &cb, sizeof(cb)); 296 if (ret != JVMTI_ERROR_NONE) { 297 print_error(jvmti, "SetEventCallbacks", ret); 298 return -1; 299 } 300 301 ret = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, 302 JVMTI_EVENT_COMPILED_METHOD_LOAD, NULL); 303 if (ret != JVMTI_ERROR_NONE) { 304 print_error(jvmti, "SetEventNotificationMode(METHOD_LOAD)", ret); 305 return -1; 306 } 307 308 ret = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, 309 JVMTI_EVENT_DYNAMIC_CODE_GENERATED, NULL); 310 if (ret != JVMTI_ERROR_NONE) { 311 print_error(jvmti, "SetEventNotificationMode(CODE_GENERATED)", ret); 312 return -1; 313 } 314 return 0; 315 } 316 317 JNIEXPORT void JNICALL 318 Agent_OnUnload(JavaVM *jvm __maybe_unused) 319 { 320 int ret; 321 322 ret = jvmti_close(jvmti_agent); 323 if (ret) 324 errx(1, "Error: op_close_agent()"); 325 } 326