From 6901da648834927bd27917e04f9c94940e3b45aa Mon Sep 17 00:00:00 2001 From: Colin Date: Sat, 20 Sep 2025 09:55:30 +0000 Subject: [PATCH] init qemu and gdb. --- .vscode/settings.json | 7 + Makefile | 44 + build/kernel.bin | Bin 0 -> 150 bytes build/kernel.dump | 93 ++ build/kernel.elf | Bin 0 -> 7248 bytes gdbstub/gdbstub.c | 2519 +++++++++++++++++++++++++++++++++++++++++ gdbstub/internals.h | 240 ++++ gdbstub/meson.build | 21 + gdbstub/syscalls.c | 206 ++++ gdbstub/system.c | 669 +++++++++++ gdbstub/trace-events | 32 + gdbstub/trace.h | 1 + gdbstub/user-target.c | 424 +++++++ gdbstub/user.c | 955 ++++++++++++++++ kernel.c | 14 + kernel_obj | Bin 0 -> 6216 bytes link.ld | 36 + start.s | 14 + 18 files changed, 5275 insertions(+) create mode 100644 .vscode/settings.json create mode 100755 Makefile create mode 100755 build/kernel.bin create mode 100644 build/kernel.dump create mode 100755 build/kernel.elf create mode 100644 gdbstub/gdbstub.c create mode 100644 gdbstub/internals.h create mode 100644 gdbstub/meson.build create mode 100644 gdbstub/syscalls.c create mode 100644 gdbstub/system.c create mode 100644 gdbstub/trace-events create mode 100644 gdbstub/trace.h create mode 100644 gdbstub/user-target.c create mode 100644 gdbstub/user.c create mode 100755 kernel.c create mode 100755 kernel_obj create mode 100644 link.ld create mode 100644 start.s diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0c4466e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "files.associations": { + "BUILD": "bazel", + "*.inc": "cpp", + "stdio.h": "c" + } +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100755 index 0000000..72b1b27 --- /dev/null +++ b/Makefile @@ -0,0 +1,44 @@ +CFLAGS = -I../include -Wall -Wextra +LDFLAGS = + +CURDIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) + +OUT := build + +BIN = $(OUT)/ +SHELL_HACK := $(shell mkdir -p $(OUT)) + + +TEST_OBJ = $(OUT)/kernel.elf +TEST_DUMP = $(OUT)/kernel.dump +TEST_BIN = $(OUT)/kernel.bin + + +.PHONY: kernel clean + +kernel: $(TEST_OBJ) $(TEST_BIN) + +$(TEST_OBJ): +# riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -Wl,-Ttext=0x0 -nostdlib -g -o $@ $< +# riscv64-unknown-elf-gcc -march=rv64gc -mabi=lp64 -Wl,-Ttext=0x0 -nostdlib -g -o $@ $< + riscv64-unknown-elf-gcc -g -march=rv64gc -mabi=lp64d -nostdlib -T link.ld -o $@ kernel.c start.s + riscv64-unknown-elf-objdump -d -S $@ > $(TEST_DUMP) + +$(TEST_BIN): $(TEST_OBJ) + riscv64-unknown-elf-objcopy -O binary $< $@ + +clean: + $(RM) -r $(BIN) + +qemu: kernel + qemu-system-riscv64 -machine virt -smp 1 -bios none -kernel $(TEST_OBJ) -nographic -s -S + + +# gdb: kernel +# gdb-multiarch \ +# -ex "file $(TEST_OBJ)" \ +# -ex "set debug remote 1" \ +# -ex "target remote localhost:1234" \ + +gdb: kernel + gdb-multiarch $(TEST_OBJ) -ex "target remote localhost:1234" \ No newline at end of file diff --git a/build/kernel.bin b/build/kernel.bin new file mode 100755 index 0000000000000000000000000000000000000000..38759046ce61f3ad144981226f0902c505f4ca85 GIT binary patch literal 150 zcmWeEa{1g8EogX9u;y1e6wJs`PDscsx4geJBLKFZ1 literal 0 HcmV?d00001 diff --git a/build/kernel.dump b/build/kernel.dump new file mode 100644 index 0000000..c4a4095 --- /dev/null +++ b/build/kernel.dump @@ -0,0 +1,93 @@ + +build/kernel.elf: file format elf64-littleriscv + + +Disassembly of section .text: + +0000000080000000 <_start>: +.section .text.entry +.global _start + +_start: + # 初始化栈(使用链接脚本中定义的 stack_top) + la sp, stack_top + 80000000: 00000117 auipc sp,0x0 + 80000004: 0a010113 addi sp,sp,160 # 800000a0 + + # 调用 main 函数 + call main + 80000008: 030000ef jal 80000038
+ +000000008000000c : + + # main 返回后进入死循环 +loop: + j loop + 8000000c: a001 j 8000000c + +000000008000000e : + +int add(int a, int b) { + 8000000e: 1101 addi sp,sp,-32 + 80000010: ec22 sd s0,24(sp) + 80000012: 1000 addi s0,sp,32 + 80000014: 87aa mv a5,a0 + 80000016: 872e mv a4,a1 + 80000018: fef42623 sw a5,-20(s0) + 8000001c: 87ba mv a5,a4 + 8000001e: fef42423 sw a5,-24(s0) + return a + b; // 待调试的函数 + 80000022: fec42783 lw a5,-20(s0) + 80000026: 873e mv a4,a5 + 80000028: fe842783 lw a5,-24(s0) + 8000002c: 9fb9 addw a5,a5,a4 + 8000002e: 2781 sext.w a5,a5 +} + 80000030: 853e mv a0,a5 + 80000032: 6462 ld s0,24(sp) + 80000034: 6105 addi sp,sp,32 + 80000036: 8082 ret + +0000000080000038
: + +int main() { + 80000038: 7179 addi sp,sp,-48 + 8000003a: f406 sd ra,40(sp) + 8000003c: f022 sd s0,32(sp) + 8000003e: 1800 addi s0,sp,48 + int x = 5; + 80000040: 4795 li a5,5 + 80000042: fef42623 sw a5,-20(s0) + int y = 3; + 80000046: 478d li a5,3 + 80000048: fef42423 sw a5,-24(s0) + int result = add(x, y); + 8000004c: fe842703 lw a4,-24(s0) + 80000050: fec42783 lw a5,-20(s0) + 80000054: 85ba mv a1,a4 + 80000056: 853e mv a0,a5 + 80000058: fb7ff0ef jal 8000000e + 8000005c: 87aa mv a5,a0 + 8000005e: fef42223 sw a5,-28(s0) + volatile char *tohost_addr = (volatile char *)(0x80001000 - 4); + 80000062: 200007b7 lui a5,0x20000 + 80000066: 3ff78793 addi a5,a5,1023 # 200003ff <_start-0x5ffffc01> + 8000006a: 078a slli a5,a5,0x2 + 8000006c: fcf43c23 sd a5,-40(s0) + *tohost_addr = 0xff; + 80000070: fd843783 ld a5,-40(s0) + 80000074: 577d li a4,-1 + 80000076: 00e78023 sb a4,0(a5) + *tohost_addr = result; + 8000007a: fe442783 lw a5,-28(s0) + 8000007e: 0ff7f713 zext.b a4,a5 + 80000082: fd843783 ld a5,-40(s0) + 80000086: 00e78023 sb a4,0(a5) + return 0; + 8000008a: 4781 li a5,0 +} + 8000008c: 853e mv a0,a5 + 8000008e: 70a2 ld ra,40(sp) + 80000090: 7402 ld s0,32(sp) + 80000092: 6145 addi sp,sp,48 + 80000094: 8082 ret diff --git a/build/kernel.elf b/build/kernel.elf new file mode 100755 index 0000000000000000000000000000000000000000..00852e3591892ccfe58fe488ce4f94af32046a81 GIT binary patch literal 7248 zcmeHM&2Jk;6o0d8$99}JPEtss3dQ*-DIalSH}ngdrcO(!R8@rvQ6#`!uQ#z(uWi=r zD6I;0NTXDhkXp4A2~`mFgnHtJsJI|;;KYG{0wj=HAW|g?DeukB*vZC}N^snf_I>=` z$IQH$*;(mB2Sh*xDEkn*y3Mh>=}8wL zdm~@G9U{z^i~)0j~mH1-uG8 zZUuVqhj=$*t#<)bw#qsOe}U-jmH5ig(`)N@R)&Vx)|W=UU7uW88o9Xs)n|(%lgpW` zU;ChT_T84X!F$mA(F}QhI76N)`jGdX<;mr{_bxoRy)rbow*F-RV%JvgAG{nGdTH&? zr9Bt_dhd;)+K;d_a(KP_{{0R@NMD?pT)tIS>?4@{dmO$WSdeBxUl-shA|bpY05y^? z5fRNlivnx$FCp72?C%I0yGbZpfBDu0zno$L%vtYMayP^DZDp1nr9KG#%`(_w2Pk93~UoU0Su#+X7o9EeM-J!pj~B@ z7Vx@`7pt)eP=osi*o)zP1A(L63VwD`5Q;cZ*g;gka2ind3CP#Uc0{_vYXA2BPSzEO z_F#8$cCZg4!_McR7=2B`3x$40yF_9AN{2HH)G(q4LW*3hZhxD~Lfe4(8S)77HdP4)kq1ymjKyFWpFJ?7|k(7-ssv7uNay;oKF2z((Q=0upWnd7g= zrW1Q&iCytzd^|R4YMK0jLV5Q@7Ug_J8?BTL{XlX&u`iKK>>|6R=MGr&yC?FdHk;Mc z=kj{RLON>{bt4}eoddg+D^=_?Ze$BAqf#x{U~2gy=sC@Timh38yaHpnl4*?Tr9!?q zmNn*$La979m(5h{Y6fUbhfcOzyvRy=b4Ep~c!MycjQONUFb)KDy=0mOK1<{LSjRQnw(^;(-N-O&X{LdN z`ybkN%t5QQVW-!4+9;=VzBaWD4+)MBAf+dfJaO_o-VpJvQ{1gi_|B+n06S53V`P@` ze{Aq2(d|3Y1S7vIokRh?c37Rd<5OsHW8{3I%LzH{usRjfs_U6uG?_Fh3^y8Ryl1D883MbO(eVHQ{w-n(MBkjWSr#mCe. + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/ctype.h" +#include "qemu/cutils.h" +#include "qemu/module.h" +#include "qemu/error-report.h" +#include "qemu/target-info.h" +#include "trace.h" +#include "exec/gdbstub.h" +#include "gdbstub/commands.h" +#include "gdbstub/syscalls.h" +#ifdef CONFIG_USER_ONLY +#include "accel/tcg/vcpu-state.h" +#include "gdbstub/user.h" +#else +#include "hw/cpu/cluster.h" +#include "hw/boards.h" +#endif +#include "hw/core/cpu.h" + +#include "system/hw_accel.h" +#include "system/runstate.h" +#include "exec/replay-core.h" +#include "exec/hwaddr.h" + +#include "internals.h" + +typedef struct GDBRegisterState { + int base_reg; + gdb_get_reg_cb get_reg; + gdb_set_reg_cb set_reg; + const GDBFeature *feature; +} GDBRegisterState; + +GDBState gdbserver_state; + +void gdb_init_gdbserver_state(void) +{ + g_assert(!gdbserver_state.init); + memset(&gdbserver_state, 0, sizeof(GDBState)); + gdbserver_state.init = true; + gdbserver_state.str_buf = g_string_new(NULL); + gdbserver_state.mem_buf = g_byte_array_sized_new(MAX_PACKET_LENGTH); + gdbserver_state.last_packet = g_byte_array_sized_new(MAX_PACKET_LENGTH + 4); + + /* + * What single-step modes are supported is accelerator dependent. + * By default try to use no IRQs and no timers while single + * stepping so as to make single stepping like a typical ICE HW step. + */ + gdbserver_state.supported_sstep_flags = accel_supported_gdbstub_sstep_flags(); + gdbserver_state.sstep_flags = SSTEP_ENABLE | SSTEP_NOIRQ | SSTEP_NOTIMER; + gdbserver_state.sstep_flags &= gdbserver_state.supported_sstep_flags; +} + +/* writes 2*len+1 bytes in buf */ +void gdb_memtohex(GString *buf, const uint8_t *mem, int len) +{ + int i, c; + for(i = 0; i < len; i++) { + c = mem[i]; + g_string_append_c(buf, tohex(c >> 4)); + g_string_append_c(buf, tohex(c & 0xf)); + } + g_string_append_c(buf, '\0'); +} + +void gdb_hextomem(GByteArray *mem, const char *buf, int len) +{ + int i; + + for(i = 0; i < len; i++) { + guint8 byte = fromhex(buf[0]) << 4 | fromhex(buf[1]); + g_byte_array_append(mem, &byte, 1); + buf += 2; + } +} + +static void hexdump(const char *buf, int len, + void (*trace_fn)(size_t ofs, char const *text)) +{ + char line_buffer[3 * 16 + 4 + 16 + 1]; + + size_t i; + for (i = 0; i < len || (i & 0xF); ++i) { + size_t byte_ofs = i & 15; + + if (byte_ofs == 0) { + memset(line_buffer, ' ', 3 * 16 + 4 + 16); + line_buffer[3 * 16 + 4 + 16] = 0; + } + + size_t col_group = (i >> 2) & 3; + size_t hex_col = byte_ofs * 3 + col_group; + size_t txt_col = 3 * 16 + 4 + byte_ofs; + + if (i < len) { + char value = buf[i]; + + line_buffer[hex_col + 0] = tohex((value >> 4) & 0xF); + line_buffer[hex_col + 1] = tohex((value >> 0) & 0xF); + line_buffer[txt_col + 0] = (value >= ' ' && value < 127) + ? value + : '.'; + } + + if (byte_ofs == 0xF) + trace_fn(i & -16, line_buffer); + } +} + +/* return -1 if error, 0 if OK */ +int gdb_put_packet_binary(const char *buf, int len, bool dump) +{ + int csum, i; + uint8_t footer[3]; + + if (dump && trace_event_get_state_backends(TRACE_GDBSTUB_IO_BINARYREPLY)) { + hexdump(buf, len, trace_gdbstub_io_binaryreply); + } + + for(;;) { + g_byte_array_set_size(gdbserver_state.last_packet, 0); + g_byte_array_append(gdbserver_state.last_packet, + (const uint8_t *) "$", 1); + g_byte_array_append(gdbserver_state.last_packet, + (const uint8_t *) buf, len); + csum = 0; + for(i = 0; i < len; i++) { + csum += buf[i]; + } + footer[0] = '#'; + footer[1] = tohex((csum >> 4) & 0xf); + footer[2] = tohex((csum) & 0xf); + g_byte_array_append(gdbserver_state.last_packet, footer, 3); + + gdb_put_buffer(gdbserver_state.last_packet->data, + gdbserver_state.last_packet->len); + + if (gdb_got_immediate_ack()) { + break; + } + } + return 0; +} + +/* return -1 if error, 0 if OK */ +int gdb_put_packet(const char *buf) +{ + trace_gdbstub_io_reply(buf); + + return gdb_put_packet_binary(buf, strlen(buf), false); +} + +void gdb_put_strbuf(void) +{ + gdb_put_packet(gdbserver_state.str_buf->str); +} + +/* Encode data using the encoding for 'x' packets. */ +void gdb_memtox(GString *buf, const char *mem, int len) +{ + char c; + + while (len--) { + c = *(mem++); + switch (c) { + case '#': case '$': case '*': case '}': + g_string_append_c(buf, '}'); + g_string_append_c(buf, c ^ 0x20); + break; + default: + g_string_append_c(buf, c); + break; + } + } +} + +static uint32_t gdb_get_cpu_pid(CPUState *cpu) +{ +#ifdef CONFIG_USER_ONLY + return getpid(); +#else + if (cpu->cluster_index == UNASSIGNED_CLUSTER_INDEX) { + /* Return the default process' PID */ + int index = gdbserver_state.process_num - 1; + return gdbserver_state.processes[index].pid; + } + return cpu->cluster_index + 1; +#endif +} + +GDBProcess *gdb_get_process(uint32_t pid) +{ + int i; + + if (!pid) { + /* 0 means any process, we take the first one */ + return &gdbserver_state.processes[0]; + } + + for (i = 0; i < gdbserver_state.process_num; i++) { + if (gdbserver_state.processes[i].pid == pid) { + return &gdbserver_state.processes[i]; + } + } + + return NULL; +} + +static GDBProcess *gdb_get_cpu_process(CPUState *cpu) +{ + return gdb_get_process(gdb_get_cpu_pid(cpu)); +} + +static CPUState *find_cpu(uint32_t thread_id) +{ + CPUState *cpu; + + CPU_FOREACH(cpu) { + if (gdb_get_cpu_index(cpu) == thread_id) { + return cpu; + } + } + + return NULL; +} + +CPUState *gdb_get_first_cpu_in_process(GDBProcess *process) +{ + CPUState *cpu; + + CPU_FOREACH(cpu) { + if (gdb_get_cpu_pid(cpu) == process->pid) { + return cpu; + } + } + + return NULL; +} + +static CPUState *gdb_next_cpu_in_process(CPUState *cpu) +{ + uint32_t pid = gdb_get_cpu_pid(cpu); + cpu = CPU_NEXT(cpu); + + while (cpu) { + if (gdb_get_cpu_pid(cpu) == pid) { + break; + } + + cpu = CPU_NEXT(cpu); + } + + return cpu; +} + +/* Return the cpu following @cpu, while ignoring unattached processes. */ +static CPUState *gdb_next_attached_cpu(CPUState *cpu) +{ + cpu = CPU_NEXT(cpu); + + while (cpu) { + if (gdb_get_cpu_process(cpu)->attached) { + break; + } + + cpu = CPU_NEXT(cpu); + } + + return cpu; +} + +/* Return the first attached cpu */ +CPUState *gdb_first_attached_cpu(void) +{ + CPUState *cpu = first_cpu; + GDBProcess *process = gdb_get_cpu_process(cpu); + + if (!process->attached) { + return gdb_next_attached_cpu(cpu); + } + + return cpu; +} + +static CPUState *gdb_get_cpu(uint32_t pid, uint32_t tid) +{ + GDBProcess *process; + CPUState *cpu; + + if (!pid && !tid) { + /* 0 means any process/thread, we take the first attached one */ + return gdb_first_attached_cpu(); + } else if (pid && !tid) { + /* any thread in a specific process */ + process = gdb_get_process(pid); + + if (process == NULL) { + return NULL; + } + + if (!process->attached) { + return NULL; + } + + return gdb_get_first_cpu_in_process(process); + } else { + /* a specific thread */ + cpu = find_cpu(tid); + + if (cpu == NULL) { + return NULL; + } + + process = gdb_get_cpu_process(cpu); + + if (pid && process->pid != pid) { + return NULL; + } + + if (!process->attached) { + return NULL; + } + + return cpu; + } +} + +static const char *get_feature_xml(const char *p, const char **newp, + GDBProcess *process) +{ + CPUState *cpu = gdb_get_first_cpu_in_process(process); + GDBRegisterState *r; + size_t len; + + /* + * qXfer:features:read:ANNEX:OFFSET,LENGTH' + * ^p ^newp + */ + char *term = strchr(p, ':'); + *newp = term + 1; + len = term - p; + + /* Is it the main target xml? */ + if (strncmp(p, "target.xml", len) == 0) { + if (!process->target_xml) { + g_autoptr(GPtrArray) xml = g_ptr_array_new_with_free_func(g_free); + + g_ptr_array_add( + xml, + g_strdup("" + "" + "")); + + if (cpu->cc->gdb_arch_name) { + g_ptr_array_add( + xml, + g_markup_printf_escaped("%s", + cpu->cc->gdb_arch_name(cpu))); + } + for (guint i = 0; i < cpu->gdb_regs->len; i++) { + r = &g_array_index(cpu->gdb_regs, GDBRegisterState, i); + g_ptr_array_add( + xml, + g_markup_printf_escaped("", + r->feature->xmlname)); + } + g_ptr_array_add(xml, g_strdup("")); + g_ptr_array_add(xml, NULL); + + process->target_xml = g_strjoinv(NULL, (void *)xml->pdata); + } + return process->target_xml; + } + /* Is it one of the features? */ + for (guint i = 0; i < cpu->gdb_regs->len; i++) { + r = &g_array_index(cpu->gdb_regs, GDBRegisterState, i); + if (strncmp(p, r->feature->xmlname, len) == 0) { + return r->feature->xml; + } + } + + /* failed */ + return NULL; +} + +void gdb_feature_builder_init(GDBFeatureBuilder *builder, GDBFeature *feature, + const char *name, const char *xmlname, + int base_reg) +{ + char *header = g_markup_printf_escaped( + "" + "" + "", + name); + + builder->feature = feature; + builder->xml = g_ptr_array_new(); + g_ptr_array_add(builder->xml, header); + builder->regs = g_ptr_array_new(); + builder->base_reg = base_reg; + feature->xmlname = xmlname; + feature->name = name; +} + +void gdb_feature_builder_append_tag(const GDBFeatureBuilder *builder, + const char *format, ...) +{ + va_list ap; + va_start(ap, format); + g_ptr_array_add(builder->xml, g_markup_vprintf_escaped(format, ap)); + va_end(ap); +} + +void gdb_feature_builder_append_reg(const GDBFeatureBuilder *builder, + const char *name, + int bitsize, + int regnum, + const char *type, + const char *group) +{ + if (builder->regs->len <= regnum) { + g_ptr_array_set_size(builder->regs, regnum + 1); + } + + builder->regs->pdata[regnum] = (gpointer *)name; + + if (group) { + gdb_feature_builder_append_tag( + builder, + "", + name, bitsize, builder->base_reg + regnum, type, group); + } else { + gdb_feature_builder_append_tag( + builder, + "", + name, bitsize, builder->base_reg + regnum, type); + } +} + +void gdb_feature_builder_end(const GDBFeatureBuilder *builder) +{ + g_ptr_array_add(builder->xml, (void *)""); + g_ptr_array_add(builder->xml, NULL); + + builder->feature->xml = g_strjoinv(NULL, (void *)builder->xml->pdata); + + for (guint i = 0; i < builder->xml->len - 2; i++) { + g_free(g_ptr_array_index(builder->xml, i)); + } + + g_ptr_array_free(builder->xml, TRUE); + + builder->feature->num_regs = builder->regs->len; + builder->feature->regs = (void *)g_ptr_array_free(builder->regs, FALSE); +} + +const GDBFeature *gdb_find_static_feature(const char *xmlname) +{ + const GDBFeature *feature; + + for (feature = gdb_static_features; feature->xmlname; feature++) { + if (!strcmp(feature->xmlname, xmlname)) { + return feature; + } + } + + g_assert_not_reached(); +} + +GArray *gdb_get_register_list(CPUState *cpu) +{ + GArray *results = g_array_new(true, true, sizeof(GDBRegDesc)); + + /* registers are only available once the CPU is initialised */ + if (!cpu->gdb_regs) { + return results; + } + + for (int f = 0; f < cpu->gdb_regs->len; f++) { + GDBRegisterState *r = &g_array_index(cpu->gdb_regs, GDBRegisterState, f); + for (int i = 0; i < r->feature->num_regs; i++) { + const char *name = r->feature->regs[i]; + GDBRegDesc desc = { + r->base_reg + i, + name, + r->feature->name + }; + g_array_append_val(results, desc); + } + } + + return results; +} + +int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg) +{ + GDBRegisterState *r; + + if (reg < cpu->cc->gdb_num_core_regs) { + return cpu->cc->gdb_read_register(cpu, buf, reg); + } + + for (guint i = 0; i < cpu->gdb_regs->len; i++) { + r = &g_array_index(cpu->gdb_regs, GDBRegisterState, i); + if (r->base_reg <= reg && reg < r->base_reg + r->feature->num_regs) { + return r->get_reg(cpu, buf, reg - r->base_reg); + } + } + return 0; +} + +int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg) +{ + GDBRegisterState *r; + + if (reg < cpu->cc->gdb_num_core_regs) { + return cpu->cc->gdb_write_register(cpu, mem_buf, reg); + } + + for (guint i = 0; i < cpu->gdb_regs->len; i++) { + r = &g_array_index(cpu->gdb_regs, GDBRegisterState, i); + if (r->base_reg <= reg && reg < r->base_reg + r->feature->num_regs) { + return r->set_reg(cpu, mem_buf, reg - r->base_reg); + } + } + return 0; +} + +static void gdb_register_feature(CPUState *cpu, int base_reg, + gdb_get_reg_cb get_reg, gdb_set_reg_cb set_reg, + const GDBFeature *feature) +{ + GDBRegisterState s = { + .base_reg = base_reg, + .get_reg = get_reg, + .set_reg = set_reg, + .feature = feature + }; + + g_array_append_val(cpu->gdb_regs, s); +} + +static const char *gdb_get_core_xml_file(CPUState *cpu) +{ + CPUClass *cc = cpu->cc; + + /* + * The CPU class can provide the XML filename via a method, + * or as a simple fixed string field. + */ + if (cc->gdb_get_core_xml_file) { + return cc->gdb_get_core_xml_file(cpu); + } + return cc->gdb_core_xml_file; +} + +void gdb_init_cpu(CPUState *cpu) +{ + CPUClass *cc = cpu->cc; + const GDBFeature *feature; + const char *xmlfile = gdb_get_core_xml_file(cpu); + + cpu->gdb_regs = g_array_new(false, false, sizeof(GDBRegisterState)); + + if (xmlfile) { + feature = gdb_find_static_feature(xmlfile); + gdb_register_feature(cpu, 0, + cc->gdb_read_register, cc->gdb_write_register, + feature); + cpu->gdb_num_regs = cpu->gdb_num_g_regs = feature->num_regs; + } + + if (cc->gdb_num_core_regs) { + cpu->gdb_num_regs = cpu->gdb_num_g_regs = cc->gdb_num_core_regs; + } +} + +void gdb_register_coprocessor(CPUState *cpu, + gdb_get_reg_cb get_reg, gdb_set_reg_cb set_reg, + const GDBFeature *feature, int g_pos) +{ + GDBRegisterState *s; + guint i; + int base_reg = cpu->gdb_num_regs; + + for (i = 0; i < cpu->gdb_regs->len; i++) { + /* Check for duplicates. */ + s = &g_array_index(cpu->gdb_regs, GDBRegisterState, i); + if (s->feature == feature) { + return; + } + } + + gdb_register_feature(cpu, base_reg, get_reg, set_reg, feature); + + /* Add to end of list. */ + cpu->gdb_num_regs += feature->num_regs; + if (g_pos) { + if (g_pos != base_reg) { + error_report("Error: Bad gdb register numbering for '%s', " + "expected %d got %d", feature->xml, g_pos, base_reg); + } else { + cpu->gdb_num_g_regs = cpu->gdb_num_regs; + } + } +} + +void gdb_unregister_coprocessor_all(CPUState *cpu) +{ + /* + * Safe to nuke everything. GDBRegisterState::xml is static const char so + * it won't be freed + */ + g_array_free(cpu->gdb_regs, true); + + cpu->gdb_regs = NULL; + cpu->gdb_num_regs = 0; + cpu->gdb_num_g_regs = 0; +} + +static void gdb_process_breakpoint_remove_all(GDBProcess *p) +{ + CPUState *cpu = gdb_get_first_cpu_in_process(p); + + while (cpu) { + gdb_breakpoint_remove_all(cpu); + cpu = gdb_next_cpu_in_process(cpu); + } +} + + +static void gdb_set_cpu_pc(vaddr pc) +{ + CPUState *cpu = gdbserver_state.c_cpu; + + cpu_synchronize_state(cpu); + cpu_set_pc(cpu, pc); +} + +void gdb_append_thread_id(CPUState *cpu, GString *buf) +{ + if (gdbserver_state.multiprocess) { + g_string_append_printf(buf, "p%02x.%02x", + gdb_get_cpu_pid(cpu), gdb_get_cpu_index(cpu)); + } else { + g_string_append_printf(buf, "%02x", gdb_get_cpu_index(cpu)); + } +} + +static GDBThreadIdKind read_thread_id(const char *buf, const char **end_buf, + uint32_t *pid, uint32_t *tid) +{ + unsigned long p, t; + int ret; + + if (*buf == 'p') { + buf++; + ret = qemu_strtoul(buf, &buf, 16, &p); + + if (ret) { + return GDB_READ_THREAD_ERR; + } + + /* Skip '.' */ + buf++; + } else { + p = 0; + } + + ret = qemu_strtoul(buf, &buf, 16, &t); + + if (ret) { + return GDB_READ_THREAD_ERR; + } + + *end_buf = buf; + + if (p == -1) { + return GDB_ALL_PROCESSES; + } + + if (pid) { + *pid = p; + } + + if (t == -1) { + return GDB_ALL_THREADS; + } + + if (tid) { + *tid = t; + } + + return GDB_ONE_THREAD; +} + +/** + * gdb_handle_vcont - Parses and handles a vCont packet. + * returns -ENOTSUP if a command is unsupported, -EINVAL or -ERANGE if there is + * a format error, 0 on success. + */ +static int gdb_handle_vcont(const char *p) +{ + int res, signal = 0; + char cur_action; + unsigned long tmp; + uint32_t pid, tid; + GDBProcess *process; + CPUState *cpu; + GDBThreadIdKind kind; + unsigned int max_cpus = gdb_get_max_cpus(); + /* uninitialised CPUs stay 0 */ + g_autofree char *newstates = g_new0(char, max_cpus); + + /* mark valid CPUs with 1 */ + CPU_FOREACH(cpu) { + newstates[cpu->cpu_index] = 1; + } + + /* + * res keeps track of what error we are returning, with -ENOTSUP meaning + * that the command is unknown or unsupported, thus returning an empty + * packet, while -EINVAL and -ERANGE cause an E22 packet, due to invalid, + * or incorrect parameters passed. + */ + res = 0; + + /* + * target_count and last_target keep track of how many CPUs we are going to + * step or resume, and a pointer to the state structure of one of them, + * respectively + */ + int target_count = 0; + CPUState *last_target = NULL; + + while (*p) { + if (*p++ != ';') { + return -ENOTSUP; + } + + cur_action = *p++; + if (cur_action == 'C' || cur_action == 'S') { + cur_action = qemu_tolower(cur_action); + res = qemu_strtoul(p, &p, 16, &tmp); + if (res) { + return res; + } + signal = gdb_signal_to_target(tmp); + } else if (cur_action != 'c' && cur_action != 's') { + /* unknown/invalid/unsupported command */ + return -ENOTSUP; + } + + if (*p == '\0' || *p == ';') { + /* + * No thread specifier, action is on "all threads". The + * specification is unclear regarding the process to act on. We + * choose all processes. + */ + kind = GDB_ALL_PROCESSES; + } else if (*p++ == ':') { + kind = read_thread_id(p, &p, &pid, &tid); + } else { + return -ENOTSUP; + } + + switch (kind) { + case GDB_READ_THREAD_ERR: + return -EINVAL; + + case GDB_ALL_PROCESSES: + cpu = gdb_first_attached_cpu(); + while (cpu) { + if (newstates[cpu->cpu_index] == 1) { + newstates[cpu->cpu_index] = cur_action; + + target_count++; + last_target = cpu; + } + + cpu = gdb_next_attached_cpu(cpu); + } + break; + + case GDB_ALL_THREADS: + process = gdb_get_process(pid); + + if (!process->attached) { + return -EINVAL; + } + + cpu = gdb_get_first_cpu_in_process(process); + while (cpu) { + if (newstates[cpu->cpu_index] == 1) { + newstates[cpu->cpu_index] = cur_action; + + target_count++; + last_target = cpu; + } + + cpu = gdb_next_cpu_in_process(cpu); + } + break; + + case GDB_ONE_THREAD: + cpu = gdb_get_cpu(pid, tid); + + /* invalid CPU/thread specified */ + if (!cpu) { + return -EINVAL; + } + + /* only use if no previous match occourred */ + if (newstates[cpu->cpu_index] == 1) { + newstates[cpu->cpu_index] = cur_action; + + target_count++; + last_target = cpu; + } + break; + } + } + + /* + * if we're about to resume a specific set of CPUs/threads, make it so that + * in case execution gets interrupted, we can send GDB a stop reply with a + * correct value. it doesn't really matter which CPU we tell GDB the signal + * happened in (VM pauses stop all of them anyway), so long as it is one of + * the ones we resumed/single stepped here. + */ + if (target_count > 0) { + gdbserver_state.c_cpu = last_target; + } + + gdbserver_state.signal = signal; + gdb_continue_partial(newstates); + return res; +} + +static const char *cmd_next_param(const char *param, const char delimiter) +{ + static const char all_delimiters[] = ",;:="; + char curr_delimiters[2] = {0}; + const char *delimiters; + + if (delimiter == '?') { + delimiters = all_delimiters; + } else if (delimiter == '0') { + return strchr(param, '\0'); + } else if (delimiter == '.' && *param) { + return param + 1; + } else { + curr_delimiters[0] = delimiter; + delimiters = curr_delimiters; + } + + param += strcspn(param, delimiters); + if (*param) { + param++; + } + return param; +} + +static int cmd_parse_params(const char *data, const char *schema, + GArray *params) +{ + const char *curr_schema, *curr_data; + + g_assert(schema); + g_assert(params->len == 0); + + curr_schema = schema; + curr_data = data; + while (curr_schema[0] && curr_schema[1] && *curr_data) { + GdbCmdVariant this_param; + + switch (curr_schema[0]) { + case 'l': + if (qemu_strtoul(curr_data, &curr_data, 16, + &this_param.val_ul)) { + return -EINVAL; + } + curr_data = cmd_next_param(curr_data, curr_schema[1]); + g_array_append_val(params, this_param); + break; + case 'L': + if (qemu_strtou64(curr_data, &curr_data, 16, + (uint64_t *)&this_param.val_ull)) { + return -EINVAL; + } + curr_data = cmd_next_param(curr_data, curr_schema[1]); + g_array_append_val(params, this_param); + break; + case 's': + this_param.data = curr_data; + curr_data = cmd_next_param(curr_data, curr_schema[1]); + g_array_append_val(params, this_param); + break; + case 'o': + this_param.opcode = *(uint8_t *)curr_data; + curr_data = cmd_next_param(curr_data, curr_schema[1]); + g_array_append_val(params, this_param); + break; + case 't': + this_param.thread_id.kind = + read_thread_id(curr_data, &curr_data, + &this_param.thread_id.pid, + &this_param.thread_id.tid); + curr_data = cmd_next_param(curr_data, curr_schema[1]); + g_array_append_val(params, this_param); + break; + case '?': + curr_data = cmd_next_param(curr_data, curr_schema[1]); + break; + default: + return -EINVAL; + } + curr_schema += 2; + } + + return 0; +} + +static inline int startswith(const char *string, const char *pattern) +{ + return !strncmp(string, pattern, strlen(pattern)); +} + +static bool process_string_cmd(const char *data, + const GdbCmdParseEntry *cmds, int num_cmds) +{ + int i; + g_autoptr(GArray) params = g_array_new(false, true, sizeof(GdbCmdVariant)); + + if (!cmds) { + return false; + } + + for (i = 0; i < num_cmds; i++) { + const GdbCmdParseEntry *cmd = &cmds[i]; + void *user_ctx = NULL; + g_assert(cmd->handler && cmd->cmd); + + if ((cmd->cmd_startswith && !startswith(data, cmd->cmd)) || + (!cmd->cmd_startswith && strcmp(cmd->cmd, data))) { + continue; + } + + if (cmd->schema) { + if (cmd_parse_params(&data[strlen(cmd->cmd)], + cmd->schema, params)) { + return false; + } + } + + if (cmd->need_cpu_context) { + user_ctx = (void *)gdbserver_state.g_cpu; + } + + gdbserver_state.allow_stop_reply = cmd->allow_stop_reply; + cmd->handler(params, user_ctx); + return true; + } + + return false; +} + +static void run_cmd_parser(const char *data, const GdbCmdParseEntry *cmd) +{ + if (!data) { + return; + } + + g_string_set_size(gdbserver_state.str_buf, 0); + g_byte_array_set_size(gdbserver_state.mem_buf, 0); + + /* In case there was an error during the command parsing we must + * send a NULL packet to indicate the command is not supported */ + if (!process_string_cmd(data, cmd, 1)) { + gdb_put_packet(""); + } +} + +static void handle_detach(GArray *params, void *user_ctx) +{ + GDBProcess *process; + uint32_t pid = 1; + + if (gdbserver_state.multiprocess) { + if (!params->len) { + gdb_put_packet("E22"); + return; + } + + pid = gdb_get_cmd_param(params, 0)->val_ul; + } + +#ifdef CONFIG_USER_ONLY + if (gdb_handle_detach_user(pid)) { + return; + } +#endif + + process = gdb_get_process(pid); + gdb_process_breakpoint_remove_all(process); + process->attached = false; + + if (pid == gdb_get_cpu_pid(gdbserver_state.c_cpu)) { + gdbserver_state.c_cpu = gdb_first_attached_cpu(); + } + + if (pid == gdb_get_cpu_pid(gdbserver_state.g_cpu)) { + gdbserver_state.g_cpu = gdb_first_attached_cpu(); + } + + if (!gdbserver_state.c_cpu) { + /* No more process attached */ + gdb_disable_syscalls(); + gdb_continue(); + } + gdb_put_packet("OK"); +} + +static void handle_thread_alive(GArray *params, void *user_ctx) +{ + CPUState *cpu; + + if (!params->len) { + gdb_put_packet("E22"); + return; + } + + if (gdb_get_cmd_param(params, 0)->thread_id.kind == GDB_READ_THREAD_ERR) { + gdb_put_packet("E22"); + return; + } + + cpu = gdb_get_cpu(gdb_get_cmd_param(params, 0)->thread_id.pid, + gdb_get_cmd_param(params, 0)->thread_id.tid); + if (!cpu) { + gdb_put_packet("E22"); + return; + } + + gdb_put_packet("OK"); +} + +static void handle_continue(GArray *params, void *user_ctx) +{ + if (params->len) { + gdb_set_cpu_pc(gdb_get_cmd_param(params, 0)->val_ull); + } + + gdbserver_state.signal = 0; + gdb_continue(); +} + +static void handle_cont_with_sig(GArray *params, void *user_ctx) +{ + unsigned long signal = 0; + + /* + * Note: C sig;[addr] is currently unsupported and we simply + * omit the addr parameter + */ + if (params->len) { + signal = gdb_get_cmd_param(params, 0)->val_ul; + } + + gdbserver_state.signal = gdb_signal_to_target(signal); + if (gdbserver_state.signal == -1) { + gdbserver_state.signal = 0; + } + gdb_continue(); +} + +static void handle_set_thread(GArray *params, void *user_ctx) +{ + uint32_t pid, tid; + CPUState *cpu; + + if (params->len != 2) { + gdb_put_packet("E22"); + return; + } + + if (gdb_get_cmd_param(params, 1)->thread_id.kind == GDB_READ_THREAD_ERR) { + gdb_put_packet("E22"); + return; + } + + if (gdb_get_cmd_param(params, 1)->thread_id.kind != GDB_ONE_THREAD) { + gdb_put_packet("OK"); + return; + } + + pid = gdb_get_cmd_param(params, 1)->thread_id.pid; + tid = gdb_get_cmd_param(params, 1)->thread_id.tid; +#ifdef CONFIG_USER_ONLY + if (gdb_handle_set_thread_user(pid, tid)) { + return; + } +#endif + cpu = gdb_get_cpu(pid, tid); + if (!cpu) { + gdb_put_packet("E22"); + return; + } + + /* + * Note: This command is deprecated and modern gdb's will be using the + * vCont command instead. + */ + switch (gdb_get_cmd_param(params, 0)->opcode) { + case 'c': + gdbserver_state.c_cpu = cpu; + gdb_put_packet("OK"); + break; + case 'g': + gdbserver_state.g_cpu = cpu; + gdb_put_packet("OK"); + break; + default: + gdb_put_packet("E22"); + break; + } +} + +static void handle_insert_bp(GArray *params, void *user_ctx) +{ + int res; + + if (params->len != 3) { + gdb_put_packet("E22"); + return; + } + + res = gdb_breakpoint_insert(gdbserver_state.c_cpu, + gdb_get_cmd_param(params, 0)->val_ul, + gdb_get_cmd_param(params, 1)->val_ull, + gdb_get_cmd_param(params, 2)->val_ull); + if (res >= 0) { + gdb_put_packet("OK"); + return; + } else if (res == -ENOSYS) { + gdb_put_packet(""); + return; + } + + gdb_put_packet("E22"); +} + +static void handle_remove_bp(GArray *params, void *user_ctx) +{ + int res; + + if (params->len != 3) { + gdb_put_packet("E22"); + return; + } + + res = gdb_breakpoint_remove(gdbserver_state.c_cpu, + gdb_get_cmd_param(params, 0)->val_ul, + gdb_get_cmd_param(params, 1)->val_ull, + gdb_get_cmd_param(params, 2)->val_ull); + if (res >= 0) { + gdb_put_packet("OK"); + return; + } else if (res == -ENOSYS) { + gdb_put_packet(""); + return; + } + + gdb_put_packet("E22"); +} + +/* + * handle_set/get_reg + * + * Older gdb are really dumb, and don't use 'G/g' if 'P/p' is available. + * This works, but can be very slow. Anything new enough to understand + * XML also knows how to use this properly. However to use this we + * need to define a local XML file as well as be talking to a + * reasonably modern gdb. Responding with an empty packet will cause + * the remote gdb to fallback to older methods. + */ + +static void handle_set_reg(GArray *params, void *user_ctx) +{ + int reg_size; + + if (params->len != 2) { + gdb_put_packet("E22"); + return; + } + + reg_size = strlen(gdb_get_cmd_param(params, 1)->data) / 2; + gdb_hextomem(gdbserver_state.mem_buf, gdb_get_cmd_param(params, 1)->data, reg_size); + gdb_write_register(gdbserver_state.g_cpu, gdbserver_state.mem_buf->data, + gdb_get_cmd_param(params, 0)->val_ull); + gdb_put_packet("OK"); +} + +static void handle_get_reg(GArray *params, void *user_ctx) +{ + int reg_size; + + if (!params->len) { + gdb_put_packet("E14"); + return; + } + + reg_size = gdb_read_register(gdbserver_state.g_cpu, + gdbserver_state.mem_buf, + gdb_get_cmd_param(params, 0)->val_ull); + if (!reg_size) { + gdb_put_packet("E14"); + return; + } else { + g_byte_array_set_size(gdbserver_state.mem_buf, reg_size); + } + + gdb_memtohex(gdbserver_state.str_buf, + gdbserver_state.mem_buf->data, reg_size); + gdb_put_strbuf(); +} + +static void handle_write_mem(GArray *params, void *user_ctx) +{ + if (params->len != 3) { + gdb_put_packet("E22"); + return; + } + + /* gdb_hextomem() reads 2*len bytes */ + if (gdb_get_cmd_param(params, 1)->val_ull > + strlen(gdb_get_cmd_param(params, 2)->data) / 2) { + gdb_put_packet("E22"); + return; + } + + gdb_hextomem(gdbserver_state.mem_buf, gdb_get_cmd_param(params, 2)->data, + gdb_get_cmd_param(params, 1)->val_ull); + if (gdb_target_memory_rw_debug(gdbserver_state.g_cpu, + gdb_get_cmd_param(params, 0)->val_ull, + gdbserver_state.mem_buf->data, + gdbserver_state.mem_buf->len, true)) { + gdb_put_packet("E14"); + return; + } + + gdb_put_packet("OK"); +} + +static void handle_read_mem(GArray *params, void *user_ctx) +{ + if (params->len != 2) { + gdb_put_packet("E22"); + return; + } + + /* gdb_memtohex() doubles the required space */ + if (gdb_get_cmd_param(params, 1)->val_ull > MAX_PACKET_LENGTH / 2) { + gdb_put_packet("E22"); + return; + } + + g_byte_array_set_size(gdbserver_state.mem_buf, + gdb_get_cmd_param(params, 1)->val_ull); + + if (gdb_target_memory_rw_debug(gdbserver_state.g_cpu, + gdb_get_cmd_param(params, 0)->val_ull, + gdbserver_state.mem_buf->data, + gdbserver_state.mem_buf->len, false)) { + gdb_put_packet("E14"); + return; + } + + gdb_memtohex(gdbserver_state.str_buf, gdbserver_state.mem_buf->data, + gdbserver_state.mem_buf->len); + gdb_put_strbuf(); +} + +static void handle_write_all_regs(GArray *params, void *user_ctx) +{ + int reg_id; + size_t len; + uint8_t *registers; + int reg_size; + + if (!params->len) { + return; + } + + cpu_synchronize_state(gdbserver_state.g_cpu); + len = strlen(gdb_get_cmd_param(params, 0)->data) / 2; + gdb_hextomem(gdbserver_state.mem_buf, gdb_get_cmd_param(params, 0)->data, len); + registers = gdbserver_state.mem_buf->data; + for (reg_id = 0; + reg_id < gdbserver_state.g_cpu->gdb_num_g_regs && len > 0; + reg_id++) { + reg_size = gdb_write_register(gdbserver_state.g_cpu, registers, reg_id); + len -= reg_size; + registers += reg_size; + } + gdb_put_packet("OK"); +} + +static void handle_read_all_regs(GArray *params, void *user_ctx) +{ + int reg_id; + size_t len; + + cpu_synchronize_state(gdbserver_state.g_cpu); + g_byte_array_set_size(gdbserver_state.mem_buf, 0); + len = 0; + for (reg_id = 0; reg_id < gdbserver_state.g_cpu->gdb_num_g_regs; reg_id++) { + len += gdb_read_register(gdbserver_state.g_cpu, + gdbserver_state.mem_buf, + reg_id); + g_assert(len == gdbserver_state.mem_buf->len); + } + + gdb_memtohex(gdbserver_state.str_buf, gdbserver_state.mem_buf->data, len); + gdb_put_strbuf(); +} + + +static void handle_step(GArray *params, void *user_ctx) +{ + if (params->len) { + gdb_set_cpu_pc(gdb_get_cmd_param(params, 0)->val_ull); + } + + cpu_single_step(gdbserver_state.c_cpu, gdbserver_state.sstep_flags); + gdb_continue(); +} + +static void handle_backward(GArray *params, void *user_ctx) +{ + if (!gdb_can_reverse()) { + gdb_put_packet("E22"); + } + if (params->len == 1) { + switch (gdb_get_cmd_param(params, 0)->opcode) { + case 's': + if (replay_reverse_step()) { + gdb_continue(); + } else { + gdb_put_packet("E14"); + } + return; + case 'c': + if (replay_reverse_continue()) { + gdb_continue(); + } else { + gdb_put_packet("E14"); + } + return; + } + } + + /* Default invalid command */ + gdb_put_packet(""); +} + +static void handle_v_cont_query(GArray *params, void *user_ctx) +{ + gdb_put_packet("vCont;c;C;s;S"); +} + +static void handle_v_cont(GArray *params, void *user_ctx) +{ + int res; + + if (!params->len) { + return; + } + + res = gdb_handle_vcont(gdb_get_cmd_param(params, 0)->data); + if ((res == -EINVAL) || (res == -ERANGE)) { + gdb_put_packet("E22"); + } else if (res) { + gdb_put_packet(""); + } +} + +static void handle_v_attach(GArray *params, void *user_ctx) +{ + GDBProcess *process; + CPUState *cpu; + + g_string_assign(gdbserver_state.str_buf, "E22"); + if (!params->len) { + goto cleanup; + } + + process = gdb_get_process(gdb_get_cmd_param(params, 0)->val_ul); + if (!process) { + goto cleanup; + } + + cpu = gdb_get_first_cpu_in_process(process); + if (!cpu) { + goto cleanup; + } + + process->attached = true; + gdbserver_state.g_cpu = cpu; + gdbserver_state.c_cpu = cpu; + + if (gdbserver_state.allow_stop_reply) { + g_string_printf(gdbserver_state.str_buf, "T%02xthread:", GDB_SIGNAL_TRAP); + gdb_append_thread_id(cpu, gdbserver_state.str_buf); + g_string_append_c(gdbserver_state.str_buf, ';'); + gdbserver_state.allow_stop_reply = false; +cleanup: + gdb_put_strbuf(); + } +} + +static void handle_v_kill(GArray *params, void *user_ctx) +{ + /* Kill the target */ + gdb_put_packet("OK"); + error_report("QEMU: Terminated via GDBstub"); + gdb_exit(0); + gdb_qemu_exit(0); +} + +static const GdbCmdParseEntry gdb_v_commands_table[] = { + /* Order is important if has same prefix */ + { + .handler = handle_v_cont_query, + .cmd = "Cont?", + .cmd_startswith = true + }, + { + .handler = handle_v_cont, + .cmd = "Cont", + .cmd_startswith = true, + .allow_stop_reply = true, + .schema = "s0" + }, + { + .handler = handle_v_attach, + .cmd = "Attach;", + .cmd_startswith = true, + .allow_stop_reply = true, + .schema = "l0" + }, + { + .handler = handle_v_kill, + .cmd = "Kill;", + .cmd_startswith = true + }, +#ifdef CONFIG_USER_ONLY + /* + * Host I/O Packets. See [1] for details. + * [1] https://sourceware.org/gdb/onlinedocs/gdb/Host-I_002fO-Packets.html + */ + { + .handler = gdb_handle_v_file_open, + .cmd = "File:open:", + .cmd_startswith = true, + .schema = "s,L,L0" + }, + { + .handler = gdb_handle_v_file_close, + .cmd = "File:close:", + .cmd_startswith = true, + .schema = "l0" + }, + { + .handler = gdb_handle_v_file_pread, + .cmd = "File:pread:", + .cmd_startswith = true, + .schema = "l,L,L0" + }, + { + .handler = gdb_handle_v_file_readlink, + .cmd = "File:readlink:", + .cmd_startswith = true, + .schema = "s0" + }, +#endif +}; + +static void handle_v_commands(GArray *params, void *user_ctx) +{ + if (!params->len) { + return; + } + + if (!process_string_cmd(gdb_get_cmd_param(params, 0)->data, + gdb_v_commands_table, + ARRAY_SIZE(gdb_v_commands_table))) { + gdb_put_packet(""); + } +} + +static void handle_query_qemu_sstepbits(GArray *params, void *user_ctx) +{ + g_string_printf(gdbserver_state.str_buf, "ENABLE=%x", SSTEP_ENABLE); + + if (gdbserver_state.supported_sstep_flags & SSTEP_NOIRQ) { + g_string_append_printf(gdbserver_state.str_buf, ",NOIRQ=%x", + SSTEP_NOIRQ); + } + + if (gdbserver_state.supported_sstep_flags & SSTEP_NOTIMER) { + g_string_append_printf(gdbserver_state.str_buf, ",NOTIMER=%x", + SSTEP_NOTIMER); + } + + gdb_put_strbuf(); +} + +static void handle_set_qemu_sstep(GArray *params, void *user_ctx) +{ + int new_sstep_flags; + + if (!params->len) { + return; + } + + new_sstep_flags = gdb_get_cmd_param(params, 0)->val_ul; + + if (new_sstep_flags & ~gdbserver_state.supported_sstep_flags) { + gdb_put_packet("E22"); + return; + } + + gdbserver_state.sstep_flags = new_sstep_flags; + gdb_put_packet("OK"); +} + +static void handle_query_qemu_sstep(GArray *params, void *user_ctx) +{ + g_string_printf(gdbserver_state.str_buf, "0x%x", + gdbserver_state.sstep_flags); + gdb_put_strbuf(); +} + +static void handle_query_curr_tid(GArray *params, void *user_ctx) +{ + CPUState *cpu; + GDBProcess *process; + + /* + * "Current thread" remains vague in the spec, so always return + * the first thread of the current process (gdb returns the + * first thread). + */ + process = gdb_get_cpu_process(gdbserver_state.g_cpu); + cpu = gdb_get_first_cpu_in_process(process); + g_string_assign(gdbserver_state.str_buf, "QC"); + gdb_append_thread_id(cpu, gdbserver_state.str_buf); + gdb_put_strbuf(); +} + +static void handle_query_threads(GArray *params, void *user_ctx) +{ + if (!gdbserver_state.query_cpu) { + gdb_put_packet("l"); + return; + } + + g_string_assign(gdbserver_state.str_buf, "m"); + gdb_append_thread_id(gdbserver_state.query_cpu, gdbserver_state.str_buf); + gdb_put_strbuf(); + gdbserver_state.query_cpu = gdb_next_attached_cpu(gdbserver_state.query_cpu); +} + +static void handle_query_gdb_server_version(GArray *params, void *user_ctx) +{ +#if defined(CONFIG_USER_ONLY) + g_string_printf(gdbserver_state.str_buf, "name:qemu-%s;version:%s;", + target_name(), QEMU_VERSION); +#else + g_string_printf(gdbserver_state.str_buf, "name:qemu-system-%s;version:%s;", + target_name(), QEMU_VERSION); +#endif + gdb_put_strbuf(); +} + +static void handle_query_first_threads(GArray *params, void *user_ctx) +{ + gdbserver_state.query_cpu = gdb_first_attached_cpu(); + handle_query_threads(params, user_ctx); +} + +static void handle_query_thread_extra(GArray *params, void *user_ctx) +{ + g_autoptr(GString) rs = g_string_new(NULL); + CPUState *cpu; + + if (!params->len || + gdb_get_cmd_param(params, 0)->thread_id.kind == GDB_READ_THREAD_ERR) { + gdb_put_packet("E22"); + return; + } + + cpu = gdb_get_cpu(gdb_get_cmd_param(params, 0)->thread_id.pid, + gdb_get_cmd_param(params, 0)->thread_id.tid); + if (!cpu) { + return; + } + + cpu_synchronize_state(cpu); + + if (gdbserver_state.multiprocess && (gdbserver_state.process_num > 1)) { + /* Print the CPU model and name in multiprocess mode */ + ObjectClass *oc = object_get_class(OBJECT(cpu)); + const char *cpu_model = object_class_get_name(oc); + const char *cpu_name = + object_get_canonical_path_component(OBJECT(cpu)); + g_string_printf(rs, "%s %s [%s]", cpu_model, cpu_name, + cpu->halted ? "halted " : "running"); + } else { + g_string_printf(rs, "CPU#%d [%s]", cpu->cpu_index, + cpu->halted ? "halted " : "running"); + } + trace_gdbstub_op_extra_info(rs->str); + gdb_memtohex(gdbserver_state.str_buf, (uint8_t *)rs->str, rs->len); + gdb_put_strbuf(); +} + + +static char **extra_query_flags; + +void gdb_extend_qsupported_features(char *qflags) +{ + if (!extra_query_flags) { + extra_query_flags = g_new0(char *, 2); + extra_query_flags[0] = g_strdup(qflags); + } else if (!g_strv_contains((const gchar * const *) extra_query_flags, + qflags)) { + int len = g_strv_length(extra_query_flags); + extra_query_flags = g_realloc_n(extra_query_flags, len + 2, + sizeof(char *)); + extra_query_flags[len] = g_strdup(qflags); + } +} + +static void handle_query_supported(GArray *params, void *user_ctx) +{ + g_string_printf(gdbserver_state.str_buf, "PacketSize=%x", MAX_PACKET_LENGTH); + if (gdb_get_core_xml_file(first_cpu)) { + g_string_append(gdbserver_state.str_buf, ";qXfer:features:read+"); + } + + if (gdb_can_reverse()) { + g_string_append(gdbserver_state.str_buf, + ";ReverseStep+;ReverseContinue+"); + } + +#if defined(CONFIG_USER_ONLY) +#if defined(CONFIG_LINUX) + if (get_task_state(gdbserver_state.c_cpu)) { + g_string_append(gdbserver_state.str_buf, ";qXfer:auxv:read+"); + } + g_string_append(gdbserver_state.str_buf, ";QCatchSyscalls+"); + + g_string_append(gdbserver_state.str_buf, ";qXfer:siginfo:read+"); +#endif + g_string_append(gdbserver_state.str_buf, ";qXfer:exec-file:read+"); +#endif + + if (params->len) { + const char *gdb_supported = gdb_get_cmd_param(params, 0)->data; + + if (strstr(gdb_supported, "multiprocess+")) { + gdbserver_state.multiprocess = true; + } +#if defined(CONFIG_USER_ONLY) + gdb_handle_query_supported_user(gdb_supported); +#endif + } + + g_string_append(gdbserver_state.str_buf, ";vContSupported+;multiprocess+"); + + if (extra_query_flags) { + int extras = g_strv_length(extra_query_flags); + for (int i = 0; i < extras; i++) { + g_string_append(gdbserver_state.str_buf, extra_query_flags[i]); + } + } + + gdb_put_strbuf(); +} + +static void handle_query_xfer_features(GArray *params, void *user_ctx) +{ + GDBProcess *process; + unsigned long len, total_len, addr; + const char *xml; + const char *p; + + if (params->len < 3) { + gdb_put_packet("E22"); + return; + } + + process = gdb_get_cpu_process(gdbserver_state.g_cpu); + if (!gdb_get_core_xml_file(gdbserver_state.g_cpu)) { + gdb_put_packet(""); + return; + } + + p = gdb_get_cmd_param(params, 0)->data; + xml = get_feature_xml(p, &p, process); + if (!xml) { + gdb_put_packet("E00"); + return; + } + + addr = gdb_get_cmd_param(params, 1)->val_ul; + len = gdb_get_cmd_param(params, 2)->val_ul; + total_len = strlen(xml); + if (addr > total_len) { + gdb_put_packet("E00"); + return; + } + + if (len > (MAX_PACKET_LENGTH - 5) / 2) { + len = (MAX_PACKET_LENGTH - 5) / 2; + } + + if (len < total_len - addr) { + g_string_assign(gdbserver_state.str_buf, "m"); + gdb_memtox(gdbserver_state.str_buf, xml + addr, len); + } else { + g_string_assign(gdbserver_state.str_buf, "l"); + gdb_memtox(gdbserver_state.str_buf, xml + addr, total_len - addr); + } + + gdb_put_packet_binary(gdbserver_state.str_buf->str, + gdbserver_state.str_buf->len, true); +} + +static void handle_query_qemu_supported(GArray *params, void *user_ctx) +{ + g_string_printf(gdbserver_state.str_buf, "sstepbits;sstep"); +#ifndef CONFIG_USER_ONLY + g_string_append(gdbserver_state.str_buf, ";PhyMemMode"); +#endif + gdb_put_strbuf(); +} + +static const GdbCmdParseEntry gdb_gen_query_set_common_table[] = { + /* Order is important if has same prefix */ + { + .handler = handle_query_qemu_sstepbits, + .cmd = "qemu.sstepbits", + }, + { + .handler = handle_query_qemu_sstep, + .cmd = "qemu.sstep", + }, + { + .handler = handle_set_qemu_sstep, + .cmd = "qemu.sstep=", + .cmd_startswith = true, + .schema = "l0" + }, +}; + +/** + * extend_table() - extend one of the command tables + * @table: the command table to extend (or NULL) + * @extensions: a list of GdbCmdParseEntry pointers + * + * The entries themselves should be pointers to static const + * GdbCmdParseEntry entries. If the entry is already in the table we + * skip adding it again. + * + * Returns (a potentially freshly allocated) GPtrArray of GdbCmdParseEntry + */ +static GPtrArray *extend_table(GPtrArray *table, GPtrArray *extensions) +{ + if (!table) { + table = g_ptr_array_new(); + } + + for (int i = 0; i < extensions->len; i++) { + gpointer entry = g_ptr_array_index(extensions, i); + if (!g_ptr_array_find(table, entry, NULL)) { + g_ptr_array_add(table, entry); + } + } + + return table; +} + +/** + * process_extended_table() - run through an extended command table + * @table: the command table to check + * @data: parameters + * + * returns true if the command was found and executed + */ +static bool process_extended_table(GPtrArray *table, const char *data) +{ + for (int i = 0; i < table->len; i++) { + const GdbCmdParseEntry *entry = g_ptr_array_index(table, i); + if (process_string_cmd(data, entry, 1)) { + return true; + } + } + return false; +} + + +/* Ptr to GdbCmdParseEntry */ +static GPtrArray *extended_query_table; + +void gdb_extend_query_table(GPtrArray *new_queries) +{ + extended_query_table = extend_table(extended_query_table, new_queries); +} + +static const GdbCmdParseEntry gdb_gen_query_table[] = { + { + .handler = handle_query_curr_tid, + .cmd = "C", + }, + { + .handler = handle_query_threads, + .cmd = "sThreadInfo", + }, + { + .handler = handle_query_gdb_server_version, + .cmd = "GDBServerVersion", + }, + { + .handler = handle_query_first_threads, + .cmd = "fThreadInfo", + }, + { + .handler = handle_query_thread_extra, + .cmd = "ThreadExtraInfo,", + .cmd_startswith = true, + .schema = "t0" + }, +#ifdef CONFIG_USER_ONLY + { + .handler = gdb_handle_query_offsets, + .cmd = "Offsets", + }, +#else + { + .handler = gdb_handle_query_rcmd, + .cmd = "Rcmd,", + .cmd_startswith = true, + .schema = "s0" + }, +#endif + { + .handler = handle_query_supported, + .cmd = "Supported:", + .cmd_startswith = true, + .schema = "s0" + }, + { + .handler = handle_query_supported, + .cmd = "Supported", + .schema = "s0" + }, + { + .handler = handle_query_xfer_features, + .cmd = "Xfer:features:read:", + .cmd_startswith = true, + .schema = "s:l,l0" + }, +#if defined(CONFIG_USER_ONLY) +#if defined(CONFIG_LINUX) + { + .handler = gdb_handle_query_xfer_auxv, + .cmd = "Xfer:auxv:read::", + .cmd_startswith = true, + .schema = "l,l0" + }, + { + .handler = gdb_handle_query_xfer_siginfo, + .cmd = "Xfer:siginfo:read::", + .cmd_startswith = true, + .schema = "l,l0" + }, +#endif + { + .handler = gdb_handle_query_xfer_exec_file, + .cmd = "Xfer:exec-file:read:", + .cmd_startswith = true, + .schema = "l:l,l0" + }, +#endif + { + .handler = gdb_handle_query_attached, + .cmd = "Attached:", + .cmd_startswith = true + }, + { + .handler = gdb_handle_query_attached, + .cmd = "Attached", + }, + { + .handler = handle_query_qemu_supported, + .cmd = "qemu.Supported", + }, +#ifndef CONFIG_USER_ONLY + { + .handler = gdb_handle_query_qemu_phy_mem_mode, + .cmd = "qemu.PhyMemMode", + }, +#endif +}; + +/* Ptr to GdbCmdParseEntry */ +static GPtrArray *extended_set_table; + +void gdb_extend_set_table(GPtrArray *new_set) +{ + extended_set_table = extend_table(extended_set_table, new_set); +} + +static const GdbCmdParseEntry gdb_gen_set_table[] = { + /* Order is important if has same prefix */ + { + .handler = handle_set_qemu_sstep, + .cmd = "qemu.sstep:", + .cmd_startswith = true, + .schema = "l0" + }, +#ifndef CONFIG_USER_ONLY + { + .handler = gdb_handle_set_qemu_phy_mem_mode, + .cmd = "qemu.PhyMemMode:", + .cmd_startswith = true, + .schema = "l0" + }, +#endif +#if defined(CONFIG_USER_ONLY) + { + .handler = gdb_handle_set_catch_syscalls, + .cmd = "CatchSyscalls:", + .cmd_startswith = true, + .schema = "s0", + }, +#endif +}; + +static void handle_gen_query(GArray *params, void *user_ctx) +{ + const char *data; + + if (!params->len) { + return; + } + + data = gdb_get_cmd_param(params, 0)->data; + + if (process_string_cmd(data, + gdb_gen_query_set_common_table, + ARRAY_SIZE(gdb_gen_query_set_common_table))) { + return; + } + + if (process_string_cmd(data, + gdb_gen_query_table, + ARRAY_SIZE(gdb_gen_query_table))) { + return; + } + + if (extended_query_table && + process_extended_table(extended_query_table, data)) { + return; + } + + /* Can't handle query, return Empty response. */ + gdb_put_packet(""); +} + +static void handle_gen_set(GArray *params, void *user_ctx) +{ + const char *data; + + if (!params->len) { + return; + } + + data = gdb_get_cmd_param(params, 0)->data; + + if (process_string_cmd(data, + gdb_gen_query_set_common_table, + ARRAY_SIZE(gdb_gen_query_set_common_table))) { + return; + } + + if (process_string_cmd(data, + gdb_gen_set_table, + ARRAY_SIZE(gdb_gen_set_table))) { + return; + } + + if (extended_set_table && + process_extended_table(extended_set_table, data)) { + return; + } + + /* Can't handle set, return Empty response. */ + gdb_put_packet(""); +} + +static void handle_target_halt(GArray *params, void *user_ctx) +{ + if (gdbserver_state.allow_stop_reply) { + g_string_printf(gdbserver_state.str_buf, "T%02xthread:", GDB_SIGNAL_TRAP); + gdb_append_thread_id(gdbserver_state.c_cpu, gdbserver_state.str_buf); + g_string_append_c(gdbserver_state.str_buf, ';'); + gdb_put_strbuf(); + gdbserver_state.allow_stop_reply = false; + } + /* + * Remove all the breakpoints when this query is issued, + * because gdb is doing an initial connect and the state + * should be cleaned up. + */ + gdb_breakpoint_remove_all(gdbserver_state.c_cpu); +} + +static int gdb_handle_packet(const char *line_buf) +{ + const GdbCmdParseEntry *cmd_parser = NULL; + + trace_gdbstub_io_command(line_buf); + + switch (line_buf[0]) { + case '!': + gdb_put_packet("OK"); + break; + case '?': + { + static const GdbCmdParseEntry target_halted_cmd_desc = { + .handler = handle_target_halt, + .cmd = "?", + .cmd_startswith = true, + .allow_stop_reply = true, + }; + cmd_parser = &target_halted_cmd_desc; + } + break; + case 'c': + { + static const GdbCmdParseEntry continue_cmd_desc = { + .handler = handle_continue, + .cmd = "c", + .cmd_startswith = true, + .allow_stop_reply = true, + .schema = "L0" + }; + cmd_parser = &continue_cmd_desc; + } + break; + case 'C': + { + static const GdbCmdParseEntry cont_with_sig_cmd_desc = { + .handler = handle_cont_with_sig, + .cmd = "C", + .cmd_startswith = true, + .allow_stop_reply = true, + .schema = "l0" + }; + cmd_parser = &cont_with_sig_cmd_desc; + } + break; + case 'v': + { + static const GdbCmdParseEntry v_cmd_desc = { + .handler = handle_v_commands, + .cmd = "v", + .cmd_startswith = true, + .schema = "s0" + }; + cmd_parser = &v_cmd_desc; + } + break; + case 'k': + /* Kill the target */ + error_report("QEMU: Terminated via GDBstub"); + gdb_exit(0); + gdb_qemu_exit(0); + break; + case 'D': + { + static const GdbCmdParseEntry detach_cmd_desc = { + .handler = handle_detach, + .cmd = "D", + .cmd_startswith = true, + .schema = "?.l0" + }; + cmd_parser = &detach_cmd_desc; + } + break; + case 's': + { + static const GdbCmdParseEntry step_cmd_desc = { + .handler = handle_step, + .cmd = "s", + .cmd_startswith = true, + .allow_stop_reply = true, + .schema = "L0" + }; + cmd_parser = &step_cmd_desc; + } + break; + case 'b': + { + static const GdbCmdParseEntry backward_cmd_desc = { + .handler = handle_backward, + .cmd = "b", + .cmd_startswith = true, + .allow_stop_reply = true, + .schema = "o0" + }; + cmd_parser = &backward_cmd_desc; + } + break; + case 'F': + { + static const GdbCmdParseEntry file_io_cmd_desc = { + .handler = gdb_handle_file_io, + .cmd = "F", + .cmd_startswith = true, + .schema = "L,L,o0" + }; + cmd_parser = &file_io_cmd_desc; + } + break; + case 'g': + { + static const GdbCmdParseEntry read_all_regs_cmd_desc = { + .handler = handle_read_all_regs, + .cmd = "g", + .cmd_startswith = true + }; + cmd_parser = &read_all_regs_cmd_desc; + } + break; + case 'G': + { + static const GdbCmdParseEntry write_all_regs_cmd_desc = { + .handler = handle_write_all_regs, + .cmd = "G", + .cmd_startswith = true, + .schema = "s0" + }; + cmd_parser = &write_all_regs_cmd_desc; + } + break; + case 'm': + { + static const GdbCmdParseEntry read_mem_cmd_desc = { + .handler = handle_read_mem, + .cmd = "m", + .cmd_startswith = true, + .schema = "L,L0" + }; + cmd_parser = &read_mem_cmd_desc; + } + break; + case 'M': + { + static const GdbCmdParseEntry write_mem_cmd_desc = { + .handler = handle_write_mem, + .cmd = "M", + .cmd_startswith = true, + .schema = "L,L:s0" + }; + cmd_parser = &write_mem_cmd_desc; + } + break; + case 'p': + { + static const GdbCmdParseEntry get_reg_cmd_desc = { + .handler = handle_get_reg, + .cmd = "p", + .cmd_startswith = true, + .schema = "L0" + }; + cmd_parser = &get_reg_cmd_desc; + } + break; + case 'P': + { + static const GdbCmdParseEntry set_reg_cmd_desc = { + .handler = handle_set_reg, + .cmd = "P", + .cmd_startswith = true, + .schema = "L?s0" + }; + cmd_parser = &set_reg_cmd_desc; + } + break; + case 'Z': + { + static const GdbCmdParseEntry insert_bp_cmd_desc = { + .handler = handle_insert_bp, + .cmd = "Z", + .cmd_startswith = true, + .schema = "l?L?L0" + }; + cmd_parser = &insert_bp_cmd_desc; + } + break; + case 'z': + { + static const GdbCmdParseEntry remove_bp_cmd_desc = { + .handler = handle_remove_bp, + .cmd = "z", + .cmd_startswith = true, + .schema = "l?L?L0" + }; + cmd_parser = &remove_bp_cmd_desc; + } + break; + case 'H': + { + static const GdbCmdParseEntry set_thread_cmd_desc = { + .handler = handle_set_thread, + .cmd = "H", + .cmd_startswith = true, + .schema = "o.t0" + }; + cmd_parser = &set_thread_cmd_desc; + } + break; + case 'T': + { + static const GdbCmdParseEntry thread_alive_cmd_desc = { + .handler = handle_thread_alive, + .cmd = "T", + .cmd_startswith = true, + .schema = "t0" + }; + cmd_parser = &thread_alive_cmd_desc; + } + break; + case 'q': + { + static const GdbCmdParseEntry gen_query_cmd_desc = { + .handler = handle_gen_query, + .cmd = "q", + .cmd_startswith = true, + .schema = "s0" + }; + cmd_parser = &gen_query_cmd_desc; + } + break; + case 'Q': + { + static const GdbCmdParseEntry gen_set_cmd_desc = { + .handler = handle_gen_set, + .cmd = "Q", + .cmd_startswith = true, + .schema = "s0" + }; + cmd_parser = &gen_set_cmd_desc; + } + break; + default: + /* put empty packet */ + gdb_put_packet(""); + break; + } + + if (cmd_parser) { + run_cmd_parser(line_buf, cmd_parser); + } + + return RS_IDLE; +} + +void gdb_set_stop_cpu(CPUState *cpu) +{ + GDBProcess *p = gdb_get_cpu_process(cpu); + + if (!p->attached) { + /* + * Having a stop CPU corresponding to a process that is not attached + * confuses GDB. So we ignore the request. + */ + return; + } + + gdbserver_state.c_cpu = cpu; + gdbserver_state.g_cpu = cpu; +} + +void gdb_read_byte(uint8_t ch) +{ + uint8_t reply; + + gdbserver_state.allow_stop_reply = false; +#ifndef CONFIG_USER_ONLY + if (gdbserver_state.last_packet->len) { + /* Waiting for a response to the last packet. If we see the start + of a new command then abandon the previous response. */ + if (ch == '-') { + trace_gdbstub_err_got_nack(); + gdb_put_buffer(gdbserver_state.last_packet->data, + gdbserver_state.last_packet->len); + } else if (ch == '+') { + trace_gdbstub_io_got_ack(); + } else { + trace_gdbstub_io_got_unexpected(ch); + } + + if (ch == '+' || ch == '$') { + g_byte_array_set_size(gdbserver_state.last_packet, 0); + } + if (ch != '$') + return; + } + if (runstate_is_running()) { + /* + * When the CPU is running, we cannot do anything except stop + * it when receiving a char. This is expected on a Ctrl-C in the + * gdb client. Because we are in all-stop mode, gdb sends a + * 0x03 byte which is not a usual packet, so we handle it specially + * here, but it does expect a stop reply. + */ + if (ch != 0x03) { + trace_gdbstub_err_unexpected_runpkt(ch); + } else { + gdbserver_state.allow_stop_reply = true; + } + vm_stop(RUN_STATE_PAUSED); + } else +#endif + { + switch(gdbserver_state.state) { + case RS_IDLE: + if (ch == '$') { + /* start of command packet */ + gdbserver_state.line_buf_index = 0; + gdbserver_state.line_sum = 0; + gdbserver_state.state = RS_GETLINE; + } else if (ch == '+') { + /* + * do nothing, gdb may preemptively send out ACKs on + * initial connection + */ + } else { + trace_gdbstub_err_garbage(ch); + } + break; + case RS_GETLINE: + if (ch == '}') { + /* start escape sequence */ + gdbserver_state.state = RS_GETLINE_ESC; + gdbserver_state.line_sum += ch; + } else if (ch == '*') { + /* start run length encoding sequence */ + gdbserver_state.state = RS_GETLINE_RLE; + gdbserver_state.line_sum += ch; + } else if (ch == '#') { + /* end of command, start of checksum*/ + gdbserver_state.state = RS_CHKSUM1; + } else if (gdbserver_state.line_buf_index >= sizeof(gdbserver_state.line_buf) - 1) { + trace_gdbstub_err_overrun(); + gdbserver_state.state = RS_IDLE; + } else { + /* unescaped command character */ + gdbserver_state.line_buf[gdbserver_state.line_buf_index++] = ch; + gdbserver_state.line_sum += ch; + } + break; + case RS_GETLINE_ESC: + if (ch == '#') { + /* unexpected end of command in escape sequence */ + gdbserver_state.state = RS_CHKSUM1; + } else if (gdbserver_state.line_buf_index >= sizeof(gdbserver_state.line_buf) - 1) { + /* command buffer overrun */ + trace_gdbstub_err_overrun(); + gdbserver_state.state = RS_IDLE; + } else { + /* parse escaped character and leave escape state */ + gdbserver_state.line_buf[gdbserver_state.line_buf_index++] = ch ^ 0x20; + gdbserver_state.line_sum += ch; + gdbserver_state.state = RS_GETLINE; + } + break; + case RS_GETLINE_RLE: + /* + * Run-length encoding is explained in "Debugging with GDB / + * Appendix E GDB Remote Serial Protocol / Overview". + */ + if (ch < ' ' || ch == '#' || ch == '$' || ch > 126) { + /* invalid RLE count encoding */ + trace_gdbstub_err_invalid_repeat(ch); + gdbserver_state.state = RS_GETLINE; + } else { + /* decode repeat length */ + int repeat = ch - ' ' + 3; + if (gdbserver_state.line_buf_index + repeat >= sizeof(gdbserver_state.line_buf) - 1) { + /* that many repeats would overrun the command buffer */ + trace_gdbstub_err_overrun(); + gdbserver_state.state = RS_IDLE; + } else if (gdbserver_state.line_buf_index < 1) { + /* got a repeat but we have nothing to repeat */ + trace_gdbstub_err_invalid_rle(); + gdbserver_state.state = RS_GETLINE; + } else { + /* repeat the last character */ + memset(gdbserver_state.line_buf + gdbserver_state.line_buf_index, + gdbserver_state.line_buf[gdbserver_state.line_buf_index - 1], repeat); + gdbserver_state.line_buf_index += repeat; + gdbserver_state.line_sum += ch; + gdbserver_state.state = RS_GETLINE; + } + } + break; + case RS_CHKSUM1: + /* get high hex digit of checksum */ + if (!isxdigit(ch)) { + trace_gdbstub_err_checksum_invalid(ch); + gdbserver_state.state = RS_GETLINE; + break; + } + gdbserver_state.line_buf[gdbserver_state.line_buf_index] = '\0'; + gdbserver_state.line_csum = fromhex(ch) << 4; + gdbserver_state.state = RS_CHKSUM2; + break; + case RS_CHKSUM2: + /* get low hex digit of checksum */ + if (!isxdigit(ch)) { + trace_gdbstub_err_checksum_invalid(ch); + gdbserver_state.state = RS_GETLINE; + break; + } + gdbserver_state.line_csum |= fromhex(ch); + + if (gdbserver_state.line_csum != (gdbserver_state.line_sum & 0xff)) { + trace_gdbstub_err_checksum_incorrect(gdbserver_state.line_sum, gdbserver_state.line_csum); + /* send NAK reply */ + reply = '-'; + gdb_put_buffer(&reply, 1); + gdbserver_state.state = RS_IDLE; + } else { + /* send ACK reply */ + reply = '+'; + gdb_put_buffer(&reply, 1); + gdbserver_state.state = gdb_handle_packet(gdbserver_state.line_buf); + } + break; + default: + abort(); + } + } +} + +/* + * Create the process that will contain all the "orphan" CPUs (that are not + * part of a CPU cluster). Note that if this process contains no CPUs, it won't + * be attachable and thus will be invisible to the user. + */ +void gdb_create_default_process(GDBState *s) +{ + GDBProcess *process; + int pid; + +#ifdef CONFIG_USER_ONLY + assert(gdbserver_state.process_num == 0); + pid = getpid(); +#else + if (gdbserver_state.process_num) { + pid = s->processes[s->process_num - 1].pid; + } else { + pid = 0; + } + /* We need an available PID slot for this process */ + assert(pid < UINT32_MAX); + pid++; +#endif + + s->processes = g_renew(GDBProcess, s->processes, ++s->process_num); + process = &s->processes[s->process_num - 1]; + process->pid = pid; + process->attached = false; + process->target_xml = NULL; +} + diff --git a/gdbstub/internals.h b/gdbstub/internals.h new file mode 100644 index 0000000..92466b2 --- /dev/null +++ b/gdbstub/internals.h @@ -0,0 +1,240 @@ +/* + * gdbstub internals + * + * Copyright (c) 2022 Linaro Ltd + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef GDBSTUB_INTERNALS_H +#define GDBSTUB_INTERNALS_H + +#include "exec/cpu-common.h" + +/* + * Most "large" transfers (e.g. memory reads, feature XML + * transfer) have mechanisms in the gdb protocol for splitting + * them. However, register values in particular cannot currently + * be split. This packet size must therefore be at least big enough + * for the worst-case register size. Currently that is Arm SME + * ZA storage with a 256x256 byte value. We also must account + * for the conversion from raw data to hex in gdb_memtohex(), + * which writes 2 * size bytes, and for other protocol overhead + * including command, register number and checksum which add + * another 4 bytes of overhead. However, to be consistent with + * the changes made in gdbserver to address this same requirement, + * we add a total of 32 bytes to account for protocol overhead + * (unclear why specifically 32 bytes), bringing the value of + * MAX_PACKET_LENGTH to 2 * 256 * 256 + 32 = 131104. + * + * The commit making this change for gdbserver can be found here: + * https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h= + * b816042e88583f280ad186ff124ab84d31fb592b + */ +#define MAX_PACKET_LENGTH 131104 + +/* + * Shared structures and definitions + */ + +enum { + GDB_SIGNAL_0 = 0, + GDB_SIGNAL_INT = 2, + GDB_SIGNAL_QUIT = 3, + GDB_SIGNAL_TRAP = 5, + GDB_SIGNAL_ABRT = 6, + GDB_SIGNAL_ALRM = 14, + GDB_SIGNAL_STOP = 17, + GDB_SIGNAL_IO = 23, + GDB_SIGNAL_XCPU = 24, + GDB_SIGNAL_UNKNOWN = 143 +}; + +typedef struct GDBProcess { + uint32_t pid; + bool attached; + char *target_xml; +} GDBProcess; + +enum RSState { + RS_INACTIVE, + RS_IDLE, + RS_GETLINE, + RS_GETLINE_ESC, + RS_GETLINE_RLE, + RS_CHKSUM1, + RS_CHKSUM2, +}; + +typedef struct GDBState { + bool init; /* have we been initialised? */ + CPUState *c_cpu; /* current CPU for step/continue ops */ + CPUState *g_cpu; /* current CPU for other ops */ + CPUState *query_cpu; /* for q{f|s}ThreadInfo */ + enum RSState state; /* parsing state */ + char line_buf[MAX_PACKET_LENGTH]; + int line_buf_index; + int line_sum; /* running checksum */ + int line_csum; /* checksum at the end of the packet */ + GByteArray *last_packet; + int signal; + bool multiprocess; + GDBProcess *processes; + int process_num; + GString *str_buf; + GByteArray *mem_buf; + int sstep_flags; + int supported_sstep_flags; + /* + * Whether we are allowed to send a stop reply packet at this moment. + * Must be set off after sending the stop reply itself. + */ + bool allow_stop_reply; +} GDBState; + +/* lives in main gdbstub.c */ +extern GDBState gdbserver_state; + +/* + * Inline utility function, convert from int to hex and back + */ + +static inline int fromhex(int v) +{ + if (v >= '0' && v <= '9') { + return v - '0'; + } else if (v >= 'A' && v <= 'F') { + return v - 'A' + 10; + } else if (v >= 'a' && v <= 'f') { + return v - 'a' + 10; + } else { + return 0; + } +} + +static inline int tohex(int v) +{ + if (v < 10) { + return v + '0'; + } else { + return v - 10 + 'a'; + } +} + +/* + * Connection helpers for both system and user backends + */ + +void gdb_put_strbuf(void); +int gdb_put_packet_binary(const char *buf, int len, bool dump); +void gdb_memtohex(GString *buf, const uint8_t *mem, int len); +void gdb_memtox(GString *buf, const char *mem, int len); +void gdb_read_byte(uint8_t ch); + +/* + * Packet acknowledgement - we handle this slightly differently + * between user and system mode, mainly to deal with the differences + * between the flexible chardev and the direct fd approaches. + * + * We currently don't support a negotiated QStartNoAckMode + */ + +/** + * gdb_got_immediate_ack() - check ok to continue + * + * Returns true to continue, false to re-transmit for user only, the + * system stub always returns true. + */ +bool gdb_got_immediate_ack(void); +/* utility helpers */ +GDBProcess *gdb_get_process(uint32_t pid); +CPUState *gdb_get_first_cpu_in_process(GDBProcess *process); +CPUState *gdb_first_attached_cpu(void); +void gdb_append_thread_id(CPUState *cpu, GString *buf); +int gdb_get_cpu_index(CPUState *cpu); +unsigned int gdb_get_max_cpus(void); /* both */ +bool gdb_can_reverse(void); /* system emulation, stub for user */ +int gdb_target_sigtrap(void); /* user */ + +void gdb_create_default_process(GDBState *s); + +/* signal mapping, common for system, specialised for user-mode */ +int gdb_signal_to_target(int sig); +int gdb_target_signal_to_gdb(int sig); + +int gdb_get_char(void); /* user only */ + +/** + * gdb_continue() - handle continue in mode specific way. + */ +void gdb_continue(void); + +/** + * gdb_continue_partial() - handle partial continue in mode specific way. + */ +int gdb_continue_partial(char *newstates); + +/* + * Helpers with separate system and user implementations + */ +void gdb_put_buffer(const uint8_t *buf, int len); + +/* + * Command handlers - either specialised or system or user only + */ +void gdb_init_gdbserver_state(void); + +void gdb_handle_query_rcmd(GArray *params, void *ctx); /* system */ +void gdb_handle_query_offsets(GArray *params, void *user_ctx); /* user */ +void gdb_handle_query_xfer_auxv(GArray *params, void *user_ctx); /*user */ +void gdb_handle_query_xfer_siginfo(GArray *params, void *user_ctx); /*user */ +void gdb_handle_v_file_open(GArray *params, void *user_ctx); /* user */ +void gdb_handle_v_file_close(GArray *params, void *user_ctx); /* user */ +void gdb_handle_v_file_pread(GArray *params, void *user_ctx); /* user */ +void gdb_handle_v_file_readlink(GArray *params, void *user_ctx); /* user */ +void gdb_handle_query_xfer_exec_file(GArray *params, void *user_ctx); /* user */ +void gdb_handle_set_catch_syscalls(GArray *params, void *user_ctx); /* user */ +void gdb_handle_query_supported_user(const char *gdb_supported); /* user */ +bool gdb_handle_set_thread_user(uint32_t pid, uint32_t tid); /* user */ +bool gdb_handle_detach_user(uint32_t pid); /* user */ + +void gdb_handle_query_attached(GArray *params, void *ctx); /* both */ + +/* system only */ +void gdb_handle_query_qemu_phy_mem_mode(GArray *params, void *ctx); +void gdb_handle_set_qemu_phy_mem_mode(GArray *params, void *ctx); + +/* sycall handling */ +void gdb_handle_file_io(GArray *params, void *user_ctx); +bool gdb_handled_syscall(void); +void gdb_disable_syscalls(void); +void gdb_syscall_reset(void); + +/* user/system specific syscall handling */ +void gdb_syscall_handling(const char *syscall_packet); + +/* + * Break/Watch point support - there is an implementation for system + * and user mode. + */ +bool gdb_supports_guest_debug(void); +int gdb_breakpoint_insert(CPUState *cs, int type, vaddr addr, vaddr len); +int gdb_breakpoint_remove(CPUState *cs, int type, vaddr addr, vaddr len); +void gdb_breakpoint_remove_all(CPUState *cs); + +/** + * gdb_target_memory_rw_debug() - handle debug access to memory + * @cs: CPUState + * @addr: nominal address, could be an entire physical address + * @buf: data + * @len: length of access + * @is_write: is it a write operation + * + * This function is specialised depending on the mode we are running + * in. For system guests we can switch the interpretation of the + * address to a physical address. + */ +int gdb_target_memory_rw_debug(CPUState *cs, hwaddr addr, + uint8_t *buf, int len, bool is_write); + +#endif /* GDBSTUB_INTERNALS_H */ diff --git a/gdbstub/meson.build b/gdbstub/meson.build new file mode 100644 index 0000000..15c666f --- /dev/null +++ b/gdbstub/meson.build @@ -0,0 +1,21 @@ +# +# The main gdbstub still relies on per-build definitions of various +# types. The bits pushed to system/user.c try to use guest agnostic +# types such as hwaddr. +# + +# We build two versions of gdbstub, one for each mode +user_ss.add(files( + 'gdbstub.c', + 'syscalls.c', + 'user.c' +)) + +system_ss.add(files( + 'gdbstub.c', + 'syscalls.c', + 'system.c' +)) + +# The user-target is specialised by the guest +specific_ss.add(when: 'CONFIG_USER_ONLY', if_true: files('user-target.c')) diff --git a/gdbstub/syscalls.c b/gdbstub/syscalls.c new file mode 100644 index 0000000..e855df2 --- /dev/null +++ b/gdbstub/syscalls.c @@ -0,0 +1,206 @@ +/* + * GDB Syscall Handling + * + * GDB can execute syscalls on the guests behalf, currently used by + * the various semihosting extensions. + * + * Copyright (c) 2003-2005 Fabrice Bellard + * Copyright (c) 2023 Linaro Ltd + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/error-report.h" +#include "semihosting/semihost.h" +#include "system/runstate.h" +#include "gdbstub/user.h" +#include "gdbstub/syscalls.h" +#include "gdbstub/commands.h" +#include "trace.h" +#include "internals.h" + +/* Syscall specific state */ +typedef struct { + char syscall_buf[256]; + gdb_syscall_complete_cb current_syscall_cb; +} GDBSyscallState; + +static GDBSyscallState gdbserver_syscall_state; + +/* + * Return true if there is a GDB currently connected to the stub + * and attached to a CPU + */ +static bool gdb_attached(void) +{ + return gdbserver_state.init && gdbserver_state.c_cpu; +} + +static enum { + GDB_SYS_UNKNOWN, + GDB_SYS_ENABLED, + GDB_SYS_DISABLED, +} gdb_syscall_mode; + +/* Decide if either remote gdb syscalls or native file IO should be used. */ +int use_gdb_syscalls(void) +{ + SemihostingTarget target = semihosting_get_target(); + if (target == SEMIHOSTING_TARGET_NATIVE) { + /* -semihosting-config target=native */ + return false; + } else if (target == SEMIHOSTING_TARGET_GDB) { + /* -semihosting-config target=gdb */ + return true; + } + + /* -semihosting-config target=auto */ + /* On the first call check if gdb is connected and remember. */ + if (gdb_syscall_mode == GDB_SYS_UNKNOWN) { + gdb_syscall_mode = gdb_attached() ? GDB_SYS_ENABLED : GDB_SYS_DISABLED; + } + return gdb_syscall_mode == GDB_SYS_ENABLED; +} + +/* called when the stub detaches */ +void gdb_disable_syscalls(void) +{ + gdb_syscall_mode = GDB_SYS_DISABLED; +} + +void gdb_syscall_reset(void) +{ + gdbserver_syscall_state.current_syscall_cb = NULL; +} + +bool gdb_handled_syscall(void) +{ + if (gdbserver_syscall_state.current_syscall_cb) { + gdb_put_packet(gdbserver_syscall_state.syscall_buf); + return true; + } + + return false; +} + +/* + * Send a gdb syscall request. + * This accepts limited printf-style format specifiers, specifically: + * %x - target_ulong argument printed in hex. + * %lx - 64-bit argument printed in hex. + * %s - string pointer (target_ulong) and length (int) pair. + */ +void gdb_do_syscall(gdb_syscall_complete_cb cb, const char *fmt, ...) +{ + char *p, *p_end; + va_list va; + + if (!gdb_attached()) { + return; + } + + gdbserver_syscall_state.current_syscall_cb = cb; + va_start(va, fmt); + + p = gdbserver_syscall_state.syscall_buf; + p_end = p + sizeof(gdbserver_syscall_state.syscall_buf); + *(p++) = 'F'; + while (*fmt) { + if (*fmt == '%') { + uint64_t i64; + uint32_t i32; + + fmt++; + switch (*fmt++) { + case 'x': + i32 = va_arg(va, uint32_t); + p += snprintf(p, p_end - p, "%" PRIx32, i32); + break; + case 'l': + if (*(fmt++) != 'x') { + goto bad_format; + } + i64 = va_arg(va, uint64_t); + p += snprintf(p, p_end - p, "%" PRIx64, i64); + break; + case 's': + i64 = va_arg(va, uint64_t); + i32 = va_arg(va, uint32_t); + p += snprintf(p, p_end - p, "%" PRIx64 "/%x" PRIx32, i64, i32); + break; + default: + bad_format: + error_report("gdbstub: Bad syscall format string '%s'", + fmt - 1); + break; + } + } else { + *(p++) = *(fmt++); + } + } + *p = 0; + + va_end(va); + gdb_syscall_handling(gdbserver_syscall_state.syscall_buf); +} + +/* + * GDB Command Handlers + */ + +void gdb_handle_file_io(GArray *params, void *user_ctx) +{ + if (params->len >= 1 && gdbserver_syscall_state.current_syscall_cb) { + uint64_t ret; + int err; + + ret = gdb_get_cmd_param(params, 0)->val_ull; + if (params->len >= 2) { + err = gdb_get_cmd_param(params, 1)->val_ull; + } else { + err = 0; + } + + /* Convert GDB error numbers back to host error numbers. */ +#define E(X) case GDB_E##X: err = E##X; break + switch (err) { + case 0: + break; + E(PERM); + E(NOENT); + E(INTR); + E(BADF); + E(ACCES); + E(FAULT); + E(BUSY); + E(EXIST); + E(NODEV); + E(NOTDIR); + E(ISDIR); + E(INVAL); + E(NFILE); + E(MFILE); + E(FBIG); + E(NOSPC); + E(SPIPE); + E(ROFS); + E(NAMETOOLONG); + default: + err = EINVAL; + break; + } +#undef E + + gdbserver_syscall_state.current_syscall_cb(gdbserver_state.c_cpu, + ret, err); + gdbserver_syscall_state.current_syscall_cb = NULL; + } + + if (params->len >= 3 && gdb_get_cmd_param(params, 2)->opcode == (uint8_t)'C') { + gdb_put_packet("T02"); + return; + } + + gdb_continue(); +} diff --git a/gdbstub/system.c b/gdbstub/system.c new file mode 100644 index 0000000..5be0d3c --- /dev/null +++ b/gdbstub/system.c @@ -0,0 +1,669 @@ +/* + * gdb server stub - system specific bits + * + * Debug integration depends on support from the individual + * accelerators so most of this involves calling the ops helpers. + * + * Copyright (c) 2003-2005 Fabrice Bellard + * Copyright (c) 2022 Linaro Ltd + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/error-report.h" +#include "qemu/cutils.h" +#include "exec/gdbstub.h" +#include "gdbstub/syscalls.h" +#include "gdbstub/commands.h" +#include "exec/hwaddr.h" +#include "exec/tb-flush.h" +#include "accel/accel-ops.h" +#include "accel/accel-cpu-ops.h" +#include "system/cpus.h" +#include "system/runstate.h" +#include "system/replay.h" +#include "system/tcg.h" +#include "hw/core/cpu.h" +#include "hw/cpu/cluster.h" +#include "hw/boards.h" +#include "chardev/char.h" +#include "chardev/char-fe.h" +#include "monitor/monitor.h" +#include "trace.h" +#include "internals.h" + +/* System emulation specific state */ +typedef struct { + CharBackend chr; + Chardev *mon_chr; +} GDBSystemState; + +GDBSystemState gdbserver_system_state; + +static void reset_gdbserver_state(void) +{ + g_free(gdbserver_state.processes); + gdbserver_state.processes = NULL; + gdbserver_state.process_num = 0; + gdbserver_state.allow_stop_reply = false; +} + +/* + * Return the GDB index for a given vCPU state. + * + * In system mode GDB numbers CPUs from 1 as 0 is reserved as an "any + * cpu" index. + */ +int gdb_get_cpu_index(CPUState *cpu) +{ + return cpu->cpu_index + 1; +} + +/* + * We check the status of the last message in the chardev receive code + */ +bool gdb_got_immediate_ack(void) +{ + return true; +} + +/* + * GDB Connection management. For system emulation we do all of this + * via our existing Chardev infrastructure which allows us to support + * network and unix sockets. + */ + +void gdb_put_buffer(const uint8_t *buf, int len) +{ + /* + * XXX this blocks entire thread. Rewrite to use + * qemu_chr_fe_write and background I/O callbacks + */ + qemu_chr_fe_write_all(&gdbserver_system_state.chr, buf, len); +} + +static void gdb_chr_event(void *opaque, QEMUChrEvent event) +{ + int i; + GDBState *s = (GDBState *) opaque; + + switch (event) { + case CHR_EVENT_OPENED: + /* Start with first process attached, others detached */ + for (i = 0; i < s->process_num; i++) { + s->processes[i].attached = !i; + } + + s->c_cpu = gdb_first_attached_cpu(); + s->g_cpu = s->c_cpu; + + vm_stop(RUN_STATE_PAUSED); + replay_gdb_attached(); + break; + default: + break; + } +} + +/* + * In system-mode we stop the VM and wait to send the syscall packet + * until notification that the CPU has stopped. This must be done + * because if the packet is sent now the reply from the syscall + * request could be received while the CPU is still in the running + * state, which can cause packets to be dropped and state transition + * 'T' packets to be sent while the syscall is still being processed. + */ +void gdb_syscall_handling(const char *syscall_packet) +{ + vm_stop(RUN_STATE_DEBUG); + qemu_cpu_kick(gdbserver_state.c_cpu); +} + +static void gdb_vm_state_change(void *opaque, bool running, RunState state) +{ + CPUState *cpu = gdbserver_state.c_cpu; + g_autoptr(GString) buf = g_string_new(NULL); + g_autoptr(GString) tid = g_string_new(NULL); + const char *type; + int ret; + + if (running || gdbserver_state.state == RS_INACTIVE) { + return; + } + + /* Is there a GDB syscall waiting to be sent? */ + if (gdb_handled_syscall()) { + return; + } + + if (cpu == NULL) { + /* No process attached */ + return; + } + + if (!gdbserver_state.allow_stop_reply) { + return; + } + + gdb_append_thread_id(cpu, tid); + + switch (state) { + case RUN_STATE_DEBUG: + if (cpu->watchpoint_hit) { + switch (cpu->watchpoint_hit->flags & BP_MEM_ACCESS) { + case BP_MEM_READ: + type = "r"; + break; + case BP_MEM_ACCESS: + type = "a"; + break; + default: + type = ""; + break; + } + trace_gdbstub_hit_watchpoint(type, + gdb_get_cpu_index(cpu), + cpu->watchpoint_hit->vaddr); + g_string_printf(buf, "T%02xthread:%s;%swatch:%" VADDR_PRIx ";", + GDB_SIGNAL_TRAP, tid->str, type, + cpu->watchpoint_hit->vaddr); + cpu->watchpoint_hit = NULL; + goto send_packet; + } else { + trace_gdbstub_hit_break(); + } + if (tcg_enabled()) { + tb_flush(cpu); + } + ret = GDB_SIGNAL_TRAP; + break; + case RUN_STATE_PAUSED: + trace_gdbstub_hit_paused(); + ret = GDB_SIGNAL_INT; + break; + case RUN_STATE_SHUTDOWN: + trace_gdbstub_hit_shutdown(); + ret = GDB_SIGNAL_QUIT; + break; + case RUN_STATE_IO_ERROR: + trace_gdbstub_hit_io_error(); + ret = GDB_SIGNAL_STOP; + break; + case RUN_STATE_WATCHDOG: + trace_gdbstub_hit_watchdog(); + ret = GDB_SIGNAL_ALRM; + break; + case RUN_STATE_INTERNAL_ERROR: + trace_gdbstub_hit_internal_error(); + ret = GDB_SIGNAL_ABRT; + break; + case RUN_STATE_SAVE_VM: + case RUN_STATE_RESTORE_VM: + return; + case RUN_STATE_FINISH_MIGRATE: + ret = GDB_SIGNAL_XCPU; + break; + default: + trace_gdbstub_hit_unknown(state); + ret = GDB_SIGNAL_UNKNOWN; + break; + } + gdb_set_stop_cpu(cpu); + g_string_printf(buf, "T%02xthread:%s;", ret, tid->str); + +send_packet: + gdb_put_packet(buf->str); + gdbserver_state.allow_stop_reply = false; + + /* disable single step if it was enabled */ + cpu_single_step(cpu, 0); +} + +#ifndef _WIN32 +static void gdb_sigterm_handler(int signal) +{ + if (runstate_is_running()) { + vm_stop(RUN_STATE_PAUSED); + } +} +#endif + +static int gdb_monitor_write(Chardev *chr, const uint8_t *buf, int len) +{ + g_autoptr(GString) hex_buf = g_string_new("O"); + gdb_memtohex(hex_buf, buf, len); + gdb_put_packet(hex_buf->str); + return len; +} + +static void gdb_monitor_open(Chardev *chr, ChardevBackend *backend, + bool *be_opened, Error **errp) +{ + *be_opened = false; +} + +static void char_gdb_class_init(ObjectClass *oc, const void *data) +{ + ChardevClass *cc = CHARDEV_CLASS(oc); + + cc->internal = true; + cc->open = gdb_monitor_open; + cc->chr_write = gdb_monitor_write; +} + +#define TYPE_CHARDEV_GDB "chardev-gdb" + +static const TypeInfo char_gdb_type_info = { + .name = TYPE_CHARDEV_GDB, + .parent = TYPE_CHARDEV, + .class_init = char_gdb_class_init, +}; + +static int gdb_chr_can_receive(void *opaque) +{ + /* + * We can handle an arbitrarily large amount of data. + * Pick the maximum packet size, which is as good as anything. + */ + return MAX_PACKET_LENGTH; +} + +static void gdb_chr_receive(void *opaque, const uint8_t *buf, int size) +{ + int i; + + for (i = 0; i < size; i++) { + gdb_read_byte(buf[i]); + } +} + +static int find_cpu_clusters(Object *child, void *opaque) +{ + if (object_dynamic_cast(child, TYPE_CPU_CLUSTER)) { + GDBState *s = (GDBState *) opaque; + CPUClusterState *cluster = CPU_CLUSTER(child); + GDBProcess *process; + + s->processes = g_renew(GDBProcess, s->processes, ++s->process_num); + + process = &s->processes[s->process_num - 1]; + + /* + * GDB process IDs -1 and 0 are reserved. To avoid subtle errors at + * runtime, we enforce here that the machine does not use a cluster ID + * that would lead to PID 0. + */ + assert(cluster->cluster_id != UINT32_MAX); + process->pid = cluster->cluster_id + 1; + process->attached = false; + process->target_xml = NULL; + + return 0; + } + + return object_child_foreach(child, find_cpu_clusters, opaque); +} + +static int pid_order(const void *a, const void *b) +{ + GDBProcess *pa = (GDBProcess *) a; + GDBProcess *pb = (GDBProcess *) b; + + if (pa->pid < pb->pid) { + return -1; + } else if (pa->pid > pb->pid) { + return 1; + } else { + return 0; + } +} + +static void create_processes(GDBState *s) +{ + object_child_foreach(object_get_root(), find_cpu_clusters, s); + + if (gdbserver_state.processes) { + /* Sort by PID */ + qsort(gdbserver_state.processes, + gdbserver_state.process_num, + sizeof(gdbserver_state.processes[0]), + pid_order); + } + + gdb_create_default_process(s); +} + +bool gdbserver_start(const char *device, Error **errp) +{ + Chardev *chr = NULL; + Chardev *mon_chr; + g_autoptr(GString) cs = g_string_new(device); + + if (!first_cpu) { + error_setg(errp, "gdbstub: meaningless to attach gdb to a " + "machine without any CPU."); + return false; + } + + if (!gdb_supports_guest_debug()) { + error_setg(errp, "gdbstub: current accelerator doesn't " + "support guest debugging"); + return false; + } + + if (cs->len == 0) { + error_setg(errp, "gdbstub: missing connection string"); + return false; + } + + trace_gdbstub_op_start(cs->str); + + if (g_strcmp0(cs->str, "none") != 0) { + if (g_str_has_prefix(cs->str, "tcp:")) { + /* enforce required TCP attributes */ + g_string_append_printf(cs, ",wait=off,nodelay=on,server=on"); + } +#ifndef _WIN32 + else if (strcmp(device, "stdio") == 0) { + struct sigaction act; + + memset(&act, 0, sizeof(act)); + act.sa_handler = gdb_sigterm_handler; + sigaction(SIGINT, &act, NULL); + } +#endif + /* + * FIXME: it's a bit weird to allow using a mux chardev here + * and implicitly setup a monitor. We may want to break this. + */ + chr = qemu_chr_new_noreplay("gdb", cs->str, true, NULL); + if (!chr) { + error_setg(errp, "gdbstub: couldn't create chardev"); + return false; + } + } + + if (!gdbserver_state.init) { + gdb_init_gdbserver_state(); + + qemu_add_vm_change_state_handler(gdb_vm_state_change, NULL); + + /* Initialize a monitor terminal for gdb */ + mon_chr = qemu_chardev_new(NULL, TYPE_CHARDEV_GDB, + NULL, NULL, &error_abort); + monitor_init_hmp(mon_chr, false, &error_abort); + } else { + qemu_chr_fe_deinit(&gdbserver_system_state.chr, true); + mon_chr = gdbserver_system_state.mon_chr; + reset_gdbserver_state(); + } + + create_processes(&gdbserver_state); + + if (chr) { + qemu_chr_fe_init(&gdbserver_system_state.chr, chr, &error_abort); + qemu_chr_fe_set_handlers(&gdbserver_system_state.chr, + gdb_chr_can_receive, + gdb_chr_receive, gdb_chr_event, + NULL, &gdbserver_state, NULL, true); + } + gdbserver_state.state = chr ? RS_IDLE : RS_INACTIVE; + gdbserver_system_state.mon_chr = mon_chr; + gdb_syscall_reset(); + + return true; +} + +static void register_types(void) +{ + type_register_static(&char_gdb_type_info); +} + +type_init(register_types); + +/* Tell the remote gdb that the process has exited. */ +void gdb_exit(int code) +{ + char buf[4]; + + if (!gdbserver_state.init) { + return; + } + + trace_gdbstub_op_exiting((uint8_t)code); + + if (gdbserver_state.allow_stop_reply) { + snprintf(buf, sizeof(buf), "W%02x", (uint8_t)code); + gdb_put_packet(buf); + gdbserver_state.allow_stop_reply = false; + } + + qemu_chr_fe_deinit(&gdbserver_system_state.chr, true); +} + +void gdb_qemu_exit(int code) +{ + qemu_system_shutdown_request_with_code(SHUTDOWN_CAUSE_GUEST_SHUTDOWN, + code); +} + +/* + * Memory access + */ +static int phy_memory_mode; + +int gdb_target_memory_rw_debug(CPUState *cpu, hwaddr addr, + uint8_t *buf, int len, bool is_write) +{ + if (phy_memory_mode) { + if (is_write) { + cpu_physical_memory_write(addr, buf, len); + } else { + cpu_physical_memory_read(addr, buf, len); + } + return 0; + } + + if (cpu->cc->memory_rw_debug) { + return cpu->cc->memory_rw_debug(cpu, addr, buf, len, is_write); + } + + return cpu_memory_rw_debug(cpu, addr, buf, len, is_write); +} + +/* + * cpu helpers + */ + +unsigned int gdb_get_max_cpus(void) +{ + MachineState *ms = MACHINE(qdev_get_machine()); + return ms->smp.max_cpus; +} + +bool gdb_can_reverse(void) +{ + return replay_mode == REPLAY_MODE_PLAY; +} + +/* + * Softmmu specific command helpers + */ + +void gdb_handle_query_qemu_phy_mem_mode(GArray *params, + void *ctx) +{ + g_string_printf(gdbserver_state.str_buf, "%d", phy_memory_mode); + gdb_put_strbuf(); +} + +void gdb_handle_set_qemu_phy_mem_mode(GArray *params, void *ctx) +{ + if (!params->len) { + gdb_put_packet("E22"); + return; + } + + if (!gdb_get_cmd_param(params, 0)->val_ul) { + phy_memory_mode = 0; + } else { + phy_memory_mode = 1; + } + gdb_put_packet("OK"); +} + +void gdb_handle_query_rcmd(GArray *params, void *ctx) +{ + const guint8 zero = 0; + int len; + + if (!params->len) { + gdb_put_packet("E22"); + return; + } + + len = strlen(gdb_get_cmd_param(params, 0)->data); + if (len % 2) { + gdb_put_packet("E01"); + return; + } + + g_assert(gdbserver_state.mem_buf->len == 0); + len = len / 2; + gdb_hextomem(gdbserver_state.mem_buf, gdb_get_cmd_param(params, 0)->data, len); + g_byte_array_append(gdbserver_state.mem_buf, &zero, 1); + qemu_chr_be_write(gdbserver_system_state.mon_chr, + gdbserver_state.mem_buf->data, + gdbserver_state.mem_buf->len); + gdb_put_packet("OK"); +} + +/* + * Execution state helpers + */ + +void gdb_handle_query_attached(GArray *params, void *ctx) +{ + gdb_put_packet("1"); +} + +void gdb_continue(void) +{ + if (!runstate_needs_reset()) { + trace_gdbstub_op_continue(); + vm_start(); + } +} + +/* + * Resume execution, per CPU actions. + */ +int gdb_continue_partial(char *newstates) +{ + CPUState *cpu; + int res = 0; + int flag = 0; + + if (!runstate_needs_reset()) { + bool step_requested = false; + CPU_FOREACH(cpu) { + if (newstates[cpu->cpu_index] == 's') { + step_requested = true; + break; + } + } + + if (vm_prepare_start(step_requested)) { + return 0; + } + + CPU_FOREACH(cpu) { + switch (newstates[cpu->cpu_index]) { + case 0: + case 1: + break; /* nothing to do here */ + case 's': + trace_gdbstub_op_stepping(cpu->cpu_index); + cpu_single_step(cpu, gdbserver_state.sstep_flags); + cpu_resume(cpu); + flag = 1; + break; + case 'c': + trace_gdbstub_op_continue_cpu(cpu->cpu_index); + cpu_resume(cpu); + flag = 1; + break; + default: + res = -1; + break; + } + } + } + if (flag) { + qemu_clock_enable(QEMU_CLOCK_VIRTUAL, true); + } + return res; +} + +/* + * Signal Handling - in system mode we only need SIGINT and SIGTRAP; other + * signals are not yet supported. + */ + +enum { + TARGET_SIGINT = 2, + TARGET_SIGTRAP = 5 +}; + +int gdb_signal_to_target(int sig) +{ + switch (sig) { + case 2: + return TARGET_SIGINT; + case 5: + return TARGET_SIGTRAP; + default: + return -1; + } +} + +/* + * Break/Watch point helpers + */ + +bool gdb_supports_guest_debug(void) +{ + const AccelOpsClass *ops = cpus_get_accel(); + if (ops->supports_guest_debug) { + return ops->supports_guest_debug(); + } + return false; +} + +int gdb_breakpoint_insert(CPUState *cs, int type, vaddr addr, vaddr len) +{ + const AccelOpsClass *ops = cpus_get_accel(); + if (ops->insert_breakpoint) { + return ops->insert_breakpoint(cs, type, addr, len); + } + return -ENOSYS; +} + +int gdb_breakpoint_remove(CPUState *cs, int type, vaddr addr, vaddr len) +{ + const AccelOpsClass *ops = cpus_get_accel(); + if (ops->remove_breakpoint) { + return ops->remove_breakpoint(cs, type, addr, len); + } + return -ENOSYS; +} + +void gdb_breakpoint_remove_all(CPUState *cs) +{ + const AccelOpsClass *ops = cpus_get_accel(); + if (ops->remove_all_breakpoints) { + ops->remove_all_breakpoints(cs); + } +} diff --git a/gdbstub/trace-events b/gdbstub/trace-events new file mode 100644 index 0000000..4fd126a --- /dev/null +++ b/gdbstub/trace-events @@ -0,0 +1,32 @@ +# See docs/devel/tracing.rst for syntax documentation. + +# gdbstub.c +gdbstub_op_start(const char *device) "Starting gdbstub using device %s" +gdbstub_op_exiting(uint8_t code) "notifying exit with code=0x%02x" +gdbstub_op_continue(void) "Continuing all CPUs" +gdbstub_op_continue_cpu(int cpu_index) "Continuing CPU %d" +gdbstub_op_stepping(int cpu_index) "Stepping CPU %d" +gdbstub_op_extra_info(const char *info) "Thread extra info: %s" +gdbstub_hit_internal_error(void) "RUN_STATE_INTERNAL_ERROR" +gdbstub_hit_break(void) "RUN_STATE_DEBUG" +gdbstub_hit_paused(void) "RUN_STATE_PAUSED" +gdbstub_hit_shutdown(void) "RUN_STATE_SHUTDOWN" +gdbstub_hit_io_error(void) "RUN_STATE_IO_ERROR" +gdbstub_hit_watchdog(void) "RUN_STATE_WATCHDOG" +gdbstub_hit_unknown(int state) "Unknown run state=0x%x" +gdbstub_io_reply(const char *message) "Sent: %s" +gdbstub_io_binaryreply(size_t ofs, const char *line) "0x%04zx: %s" +gdbstub_io_command(const char *command) "Received: %s" +gdbstub_io_got_ack(void) "Got ACK" +gdbstub_io_got_unexpected(uint8_t ch) "Got 0x%02x when expecting ACK/NACK" +gdbstub_err_got_nack(void) "Got NACK, retransmitting" +gdbstub_err_garbage(uint8_t ch) "received garbage between packets: 0x%02x" +gdbstub_err_overrun(void) "command buffer overrun, dropping command" +gdbstub_err_invalid_repeat(uint8_t ch) "got invalid RLE count: 0x%02x" +gdbstub_err_invalid_rle(void) "got invalid RLE sequence" +gdbstub_err_checksum_invalid(uint8_t ch) "got invalid command checksum digit: 0x%02x" +gdbstub_err_checksum_incorrect(uint8_t expected, uint8_t got) "got command packet with incorrect checksum, expected=0x%02x, received=0x%02x" +gdbstub_err_unexpected_runpkt(uint8_t ch) "unexpected packet (0x%02x) while target running" + +# system.c +gdbstub_hit_watchpoint(const char *type, int cpu_gdb_index, uint64_t vaddr) "Watchpoint hit, type=\"%s\" cpu=%d, vaddr=0x%" PRIx64 "" diff --git a/gdbstub/trace.h b/gdbstub/trace.h new file mode 100644 index 0000000..dee87b1 --- /dev/null +++ b/gdbstub/trace.h @@ -0,0 +1 @@ +#include "trace/trace-gdbstub.h" diff --git a/gdbstub/user-target.c b/gdbstub/user-target.c new file mode 100644 index 0000000..43231e6 --- /dev/null +++ b/gdbstub/user-target.c @@ -0,0 +1,424 @@ +/* + * Target specific user-mode handling + * + * Copyright (c) 2003-2005 Fabrice Bellard + * Copyright (c) 2022 Linaro Ltd + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "exec/gdbstub.h" +#include "gdbstub/commands.h" +#include "qemu.h" +#include "internals.h" +#ifdef CONFIG_LINUX +#include "linux-user/loader.h" +#include "linux-user/qemu.h" +#endif + +/* + * Map target signal numbers to GDB protocol signal numbers and vice + * versa. For user emulation's currently supported systems, we can + * assume most signals are defined. + */ + +static int gdb_signal_table[] = { + 0, + TARGET_SIGHUP, + TARGET_SIGINT, + TARGET_SIGQUIT, + TARGET_SIGILL, + TARGET_SIGTRAP, + TARGET_SIGABRT, + -1, /* SIGEMT */ + TARGET_SIGFPE, + TARGET_SIGKILL, + TARGET_SIGBUS, + TARGET_SIGSEGV, + TARGET_SIGSYS, + TARGET_SIGPIPE, + TARGET_SIGALRM, + TARGET_SIGTERM, + TARGET_SIGURG, + TARGET_SIGSTOP, + TARGET_SIGTSTP, + TARGET_SIGCONT, + TARGET_SIGCHLD, + TARGET_SIGTTIN, + TARGET_SIGTTOU, + TARGET_SIGIO, + TARGET_SIGXCPU, + TARGET_SIGXFSZ, + TARGET_SIGVTALRM, + TARGET_SIGPROF, + TARGET_SIGWINCH, + -1, /* SIGLOST */ + TARGET_SIGUSR1, + TARGET_SIGUSR2, +#ifdef TARGET_SIGPWR + TARGET_SIGPWR, +#else + -1, +#endif + -1, /* SIGPOLL */ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, +#ifdef __SIGRTMIN + __SIGRTMIN + 1, + __SIGRTMIN + 2, + __SIGRTMIN + 3, + __SIGRTMIN + 4, + __SIGRTMIN + 5, + __SIGRTMIN + 6, + __SIGRTMIN + 7, + __SIGRTMIN + 8, + __SIGRTMIN + 9, + __SIGRTMIN + 10, + __SIGRTMIN + 11, + __SIGRTMIN + 12, + __SIGRTMIN + 13, + __SIGRTMIN + 14, + __SIGRTMIN + 15, + __SIGRTMIN + 16, + __SIGRTMIN + 17, + __SIGRTMIN + 18, + __SIGRTMIN + 19, + __SIGRTMIN + 20, + __SIGRTMIN + 21, + __SIGRTMIN + 22, + __SIGRTMIN + 23, + __SIGRTMIN + 24, + __SIGRTMIN + 25, + __SIGRTMIN + 26, + __SIGRTMIN + 27, + __SIGRTMIN + 28, + __SIGRTMIN + 29, + __SIGRTMIN + 30, + __SIGRTMIN + 31, + -1, /* SIGCANCEL */ + __SIGRTMIN, + __SIGRTMIN + 32, + __SIGRTMIN + 33, + __SIGRTMIN + 34, + __SIGRTMIN + 35, + __SIGRTMIN + 36, + __SIGRTMIN + 37, + __SIGRTMIN + 38, + __SIGRTMIN + 39, + __SIGRTMIN + 40, + __SIGRTMIN + 41, + __SIGRTMIN + 42, + __SIGRTMIN + 43, + __SIGRTMIN + 44, + __SIGRTMIN + 45, + __SIGRTMIN + 46, + __SIGRTMIN + 47, + __SIGRTMIN + 48, + __SIGRTMIN + 49, + __SIGRTMIN + 50, + __SIGRTMIN + 51, + __SIGRTMIN + 52, + __SIGRTMIN + 53, + __SIGRTMIN + 54, + __SIGRTMIN + 55, + __SIGRTMIN + 56, + __SIGRTMIN + 57, + __SIGRTMIN + 58, + __SIGRTMIN + 59, + __SIGRTMIN + 60, + __SIGRTMIN + 61, + __SIGRTMIN + 62, + __SIGRTMIN + 63, + __SIGRTMIN + 64, + __SIGRTMIN + 65, + __SIGRTMIN + 66, + __SIGRTMIN + 67, + __SIGRTMIN + 68, + __SIGRTMIN + 69, + __SIGRTMIN + 70, + __SIGRTMIN + 71, + __SIGRTMIN + 72, + __SIGRTMIN + 73, + __SIGRTMIN + 74, + __SIGRTMIN + 75, + __SIGRTMIN + 76, + __SIGRTMIN + 77, + __SIGRTMIN + 78, + __SIGRTMIN + 79, + __SIGRTMIN + 80, + __SIGRTMIN + 81, + __SIGRTMIN + 82, + __SIGRTMIN + 83, + __SIGRTMIN + 84, + __SIGRTMIN + 85, + __SIGRTMIN + 86, + __SIGRTMIN + 87, + __SIGRTMIN + 88, + __SIGRTMIN + 89, + __SIGRTMIN + 90, + __SIGRTMIN + 91, + __SIGRTMIN + 92, + __SIGRTMIN + 93, + __SIGRTMIN + 94, + __SIGRTMIN + 95, + -1, /* SIGINFO */ + -1, /* UNKNOWN */ + -1, /* DEFAULT */ + -1, + -1, + -1, + -1, + -1, + -1 +#endif +}; + +int gdb_signal_to_target(int sig) +{ + if (sig < ARRAY_SIZE(gdb_signal_table)) { + return gdb_signal_table[sig]; + } else { + return -1; + } +} + +int gdb_target_signal_to_gdb(int sig) +{ + int i; + for (i = 0; i < ARRAY_SIZE(gdb_signal_table); i++) { + if (gdb_signal_table[i] == sig) { + return i; + } + } + return GDB_SIGNAL_UNKNOWN; +} + +int gdb_get_cpu_index(CPUState *cpu) +{ + TaskState *ts = get_task_state(cpu); + return ts ? ts->ts_tid : -1; +} + +/* + * User-mode specific command helpers + */ + +void gdb_handle_query_offsets(GArray *params, void *user_ctx) +{ + TaskState *ts; + + ts = get_task_state(gdbserver_state.c_cpu); + g_string_printf(gdbserver_state.str_buf, + "Text=" TARGET_ABI_FMT_lx + ";Data=" TARGET_ABI_FMT_lx + ";Bss=" TARGET_ABI_FMT_lx, + ts->info->code_offset, + ts->info->data_offset, + ts->info->data_offset); + gdb_put_strbuf(); +} + +#if defined(CONFIG_LINUX) +/* Partial user only duplicate of helper in gdbstub.c */ +static inline int target_memory_rw_debug(CPUState *cpu, target_ulong addr, + uint8_t *buf, int len, bool is_write) +{ + if (cpu->cc->memory_rw_debug) { + return cpu->cc->memory_rw_debug(cpu, addr, buf, len, is_write); + } + return cpu_memory_rw_debug(cpu, addr, buf, len, is_write); +} + +void gdb_handle_query_xfer_auxv(GArray *params, void *user_ctx) +{ + TaskState *ts; + unsigned long offset, len, saved_auxv, auxv_len; + + if (params->len < 2) { + gdb_put_packet("E22"); + return; + } + + offset = gdb_get_cmd_param(params, 0)->val_ul; + len = gdb_get_cmd_param(params, 1)->val_ul; + ts = get_task_state(gdbserver_state.c_cpu); + saved_auxv = ts->info->saved_auxv; + auxv_len = ts->info->auxv_len; + + if (offset >= auxv_len) { + gdb_put_packet("E00"); + return; + } + + if (len > (MAX_PACKET_LENGTH - 5) / 2) { + len = (MAX_PACKET_LENGTH - 5) / 2; + } + + if (len < auxv_len - offset) { + g_string_assign(gdbserver_state.str_buf, "m"); + } else { + g_string_assign(gdbserver_state.str_buf, "l"); + len = auxv_len - offset; + } + + g_byte_array_set_size(gdbserver_state.mem_buf, len); + if (target_memory_rw_debug(gdbserver_state.g_cpu, saved_auxv + offset, + gdbserver_state.mem_buf->data, len, false)) { + gdb_put_packet("E14"); + return; + } + + gdb_memtox(gdbserver_state.str_buf, + (const char *)gdbserver_state.mem_buf->data, len); + gdb_put_packet_binary(gdbserver_state.str_buf->str, + gdbserver_state.str_buf->len, true); +} +#endif + +static const char *get_filename_param(GArray *params, int i) +{ + const char *hex_filename = gdb_get_cmd_param(params, i)->data; + gdb_hextomem(gdbserver_state.mem_buf, hex_filename, + strlen(hex_filename) / 2); + g_byte_array_append(gdbserver_state.mem_buf, (const guint8 *)"", 1); + return (const char *)gdbserver_state.mem_buf->data; +} + +static void hostio_reply_with_data(const void *buf, size_t n) +{ + g_string_printf(gdbserver_state.str_buf, "F%zx;", n); + gdb_memtox(gdbserver_state.str_buf, buf, n); + gdb_put_packet_binary(gdbserver_state.str_buf->str, + gdbserver_state.str_buf->len, true); +} + +void gdb_handle_v_file_open(GArray *params, void *user_ctx) +{ + const char *filename = get_filename_param(params, 0); + uint64_t flags = gdb_get_cmd_param(params, 1)->val_ull; + uint64_t mode = gdb_get_cmd_param(params, 2)->val_ull; + +#ifdef CONFIG_LINUX + int fd = do_guest_openat(cpu_env(gdbserver_state.g_cpu), 0, filename, + flags, mode, false); +#else + int fd = open(filename, flags, mode); +#endif + if (fd < 0) { + g_string_printf(gdbserver_state.str_buf, "F-1,%x", errno); + } else { + g_string_printf(gdbserver_state.str_buf, "F%x", fd); + } + gdb_put_strbuf(); +} + +void gdb_handle_v_file_close(GArray *params, void *user_ctx) +{ + int fd = gdb_get_cmd_param(params, 0)->val_ul; + + if (close(fd) == -1) { + g_string_printf(gdbserver_state.str_buf, "F-1,%x", errno); + gdb_put_strbuf(); + return; + } + + gdb_put_packet("F00"); +} + +void gdb_handle_v_file_pread(GArray *params, void *user_ctx) +{ + int fd = gdb_get_cmd_param(params, 0)->val_ul; + size_t count = gdb_get_cmd_param(params, 1)->val_ull; + off_t offset = gdb_get_cmd_param(params, 2)->val_ull; + + size_t bufsiz = MIN(count, BUFSIZ); + g_autofree char *buf = g_try_malloc(bufsiz); + if (buf == NULL) { + gdb_put_packet("E12"); + return; + } + + ssize_t n = pread(fd, buf, bufsiz, offset); + if (n < 0) { + g_string_printf(gdbserver_state.str_buf, "F-1,%x", errno); + gdb_put_strbuf(); + return; + } + hostio_reply_with_data(buf, n); +} + +void gdb_handle_v_file_readlink(GArray *params, void *user_ctx) +{ + const char *filename = get_filename_param(params, 0); + + g_autofree char *buf = g_try_malloc(BUFSIZ); + if (buf == NULL) { + gdb_put_packet("E12"); + return; + } + +#ifdef CONFIG_LINUX + ssize_t n = do_guest_readlink(filename, buf, BUFSIZ); +#else + ssize_t n = readlink(filename, buf, BUFSIZ); +#endif + if (n < 0) { + g_string_printf(gdbserver_state.str_buf, "F-1,%x", errno); + gdb_put_strbuf(); + return; + } + hostio_reply_with_data(buf, n); +} + +void gdb_handle_query_xfer_exec_file(GArray *params, void *user_ctx) +{ + uint32_t pid = gdb_get_cmd_param(params, 0)->val_ul; + uint32_t offset = gdb_get_cmd_param(params, 1)->val_ul; + uint32_t length = gdb_get_cmd_param(params, 2)->val_ul; + + GDBProcess *process = gdb_get_process(pid); + if (!process) { + gdb_put_packet("E00"); + return; + } + + CPUState *cpu = gdb_get_first_cpu_in_process(process); + if (!cpu) { + gdb_put_packet("E00"); + return; + } + + TaskState *ts = get_task_state(cpu); + if (!ts || !ts->bprm || !ts->bprm->filename) { + gdb_put_packet("E00"); + return; + } + + size_t total_length = strlen(ts->bprm->filename); + if (offset > total_length) { + gdb_put_packet("E00"); + return; + } + if (offset + length > total_length) { + length = total_length - offset; + } + + g_string_printf(gdbserver_state.str_buf, "l%.*s", length, + ts->bprm->filename + offset); + gdb_put_strbuf(); +} + +int gdb_target_sigtrap(void) +{ + return TARGET_SIGTRAP; +} diff --git a/gdbstub/user.c b/gdbstub/user.c new file mode 100644 index 0000000..67403e5 --- /dev/null +++ b/gdbstub/user.c @@ -0,0 +1,955 @@ +/* + * gdbstub user-mode helper routines. + * + * We know for user-mode we are using TCG so we can call stuff directly. + * + * Copyright (c) 2003-2005 Fabrice Bellard + * Copyright (c) 2022 Linaro Ltd + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/cutils.h" +#include "qemu/sockets.h" +#include "qapi/error.h" +#include "exec/hwaddr.h" +#include "exec/tb-flush.h" +#include "exec/gdbstub.h" +#include "gdbstub/commands.h" +#include "gdbstub/syscalls.h" +#include "gdbstub/user.h" +#include "gdbstub/enums.h" +#include "hw/core/cpu.h" +#include "user/signal.h" +#include "trace.h" +#include "internals.h" + +#define GDB_NR_SYSCALLS 1024 +typedef unsigned long GDBSyscallsMask[BITS_TO_LONGS(GDB_NR_SYSCALLS)]; + +/* + * Forked child talks to its parent in order to let GDB enforce the + * follow-fork-mode. This happens inside a start_exclusive() section, so that + * the other threads, which may be forking too, do not interfere. The + * implementation relies on GDB not sending $vCont until it has detached + * either from the parent (follow-fork-mode child) or from the child + * (follow-fork-mode parent). + * + * The parent and the child share the GDB socket; at any given time only one + * of them is allowed to use it, as is reflected in the respective fork_state. + * This is negotiated via the fork_sockets pair as a reaction to $Hg. + * + * Below is a short summary of the possible state transitions: + * + * ENABLED : Terminal state. + * DISABLED : Terminal state. + * ACTIVE : Parent initial state. + * INACTIVE : Child initial state. + * ACTIVE -> DEACTIVATING: On $Hg. + * ACTIVE -> ENABLING : On $D. + * ACTIVE -> DISABLING : On $D. + * ACTIVE -> DISABLED : On communication error. + * DEACTIVATING -> INACTIVE : On gdb_read_byte() return. + * DEACTIVATING -> DISABLED : On communication error. + * INACTIVE -> ACTIVE : On $Hg in the peer. + * INACTIVE -> ENABLE : On $D in the peer. + * INACTIVE -> DISABLE : On $D in the peer. + * INACTIVE -> DISABLED : On communication error. + * ENABLING -> ENABLED : On gdb_read_byte() return. + * ENABLING -> DISABLED : On communication error. + * DISABLING -> DISABLED : On gdb_read_byte() return. + */ +enum GDBForkState { + /* Fully owning the GDB socket. */ + GDB_FORK_ENABLED, + /* Working with the GDB socket; the peer is inactive. */ + GDB_FORK_ACTIVE, + /* Handing off the GDB socket to the peer. */ + GDB_FORK_DEACTIVATING, + /* The peer is working with the GDB socket. */ + GDB_FORK_INACTIVE, + /* Asking the peer to close its GDB socket fd. */ + GDB_FORK_ENABLING, + /* Asking the peer to take over, closing our GDB socket fd. */ + GDB_FORK_DISABLING, + /* The peer has taken over, our GDB socket fd is closed. */ + GDB_FORK_DISABLED, +}; + +enum GDBForkMessage { + GDB_FORK_ACTIVATE = 'a', + GDB_FORK_ENABLE = 'e', + GDB_FORK_DISABLE = 'd', +}; + +/* User-mode specific state */ +typedef struct { + int fd; + char *socket_path; + int running_state; + /* + * Store syscalls mask without memory allocation in order to avoid + * implementing synchronization. + */ + bool catch_all_syscalls; + GDBSyscallsMask catch_syscalls_mask; + bool fork_events; + enum GDBForkState fork_state; + int fork_sockets[2]; + pid_t fork_peer_pid, fork_peer_tid; + uint8_t siginfo[MAX_SIGINFO_LENGTH]; + unsigned long siginfo_len; +} GDBUserState; + +static GDBUserState gdbserver_user_state; + +int gdb_get_char(void) +{ + uint8_t ch; + int ret; + + for (;;) { + ret = recv(gdbserver_user_state.fd, &ch, 1, 0); + if (ret < 0) { + if (errno == ECONNRESET) { + gdbserver_user_state.fd = -1; + } + if (errno != EINTR) { + return -1; + } + } else if (ret == 0) { + close(gdbserver_user_state.fd); + gdbserver_user_state.fd = -1; + return -1; + } else { + break; + } + } + return ch; +} + +bool gdb_got_immediate_ack(void) +{ + int i; + + i = gdb_get_char(); + if (i < 0) { + /* no response, continue anyway */ + return true; + } + + if (i == '+') { + /* received correctly, continue */ + return true; + } + + /* anything else, including '-' then try again */ + return false; +} + +void gdb_put_buffer(const uint8_t *buf, int len) +{ + int ret; + + while (len > 0) { + ret = send(gdbserver_user_state.fd, buf, len, 0); + if (ret < 0) { + if (errno != EINTR) { + return; + } + } else { + buf += ret; + len -= ret; + } + } +} + +/* Tell the remote gdb that the process has exited. */ +void gdb_exit(int code) +{ + char buf[4]; + + if (!gdbserver_state.init) { + return; + } + if (gdbserver_user_state.socket_path) { + unlink(gdbserver_user_state.socket_path); + } + if (gdbserver_user_state.fd < 0) { + return; + } + + trace_gdbstub_op_exiting((uint8_t)code); + + if (gdbserver_state.allow_stop_reply) { + snprintf(buf, sizeof(buf), "W%02x", (uint8_t)code); + gdb_put_packet(buf); + gdbserver_state.allow_stop_reply = false; + } + +} + +void gdb_qemu_exit(int code) +{ + exit(code); +} + +int gdb_handlesig(CPUState *cpu, int sig, const char *reason, void *siginfo, + int siginfo_len) +{ + char buf[256]; + int n; + + if (!gdbserver_state.init || gdbserver_user_state.fd < 0) { + return sig; + } + + if (siginfo) { + /* + * Save target-specific siginfo. + * + * siginfo size, i.e. siginfo_len, is asserted at compile-time to fit in + * gdbserver_user_state.siginfo, usually in the source file calling + * gdb_handlesig. See, for instance, {linux,bsd}-user/signal.c. + */ + memcpy(gdbserver_user_state.siginfo, siginfo, siginfo_len); + gdbserver_user_state.siginfo_len = siginfo_len; + } + + /* disable single step if it was enabled */ + cpu_single_step(cpu, 0); + tb_flush(cpu); + + if (sig != 0) { + gdb_set_stop_cpu(cpu); + if (gdbserver_state.allow_stop_reply) { + g_string_printf(gdbserver_state.str_buf, + "T%02xthread:", gdb_target_signal_to_gdb(sig)); + gdb_append_thread_id(cpu, gdbserver_state.str_buf); + g_string_append_c(gdbserver_state.str_buf, ';'); + if (reason) { + g_string_append(gdbserver_state.str_buf, reason); + } + gdb_put_strbuf(); + gdbserver_state.allow_stop_reply = false; + } + } + /* + * gdb_put_packet() might have detected that the peer terminated the + * connection. + */ + if (gdbserver_user_state.fd < 0) { + return sig; + } + + sig = 0; + gdbserver_state.state = RS_IDLE; + gdbserver_user_state.running_state = 0; + while (gdbserver_user_state.running_state == 0) { + n = read(gdbserver_user_state.fd, buf, 256); + if (n > 0) { + int i; + + for (i = 0; i < n; i++) { + gdb_read_byte(buf[i]); + } + } else { + /* + * XXX: Connection closed. Should probably wait for another + * connection before continuing. + */ + if (n == 0) { + close(gdbserver_user_state.fd); + } + gdbserver_user_state.fd = -1; + return sig; + } + } + sig = gdbserver_state.signal; + gdbserver_state.signal = 0; + return sig; +} + +/* Tell the remote gdb that the process has exited due to SIG. */ +void gdb_signalled(CPUArchState *env, int sig) +{ + char buf[4]; + + if (!gdbserver_state.init || gdbserver_user_state.fd < 0 || + !gdbserver_state.allow_stop_reply) { + return; + } + + snprintf(buf, sizeof(buf), "X%02x", gdb_target_signal_to_gdb(sig)); + gdb_put_packet(buf); + gdbserver_state.allow_stop_reply = false; +} + +static void gdb_accept_init(int fd) +{ + gdb_init_gdbserver_state(); + gdb_create_default_process(&gdbserver_state); + gdbserver_state.processes[0].attached = true; + gdbserver_state.c_cpu = gdb_first_attached_cpu(); + gdbserver_state.g_cpu = gdbserver_state.c_cpu; + gdbserver_user_state.fd = fd; +} + +static bool gdb_accept_socket(int gdb_fd) +{ + int fd; + + for (;;) { + fd = accept(gdb_fd, NULL, NULL); + if (fd < 0 && errno != EINTR) { + perror("accept socket"); + return false; + } else if (fd >= 0) { + qemu_set_cloexec(fd); + break; + } + } + + gdb_accept_init(fd); + return true; +} + +static int gdbserver_open_socket(const char *path, Error **errp) +{ + g_autoptr(GString) buf = g_string_new(""); + char *pid_placeholder; + + pid_placeholder = strstr(path, "%d"); + if (pid_placeholder != NULL) { + g_string_append_len(buf, path, pid_placeholder - path); + g_string_append_printf(buf, "%d", qemu_get_thread_id()); + g_string_append(buf, pid_placeholder + 2); + path = buf->str; + } + + return unix_listen(path, errp); +} + +static bool gdb_accept_tcp(int gdb_fd) +{ + struct sockaddr_in sockaddr = {}; + socklen_t len; + int fd; + + for (;;) { + len = sizeof(sockaddr); + fd = accept(gdb_fd, (struct sockaddr *)&sockaddr, &len); + if (fd < 0 && errno != EINTR) { + perror("accept"); + return false; + } else if (fd >= 0) { + qemu_set_cloexec(fd); + break; + } + } + + /* set short latency */ + if (socket_set_nodelay(fd)) { + perror("setsockopt"); + close(fd); + return false; + } + + gdb_accept_init(fd); + return true; +} + +static int gdbserver_open_port(int port, Error **errp) +{ + struct sockaddr_in sockaddr; + int fd, ret; + + fd = socket(PF_INET, SOCK_STREAM, 0); + if (fd < 0) { + error_setg_errno(errp, errno, "Failed to create socket"); + return -1; + } + qemu_set_cloexec(fd); + + 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) { + error_setg_errno(errp, errno, "Failed to bind socket"); + close(fd); + return -1; + } + ret = listen(fd, 1); + if (ret < 0) { + error_setg_errno(errp, errno, "Failed to listen to socket"); + close(fd); + return -1; + } + + return fd; +} + +static bool gdbserver_accept(int port, int gdb_fd, const char *path) +{ + bool ret; + + if (port > 0) { + ret = gdb_accept_tcp(gdb_fd); + } else { + ret = gdb_accept_socket(gdb_fd); + if (ret) { + gdbserver_user_state.socket_path = g_strdup(path); + } + } + + if (!ret) { + close(gdb_fd); + } + + return ret; +} + +struct { + int port; + int gdb_fd; + char *path; +} gdbserver_args; + +static void do_gdb_handlesig(CPUState *cs, run_on_cpu_data arg) +{ + int sig; + + sig = target_to_host_signal(gdb_handlesig(cs, 0, NULL, NULL, 0)); + if (sig >= 1 && sig < NSIG) { + qemu_kill_thread(gdb_get_cpu_index(cs), sig); + } +} + +static void *gdbserver_accept_thread(void *arg) +{ + if (gdbserver_accept(gdbserver_args.port, gdbserver_args.gdb_fd, + gdbserver_args.path)) { + CPUState *cs = first_cpu; + + async_safe_run_on_cpu(cs, do_gdb_handlesig, RUN_ON_CPU_NULL); + qemu_kill_thread(gdb_get_cpu_index(cs), host_interrupt_signal); + } + + g_free(gdbserver_args.path); + gdbserver_args.path = NULL; + + return NULL; +} + +#define USAGE "\nUsage: -g {port|path}[,suspend={y|n}]" + +bool gdbserver_start(const char *args, Error **errp) +{ + g_auto(GStrv) argv = g_strsplit(args, ",", 0); + const char *port_or_path = NULL; + bool suspend = true; + int gdb_fd, port; + GStrv arg; + + for (arg = argv; *arg; arg++) { + g_auto(GStrv) tokens = g_strsplit(*arg, "=", 2); + + if (g_strcmp0(tokens[0], "suspend") == 0) { + if (tokens[1] == NULL) { + error_setg(errp, + "gdbstub: missing \"suspend\" option value" USAGE); + return false; + } else if (!qapi_bool_parse(tokens[0], tokens[1], + &suspend, errp)) { + return false; + } + } else { + if (port_or_path) { + error_setg(errp, "gdbstub: unknown option \"%s\"" USAGE, *arg); + return false; + } + port_or_path = *arg; + } + } + if (!port_or_path) { + error_setg(errp, "gdbstub: port or path not specified" USAGE); + return false; + } + + port = g_ascii_strtoull(port_or_path, NULL, 10); + if (port > 0) { + gdb_fd = gdbserver_open_port(port, errp); + } else { + gdb_fd = gdbserver_open_socket(port_or_path, errp); + } + if (gdb_fd < 0) { + return false; + } + + if (suspend) { + if (gdbserver_accept(port, gdb_fd, port_or_path)) { + gdb_handlesig(first_cpu, 0, NULL, NULL, 0); + return true; + } else { + error_setg(errp, "gdbstub: failed to accept connection"); + return false; + } + } else { + QemuThread thread; + + gdbserver_args.port = port; + gdbserver_args.gdb_fd = gdb_fd; + gdbserver_args.path = g_strdup(port_or_path); + qemu_thread_create(&thread, "gdb-accept", + &gdbserver_accept_thread, NULL, + QEMU_THREAD_DETACHED); + return true; + } +} + +void gdbserver_fork_start(void) +{ + if (!gdbserver_state.init || gdbserver_user_state.fd < 0) { + return; + } + if (!gdbserver_user_state.fork_events || + qemu_socketpair(AF_UNIX, SOCK_STREAM, 0, + gdbserver_user_state.fork_sockets) < 0) { + gdbserver_user_state.fork_state = GDB_FORK_DISABLED; + return; + } + gdbserver_user_state.fork_state = GDB_FORK_INACTIVE; + gdbserver_user_state.fork_peer_pid = getpid(); + gdbserver_user_state.fork_peer_tid = qemu_get_thread_id(); +} + +static void disable_gdbstub(CPUState *thread_cpu) +{ + CPUState *cpu; + + close(gdbserver_user_state.fd); + gdbserver_user_state.fd = -1; + CPU_FOREACH(cpu) { + cpu_breakpoint_remove_all(cpu, BP_GDB); + /* no cpu_watchpoint_remove_all for user-mode */ + cpu_single_step(cpu, 0); + } + tb_flush(thread_cpu); +} + +void gdbserver_fork_end(CPUState *cpu, pid_t pid) +{ + char b; + int fd; + + if (!gdbserver_state.init || gdbserver_user_state.fd < 0) { + return; + } + + if (pid == -1) { + if (gdbserver_user_state.fork_state != GDB_FORK_DISABLED) { + g_assert(gdbserver_user_state.fork_state == GDB_FORK_INACTIVE); + close(gdbserver_user_state.fork_sockets[0]); + close(gdbserver_user_state.fork_sockets[1]); + } + return; + } + + if (gdbserver_user_state.fork_state == GDB_FORK_DISABLED) { + if (pid == 0) { + disable_gdbstub(cpu); + } + return; + } + + if (pid == 0) { + close(gdbserver_user_state.fork_sockets[0]); + fd = gdbserver_user_state.fork_sockets[1]; + g_assert(gdbserver_state.process_num == 1); + g_assert(gdbserver_state.processes[0].pid == + gdbserver_user_state.fork_peer_pid); + g_assert(gdbserver_state.processes[0].attached); + gdbserver_state.processes[0].pid = getpid(); + } else { + close(gdbserver_user_state.fork_sockets[1]); + fd = gdbserver_user_state.fork_sockets[0]; + gdbserver_user_state.fork_state = GDB_FORK_ACTIVE; + gdbserver_user_state.fork_peer_pid = pid; + gdbserver_user_state.fork_peer_tid = pid; + + if (!gdbserver_state.allow_stop_reply) { + goto fail; + } + g_string_printf(gdbserver_state.str_buf, + "T%02xfork:p%02x.%02x;thread:p%02x.%02x;", + gdb_target_signal_to_gdb(gdb_target_sigtrap()), + pid, pid, (int)getpid(), qemu_get_thread_id()); + gdb_put_strbuf(); + } + + gdbserver_state.state = RS_IDLE; + gdbserver_state.allow_stop_reply = false; + gdbserver_user_state.running_state = 0; + for (;;) { + switch (gdbserver_user_state.fork_state) { + case GDB_FORK_ENABLED: + if (gdbserver_user_state.running_state) { + close(fd); + return; + } + QEMU_FALLTHROUGH; + case GDB_FORK_ACTIVE: + if (read(gdbserver_user_state.fd, &b, 1) != 1) { + goto fail; + } + gdb_read_byte(b); + break; + case GDB_FORK_DEACTIVATING: + b = GDB_FORK_ACTIVATE; + if (write(fd, &b, 1) != 1) { + goto fail; + } + gdbserver_user_state.fork_state = GDB_FORK_INACTIVE; + break; + case GDB_FORK_INACTIVE: + if (read(fd, &b, 1) != 1) { + goto fail; + } + switch (b) { + case GDB_FORK_ACTIVATE: + gdbserver_user_state.fork_state = GDB_FORK_ACTIVE; + break; + case GDB_FORK_ENABLE: + gdbserver_user_state.fork_state = GDB_FORK_ENABLED; + break; + case GDB_FORK_DISABLE: + gdbserver_user_state.fork_state = GDB_FORK_DISABLED; + break; + default: + g_assert_not_reached(); + } + break; + case GDB_FORK_ENABLING: + b = GDB_FORK_DISABLE; + if (write(fd, &b, 1) != 1) { + goto fail; + } + gdbserver_user_state.fork_state = GDB_FORK_ENABLED; + break; + case GDB_FORK_DISABLING: + b = GDB_FORK_ENABLE; + if (write(fd, &b, 1) != 1) { + goto fail; + } + gdbserver_user_state.fork_state = GDB_FORK_DISABLED; + break; + case GDB_FORK_DISABLED: + close(fd); + disable_gdbstub(cpu); + return; + default: + g_assert_not_reached(); + } + } + +fail: + close(fd); + if (pid == 0) { + disable_gdbstub(cpu); + } +} + +void gdb_handle_query_supported_user(const char *gdb_supported) +{ + if (strstr(gdb_supported, "fork-events+")) { + gdbserver_user_state.fork_events = true; + } + g_string_append(gdbserver_state.str_buf, ";fork-events+"); +} + +bool gdb_handle_set_thread_user(uint32_t pid, uint32_t tid) +{ + if (gdbserver_user_state.fork_state == GDB_FORK_ACTIVE && + pid == gdbserver_user_state.fork_peer_pid && + tid == gdbserver_user_state.fork_peer_tid) { + gdbserver_user_state.fork_state = GDB_FORK_DEACTIVATING; + gdb_put_packet("OK"); + return true; + } + return false; +} + +bool gdb_handle_detach_user(uint32_t pid) +{ + bool enable; + + if (gdbserver_user_state.fork_state == GDB_FORK_ACTIVE) { + enable = pid == gdbserver_user_state.fork_peer_pid; + if (enable || pid == getpid()) { + gdbserver_user_state.fork_state = enable ? GDB_FORK_ENABLING : + GDB_FORK_DISABLING; + gdb_put_packet("OK"); + return true; + } + } + return false; +} + +/* + * Execution state helpers + */ + +void gdb_handle_query_attached(GArray *params, void *user_ctx) +{ + gdb_put_packet("0"); +} + +void gdb_continue(void) +{ + gdbserver_user_state.running_state = 1; + trace_gdbstub_op_continue(); +} + +/* + * Resume execution, for user-mode emulation it's equivalent to + * gdb_continue. + */ +int gdb_continue_partial(char *newstates) +{ + CPUState *cpu; + int res = 0; + /* + * This is not exactly accurate, but it's an improvement compared to the + * previous situation, where only one CPU would be single-stepped. + */ + CPU_FOREACH(cpu) { + if (newstates[cpu->cpu_index] == 's') { + trace_gdbstub_op_stepping(cpu->cpu_index); + cpu_single_step(cpu, gdbserver_state.sstep_flags); + } + } + gdbserver_user_state.running_state = 1; + return res; +} + +/* + * Memory access helpers + */ +int gdb_target_memory_rw_debug(CPUState *cpu, hwaddr addr, + uint8_t *buf, int len, bool is_write) +{ + if (cpu->cc->memory_rw_debug) { + return cpu->cc->memory_rw_debug(cpu, addr, buf, len, is_write); + } + return cpu_memory_rw_debug(cpu, addr, buf, len, is_write); +} + +/* + * cpu helpers + */ + +unsigned int gdb_get_max_cpus(void) +{ + CPUState *cpu; + unsigned int max_cpus = 1; + + CPU_FOREACH(cpu) { + max_cpus = max_cpus <= cpu->cpu_index ? cpu->cpu_index + 1 : max_cpus; + } + + return max_cpus; +} + +/* replay not supported for user-mode */ +bool gdb_can_reverse(void) +{ + return false; +} + +/* + * Break/Watch point helpers + */ + +bool gdb_supports_guest_debug(void) +{ + /* user-mode == TCG == supported */ + return true; +} + +int gdb_breakpoint_insert(CPUState *cs, int type, vaddr addr, vaddr len) +{ + CPUState *cpu; + int err = 0; + + switch (type) { + case GDB_BREAKPOINT_SW: + case GDB_BREAKPOINT_HW: + CPU_FOREACH(cpu) { + err = cpu_breakpoint_insert(cpu, addr, BP_GDB, NULL); + if (err) { + break; + } + } + return err; + default: + /* user-mode doesn't support watchpoints */ + return -ENOSYS; + } +} + +int gdb_breakpoint_remove(CPUState *cs, int type, vaddr addr, vaddr len) +{ + CPUState *cpu; + int err = 0; + + switch (type) { + case GDB_BREAKPOINT_SW: + case GDB_BREAKPOINT_HW: + CPU_FOREACH(cpu) { + err = cpu_breakpoint_remove(cpu, addr, BP_GDB); + if (err) { + break; + } + } + return err; + default: + /* user-mode doesn't support watchpoints */ + return -ENOSYS; + } +} + +void gdb_breakpoint_remove_all(CPUState *cs) +{ + cpu_breakpoint_remove_all(cs, BP_GDB); +} + +/* + * For user-mode syscall support we send the system call immediately + * and then return control to gdb for it to process the syscall request. + * Since the protocol requires that gdb hands control back to us + * using a "here are the results" F packet, we don't need to check + * gdb_handlesig's return value (which is the signal to deliver if + * execution was resumed via a continue packet). + */ +void gdb_syscall_handling(const char *syscall_packet) +{ + gdb_put_packet(syscall_packet); + gdb_handlesig(gdbserver_state.c_cpu, 0, NULL, NULL, 0); +} + +static bool should_catch_syscall(int num) +{ + if (gdbserver_user_state.catch_all_syscalls) { + return true; + } + if (num < 0 || num >= GDB_NR_SYSCALLS) { + return false; + } + return test_bit(num, gdbserver_user_state.catch_syscalls_mask); +} + +void gdb_syscall_entry(CPUState *cs, int num) +{ + if (should_catch_syscall(num)) { + g_autofree char *reason = g_strdup_printf("syscall_entry:%x;", num); + gdb_handlesig(cs, gdb_target_sigtrap(), reason, NULL, 0); + } +} + +void gdb_syscall_return(CPUState *cs, int num) +{ + if (should_catch_syscall(num)) { + g_autofree char *reason = g_strdup_printf("syscall_return:%x;", num); + gdb_handlesig(cs, gdb_target_sigtrap(), reason, NULL, 0); + } +} + +void gdb_handle_set_catch_syscalls(GArray *params, void *user_ctx) +{ + const char *param = gdb_get_cmd_param(params, 0)->data; + GDBSyscallsMask catch_syscalls_mask; + bool catch_all_syscalls; + unsigned int num; + const char *p; + + /* "0" means not catching any syscalls. */ + if (strcmp(param, "0") == 0) { + gdbserver_user_state.catch_all_syscalls = false; + memset(gdbserver_user_state.catch_syscalls_mask, 0, + sizeof(gdbserver_user_state.catch_syscalls_mask)); + gdb_put_packet("OK"); + return; + } + + /* "1" means catching all syscalls. */ + if (strcmp(param, "1") == 0) { + gdbserver_user_state.catch_all_syscalls = true; + gdb_put_packet("OK"); + return; + } + + /* + * "1;..." means catching only the specified syscalls. + * The syscall list must not be empty. + */ + if (param[0] == '1' && param[1] == ';') { + catch_all_syscalls = false; + memset(catch_syscalls_mask, 0, sizeof(catch_syscalls_mask)); + for (p = ¶m[2];; p++) { + if (qemu_strtoui(p, &p, 16, &num) || (*p && *p != ';')) { + goto err; + } + if (num >= GDB_NR_SYSCALLS) { + /* + * Fall back to reporting all syscalls. Reporting extra + * syscalls is inefficient, but the spec explicitly allows it. + * Keep parsing in case there is a syntax error ahead. + */ + catch_all_syscalls = true; + } else { + set_bit(num, catch_syscalls_mask); + } + if (!*p) { + break; + } + } + gdbserver_user_state.catch_all_syscalls = catch_all_syscalls; + if (!catch_all_syscalls) { + memcpy(gdbserver_user_state.catch_syscalls_mask, + catch_syscalls_mask, sizeof(catch_syscalls_mask)); + } + gdb_put_packet("OK"); + return; + } + +err: + gdb_put_packet("E00"); +} + +void gdb_handle_query_xfer_siginfo(GArray *params, void *user_ctx) +{ + unsigned long offset, len; + uint8_t *siginfo_offset; + + offset = gdb_get_cmd_param(params, 0)->val_ul; + len = gdb_get_cmd_param(params, 1)->val_ul; + + if (offset + len > gdbserver_user_state.siginfo_len) { + /* Invalid offset and/or requested length. */ + gdb_put_packet("E01"); + return; + } + + siginfo_offset = (uint8_t *)gdbserver_user_state.siginfo + offset; + + /* Reply */ + g_string_assign(gdbserver_state.str_buf, "l"); + gdb_memtox(gdbserver_state.str_buf, (const char *)siginfo_offset, len); + gdb_put_packet_binary(gdbserver_state.str_buf->str, + gdbserver_state.str_buf->len, true); +} diff --git a/kernel.c b/kernel.c new file mode 100755 index 0000000..47ffa07 --- /dev/null +++ b/kernel.c @@ -0,0 +1,14 @@ + +int add(int a, int b) { + return a + b; // 待调试的函数 +} + +int main() { + int x = 5; + int y = 3; + int result = add(x, y); + volatile char *tohost_addr = (volatile char *)(0x80001000 - 4); + *tohost_addr = 0xff; + *tohost_addr = result; + return 0; +} diff --git a/kernel_obj b/kernel_obj new file mode 100755 index 0000000000000000000000000000000000000000..f1e27afb5f4847e37cc60eab9b270529843ff573 GIT binary patch literal 6216 zcmeHL&1)n@6o1tMTW6ptzj9)!gAs=KnCAs~3}2i5QW-uqQm z*Q@H+P;Z7jGefAUujucK@1l>I ziVe{J>Mu@>%zha_hp$_IIKV*$f(!&12r>|4Ajm+Ffgl4x27(L(83-~EWFW}E|I9#| zvBPYB7_v|AKsv;fef0{Y)dKtm+dO;W{^5hi0ro%m@hF@7>+tT>y~A|)_oM9gudp8m zNWUM3yP1cK=XMy5qtv(2*Eb%GkxV1UEKmUjAkgQT3h8(v(zh!iw8w4oUR^Rp^FA7U!tJ`6G0`8;5=U$ z<#1lcQA;}4Yc*gg^#=N0!S)fFhp9}YcT^xT(be8klO4xpuS9$km!e&=*td1 zMT>4TfDMe0t?V_zRbY{*HxNWZU!Xh6A~e_FXZQf70*_ptWb>1$x!7u28N+VF?1@A= z_A!g?u>n4Dc4C;N29mfVJkMSnM|NyJHW!;3Urkfs%GkT(ER7k;K`iz{j7MRsCoi2h@;pHN$o`4S376I%X?hm8&>i<9bg7IJh5zbaJ|-^9jeT z@(Xuw)P^K1+k_XwyoKYZaMCrYuX9EX4Z5^2hHr% z0P^-bZAUjyJC>^((sImO4&7Du`*zDZw z9)}FWGPfkc!)8kRrlq%!0Zu6W#KBj$yiESLqUua1QB*sk<3`DvN>HAzC&j2yIidSy;lu~sGoOVf?BmU)0PdZ_d%KVKJ@Vf4@nl}!^LPa>mciP(;p5ReQTx!-lDCPx3#agi v>#qa{$m^k#>f{hMnio?bzFvQYh~vj;qhAV*Lqfk?Kkp9mJ`oZ3A$k7-?UN18 literal 0 HcmV?d00001 diff --git a/link.ld b/link.ld new file mode 100644 index 0000000..1a86f90 --- /dev/null +++ b/link.ld @@ -0,0 +1,36 @@ +/* link.ld - 自定义链接脚本 */ +OUTPUT_ARCH(riscv) /* 目标架构:RISC-V */ +ENTRY(_start) /* 程序入口点:_start 符号 */ + +/* 内存布局定义(根据实际硬件调整) */ +MEMORY { + RAM (rwxa) : ORIGIN = 0x80000000, LENGTH = 128M /* 起始地址 0x80000000,大小 128MB */ +} + +/* 段布局 */ +SECTIONS { + .text : { /* 代码段:存放指令 */ + *(.text.entry) /* 入口代码(如 _start)优先放在最开始 */ + *(.text) /* 其他代码 */ + } > RAM /* 加载到 RAM 区域 */ + + .rodata : { /* 只读数据段:常量、字符串等 */ + *(.rodata) + } > RAM + + .data : { /* 数据段:初始化的全局变量 */ + *(.data) + } > RAM + + .bss : { /* BSS 段:未初始化的全局变量(自动清零) */ + *(.bss) + } > RAM + + /* 栈定义:从 RAM 高地址向下增长,大小 16KB */ + .stack : { + . = ALIGN(16); /* 栈地址 16 字节对齐 */ + stack_top = .; /* 栈顶地址符号 */ + . += 16K; /* 栈大小 16KB */ + stack_bottom = .; /* 栈底地址符号(供初始化用) */ + } > RAM +} \ No newline at end of file diff --git a/start.s b/start.s new file mode 100644 index 0000000..29605eb --- /dev/null +++ b/start.s @@ -0,0 +1,14 @@ +# 定义入口点 _start,对应链接脚本的 ENTRY(_start) +.section .text.entry +.global _start + +_start: + # 初始化栈(使用链接脚本中定义的 stack_top) + la sp, stack_top + + # 调用 main 函数 + call main + + # main 返回后进入死循环 +loop: + j loop