Description
Problem Statement
According to the RISC-V libc implementation, when a Linux userspace program exits via exit()
[1] or via _exit()
[2], the crt ultimately triggers the exit_group()
[3] system call, thus fallthrough to SET_CAUSE_AND_TVAL_THEN_TRAP()
. For example, the ls
command exits via the exit_group()
syscall. In contrast, some programs—such as our Doom application—may directly exits via inline assembly to trigger a syscall for trap-and-emulate cleanup routines.
The syscall number 93
is handled by rv32emu. In the SDL-enabled configuration, syscall_exit()
performs SDL cleanup and return and does not halt the RISC-V hart, allowing the emulator to continue running. However, for Linux userspace programs that directly invoke syscall 93
or syscall _exit()
[4] (and are not SDL-enabled and are not via libc's exit()
or _exit()
), the syscall_handler
will call rv_halt()
, halting the hart and causing rv32emu to exit before emulating the next instruction block. The example victim program is zstd
which available from Buildroot (other Linux userspace programs that exit via syscall 93
or syscall _exit()
will also be affected).
This bug was unexpectedly created after #551.
The issue originates from the following code snippets:
src/emulate.c
void ecall_handler(riscv_t *rv)
{
...
if (rv->priv_mode == RV_PRIV_U_MODE) {
switch (rv_get_reg(
rv,
rv_reg_a7)) { /* trap guestOS's SDL-oriented application syscall */
case 0xBEEF:
case 0xC0DE:
case 0xFEED:
case 0xBABE:
case 0xD00D:
case 93:
syscall_handler(rv);
rv->PC += 4;
break;
default:
SET_CAUSE_AND_TVAL_THEN_TRAP(rv, ECALL_U, 0);
break;
}
...
}
src/syscall.c
static void syscall_exit(riscv_t *rv)
{
#if RV32_HAS(SDL) && RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
/*
* The guestOS may repeatedly open and close the SDL window,
* and the user could close the application using the application’s
* built-in exit function. Need to trap the built-in exit and
* ensure the SDL window and SDL mixer are destroyed properly.
*/
extern void sdl_video_audio_cleanup();
sdl_video_audio_cleanup();
return;
#endif
/* simply halt cpu and save exit code.
* the application decides the usage of exit code
*/
rv_halt(rv);
vm_attr_t *attr = PRIV(rv);
attr->exit_code = rv_get_reg(rv, rv_reg_a0);
}
[1] exit(3)
[2] _exit(3)
[3] exit_group(2)
[4] _exit(2)