Files
qemu/tracestub.c
2025-06-09 20:50:11 +08:00

578 lines
17 KiB
C

/*
* CSKY trace server
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "exec/tracestub.h"
#include "qemu/log.h"
#include "cpu.h"
#include "qemu/config-file.h"
#include "qemu/thread.h"
#include "sysemu/runstate.h"
struct csky_trace_server_state traceserver;
#ifdef CONFIG_USER_ONLY
static int traceserver_fd = -1;
#endif
long long csky_trace_icount;
struct csky_trace_filter tfilter;
int waddr_num, raddr_num;
static QemuMutex cpf_lock;
static bool cpf_mt_enable;
QemuOptsList qemu_csky_trace_opts = {
.name = "csky-trace",
.head = QTAILQ_HEAD_INITIALIZER(qemu_csky_trace_opts.head),
.desc = {
{
.name = "port",
.type = QEMU_OPT_STRING,
.help = "communicate port",
},{
.name = "tb_trace",
.type = QEMU_OPT_BOOL,
.help = "trace basic blocks or not",
},{
.name = "mem_trace",
.type = QEMU_OPT_BOOL,
.help = "trace ld/st or not",
},{
.name = "x_vf_trace",
.type = QEMU_OPT_BOOL,
.help = "trace experiment vector factor",
},{
.name = "x_lmul_trace",
.type = QEMU_OPT_BOOL,
.help = "trace experiment vector lmul",
},{
.name = "auto_trace",
.type = QEMU_OPT_BOOL,
.help = "auto gen trace or not",
},{
.name = "start",
.type = QEMU_OPT_STRING,
.help = "trace start addr",
},{
.name = "exit",
.type = QEMU_OPT_STRING,
.help = "trace exit addr",
},{
.name = "x_mt_trace",
.type = QEMU_OPT_BOOL,
.help = "trace experiment multi thread",
},{
.name = "proxy_trace",
.type = QEMU_OPT_BOOL,
.help = "add inst addr for memory trace or not",
},{ /* end of list */ }
},
};
/* return the length of cpuname without the suffix "-csky-cpu" in cpu_type*/
void csky_trace_set_cpu(const char *cpu_type)
{
char *type;
memset(tfilter.cpu, 0, 20);
type = strstr(cpu_type, "-riscv-cpu");
if (type == NULL) {
type = strstr(cpu_type, "-csky-cpu");
}
/* save one char space for TRACE_CPUNAME */
if (type != NULL) {
memcpy(tfilter.cpu + 1, cpu_type, MIN(type - cpu_type, 16));
} else if (cpu_type != NULL) {
strncpy(tfilter.cpu + 1, cpu_type, 16);
}
}
void csky_trace_handle_opts(CPUState *cs, uint32_t cpuid)
{
QemuOptsList *ret;
QemuOpts *opts;
bool b;
ret = qemu_find_opts("csky-trace");
if (ret) {
opts = qemu_opts_find(ret, NULL);
if (opts) {
cs->csky_trace_features |= CSKY_TRACE;
cs->csky_trace_features |= TB_TRACE;
cs->csky_trace_features |= MEM_TRACE;
cs->csky_trace_features |= X_VF_TRACE;
cs->csky_trace_features |= X_LMUL_TRACE;
tfilter.enable = true;
tfilter.cpuid = cpuid;
tfilter.event |= TRACE_EVENT_INSN;
tfilter.event |= TRACE_EVENT_DATA;
tfilter.event |= TRACE_EVENT_X_VF;
tfilter.event |= TRACE_EVENT_X_LMUL;
b = qemu_opt_get_bool(opts, "tb_trace", true);
if (!b) {
tfilter.event &= ~TRACE_EVENT_INSN;
}
b = qemu_opt_get_bool(opts, "mem_trace", true);
if (!b) {
tfilter.event &= ~TRACE_EVENT_DATA;
}
b = qemu_opt_get_bool(opts, "x_vf_trace", false);
if (!b) {
tfilter.event &= ~TRACE_EVENT_X_VF;
}
b = qemu_opt_get_bool(opts, "x_lmul_trace", false);
if (!b) {
tfilter.event &= ~TRACE_EVENT_X_LMUL;
}
cpf_mt_enable = qemu_opt_get_bool(opts, "x_mt_trace", false);
b = qemu_opt_get_bool(opts, "auto_trace", true);
if (!b) {
tfilter.event &= ~TRACE_EVENT_DATA;
tfilter.event &= ~TRACE_EVENT_INSN;
tfilter.enable = false;
}
}
}
}
static int test = 0;
static void trace_add_syn(void)
{
uint16_t syn_start = SYN_START;
uint16_t syn_end = SYN_END;
uint16_t syn_icount = SYN_ICOUNT;
traceserver.insn_num = csky_trace_icount - traceserver.last_icount;
memcpy(traceserver.buf + traceserver.pos, &syn_start, sizeof(uint16_t));
memcpy(traceserver.buf + traceserver.pos + 2, &syn_icount,
sizeof(uint16_t));
memcpy(traceserver.buf + traceserver.pos + 4, &(traceserver.insn_num),
sizeof(uint32_t));
memcpy(traceserver.buf + traceserver.pos + 8, &syn_end,
sizeof(uint16_t));
traceserver.pos += 5 * sizeof(uint16_t);
test += traceserver.insn_num;
}
void trace_buf_alloc(bool add_sync)
{
traceserver.buf = (char *)malloc(DEFAULT_BUFFER_LEN);
memset(traceserver.buf, 0, DEFAULT_BUFFER_LEN);
traceserver.len = DEFAULT_BUFFER_LEN;
traceserver.pos = 0;
if (add_sync) {
trace_add_syn();
}
}
void trace_buf_clear(void)
{
traceserver.pos = 0;
trace_add_syn();
}
#ifdef CONFIG_USER_ONLY
static void send_packet(void)
{
int ret;
uint32_t last = traceserver.pos;
uint32_t start = 0;
while (last > 0) {
ret = send(traceserver.fd, (const uint8_t *)traceserver.buf + start,
last, 0);
if (ret < 0) {
if (errno != EINTR) {
return;
}
} else {
start += ret;
last -= ret;
}
}
}
#endif
void trace_termsig_handler(void)
{
if (traceserver.initok) {
trace_add_syn();
#ifdef CONFIG_USER_ONLY
send_packet();
close(traceserver.fd);
#else
qemu_chr_fe_write_all(&traceserver.chr,
(const uint8_t *)traceserver.buf, traceserver.pos);
qemu_chr_fe_disconnect(&traceserver.chr);
#endif
}
trace_buf_clear();
}
/* Trace_send is triggered by the target insn counter.
* It can only be called after trace device enabled
*/
void trace_send(void)
{
if (traceserver.initok) {
if (traceserver.buf == NULL) {
trace_buf_alloc(true);
}
#ifdef CONFIG_USER_ONLY
send_packet();
#else
qemu_chr_fe_write_all(&traceserver.chr,
(const uint8_t *)traceserver.buf, traceserver.pos);
#endif
}
trace_buf_clear();
traceserver.last_icount += traceserver.insn_num;
}
void trace_send_immediately(void)
{
if ((traceserver.buf != NULL) && (traceserver.initok != false)) {
if (traceserver.pos > 5 * sizeof(uint16_t)) {
traceserver.last_icount += traceserver.insn_num;
trace_add_syn();
}
#ifdef CONFIG_USER_ONLY
send_packet();
#else
qemu_chr_fe_write_all(&traceserver.chr,
(const uint8_t *)traceserver.buf, traceserver.pos);
#endif
trace_buf_clear();
}
}
void write_trace_before(uint32_t packlen, bool header)
{
if (cpf_mt_enable) {
qemu_mutex_lock(&cpf_lock);
}
if (traceserver.buf == 0) {
trace_buf_alloc(!header);
}
if ((traceserver.pos + packlen) > traceserver.len) {
traceserver.buf = (char *)realloc(traceserver.buf,
traceserver.len + 2 * 1024);
traceserver.len += 2 * 1024;
}
}
void write_trace_header(uint32_t config)
{
uint32_t version = ((CURRENT_TRACE_VERSION) << 8) | TRACE_VERSION;
uint32_t traceid = ((QEMU_TRACE_ID) << 8) | TRACE_ID;
uint32_t cpuid[6] = {0};
char *cpu = tfilter.cpu;
uint32_t packlen = 0;
uint16_t config_type = TRACE_CONFIG;
static bool header = true; /* a header or just trace config */
cpuid[0] = TRACE_CPUID;
cpuid[1] = tfilter.cpuid;
cpu[0] = CPU_NAME;
if (header) {
packlen = 9 * sizeof(uint32_t) + 1 * sizeof(uint16_t) + 20 * sizeof(char);
write_trace_before(packlen, true);
/* add version */
memcpy(traceserver.buf + traceserver.pos, &version, sizeof(uint32_t));
traceserver.pos += sizeof(uint32_t);
/* add traceid */
memcpy(traceserver.buf + traceserver.pos, &traceid, sizeof(uint32_t));
traceserver.pos += sizeof(uint32_t);
/* add cpuid */
memcpy(traceserver.buf + traceserver.pos, cpuid, sizeof(uint32_t) * 6);
traceserver.pos += sizeof(uint32_t) * 6;
/* add cpu */
memcpy(traceserver.buf + traceserver.pos, cpu, sizeof(char) * 20);
traceserver.pos += sizeof(char) * 20;
/* add trace control registers */
memcpy(traceserver.buf + traceserver.pos, &config_type,
sizeof(uint16_t));
traceserver.pos += sizeof(uint16_t);
memcpy(traceserver.buf + traceserver.pos, &config, sizeof(uint32_t));
traceserver.pos += sizeof(uint32_t);
trace_send_immediately();
header = false;
} else {
write_trace_8_8(TRACE_CONFIG, 6, 0, config);
}
}
//#define CSKY_TRACE_COMPRESS
/* compress and send the coming element */
static void csky_trace_compress(uint32_t packetlen, char *start)
{
#ifdef CSKY_TRACE_COMPRESS
uint64_t compress_element;
struct csky_trace_compress *pcompress = &traceserver.compress;
if (pcompress->lastpacket == NULL) {
pcompress->lastpacket = (char *)malloc(packetlen);
memcpy(pcompress->lastpacket, start, packetlen);
pcompress->len = packetlen;
pcompress->count = 1;
} else {
if (packetlen == pcompress->len && memcmp(pcompress->lastpacket, start,
packetlen) == 0) {
pcompress->count++;
} else {
memcpy(traceserver.buf + traceserver.pos, pcompress->lastpacket,
pcompress->len);
traceserver.pos += pcompress->len / sizeof(uint8_t);
if (pcompress->count > 1) {
write_trace_before(sizeof(uint64_t), false);
compress_element = ((uint64_t)pcompress->count << 32)
| ((1 << 24) | TRACE_COMPRESS);
memcpy(traceserver.buf + traceserver.pos, &compress_element,
sizeof(uint64_t));
traceserver.pos += sizeof(uint64_t);
}
pcompress->lastpacket = (char *)realloc(pcompress->lastpacket,
packetlen);
memcpy(pcompress->lastpacket, start, packetlen);
pcompress->len = packetlen;
pcompress->count = 1;
}
}
#else
memcpy(traceserver.buf + traceserver.pos, start, packetlen);
traceserver.pos += packetlen / sizeof(uint8_t);
if (cpf_mt_enable) {
qemu_mutex_unlock(&cpf_lock);
}
#endif
}
void write_trace_8(uint8_t type, uint32_t packlen, uint32_t value)
{
value = (value << 8) | type;
write_trace_before(packlen, false);
assert((traceserver.pos + packlen) <= traceserver.len);
csky_trace_compress(packlen, (char *)&value);
}
void write_trace_8_8(uint8_t type, uint32_t packlen, uint8_t value1
, uint32_t value2)
{
uint64_t value = type;
value = ((uint64_t)value1 << 8) | value;
value = ((uint64_t)value2 << 16 * sizeof(uint8_t)) | value;
write_trace_before(packlen, false);
assert((traceserver.pos + packlen) <= traceserver.len);
csky_trace_compress(packlen, (char *)&value);
}
void write_trace_8_24(uint8_t type, uint32_t packlen, uint32_t value1,
uint32_t value2)
{
uint64_t value = type;
value = ((uint64_t)value1 << 8) | value;
value = ((uint64_t)value2 << 32 * sizeof(uint8_t)) | value;
write_trace_before(packlen, false);
assert((traceserver.pos + packlen) <= traceserver.len);
csky_trace_compress(packlen, (char *)&value);
}
/*
void write_trace_8_seq(uint8_t type, uint32_t packlen, uint8_t *value)
{
write_trace_before(packlen, false);
assert((traceserver.pos + packlen) <= traceserver.len);
memcpy(traceserver.buf + traceserver.pos, &type, 1);
memcpy(traceserver.buf + traceserver.pos + 1 , value, packlen - 1);
traceserver.pos += packlen / sizeof(uint8_t);
}*/
#ifdef CONFIG_USER_ONLY
static int traceserver_open(int port)
{
struct sockaddr_in sockaddr;
int fd, ret;
fd = socket(PF_INET, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket");
return -1;
}
#ifndef _WIN32
fcntl(fd, F_SETFD, FD_CLOEXEC);
#endif
socket_set_fast_reuse(fd);
sockaddr.sin_family = AF_INET;
sockaddr.sin_port = htons(port);
sockaddr.sin_addr.s_addr = 0;
ret = bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
if (ret < 0) {
perror("bind");
close(fd);
return -1;
}
ret = listen(fd, 1);
if (ret < 0) {
perror("listen");
close(fd);
return -1;
}
return fd;
}
static void traceserver_accept(void)
{
struct sockaddr_in sockaddr;
socklen_t len;
int fd;
for (;;) {
len = sizeof(sockaddr);
fd = accept(traceserver_fd, (struct sockaddr *)&sockaddr, &len);
if (fd < 0 && errno != EINTR) {
perror("accept");
return;
} else if (fd >= 0) {
#ifndef _WIN32
fcntl(fd, F_SETFD, FD_CLOEXEC);
#endif
break;
}
}
/* set short latency */
socket_set_nodelay(fd);
traceserver.fd = fd;
traceserver.initok = true;
}
int traceserver_start(int port, int debug_mode)
{
traceserver_fd = traceserver_open(port);
if (traceserver_fd < 0) {
return -1;
}
traceserver_accept();
if (debug_mode) {
tfilter.event |= TRACE_EVENT_GDB;
}
if (tfilter.enable) {
write_trace_header(tfilter.event);
}
traceserver.initok = true;
qemu_mutex_init(&cpf_lock);
return 0;
}
void trace_exit_notify(void)
{
if ((traceserver.buf != NULL) && (traceserver.initok != false)) {
if (traceserver.pos > 5 * sizeof(uint16_t)) {
traceserver.last_icount += traceserver.insn_num;
trace_add_syn();
}
send_packet();
close(traceserver.fd);
trace_buf_clear();
if (traceserver.buf != NULL) {
free(traceserver.buf);
traceserver.buf = NULL;
}
traceserver.initok = false;
}
qemu_log_mask(LOG_GUEST_ERROR, "WADDR_NUM: %d\n", waddr_num);
qemu_log_mask(LOG_GUEST_ERROR, "RADDR_NUM: %d\n", raddr_num);
}
#else
static void trace_chr_event(void *opaque, QEMUChrEvent event)
{
switch (event) {
case CHR_EVENT_OPENED:
traceserver.initok = true;
if (is_gdbserver_start) {
tfilter.event |= TRACE_EVENT_GDB;
}
if (tfilter.enable) {
write_trace_header(tfilter.event);
}
if (!runstate_needs_reset() && !is_gdbserver_start) {
vm_start();
}
break;
case CHR_EVENT_BREAK:
case CHR_EVENT_CLOSED:
traceserver.initok = false;
break;
default:
break;
}
}
void trace_exit_notify(void)
{
if ((traceserver.buf != NULL) && (traceserver.initok != false)) {
if (traceserver.pos > 5 * sizeof(uint16_t)) {
traceserver.last_icount += traceserver.insn_num;
trace_add_syn();
}
traceserver.initok = false;
qemu_chr_fe_write_all(&traceserver.chr,
(const uint8_t *)traceserver.buf, traceserver.pos);
qemu_chr_fe_disconnect(&traceserver.chr);
trace_buf_clear();
if (traceserver.buf != NULL) {
free(traceserver.buf);
traceserver.buf = NULL;
}
}
}
int traceserver_start(const char *device)
{
char tracestub_device_name[128];
Chardev *chr = NULL;
if (!device) {
return -1;
}
if (strcmp(device, "none") != 0) {
/* enforce required TCP attributes */
snprintf(tracestub_device_name, sizeof(tracestub_device_name),
"tcp::%s,nowait,nodelay,server", device);
device = tracestub_device_name;
chr = qemu_chr_new_noreplay("trace", device, true, NULL);
if (!chr) {
return -1;
}
} else {
return -1;
}
if (chr) {
qemu_chr_fe_init(&traceserver.chr, chr, &error_abort);
qemu_chr_fe_set_handlers(&traceserver.chr, NULL, NULL,
trace_chr_event, NULL, NULL, NULL, true);
}
atexit(trace_exit_notify);
return 0;
}
#endif