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