Skip to content

Commit

Permalink
add hello world example for RISC-V 32 and 64 bit
Browse files Browse the repository at this point in the history
  • Loading branch information
jackdbd committed Jul 4, 2024
1 parent 673ca29 commit b884cbe
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 0 deletions.
61 changes: 61 additions & 0 deletions examples/hello-riscv/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Hello World in RISC-V

## 64-bit

Assemble:

```sh
riscv64-elf-as -march rv64i -mabi lp64 -o hello.o hello.s
```

Link:

```sh
riscv64-elf-ld -o exe --verbose hello.o
```

Execute the RISC-V ELF in QEMU:

```sh
qemu-riscv64 exe
```

Run a query on the RISC-V ELF:

```sh
poetry run sqlelf examples/hello-riscv/exe \
--sql "SELECT * FROM elf_instructions LIMIT 5;"
```

Or run a query and attach a debugger:

```sh
poetry run sqlelf-debug examples/hello-riscv/exe \
--sql "SELECT * FROM elf_instructions LIMIT 5;"
```

Double check the disassembly:

```sh
riscv64-elf-objdump --disassemble examples/hello-riscv/exe
```

## 32-bit

Assemble `hello.s` into an object file for the RISC-V 32-bit base integer instruction set (`-march rv32i`), little-endian (`-mlittle-endian`), with an ABI that follows the convention where `int`, `long` and `pointer` types are all 32-bit, with debug symbols included in the object file (`-g`):

```sh
riscv64-elf-as -march rv32i -mabi ilp32 -mlittle-endian -o hello.o hello.s -g
```

Link the object file into a RISC-V 32-bit little-endian executable (`-m elf32lriscv`), with the symbol `_start` as its entry point:

```sh
riscv64-elf-ld -e _start -m elf32lriscv -o exe --verbose hello.o
```

Execute the RISC-V ELF in QEMU:

```sh
qemu-riscv32 exe
```
41 changes: 41 additions & 0 deletions examples/hello-riscv/hello.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
.section .text
.globl _start
.equ STDOUT, 1 # File descriptor 1 is standard output (stdout)
.equ WRITE, 64 # Linux write syscall
.equ EXIT, 93 # Linux exit syscall
.equ EXIT_CODE_SUCCESS, 0

_start:
# In C, a list of parameters is passed to the kernel in a certain sequence.
# For the write system call, the parameters are structured as follows:
# ssize_t write(int fd, const void *buf, size_t count)
# The three parameters passed are:
# 1. a file descriptor (e.g. 1 for stdout)
# 2. a pointer to a character buffer (i.e. a string)
# 3. the number of characters in that string to be written.
li a0, STDOUT
la a1, buf_begin
# Load a byte from memory, zero-pad it (to a 64-bit value in RV64), and store
# the unsigned value in the destination register a2.
lbu a2, buf_size

# Store the system call number in register a7.
li a7, WRITE
# Switch to RISC-V supervisor mode (the Linux kernel runs in this mode) and
# make a request using the value stored in a7 as the system call number.
ecall

li a0, EXIT_CODE_SUCCESS
li a7, EXIT
ecall

# The .rodata section of an ELF binary contains constant values. The .rodata
# section is marked as read-only, so these values cannot change at runtime.
.section .rodata

buf_begin:
.string "Hello World!\n"
buf_size:
# Current address (the .) minus address of buf_begin = length of buffer.
# We store the result in a 8-bit word using the .byte directive.
.byte .-buf_begin

0 comments on commit b884cbe

Please sign in to comment.