Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

perf jvmti: Generate correct debug information for inlined code

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: 598b7c6919c7 ("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>

authored by

Ben Gainey and committed by
Arnaldo Carvalho de Melo
ca58d7e6 61fb26a6

+134 -36
+9 -7
tools/perf/jvmti/jvmti_agent.c
··· 384 384 } 385 385 386 386 int 387 - jvmti_write_debug_info(void *agent, uint64_t code, const char *file, 388 - jvmti_line_info_t *li, int nr_lines) 387 + jvmti_write_debug_info(void *agent, uint64_t code, 388 + int nr_lines, jvmti_line_info_t *li, 389 + const char * const * file_names) 389 390 { 390 391 struct jr_code_debug_info rec; 391 - size_t sret, len, size, flen; 392 + size_t sret, len, size, flen = 0; 392 393 uint64_t addr; 393 - const char *fn = file; 394 394 FILE *fp = agent; 395 395 int i; 396 396 ··· 405 405 return -1; 406 406 } 407 407 408 - flen = strlen(file) + 1; 408 + for (i = 0; i < nr_lines; ++i) { 409 + flen += strlen(file_names[i]) + 1; 410 + } 409 411 410 412 rec.p.id = JIT_CODE_DEBUG_INFO; 411 413 size = sizeof(rec); ··· 423 421 * file[] : source file name 424 422 */ 425 423 size += nr_lines * sizeof(struct debug_entry); 426 - size += flen * nr_lines; 424 + size += flen; 427 425 rec.p.total_size = size; 428 426 429 427 /* ··· 454 452 if (sret != 1) 455 453 goto error; 456 454 457 - sret = fwrite_unlocked(fn, flen, 1, fp); 455 + sret = fwrite_unlocked(file_names[i], strlen(file_names[i]) + 1, 1, fp); 458 456 if (sret != 1) 459 457 goto error; 460 458 }
+3 -4
tools/perf/jvmti/jvmti_agent.h
··· 14 14 unsigned long pc; 15 15 int line_number; 16 16 int discrim; /* discriminator -- 0 for now */ 17 + jmethodID methodID; 17 18 } jvmti_line_info_t; 18 19 19 20 void *jvmti_open(void); ··· 23 22 uint64_t vma, void const *code, 24 23 const unsigned int code_size); 25 24 26 - int jvmti_write_debug_info(void *agent, 27 - uint64_t code, 28 - const char *file, 25 + int jvmti_write_debug_info(void *agent, uint64_t code, int nr_lines, 29 26 jvmti_line_info_t *li, 30 - int nr_lines); 27 + const char * const * file_names); 31 28 32 29 #if defined(__cplusplus) 33 30 }
+122 -25
tools/perf/jvmti/libjvmti.c
··· 47 47 tab[lines].pc = (unsigned long)pc; 48 48 tab[lines].line_number = loc_tab[i].line_number; 49 49 tab[lines].discrim = 0; /* not yet used */ 50 + tab[lines].methodID = m; 50 51 lines++; 51 52 } else { 52 53 break; ··· 126 125 return JVMTI_ERROR_NONE; 127 126 } 128 127 128 + static void 129 + copy_class_filename(const char * class_sign, const char * file_name, char * result, size_t max_length) 130 + { 131 + /* 132 + * Assume path name is class hierarchy, this is a common practice with Java programs 133 + */ 134 + if (*class_sign == 'L') { 135 + int j, i = 0; 136 + char *p = strrchr(class_sign, '/'); 137 + if (p) { 138 + /* drop the 'L' prefix and copy up to the final '/' */ 139 + for (i = 0; i < (p - class_sign); i++) 140 + result[i] = class_sign[i+1]; 141 + } 142 + /* 143 + * append file name, we use loops and not string ops to avoid modifying 144 + * class_sign which is used later for the symbol name 145 + */ 146 + for (j = 0; i < (max_length - 1) && file_name && j < strlen(file_name); j++, i++) 147 + result[i] = file_name[j]; 148 + 149 + result[i] = '\0'; 150 + } else { 151 + /* fallback case */ 152 + size_t file_name_len = strlen(file_name); 153 + strncpy(result, file_name, file_name_len < max_length ? file_name_len : max_length); 154 + } 155 + } 156 + 157 + static jvmtiError 158 + get_source_filename(jvmtiEnv *jvmti, jmethodID methodID, char ** buffer) 159 + { 160 + jvmtiError ret; 161 + jclass decl_class; 162 + char *file_name = NULL; 163 + char *class_sign = NULL; 164 + char fn[PATH_MAX]; 165 + size_t len; 166 + 167 + ret = (*jvmti)->GetMethodDeclaringClass(jvmti, methodID, &decl_class); 168 + if (ret != JVMTI_ERROR_NONE) { 169 + print_error(jvmti, "GetMethodDeclaringClass", ret); 170 + return ret; 171 + } 172 + 173 + ret = (*jvmti)->GetSourceFileName(jvmti, decl_class, &file_name); 174 + if (ret != JVMTI_ERROR_NONE) { 175 + print_error(jvmti, "GetSourceFileName", ret); 176 + return ret; 177 + } 178 + 179 + ret = (*jvmti)->GetClassSignature(jvmti, decl_class, &class_sign, NULL); 180 + if (ret != JVMTI_ERROR_NONE) { 181 + print_error(jvmti, "GetClassSignature", ret); 182 + goto free_file_name_error; 183 + } 184 + 185 + copy_class_filename(class_sign, file_name, fn, PATH_MAX); 186 + len = strlen(fn); 187 + *buffer = malloc((len + 1) * sizeof(char)); 188 + if (!*buffer) { 189 + print_error(jvmti, "GetClassSignature", ret); 190 + ret = JVMTI_ERROR_OUT_OF_MEMORY; 191 + goto free_class_sign_error; 192 + } 193 + strcpy(*buffer, fn); 194 + ret = JVMTI_ERROR_NONE; 195 + 196 + free_class_sign_error: 197 + (*jvmti)->Deallocate(jvmti, (unsigned char *)class_sign); 198 + free_file_name_error: 199 + (*jvmti)->Deallocate(jvmti, (unsigned char *)file_name); 200 + 201 + return ret; 202 + } 203 + 204 + static jvmtiError 205 + fill_source_filenames(jvmtiEnv *jvmti, int nr_lines, 206 + const jvmti_line_info_t * line_tab, 207 + char ** file_names) 208 + { 209 + int index; 210 + jvmtiError ret; 211 + 212 + for (index = 0; index < nr_lines; ++index) { 213 + ret = get_source_filename(jvmti, line_tab[index].methodID, &(file_names[index])); 214 + if (ret != JVMTI_ERROR_NONE) 215 + return ret; 216 + } 217 + 218 + return JVMTI_ERROR_NONE; 219 + } 220 + 129 221 static void JNICALL 130 222 compiled_method_load_cb(jvmtiEnv *jvmti, 131 223 jmethodID method, ··· 229 135 const void *compile_info) 230 136 { 231 137 jvmti_line_info_t *line_tab = NULL; 138 + char ** line_file_names = NULL; 232 139 jclass decl_class; 233 140 char *class_sign = NULL; 234 141 char *func_name = NULL; 235 142 char *func_sign = NULL; 236 - char *file_name= NULL; 143 + char *file_name = NULL; 237 144 char fn[PATH_MAX]; 238 145 uint64_t addr = (uint64_t)(uintptr_t)code_addr; 239 146 jvmtiError ret; 240 147 int nr_lines = 0; /* in line_tab[] */ 241 148 size_t len; 149 + int output_debug_info = 0; 242 150 243 151 ret = (*jvmti)->GetMethodDeclaringClass(jvmti, method, 244 152 &decl_class); ··· 254 158 if (ret != JVMTI_ERROR_NONE) { 255 159 warnx("jvmti: cannot get line table for method"); 256 160 nr_lines = 0; 161 + } else if (nr_lines > 0) { 162 + line_file_names = malloc(sizeof(char*) * nr_lines); 163 + if (!line_file_names) { 164 + warnx("jvmti: cannot allocate space for line table method names"); 165 + } else { 166 + memset(line_file_names, 0, sizeof(char*) * nr_lines); 167 + ret = fill_source_filenames(jvmti, nr_lines, line_tab, line_file_names); 168 + if (ret != JVMTI_ERROR_NONE) { 169 + warnx("jvmti: fill_source_filenames failed"); 170 + } else { 171 + output_debug_info = 1; 172 + } 173 + } 257 174 } 258 175 } 259 176 ··· 290 181 goto error; 291 182 } 292 183 293 - /* 294 - * Assume path name is class hierarchy, this is a common practice with Java programs 295 - */ 296 - if (*class_sign == 'L') { 297 - int j, i = 0; 298 - char *p = strrchr(class_sign, '/'); 299 - if (p) { 300 - /* drop the 'L' prefix and copy up to the final '/' */ 301 - for (i = 0; i < (p - class_sign); i++) 302 - fn[i] = class_sign[i+1]; 303 - } 304 - /* 305 - * append file name, we use loops and not string ops to avoid modifying 306 - * class_sign which is used later for the symbol name 307 - */ 308 - for (j = 0; i < (PATH_MAX - 1) && file_name && j < strlen(file_name); j++, i++) 309 - fn[i] = file_name[j]; 310 - fn[i] = '\0'; 311 - } else { 312 - /* fallback case */ 313 - strcpy(fn, file_name); 314 - } 184 + copy_class_filename(class_sign, file_name, fn, PATH_MAX); 185 + 315 186 /* 316 187 * write source line info record if we have it 317 188 */ 318 - if (jvmti_write_debug_info(jvmti_agent, addr, fn, line_tab, nr_lines)) 319 - warnx("jvmti: write_debug_info() failed"); 189 + if (output_debug_info) 190 + if (jvmti_write_debug_info(jvmti_agent, addr, nr_lines, line_tab, (const char * const *) line_file_names)) 191 + warnx("jvmti: write_debug_info() failed"); 320 192 321 193 len = strlen(func_name) + strlen(class_sign) + strlen(func_sign) + 2; 322 194 { ··· 313 223 (*jvmti)->Deallocate(jvmti, (unsigned char *)class_sign); 314 224 (*jvmti)->Deallocate(jvmti, (unsigned char *)file_name); 315 225 free(line_tab); 226 + while (line_file_names && (nr_lines > 0)) { 227 + if (line_file_names[nr_lines - 1]) { 228 + free(line_file_names[nr_lines - 1]); 229 + } 230 + nr_lines -= 1; 231 + } 232 + free(line_file_names); 316 233 } 317 234 318 235 static void JNICALL