QEMU crashes when executing a RISC-V compressed instruction with C extension disabled.
Host environment
- Operating system: Debian 11
- OS/kernel version: (For POSIX hosts, use
uname -a) - Architecture: x86_64
- QEMU flavor: qemu-system-riscv32 (but I think this also reproduces for RV64)
- QEMU version: v7.2.0-rc3 (c4ffd91a) - I was also able to reproduce this as far back as v6.2.0
- QEMU command line:
qemu-system-riscv32 -monitor none -serial none -machine virt,accel=tcg -cpu rv32,i=true,c=false -kernel crash.elf -nographic -bios none
Emulated/Virtualized environment
- Baremetal RISC-V 32 bits (was trying to run https://github.com/picolibc/picolibc testsuite).
Description of problem
When binaries are built with compressed instructions, but QEMU is launched with C extension disabled we get a crash instead of a trap that can be handled by the fault handler. It is quite possible that this only asserts if the compressed instruction is the first instruction after a new translation block due to the unconditional trap generated by:
if (!has_ext(ctx, RVC)) {
gen_exception_illegal(ctx);
} else {
Although I would not expect the TB to be empty. Unfortunately it appears to crash before -d op prints any output.
Steps to reproduce
- Compile the following assembly code to an ELF32 binary:
clang --target=riscv32-none-elf -nostdlib -o crash.elf ./crash.S -Wl,--section-start=.text=0x80000000
.text
.global _start
.type _start,@function
_start:
# .4byte 0x300022f3 # csrr t0,mstatus
# NB: compressed instruction, if we start qemu with c=false,
# this results in the following error:
# qemu-system-riscv32: ../../upstream-qemu/accel/tcg/translate-all.c:762: int setjmp_gen_code(CPUArchState *, TranslationBlock *, target_ulong, void *, int *, int64_t *): Assertion `tb->size != 0' failed.
bne t0, t1, .Lfoo # This instruction is not strictly necessary, but it makes the debug output a bit more useful
.Lfoo:
.2byte 0x6309 # lui t1,0x2
j _start
qemu-system-riscv32 -monitor none -serial none -machine virt,accel=tcg -cpu rv32,i=true,c=false -kernel crash.elf -nographic -bios none -d in_asm,op,op_opt,unimp- Assertion failure:
qemu-system-riscv32: ../../upstream-qemu/accel/tcg/translate-all.c:762: int setjmp_gen_code(CPUArchState *, TranslationBlock *, target_ulong, void *, int *, int64_t *): Assertiontb->size != 0' failed.`
Additional information
Here is the output of -d in_asm,op,op_opt,unimp,nochain:
----------------
IN:
Priv: 3; Virt: 0
0x00001000: 00000297 auipc t0,0 # 0x1000
0x00001004: 02828613 addi a2,t0,40
0x00001008: f1402573 csrrs a0,mhartid,zero
OP:
ld_i32 tmp1,env,$0xfffffffffffffff0
brcond_i32 tmp1,$0x0,lt,$L0
---- 00001000 00000000
mov_i32 x5/t0,$0x1000
---- 00001004 00000000
add_i32 x12/a2,x5/t0,$0x28
---- 00001008 f1402573
call csrr,$0x0,$1,x10/a0,env,$0xf14
mov_i32 pc,$0x100c
exit_tb $0x0
set_label $L0
exit_tb $0x7f5824000043
OP after optimization and liveness analysis:
ld_i32 tmp1,env,$0xfffffffffffffff0 pref=0xffff
brcond_i32 tmp1,$0x0,lt,$L0 dead: 0 1
---- 00001000 00000000
mov_i32 x5/t0,$0x1000 sync: 0 dead: 0 1 pref=0xffff
---- 00001004 00000000
mov_i32 x12/a2,$0x1028 sync: 0 dead: 0 1 pref=0xffff
---- 00001008 f1402573
call csrr,$0x0,$1,x10/a0,env,$0xf14 sync: 0 dead: 0 1 2 pref=none
mov_i32 pc,$0x100c sync: 0 dead: 0 1 pref=0xffff
exit_tb $0x0
set_label $L0
exit_tb $0x7f5824000043
----------------
IN:
Priv: 3; Virt: 0
0x0000100c: 0202a583 lw a1,32(t0)
0x00001010: 0182a283 lw t0,24(t0)
0x00001014: 00028067 jr t0
OP:
ld_i32 tmp1,env,$0xfffffffffffffff0
brcond_i32 tmp1,$0x0,lt,$L0
---- 0000100c 00000000
add_i32 tmp1,x5/t0,$0x20
qemu_ld_i32 x11/a1,tmp1,leul,3
---- 00001010 00000000
add_i32 tmp1,x5/t0,$0x18
qemu_ld_i32 x5/t0,tmp1,leul,3
---- 00001014 00000000
mov_i32 pc,x5/t0
and_i32 pc,pc,$0xfffffffe
and_i32 tmp1,pc,$0x2
brcond_i32 tmp1,$0x0,ne,$L1
call lookup_tb_ptr,$0x6,$1,tmp6,env
goto_ptr tmp6
set_label $L1
st_i32 pc,env,$0x1228
mov_i32 pc,$0x1014
call raise_exception,$0x8,$0,env,$0x0
set_label $L0
exit_tb $0x7f5824000183
OP after optimization and liveness analysis:
ld_i32 tmp1,env,$0xfffffffffffffff0 pref=0xffff
brcond_i32 tmp1,$0x0,lt,$L0 dead: 0
---- 0000100c 00000000
add_i32 tmp1,x5/t0,$0x20 dead: 2 pref=0xff3f
qemu_ld_i32 x11/a1,tmp1,leul,3 sync: 0 dead: 0 1 pref=0xffff
---- 00001010 00000000
add_i32 tmp1,x5/t0,$0x18 dead: 1 2 pref=0xff3f
qemu_ld_i32 x5/t0,tmp1,leul,3 sync: 0 dead: 1 pref=0xffff
---- 00001014 00000000
mov_i32 pc,x5/t0 dead: 1 pref=0xffff
and_i32 pc,pc,$0xfffffffe sync: 0 dead: 1 2 pref=0xffff
and_i32 tmp1,pc,$0x2 dead: 1 2 pref=0xffff
brcond_i32 tmp1,$0x0,ne,$L1 dead: 0 1
call lookup_tb_ptr,$0x6,$1,tmp6,env dead: 1 pref=none
goto_ptr tmp6 dead: 0
set_label $L1
st_i32 pc,env,$0x1228 dead: 0
mov_i32 pc,$0x1014 sync: 0 dead: 0 1 pref=0xffff
call raise_exception,$0x8,$0,env,$0x0 dead: 0 1
set_label $L0
exit_tb $0x7f5824000183
----------------
IN:
Priv: 3; Virt: 0
0x80000000: 00629263 bne t0,t1,4 # 0x80000004
OP:
ld_i32 tmp1,env,$0xfffffffffffffff0
brcond_i32 tmp1,$0x0,lt,$L0
---- 80000000 00000000
mov_i32 tmp1,x5/t0
mov_i32 tmp2,x6/t1
brcond_i32 tmp1,tmp2,ne,$L1
mov_i32 pc,$0x80000004
call lookup_tb_ptr,$0x6,$1,tmp4,env
goto_ptr tmp4
set_label $L1
mov_i32 pc,$0x80000004
call lookup_tb_ptr,$0x6,$1,tmp4,env
goto_ptr tmp4
set_label $L0
exit_tb $0x7f5824000383
OP after optimization and liveness analysis:
ld_i32 tmp1,env,$0xfffffffffffffff0 pref=0xffff
brcond_i32 tmp1,$0x0,lt,$L0 dead: 0 1
---- 80000000 00000000
brcond_i32 x5/t0,x6/t1,ne,$L1 dead: 0 1
mov_i32 pc,$0x80000004 sync: 0 dead: 0 1 pref=0xffff
call lookup_tb_ptr,$0x6,$1,tmp4,env dead: 1 pref=none
goto_ptr tmp4 dead: 0
set_label $L1
mov_i32 pc,$0x80000004 sync: 0 dead: 0 1 pref=0xffff
call lookup_tb_ptr,$0x6,$1,tmp4,env dead: 1 pref=none
goto_ptr tmp4 dead: 0
set_label $L0
exit_tb $0x7f5824000383
----------------
IN:
Priv: 3; Virt: 0
qemu-system-riscv32: ../../upstream-qemu/accel/tcg/translate-all.c:762: int setjmp_gen_code(CPUArchState *, TranslationBlock *, target_ulong, void *, int *, int64_t *): Assertion `tb->size != 0' failed.