![Ben Gainey](/assets/img/avatar_default.png)
tools/perf/jvmti is broken in so far as it generates incorrect debug
information. Specifically it attributes all debug lines to the original
method being output even in the case that some code is being inlined
from elsewhere. This patch fixes the issue.
To test (from within linux/tools/perf):
export JDIR=/usr/lib/jvm/java-8-openjdk-amd64/
make
cat << __EOF > Test.java
public class Test
{
private StringBuilder b = new StringBuilder();
private void loop(int i, String... args)
{
for (String a : args)
b.append(a);
long hc = b.hashCode() * System.nanoTime();
b = new StringBuilder();
b.append(hc);
System.out.printf("Iteration %d = %d\n", i, hc);
}
public void run(String... args)
{
for (int i = 0; i < 10000; ++i)
{
loop(i, args);
}
}
public static void main(String... args)
{
Test t = new Test();
t.run(args);
}
}
__EOF
$JDIR/bin/javac Test.java
./perf record -F 10000 -g -k mono $JDIR/bin/java -agentpath:`pwd`/libperf-jvmti.so Test
./perf inject --jit -i perf.data -o perf.data.jitted
./perf annotate -i perf.data.jitted --stdio | grep Test\.java: | sort -u
Before this patch, Test.java line numbers get reported that are greater
than the number of lines in the Test.java file. They come from the
source file of the inlined function, e.g. java/lang/String.java:1085.
For further validation one can examine those lines in the JDK source
distribution and confirm that they map to inlined functions called by
Test.java.
After this patch, the filename of the inlined function is output
rather than the incorrect original source filename.
Signed-off-by: Ben Gainey <ben.gainey@arm.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Tested-by: Stephane Eranian <eranian@google.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Ben Gainey <ben.gainey@arm.com>
Cc: Colin King <colin.king@canonical.com>
Cc: Darren Hart <dvhart@infradead.org>
Cc: Jiri Olsa <jolsa@redhat.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Fixes: 598b7c6919
("perf jit: add source line info support")
Link: http://lkml.kernel.org/r/20171122182541.d25599a3eb1ada3480d142fa@arm.com
Signed-off-by: Kim Phillips <kim.phillips@arm.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
423 lines
11 KiB
C
423 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include <linux/compiler.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 */
|
|
size_t file_name_len = strlen(file_name);
|
|
strncpy(result, file_name, file_name_len < max_length ? file_name_len : 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()");
|
|
}
|