e7cd7a3916
Coverity complains that perf_marker is never unmapped.
Fix by unmapping it in perf_exit().
Fixes: Coverity CID 1507929
Fixes: 5584e2dbe8
("tcg: add perfmap and jitdump")
Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com>
Message-Id: <20230605114134.1169974-1-iii@linux.ibm.com>
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
387 lines
9.6 KiB
C
387 lines
9.6 KiB
C
/*
|
|
* Linux perf perf-<pid>.map and jit-<pid>.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/exec-all.h"
|
|
#include "qemu/timer.h"
|
|
#include "tcg/tcg.h"
|
|
|
|
#include "debuginfo.h"
|
|
#include "perf.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 & TARGET_PAGE_MASK);
|
|
} else {
|
|
#if defined(TARGET_I386)
|
|
q[insn].address -= tb->cs_base;
|
|
#endif
|
|
}
|
|
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;
|
|
}
|
|
}
|