RISC-V semi-hosting principle and practice

Embedded bare-metal debugging needs to dig out as much information as possible on the target hardware with limited resources, such as printing registers, etc., but even the seemingly simple serial port printing is unrealistic in some cases. In this case, you can The semi-host (semi-host) technology, which effectively utilizes host resources for collaborative debugging, came into being. The following is a brief introduction about semi-host.

  1. The semi-host mechanism uses the code running on the target processor (arm, riscv, etc.) to communicate with the host running the debugger and use its IO facilities.
  2. These facilities include keyboard, screen and disk IO.
  3. The embedded end uses the functions (printf, scanf) in the C library to be able to use the screen and keyboard of the host computer without having a screen and keyboard on the target system.
  4. Semi-host was first defined by ARM in 1995. It published the ARM semi-host specification and is supported by many debuggers and C libraries. Debuggers such as jlink (rtti), trace32, and ICE emulator, etc., C libraries include newlib, picolibc, etc. .
  5. RISCV also defines its own semihosting specification based on the ARM version.

How semihost works

semihosting=semi + hosting, indicating that half of the semihosting operation is executed on the target device, and the other half is executed on the host. The semihosting traps the host through a set of special software instruction sequences, such as the svc instruction of ARM and the ebreak instruction of RISCV. These instructions will start the CPU to enter the abnormal execution flow. In the abnormal execution flow, the debugging agent program is executed to handle the exception. The debugging agent program includes the logic implementation of parsing the semihosting command and realizing the communication with the host computer.

The figure below shows the process of printing debugging information to the host on a resource-constrained target platform without relying on the serial port.

The process is as follows:

  • The application on the target device calls the standard library function printf.
  • The bottom layer of the library will not redirect the call to the serial port, but after preparing the data to be sent, use the special command ebreak to notify the debugger.
  • After the debugger receives the notification, it detects the SEMI-HOST request, executes the corresponding handler, and displays the string.

The figure below is the code that triggers the SEMI-HOST request under the RISCV architecture in newlibc, in the libgloss/riscv/semihost_syscall.h file.

Since the CPU does not reserve exception numbers for semi-hosts, in order to distinguish exceptions triggered by semihosts from other exceptions, additional instructions are added around the ebreak instruction to help the debugger distinguish between “semi-host ebreak” and “regular ebreak”.

Other conventions include that the semihosting call number is passed through the register a0, the pointer of the call parameter is passed through the a1 register, and the return value is placed in a0.

The specification defines a total of 24 types of semihosting calls:

Definition of methods in picolibc

C library that supports semihosting
  1. Newlibc, as screenshot above.
  2. Picolibc, fork from newlibc. But lighter.
  3. Arm CMSIS.

As mentioned above, semihosting is mainly to solve the problem of collaborative debugging and development on platforms with limited resources. LIBC libraries such as glibc and musl libc for large-scale application scenarios such as LINUX do not need to support semihosting, because the latter mainly runs on RICH OS. , without supporting the semihosting mechanism.

QEMU semi-host test environment construction

The test application is based on picolibc, and the compilation method is as follows:

$ sudo apt install gcc-riscv64-unknown-elf meson
$ git clone https://github.com/picolibc/picolibc.git
$ cd picolibc & amp; & amp; mkdir build & amp; & amp; cd build
$ ../scripts/do-riscv-configure
$ ninja
$ suso ninja install

Compile and generate the test case test/semihost/semihost-exit-extended-failure_rv64imafdc_lp64d, then install

Compile RISCV64 QEMU:

Reference blog Qemu’s operating mechanism on ARM and X86 platforms – papaofdoudou’s blog – CSDN blog

Here, QEMU 8.0.0 is used to compile and configure:

$ ./configure --target-list=arm-softmmu,aarch64-softmmu,i386-softmmu,x86_64-softmmu,riscv32-softmmu,riscv64-softmmu,aarch64-linux-user,arm-linux-user,riscv64 -linux-user,x86_64-linux-user --audio-drv-list=alsa,sdl,pa --enable-system --enable-user --enable-linux-user --enable-sdl --enable-vnc --enable-virtfs --enable-kvm --enable-fdt --enable-debug --disable-strip --enable-debug-tcg --enable-debug-info --enable-debug --disable-strip - -enable-vnc --prefix=/home/zlcao/semihost/install
$ make
$ make install

