[PATCH v2 1/3] [sim/riscv] Add basic semi-hosting support

jaydeep.patil
2023-10-30T13:01:55Z

From: Jaydeep Patil <jaydeep.patil@imgtec.com>

Added support for basic semi-hosting calls OPEN, EXIT and GET_CMDLINE.
Added gdb.arch/riscv-exit-getcmd.c to test it.
---
gdb/testsuite/gdb.arch/riscv-exit-getcmd.c | 26 +++
gdb/testsuite/gdb.arch/riscv-exit-getcmd.exp | 27 +++
sim/riscv/riscv-sim.h | 32 +++
sim/riscv/sim-main.c | 230 ++++++++++++++++++-
4 files changed, 310 insertions(+), 5 deletions(-)
create mode 100644 gdb/testsuite/gdb.arch/riscv-exit-getcmd.c
create mode 100644 gdb/testsuite/gdb.arch/riscv-exit-getcmd.exp

diff --git a/gdb/testsuite/gdb.arch/riscv-exit-getcmd.c b/gdb/testsuite/gdb.arch/riscv-exit-getcmd.c
new file mode 100644
index 00000000000..02a97da8643
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/riscv-exit-getcmd.c
@@ -0,0 +1,26 @@
+/* This file is part of GDB, the GNU debugger.
+
+ Copyright 2023 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Test basic semi-hosting calls SYS_GET_CMDLINE and SYS_EXIT. */
+
+int
+main (int argc, char **argv)
+{
+ if (argc != 4)
+ return 1;
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.arch/riscv-exit-getcmd.exp b/gdb/testsuite/gdb.arch/riscv-exit-getcmd.exp
new file mode 100644
index 00000000000..7f5670200c2
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/riscv-exit-getcmd.exp
@@ -0,0 +1,27 @@
+# Copyright 2023 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Test basic semi-hosting calls SYS_GET_CMDLINE and SYS_EXIT.
+
+require {istarget "riscv*-*-*"}
+
+standard_testfile
+
+if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \
+ {debug quiet}] } {
+ return -1
+}
+
+gdb_test "run 1 2 3" ".*Inferior.*process.*exited normally.*"
diff --git a/sim/riscv/riscv-sim.h b/sim/riscv/riscv-sim.h
index 1bc9aa12156..bb296fa6e62 100644
--- a/sim/riscv/riscv-sim.h
+++ b/sim/riscv/riscv-sim.h
@@ -75,4 +75,36 @@ extern void initialize_env (SIM_DESC, const char * const *argv,

#define RISCV_XLEN(cpu) MACH_WORD_BITSIZE (CPU_MACH (cpu))

+#define APPLICATION_EXIT 0x20026
+
+#define SYS_OPEN 0x01
+#define SYS_GET_CMDLINE 0x15
+#define SYS_EXIT 0x18
+
+#define GDB_O_RDONLY 0x000
+#define GDB_O_WRONLY 0x001
+#define GDB_O_RDWR 0x002
+#define GDB_O_APPEND 0x008
+#define GDB_O_CREAT 0x200
+#define GDB_O_TRUNC 0x400
+#define GDB_O_BINARY 0
+
+#define SEMI_HOSTING_SLLI 0x01f01013
+#define SEMI_HOSTING_SRAI 0x40705013
+
+static int gdb_open_modeflags[12] = {
+ GDB_O_RDONLY,
+ GDB_O_RDONLY | GDB_O_BINARY,
+ GDB_O_RDWR,
+ GDB_O_RDWR | GDB_O_BINARY,
+ GDB_O_WRONLY | GDB_O_CREAT | GDB_O_TRUNC,
+ GDB_O_WRONLY | GDB_O_CREAT | GDB_O_TRUNC | GDB_O_BINARY,
+ GDB_O_RDWR | GDB_O_CREAT | GDB_O_TRUNC,
+ GDB_O_RDWR | GDB_O_CREAT | GDB_O_TRUNC | GDB_O_BINARY,
+ GDB_O_WRONLY | GDB_O_CREAT | GDB_O_APPEND,
+ GDB_O_WRONLY | GDB_O_CREAT | GDB_O_APPEND | GDB_O_BINARY,
+ GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND,
+ GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND | GDB_O_BINARY
+};
+
#endif
diff --git a/sim/riscv/sim-main.c b/sim/riscv/sim-main.c
index afdfcf50656..8e08eb9fc4e 100644
--- a/sim/riscv/sim-main.c
+++ b/sim/riscv/sim-main.c
@@ -136,6 +136,176 @@ store_csr (SIM_CPU *cpu, const char *name, int csr, unsigned_word *reg,
TRACE_REGISTER (cpu, "wrote CSR %s = %#" PRIxTW, name, val);
}

+/* Read 4 or 8 bytes of data from the core memory. ADDR and (INDEX * XLEN)
+ form the base address. */
+static uintptr_t
+get_core_data (SIM_CPU *cpu, unsigned_word addr, unsigned_word index)
+{
+ uintptr_t param;
+ int xlen = RISCV_XLEN (cpu);
+ struct riscv_sim_cpu *riscv_cpu = RISCV_SIM_CPU (cpu);
+
+ if (xlen == 64)
+ param = sim_core_read_unaligned_8 (cpu, riscv_cpu->pc, read_map,
+ addr + (index * 8));
+ else
+ param = sim_core_read_unaligned_4 (cpu, riscv_cpu->pc, read_map,
+ addr + (index * 4));
+
+ return param;
+}
+
+/* Write string in HOST_BUF at CORE_ADDR. The length of string is LEN. */
+static void
+set_core_string (SIM_CPU *cpu, unsigned_word core_addr, char *host_buf,
+ int len)
+{
+ struct riscv_sim_cpu *riscv_cpu = RISCV_SIM_CPU (cpu);
+ for (int i = 0; i < len; i++)
+ {
+ sim_core_write_unaligned_1 (cpu, riscv_cpu->pc, write_map,
+ core_addr + i, host_buf[i]);
+ }
+}
+
+/* Read string of length LEN at address ADDR. */
+static char *
+get_core_string_with_len (SIM_CPU *cpu, unsigned_word addr,
+ unsigned_word len)
+{
+ struct riscv_sim_cpu *riscv_cpu = RISCV_SIM_CPU (cpu);
+ char * str;
+ str = (char *) malloc (len + 1);
+
+ for (int i = 0; i < len; i++)
+ {
+ str[i] = sim_core_read_unaligned_1 (cpu, riscv_cpu->pc, read_map,
+ addr + i);
+ }
+ str[len] = 0;
+
+ return str;
+}
+
+/* SYS_OPEN
+ Register a1 points to a buffer containing:
+ Index 0: Pointer: Address of the file name string.
+ Index 1: Integer: File open flags.
+ Index 2: Integer: Length of the file name string.
+ File handle is returned through register a0. */
+static void
+semihosting_open (SIM_CPU *cpu)
+{
+ uintptr_t fname_addr;
+ uintptr_t flags;
+ uintptr_t fname_len;
+ char *name;
+ struct riscv_sim_cpu *riscv_cpu = RISCV_SIM_CPU (cpu);
+
+ fname_addr = (uintptr_t) get_core_data (cpu, riscv_cpu->a1, 0);
+ flags = (uintptr_t) get_core_data (cpu, riscv_cpu->a1, 1);
+ fname_len = (uintptr_t) get_core_data (cpu, riscv_cpu->a1, 2);
+
+ if (fname_len <= 0)
+ {
+ riscv_cpu->a0 = -1;
+ return;
+ }
+
+ name = get_core_string_with_len (cpu, fname_addr, fname_len);
+ riscv_cpu->a0 = sim_io_open (CPU_STATE (cpu), name,
+ gdb_open_modeflags[flags]);
+ free (name);
+}
+
+/* SYS_EXIT. In case of RV32, register a1 holds the application stop reason
+ and the exit code is decided based on it. Otherwise, register a1 points to
+ a buffer containing:
+ Index 0: Integer: Application stop reason (ignored)
+ Index 1: Integer: Exit code. */
+static void
+semihosting_exit (SIM_CPU *cpu)
+{
+ uintptr_t app_code, exit_code;
+ SIM_DESC sd = CPU_STATE (cpu);
+ struct riscv_sim_cpu *riscv_cpu = RISCV_SIM_CPU (cpu);
+ if (RISCV_XLEN (cpu) == 32)
+ {
+ app_code = riscv_cpu->a1;
+ if (app_code == APPLICATION_EXIT)
+ exit_code = 0;
+ else
+ exit_code = 1;
+ }
+ else
+ exit_code = (uintptr_t) get_core_data (cpu, riscv_cpu->a1, 1);
+ riscv_cpu->a0 = exit_code;
+ sim_engine_halt (sd, cpu, NULL, riscv_cpu->pc, sim_exited, exit_code);
+}
+
+/* SYS_GET_CMDLINE. Write command line arguments to a buffer. Arguments
+ passed to "run" command are stored in STATE_PROG_ARGV member of SIM_DESC.
+ Register a1 points to a buffer containing:
+ Index 0: Pointer: Buffer to store command line arguments
+ Index 1: Integer: Maximum length of the buffer. */
+static void
+semihosting_get_cmdline (SIM_CPU *cpu)
+{
+ int i = 0, len = 0, total_len = 0;
+ uintptr_t buf_addr, max_buf_len;
+ SIM_DESC sd = CPU_STATE (cpu);
+ char *space = " ";
+ struct riscv_sim_cpu *riscv_cpu = RISCV_SIM_CPU (cpu);
+
+ char **prog_argv = STATE_PROG_ARGV (sd);
+ if (prog_argv == NULL)
+ {
+ riscv_cpu->a0 = 1; /* Return non-zero to indicate error. */
+ return;
+ }
+
+ buf_addr = (uintptr_t) get_core_data (cpu, riscv_cpu->a1, 0);
+ max_buf_len = (uintptr_t) get_core_data (cpu, riscv_cpu->a1, 1);
+
+ while (prog_argv[i])
+ {
+ len = strlen (prog_argv[i]);
+ if ((total_len + len) > max_buf_len)
+ break;
+ set_core_string (cpu, buf_addr, prog_argv[i], len);
+ set_core_string (cpu, buf_addr + len, space, 1);
+ len++; /* Terminate it with space. */
+ buf_addr += len;
+ total_len += len;
+ i++;
+ }
+ riscv_cpu->a0 = 0; /* No error. */
+}
+
+/* Handle semi-hosting calls. Register a0 contains semi-hosting call number
+ and a1 contains pointer to a buffer containing additional arguments. */
+static int
+do_semihosting (SIM_CPU *cpu)
+{
+ struct riscv_sim_cpu *riscv_cpu = RISCV_SIM_CPU (cpu);
+ switch (riscv_cpu->a0)
+ {
+ case SYS_OPEN:
+ semihosting_open (cpu);
+ break;
+ case SYS_GET_CMDLINE:
+ semihosting_get_cmdline (cpu);
+ break;
+ case SYS_EXIT:
+ semihosting_exit (cpu);
+ break;
+ default:
+ return -1; /* Semi-hosting call not supported. */
+ }
+
+ return 0;
+}
+
static inline unsigned_word
ashiftrt (unsigned_word val, unsigned_word shift)
{
@@ -623,11 +793,60 @@ execute_i (SIM_CPU *cpu, unsigned_word iw, const struct riscv_opcode *op)
TRACE_INSN (cpu, "fence.i;");
break;
case MATCH_EBREAK:
- TRACE_INSN (cpu, "ebreak;");
- /* GDB expects us to step over EBREAK. */
- sim_engine_halt (sd, cpu, NULL, riscv_cpu->pc + 4, sim_stopped,
- SIM_SIGTRAP);
- break;
+ {
+ /* If ebreak is at PC 0 then do not check for semi-hosting. */
+ if (riscv_cpu->pc != 0)
+ {
+ /* RISC-V semi-hosting call is flagged using these three
+ instructions
+ slli zero, zero, 0x1f 0x01f01013
+ ebreak 0x00100073
+ srai zero, zero, 0x7 0x40705013
+ Register a0 holds the system call number and a1 holds the
+ pointer to parameter buffer. Do not read 4 bytes in one go
+ as we might read malformed 4 byte instruction. */
+ int iw_len;
+ sim_cia pre_pc = riscv_cpu->pc - 4;
+ unsigned_word pre_iw;
+ pre_iw = sim_core_read_aligned_2 (cpu, pre_pc, exec_map, pre_pc);
+ iw_len = riscv_insn_length (pre_iw);
+ if (iw_len == 4)
+ pre_iw |= ((unsigned_word) sim_core_read_aligned_2
+ (cpu, pre_pc, exec_map, pre_pc + 2) << 16);
+
+ if (pre_iw == SEMI_HOSTING_SLLI)
+ {
+ sim_cia post_pc = riscv_cpu->pc + 4;
+ unsigned_word post_iw;
+ post_iw = sim_core_read_aligned_2 (cpu, post_pc, exec_map,
+ post_pc);
+ iw_len = riscv_insn_length (post_iw);
+ if (iw_len == 4)
+ post_iw |= ((unsigned_word) sim_core_read_aligned_2
+ (cpu, post_pc, exec_map, post_pc + 2) << 16);
+
+ if (post_iw == SEMI_HOSTING_SRAI)
+ {
+ TRACE_INSN (cpu, "semi-hosting a0=%lx,a1=%lx;",
+ riscv_cpu->a0, riscv_cpu->a1);
+ if (do_semihosting (cpu))
+ {
+ /* Invalid semi-hosting call. */
+ TRACE_INSN (cpu, "ebreak;");
+ sim_engine_halt (sd, cpu, NULL, riscv_cpu->pc,
+ sim_stopped, SIM_SIGTRAP);
+ }
+ else
+ pc = pc + 4; /* post srai. */
+ break;
+ }
+ }
+ }
+ TRACE_INSN (cpu, "ebreak;");
+ sim_engine_halt (sd, cpu, NULL, riscv_cpu->pc, sim_stopped,
+ SIM_SIGTRAP);
+ break;
+ }
case MATCH_ECALL:
TRACE_INSN (cpu, "ecall;");
riscv_cpu->a0 = sim_syscall (cpu, riscv_cpu->a7, riscv_cpu->a0,
@@ -990,6 +1209,7 @@ execute_one (SIM_CPU *cpu, unsigned_word iw, const struct riscv_opcode *op)
case INSN_CLASS_A:
return execute_a (cpu, iw, op);
case INSN_CLASS_I:
+ case INSN_CLASS_ZICSR:
return execute_i (cpu, iw, op);
case INSN_CLASS_M:
case INSN_CLASS_ZMMUL:
--
2.25.1