/* * Linux perf perf-.map and jit-.dump integration. * * The jitdump spec can be found at [1]. * * [1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/tools/perf/Documentation/jitdump-specification.txt * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "qemu/osdep.h" #include "elf.h" #include "exec/target_page.h" #include "exec/translation-block.h" #include "qemu/timer.h" #include "tcg/debuginfo.h" #include "tcg/perf.h" #include "tcg/tcg.h" static FILE *safe_fopen_w(const char *path) { int saved_errno; FILE *f; int fd; /* Delete the old file, if any. */ unlink(path); /* Avoid symlink attacks by using O_CREAT | O_EXCL. */ fd = open(path, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); if (fd == -1) { return NULL; } /* Convert fd to FILE*. */ f = fdopen(fd, "w"); if (f == NULL) { saved_errno = errno; close(fd); errno = saved_errno; return NULL; } return f; } static FILE *perfmap; void perf_enable_perfmap(void) { char map_file[32]; snprintf(map_file, sizeof(map_file), "/tmp/perf-%d.map", getpid()); perfmap = safe_fopen_w(map_file); if (perfmap == NULL) { warn_report("Could not open %s: %s, proceeding without perfmap", map_file, strerror(errno)); } } /* Get PC and size of code JITed for guest instruction #INSN. */ static void get_host_pc_size(uintptr_t *host_pc, uint16_t *host_size, const void *start, size_t insn) { uint16_t start_off = insn ? tcg_ctx->gen_insn_end_off[insn - 1] : 0; if (host_pc) { *host_pc = (uintptr_t)start + start_off; } if (host_size) { *host_size = tcg_ctx->gen_insn_end_off[insn] - start_off; } } static const char *pretty_symbol(const struct debuginfo_query *q, size_t *len) { static __thread char buf[64]; int tmp; if (!q->symbol) { tmp = snprintf(buf, sizeof(buf), "guest-0x%"PRIx64, q->address); if (len) { *len = MIN(tmp + 1, sizeof(buf)); } return buf; } if (!q->offset) { if (len) { *len = strlen(q->symbol) + 1; } return q->symbol; } tmp = snprintf(buf, sizeof(buf), "%s+0x%"PRIx64, q->symbol, q->offset); if (len) { *len = MIN(tmp + 1, sizeof(buf)); } return buf; } static void write_perfmap_entry(const void *start, size_t insn, const struct debuginfo_query *q) { uint16_t host_size; uintptr_t host_pc; get_host_pc_size(&host_pc, &host_size, start, insn); fprintf(perfmap, "%"PRIxPTR" %"PRIx16" %s\n", host_pc, host_size, pretty_symbol(q, NULL)); } static FILE *jitdump; static size_t perf_marker_size; static void *perf_marker = MAP_FAILED; #define JITHEADER_MAGIC 0x4A695444 #define JITHEADER_VERSION 1 struct jitheader { uint32_t magic; uint32_t version; uint32_t total_size; uint32_t elf_mach; uint32_t pad1; uint32_t pid; uint64_t timestamp; uint64_t flags; }; enum jit_record_type { JIT_CODE_LOAD = 0, JIT_CODE_DEBUG_INFO = 2, }; struct jr_prefix { uint32_t id; uint32_t total_size; uint64_t timestamp; }; struct jr_code_load { struct jr_prefix p; uint32_t pid; uint32_t tid; uint64_t vma; uint64_t code_addr; uint64_t code_size; uint64_t code_index; }; struct debug_entry { uint64_t addr; int lineno; int discrim; const char name[]; }; struct jr_code_debug_info { struct jr_prefix p; uint64_t code_addr; uint64_t nr_entry; struct debug_entry entries[]; }; static uint32_t get_e_machine(void) { Elf64_Ehdr elf_header; FILE *exe; size_t n; QEMU_BUILD_BUG_ON(offsetof(Elf32_Ehdr, e_machine) != offsetof(Elf64_Ehdr, e_machine)); exe = fopen("/proc/self/exe", "r"); if (exe == NULL) { return EM_NONE; } n = fread(&elf_header, sizeof(elf_header), 1, exe); fclose(exe); if (n != 1) { return EM_NONE; } return elf_header.e_machine; } void perf_enable_jitdump(void) { struct jitheader header; char jitdump_file[32]; if (!use_rt_clock) { warn_report("CLOCK_MONOTONIC is not available, proceeding without jitdump"); return; } snprintf(jitdump_file, sizeof(jitdump_file), "jit-%d.dump", getpid()); jitdump = safe_fopen_w(jitdump_file); if (jitdump == NULL) { warn_report("Could not open %s: %s, proceeding without jitdump", jitdump_file, strerror(errno)); return; } /* * `perf inject` will see that the mapped file name in the corresponding * PERF_RECORD_MMAP or PERF_RECORD_MMAP2 event is of the form jit-%d.dump * and will process it as a jitdump file. */ perf_marker_size = qemu_real_host_page_size(); perf_marker = mmap(NULL, perf_marker_size, PROT_READ | PROT_EXEC, MAP_PRIVATE, fileno(jitdump), 0); if (perf_marker == MAP_FAILED) { warn_report("Could not map %s: %s, proceeding without jitdump", jitdump_file, strerror(errno)); fclose(jitdump); jitdump = NULL; return; } header.magic = JITHEADER_MAGIC; header.version = JITHEADER_VERSION; header.total_size = sizeof(header); header.elf_mach = get_e_machine(); header.pad1 = 0; header.pid = getpid(); header.timestamp = get_clock(); header.flags = 0; fwrite(&header, sizeof(header), 1, jitdump); } void perf_report_prologue(const void *start, size_t size) { if (perfmap) { fprintf(perfmap, "%"PRIxPTR" %zx tcg-prologue-buffer\n", (uintptr_t)start, size); } } /* Write a JIT_CODE_DEBUG_INFO jitdump entry. */ static void write_jr_code_debug_info(const void *start, const struct debuginfo_query *q, size_t icount) { struct jr_code_debug_info rec; struct debug_entry ent; uintptr_t host_pc; int insn; /* Write the header. */ rec.p.id = JIT_CODE_DEBUG_INFO; rec.p.total_size = sizeof(rec) + sizeof(ent) + 1; rec.p.timestamp = get_clock(); rec.code_addr = (uintptr_t)start; rec.nr_entry = 1; for (insn = 0; insn < icount; insn++) { if (q[insn].file) { rec.p.total_size += sizeof(ent) + strlen(q[insn].file) + 1; rec.nr_entry++; } } fwrite(&rec, sizeof(rec), 1, jitdump); /* Write the main debug entries. */ for (insn = 0; insn < icount; insn++) { if (q[insn].file) { get_host_pc_size(&host_pc, NULL, start, insn); ent.addr = host_pc; ent.lineno = q[insn].line; ent.discrim = 0; fwrite(&ent, sizeof(ent), 1, jitdump); fwrite(q[insn].file, strlen(q[insn].file) + 1, 1, jitdump); } } /* Write the trailing debug_entry. */ ent.addr = (uintptr_t)start + tcg_ctx->gen_insn_end_off[icount - 1]; ent.lineno = 0; ent.discrim = 0; fwrite(&ent, sizeof(ent), 1, jitdump); fwrite("", 1, 1, jitdump); } /* Write a JIT_CODE_LOAD jitdump entry. */ static void write_jr_code_load(const void *start, uint16_t host_size, const struct debuginfo_query *q) { static uint64_t code_index; struct jr_code_load rec; const char *symbol; size_t symbol_size; symbol = pretty_symbol(q, &symbol_size); rec.p.id = JIT_CODE_LOAD; rec.p.total_size = sizeof(rec) + symbol_size + host_size; rec.p.timestamp = get_clock(); rec.pid = getpid(); rec.tid = qemu_get_thread_id(); rec.vma = (uintptr_t)start; rec.code_addr = (uintptr_t)start; rec.code_size = host_size; rec.code_index = code_index++; fwrite(&rec, sizeof(rec), 1, jitdump); fwrite(symbol, symbol_size, 1, jitdump); fwrite(start, host_size, 1, jitdump); } void perf_report_code(uint64_t guest_pc, TranslationBlock *tb, const void *start) { struct debuginfo_query *q; size_t insn, start_words; uint64_t *gen_insn_data; if (!perfmap && !jitdump) { return; } q = g_try_malloc0_n(tb->icount, sizeof(*q)); if (!q) { return; } debuginfo_lock(); /* Query debuginfo for each guest instruction. */ gen_insn_data = tcg_ctx->gen_insn_data; start_words = tcg_ctx->insn_start_words; for (insn = 0; insn < tb->icount; insn++) { /* FIXME: This replicates the restore_state_to_opc() logic. */ q[insn].address = gen_insn_data[insn * start_words + 0]; if (tb_cflags(tb) & CF_PCREL) { q[insn].address |= (guest_pc & qemu_target_page_mask()); } q[insn].flags = DEBUGINFO_SYMBOL | (jitdump ? DEBUGINFO_LINE : 0); } debuginfo_query(q, tb->icount); /* Emit perfmap entries if needed. */ if (perfmap) { flockfile(perfmap); for (insn = 0; insn < tb->icount; insn++) { write_perfmap_entry(start, insn, &q[insn]); } funlockfile(perfmap); } /* Emit jitdump entries if needed. */ if (jitdump) { flockfile(jitdump); write_jr_code_debug_info(start, q, tb->icount); write_jr_code_load(start, tcg_ctx->gen_insn_end_off[tb->icount - 1], q); funlockfile(jitdump); } debuginfo_unlock(); g_free(q); } void perf_exit(void) { if (perfmap) { fclose(perfmap); perfmap = NULL; } if (perf_marker != MAP_FAILED) { munmap(perf_marker, perf_marker_size); perf_marker = MAP_FAILED; } if (jitdump) { fclose(jitdump); jitdump = NULL; } }