Test case:

#include <stdio.h>

int main(void)
{
printf("%s line %d, hello semihosting.\\
", __func__, __LINE__);

return 0;
}

compile

$ riscv64-unknown-elf-gcc --specs picolibc.specs --oslib=semihost -march=rv64imac -mabi=lp64 -mcmodel=medany -static main.c -o main

–oslib=semihost tells the compiler to use the semihost implementation of picolibc, since normally it also has an implementation. Use picolibc.specs to link the program to run at 0x80000000, which is the start address of most RISCV and its memory, and also the memory base address of QEMU “virt” and it. Since the toolchain used is a bare metal toolchain, this address is a physical address.

Use qemu “virt” -serial stdio to see that there is no output, because we use the printf call of the semi-host and do not use the serial terminal, so there is no output here.

./../install/bin/qemu-system-riscv64 -M virt -bios main -display none -serial stdio

But when we add -serial null -semihosting to run, the output will appear. The purpose of -serial null is to prohibit the standard output, so if there is output, it must be a semihosting mechanism, which is convenient for comparison.

$ ./../install/bin/qemu-system-riscv64 -M virt -bios main -display none -serial null -semihosting

The purpose of -bios is to prohibit opensbi from starting.

Hand rubbing bare metal semihosting use case

The previous use case uses picolibc’s semihosted version of printf, and only depends on its implementation, so we can imitate printf’s hand-wrapped output string call, pass in a0 No. 4 semihosted call, call name SYS_WRITEO, the call Output a NULL-terminated character string on the target machine to the terminal on the host side.

Implementation code:

Test case source code:

static void smh_puts(char *str)
{
asm volatile("addi a1, %0, 0\\
"
"addi a0, zero, 4\\
"
".balign 16\\
"
".option push\\
"
".option norvc\\
"
"slli zero, zero, 0x1f\\
"
"ebreak\\
"
"srai zero, zero, 0x7\\
"
".option pop\\
"
: : "r"(str) : "a0", "a1", "memory");
}

int main(void)
{
smh_puts("hello semihosting.\\
");

return 0;
}
C startup source code:
.text
.globl_start

_start:
li sp, 0x80100000
tail main

link script semihosting.ld

OUTPUT_ARCH("riscv")
ENTRY(_start)

SECTIONS
{
. = 0x80000000;
.text : {
crt0.o (.text)
main.o (.text)
}

.rodata : {
*(.rodata*)
}

.data : {
*(.data*)
}
\t
.bss : {
*(.bss*)
}
}
makefile
all:
riscv64-unknown-elf-gcc -nostdlib -march=rv64imac -mabi=lp64 -mcmodel=medany -static -c main.c -o main.o
riscv64-unknown-elf-gcc -nostdlib -march=rv64imac -mabi=lp64 -mcmodel=medany -static -c crt0.S -o crt0.o
riscv64-unknown-elf-ld -Tsemihosting.ld -static crt0.o main.o -o main

sim:
../install/bin/qemu-system-riscv64 -M virt -bios main -display none -serial null -semihosting

Test, you can see that the string is printed normally through the SEMI HOST mechanism.

qemu semihost implementation

Compared with running use cases, qemu stands in the perspective of God, which is equivalent to the host side in the SEMIHOST architecture. Its core SEMI HOST implementation logic is implemented with the function do_common_semihosting.

Taking the file operation command of SEMI HOST as an example, the century it calls is finally realized through the OPEN function of the HOST machine.

semihost implementation of fopen

Summary

Semihosting technology provides a convenient development environment to speed up development. The advantages include:

  1. Easy to debug and develop.
  2. save resources.
  3. Rapid Development and Prototyping
  4. Flexible and Portable
Reference article

ARM Semihosting – native but slow Debugging – Code Inside Out

End