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