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 | 34 | static jvmtiError |
7d7e503c NG |
35 | do_get_line_number(jvmtiEnv *jvmti, void *pc, jmethodID m, jint bci, |
36 | jvmti_line_info_t *tab) | |
598b7c69 | 37 | { |
7d7e503c | 38 | jint i, nr_lines = 0; |
598b7c69 SE |
39 | jvmtiLineNumberEntry *loc_tab = NULL; |
40 | jvmtiError ret; | |
7d7e503c | 41 | jint src_line = -1; |
598b7c69 SE |
42 | |
43 | ret = (*jvmti)->GetLineNumberTable(jvmti, m, &nr_lines, &loc_tab); | |
959f8ed4 NG |
44 | if (ret == JVMTI_ERROR_ABSENT_INFORMATION || ret == JVMTI_ERROR_NATIVE_METHOD) { |
45 | /* No debug information for this method */ | |
7d7e503c | 46 | return ret; |
959f8ed4 | 47 | } else if (ret != JVMTI_ERROR_NONE) { |
cdd75e3b | 48 | print_error(jvmti, "GetLineNumberTable", ret); |
598b7c69 | 49 | return ret; |
cdd75e3b | 50 | } |
598b7c69 | 51 | |
7d7e503c NG |
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; | |
598b7c69 | 65 | } |
7d7e503c | 66 | |
598b7c69 | 67 | (*jvmti)->Deallocate(jvmti, (unsigned char *)loc_tab); |
7d7e503c NG |
68 | |
69 | return ret; | |
598b7c69 SE |
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; | |
598b7c69 | 77 | PCStackInfo *c; |
7d7e503c | 78 | jint ret; |
598b7c69 SE |
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; | |
7d7e503c | 91 | nr_total += rec->numpcs; |
598b7c69 SE |
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; | |
7d7e503c NG |
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); | |
598b7c69 | 119 | if (ret == JVMTI_ERROR_NONE) |
7d7e503c | 120 | lines_total++; |
598b7c69 SE |
121 | } |
122 | } | |
123 | } | |
124 | *nr_lines = lines_total; | |
125 | return JVMTI_ERROR_NONE; | |
126 | } | |
dd1d0044 JO |
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 */ | |
598b7c69 | 136 | |
ca58d7e6 BG |
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 */ | |
279ab04d | 161 | strlcpy(result, file_name, max_length); |
ca58d7e6 BG |
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, 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(*buffer, 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 | ||
209045ad SE |
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, | |
598b7c69 | 236 | const void *compile_info) |
209045ad | 237 | { |
598b7c69 | 238 | jvmti_line_info_t *line_tab = NULL; |
ca58d7e6 | 239 | char ** line_file_names = NULL; |
209045ad SE |
240 | jclass decl_class; |
241 | char *class_sign = NULL; | |
242 | char *func_name = NULL; | |
243 | char *func_sign = NULL; | |
209045ad SE |
244 | uint64_t addr = (uint64_t)(uintptr_t)code_addr; |
245 | jvmtiError ret; | |
598b7c69 | 246 | int nr_lines = 0; /* in line_tab[] */ |
209045ad | 247 | size_t len; |
ca58d7e6 | 248 | int output_debug_info = 0; |
209045ad SE |
249 | |
250 | ret = (*jvmti)->GetMethodDeclaringClass(jvmti, method, | |
251 | &decl_class); | |
252 | if (ret != JVMTI_ERROR_NONE) { | |
cdd75e3b | 253 | print_error(jvmti, "GetMethodDeclaringClass", ret); |
209045ad SE |
254 | return; |
255 | } | |
256 | ||
257 | if (has_line_numbers && map && map_length) { | |
598b7c69 | 258 | ret = get_line_numbers(jvmti, compile_info, &line_tab, &nr_lines); |
209045ad | 259 | if (ret != JVMTI_ERROR_NONE) { |
959f8ed4 NG |
260 | if (ret != JVMTI_ERROR_NOT_FOUND) { |
261 | warnx("jvmti: cannot get line table for method"); | |
262 | } | |
598b7c69 | 263 | nr_lines = 0; |
ca58d7e6 BG |
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 | } | |
209045ad SE |
277 | } |
278 | } | |
279 | ||
280 | ret = (*jvmti)->GetClassSignature(jvmti, decl_class, | |
281 | &class_sign, NULL); | |
282 | if (ret != JVMTI_ERROR_NONE) { | |
cdd75e3b | 283 | print_error(jvmti, "GetClassSignature", ret); |
209045ad SE |
284 | goto error; |
285 | } | |
286 | ||
287 | ret = (*jvmti)->GetMethodName(jvmti, method, &func_name, | |
288 | &func_sign, NULL); | |
289 | if (ret != JVMTI_ERROR_NONE) { | |
cdd75e3b | 290 | print_error(jvmti, "GetMethodName", ret); |
209045ad SE |
291 | goto error; |
292 | } | |
293 | ||
209045ad SE |
294 | /* |
295 | * write source line info record if we have it | |
296 | */ | |
ca58d7e6 BG |
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"); | |
209045ad SE |
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); | |
598b7c69 | 305 | |
209045ad SE |
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); | |
598b7c69 | 313 | free(line_tab); |
ca58d7e6 BG |
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); | |
209045ad SE |
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 | |
0353631a | 338 | Agent_OnLoad(JavaVM *jvm, char *options, void *reserved __maybe_unused) |
209045ad SE |
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) { | |
cdd75e3b | 370 | print_error(jvmti, "AddCapabilities", ret); |
209045ad SE |
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; | |
cdd75e3b SE |
381 | } else if (ret != JVMTI_ERROR_NONE) |
382 | print_error(jvmti, "GetJLocationFormat", ret); | |
383 | ||
209045ad SE |
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) { | |
cdd75e3b | 392 | print_error(jvmti, "SetEventCallbacks", ret); |
209045ad SE |
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) { | |
cdd75e3b | 399 | print_error(jvmti, "SetEventNotificationMode(METHOD_LOAD)", ret); |
209045ad SE |
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) { | |
cdd75e3b | 406 | print_error(jvmti, "SetEventNotificationMode(CODE_GENERATED)", ret); |
209045ad SE |
407 | return -1; |
408 | } | |
409 | return 0; | |
410 | } | |
411 | ||
412 | JNIEXPORT void JNICALL | |
0353631a | 413 | Agent_OnUnload(JavaVM *jvm __maybe_unused) |
209045ad SE |
414 | { |
415 | int ret; | |
416 | ||
417 | ret = jvmti_close(jvmti_agent); | |
418 | if (ret) | |
419 | errx(1, "Error: op_close_agent()"); | |
420 | } |