Loading assembly to memory¶
Renode allows the user to load assembly code in several ways:
LLVMAssembler used mainly for tests,
writing hex instructions directly which can be used for custom instructions,
loading compiled ASM code directly, described below
Note
Before going further, it is recommended to read the Debugging with GDB and Debugging with VSCode chapters.
Prerequisites¶
You need to have the target architecture’s GNU toolchain, e.g RISC-V or ARM. We’ll use riscv64-unknown-elf-*
utilities (for ARM you’d use arm-none-eabi-*
).
Compiling assembly code to an executable¶
Let’s start with simple RISC-V assembly that stores and loads from memory at 0x00064000
:
.globl _start
_start:
li a0, 0x00064000
li a2, 0x5
sw zero, (a0)
lw a4, (a0)
sw a2, (a0)
lw a4, (a0)
loop:
j loop
To compile the code use your GNU toolchain’s as
and ld
:
riscv64-unknown-elf-as -march=rv64imac_zba_zbb asm.s -o asm.o
riscv64-unknown-elf-ld asm.o -o asm.elf
Let’s use this simple asm.resc file with platform:
using sysbus
machine LoadPlatformDescriptionFromString
"""
cpu: CPU.RiscV64 @ sysbus
cpuType: "rv64imac_zba_zbb"
privilegedArchitecture: PrivilegedArchitecture.Priv1_10
timeProvider: empty
dram: Memory.MappedMemory @ sysbus 0x00
size: 0x06400000
"""
machine StartGdbServer 3333
sysbus LoadELF @asm.elf
Notice, that we need CPU type and model in our platform to match compiled ASM code. Here we have model CPU.RiscV64
and cpuType
as rv64imac_zba_zbb
.
For more details go to Configuring a RISC-V CPU.
Start Renode with this .resc file:
$ renode asm.resc
(monitor) i $CWD/test.resc
(machine-0)
Now in separate terminal use the target architecture’s GDB:
$ riscv64-unknown-elf-gdb asm.elf
In GDB connect to Renode’s GDB server:
(gdb) target remote :3333
Remote debugging using :3333
0x00000000000100b0 in _start ()
It’s best to use GDB’s TUI with ASM and register view. To do that:
(gdb) tui enable
(gdb) layout asm
(gdb) layout regs
Now you can see registers and our assembly source.
To step over use si
.
┌─Register group: general────────────────────────────────────────────────────────────────────────────────────┐
│zero 0x0 0 ra 0x0 0x0 │
│sp 0x0 0x0 gp 0x0 0x0 │
│tp 0x0 0x0 t0 0x0 0 │
│t1 0x0 0 t2 0x0 0 │
│fp 0x0 0x0 s1 0x0 0 │
│a0 0x0 0 a1 0x0 0 │
│a2 0x0 0 a3 0x0 0 │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
│ >0x100b0 <_start> lui a0,0x64 │
│ 0x100b4 <_start+4> li a2,5 │
│ 0x100b6 <_start+6> sw zero,0(a0) │
│ 0x100ba <_start+10> lw a4,0(a0) │
│ 0x100bc <_start+12> sw a2,0(a0) │
│ 0x100be <_start+14> lw a4,0(a0) │
│ 0x100c0 <loop> j 0x100c0 <loop> │
│ 0x100c2 unimp │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
remote Thread 1 (asm) In: _start L?? PC: 0x100b0
(gdb)
Do note that some instructions are pseudoinstructions and the source you’ve written may not necessarily be the same as the assembly view in GDB. The generated instructions may also differ between LLVMAssembler and GNU as.
Debugging tlib¶
Note
Remember to compile and start Renode with debug flag (build.sh -d
, renode -d
) for this to work!
Renode also gives you option to see how the instructions are being translated. To do that we’ll use VSCode.
After starting Renode and attaching GDB, move to VSCode and navigate to src/Infrastructure/src/Emulator/Cores/tlib/arch/riscv/translate.c
and set the breakpoint right before the case OPC_RISC_LUI:
, and use the provided (gdb) Tlib Attach
launch script.
Search for the Renode process, it should be mono
or dotnet
type, attach to it (you may need to elevate your privileges to root, for
rootless debugging check YAMA’s ptrace_scope).
Then do si
in GDB, which should make your VSCode stop at the previously-set breakpoint.