Implement RISC-V dynamic linking - #323
Conversation
|
As the The updated GitHub Actions also downloads the 32-bit variant to validate RISC-V dynamic linking. |
Evaluate Run 32-bit applications on 64-bit Linux kernel, which is exactly RV32-on-RV64 userspace compatibility, not emulation. |
I'm not sure whether I understand correctly. Do you mean that the proposed changes should be verified on a RISC-V machine? |
See sysprog21/kbox#18 |
446b538 to
44f2f47
Compare
|
RISE RISC-V Runners' documentation explicitly states that binaries must be compiled for riscv64. According to the FAQ - What architectures are supported?.
I created another branch ( Based on both the documentation and my test, it appears that RISE RISC-V runners lack support for 32-bit executables. |
|
RISC-V calling convention:
Item 1: is done by the register allocation phase.The register allocator uses virtual registers (vreg0-vreg7) to allocate reigsters for arguments when encountering a function call. vreg0-vreg7 will be mapped to a0-a7, so first eight arguments are naturally passed to these registers. Since the current shecc only supports up to 8 arguments, no extra arguments need to be passed to the stack. Thus, we can skip this handling. Item 2: is ensured by the RISC-V code generator.When handling the stack pointer, the code generator will guarantee that Item 3:
Therefore, this item can also be considered complete. Further details and explanations can be found in |
2599d74 to
ee9db4a
Compare
de67942 to
1af9140
Compare
|
Since there may be additional requirements to address, I am still keeping this pull request as a draft currently, and will continue to make improvements if necessary. |
5f8b272 to
9be9f62
Compare
Introduce ELF_MACHINE_ARM32 (0x28) and ELF_MACHINE_RV32 (0xf3) to support architecture-specific logic in future developments.
This commit primarily improves the ELF handling and code generator to
enable the compiler to produce a dynamically linked executable targeting
the RISC-V architecture.
- Allow the ELF handling to generate RELA relocation table.
- Use REL relocation when the target architecture is Arm. Othereise,
use RELA relocation for RISC-V.
- Improve GOT generation process.
- Arm: reserve three entries.
- RISC-V: reserve two entries.
- Implement PLT generation for RISC-V.
- The generation process follows the RISC-V ABI. The first PLT entry
uses 8 instructions to call '_dl_runtime_resolve'. The subsequent
entry uses 4 instructions to perform an indirect function call via
GOT.
- Refine the function call handling for the RISC-V code generator.
- Perform a direct call for internal functions
- Otherwise, use PLT table to peform an indirect call for external
functions.
- Enhance the build system:
- Allow the build system to generate dynamically linked compilers when
targeting the RISC-V architecture.
- Detect the sysroot path of the RISC-V GNU toolchain automatically.
Modify the 'update-snapshots' and 'check-snapshots' make targets to include generation and validation of new snapshots for the RISC-V architecture using dynamic linking.
The update workflow now downloads a RISC-V GNU toolchain to provide necessary dependencies and validate the dynamically linked compiler targeting the RISC-V architecture.
Because two architecture-specific makefile fragments contain similar snippets for locating the cross-compilation toolchain path, this commit consolidates them into a shared build logic, thereby reducing code duplication.
A new shell script is introduced to validate whether generated executables targeting RISC-V correct comply with the RISC-V ABI. The tests include: - Parameter Passing: tests function calls with different numbers of arguments. - Stack Alignment: validates whether the stack is always 16-byte aligned when calling a function. - Return Values: confirms if the return value is correct after a function returns. - External Calls: verifies whether dynamically linked programs can call external functions. - Register Preservation: verify whether the contents of function argument registers are properly preserved when calling a function. - Structure Passing: validates if a small structure object can be passed correctly.
- Expand instructions on utilizing dynamic linking for both the Arm and RISC-V architectures. - Describe the stack frame layout for the RISC-V implementation. - Explain caller and callee behaviors when targeting the RISC-V architecture. - Illustrate the RISC-V PLT stub implementation, including assembly code snippets and design intentions. - Add reference links about RISC-V. - glibc implementation of '__dl_runtime_resolve' for RISC-V. - RISC-V ABIs specifications. - Improve the explanation of the runtime execution flow of a dynamically linked program. - Correct the description of callee behavior for the Arm architecture. - Clarify that registers r4-r11 are callee-saved, not caller-saved. - Explain that the saved lr is loaded into pc to return to the caller.
Since the dynamic linking is now supported for the RISC-V architecture, this updates the relevant introductions and usage guides.
9be9f62 to
14220c1
Compare
There was a problem hiding this comment.
4 issues found across 15 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name=".github/workflows/main.yml">
<violation number="1" location=".github/workflows/main.yml:23">
P2: The riscv32 glibc toolchain is downloaded and extracted on every matrix entry, including architecture: arm builds where it's never used. Wrap lines 23-25 with `if: matrix.architecture == 'riscv'` to avoid unnecessary download time (~minutes) and disk usage on ARM CI runs.</violation>
</file>
<file name="src/riscv-codegen.c">
<violation number="1" location="src/riscv-codegen.c:662">
P2: Programs whose global initialization clobbers `t1` or `t2` pass corrupted `argc`/`argv` to `main`. Preserve these values in memory or saved registers across `GLOBAL_FUNC`, restoring them before the main call.</violation>
</file>
<file name="mk/common.mk">
<violation number="1" location="mk/common.mk:46">
P2: Dynamic runs can use the developer’s home directory as qemu’s sysroot when the selected cross compiler has no configured sysroot. Treat an empty `--print-sysroot` result like `/` so the existing target-prefix fallback locates the loader.</violation>
</file>
<file name="docs/dynamic-linking.md">
<violation number="1" location="docs/dynamic-linking.md:281">
P3: The formula `N * 4` for the N-th function's GOT offset is inconsistent with the GOT[2] → offset 0 entry. If 1st function is offset 0, then N-th function offset should be (N-1)*4. Or if GOT[2] is at offset 8, then N-th function offset is (N+1)*4. Either way the table and formula contradict each other.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| sudo apt-get install -q -y qemu-user | ||
| sudo apt-get install -q -y build-essential | ||
| sudo apt-get install -q -y gcc-arm-linux-gnueabihf | ||
| sudo wget -q https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2026.07.15/riscv32-glibc-ubuntu-24.04-gcc.tar.xz |
There was a problem hiding this comment.
P2: The riscv32 glibc toolchain is downloaded and extracted on every matrix entry, including architecture: arm builds where it's never used. Wrap lines 23-25 with if: matrix.architecture == 'riscv' to avoid unnecessary download time (~minutes) and disk usage on ARM CI runs.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/main.yml, line 23:
<comment>The riscv32 glibc toolchain is downloaded and extracted on every matrix entry, including architecture: arm builds where it's never used. Wrap lines 23-25 with `if: matrix.architecture == 'riscv'` to avoid unnecessary download time (~minutes) and disk usage on ARM CI runs.</comment>
<file context>
@@ -27,6 +20,9 @@ jobs:
sudo apt-get install -q -y qemu-user
sudo apt-get install -q -y build-essential
sudo apt-get install -q -y gcc-arm-linux-gnueabihf
+ sudo wget -q https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2026.07.15/riscv32-glibc-ubuntu-24.04-gcc.tar.xz
+ sudo tar Jxf riscv32-glibc-ubuntu-24.04-gcc.tar.xz -C /opt
+ echo "/opt/riscv/bin" >> "$GITHUB_PATH"
</file context>
| * After the main function completes its execution, it must use | ||
| * the content of ra to transfer control back to __libc_start_main(). | ||
| */ | ||
| emit(__addi(__t1, __a0, 0)); |
There was a problem hiding this comment.
P2: Programs whose global initialization clobbers t1 or t2 pass corrupted argc/argv to main. Preserve these values in memory or saved registers across GLOBAL_FUNC, restoring them before the main call.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/riscv-codegen.c, line 662:
<comment>Programs whose global initialization clobbers `t1` or `t2` pass corrupted `argc`/`argv` to `main`. Preserve these values in memory or saved registers across `GLOBAL_FUNC`, restoring them before the main call.</comment>
<file context>
@@ -482,47 +601,273 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir)
+ * After the main function completes its execution, it must use
+ * the content of ra to transfer control back to __libc_start_main().
+ */
+ emit(__addi(__t1, __a0, 0));
+ emit(__addi(__t2, __a1, 0));
+ emit(__sw(__ra, __sp, -4));
</file context>
|
|
||
| ARCH_CC = $(CROSS_COMPILE)gcc | ||
|
|
||
| LD_LINUX_PATH := $(shell cd $(shell $(ARCH_CC) --print-sysroot) 2>/dev/null && pwd) |
There was a problem hiding this comment.
P2: Dynamic runs can use the developer’s home directory as qemu’s sysroot when the selected cross compiler has no configured sysroot. Treat an empty --print-sysroot result like / so the existing target-prefix fallback locates the loader.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At mk/common.mk, line 46:
<comment>Dynamic runs can use the developer’s home directory as qemu’s sysroot when the selected cross compiler has no configured sysroot. Treat an empty `--print-sysroot` result like `/` so the existing target-prefix fallback locates the loader.</comment>
<file context>
@@ -22,6 +22,47 @@ NO_COLOR = \e[0m
+
+ ARCH_CC = $(CROSS_COMPILE)gcc
+
+ LD_LINUX_PATH := $(shell cd $(shell $(ARCH_CC) --print-sysroot) 2>/dev/null && pwd)
+ ifeq ("$(LD_LINUX_PATH)","/")
+ LD_LINUX_PATH := $(shell dirname "$(shell which $(ARCH_CC))")/..
</file context>
| LD_LINUX_PATH := $(shell cd $(shell $(ARCH_CC) --print-sysroot) 2>/dev/null && pwd) | |
| LD_LINUX_PATH := $(shell sysroot="$$($(ARCH_CC) --print-sysroot)"; if [ -n "$$sysroot" ]; then cd "$$sysroot" 2>/dev/null && pwd || printf /; else printf /; fi) |
| | 1st function | `GOT[2]` | `0` | | ||
| | 2nd function | `GOT[3]` | `4` | | ||
| | ... | ... | ... | | ||
| | N-th function | `GOT[N + 1]` | `N * 4` | |
There was a problem hiding this comment.
P3: The formula N * 4 for the N-th function's GOT offset is inconsistent with the GOT[2] → offset 0 entry. If 1st function is offset 0, then N-th function offset should be (N-1)*4. Or if GOT[2] is at offset 8, then N-th function offset is (N+1)*4. Either way the table and formula contradict each other.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/dynamic-linking.md, line 281:
<comment>The formula `N * 4` for the N-th function's GOT offset is inconsistent with the GOT[2] → offset 0 entry. If 1st function is offset 0, then N-th function offset should be (N-1)*4. Or if GOT[2] is at offset 8, then N-th function offset is (N+1)*4. Either way the table and formula contradict each other.</comment>
<file context>
@@ -198,6 +254,49 @@ ldr pc, [ip]
+ | 1st function | `GOT[2]` | `0` |
+ | 2nd function | `GOT[3]` | `4` |
+ | ... | ... | ... |
+ | N-th function | `GOT[N + 1]` | `N * 4` |
+
+- `t2` is `%hi(%pcrel(.got))`, but it is not used by `__dl_runtime_resolve()`.
</file context>
| | N-th function | `GOT[N + 1]` | `N * 4` | | |
| | N-th function | `GOT[N + 1]` | `(N - 1) * 4` | |
About
|
The proposed changes primarily improve the ELF generation and the RISC-V backend, enabling the build system to generate a dynamically linked shecc targeting the RISC-V architecture.
Although the current changes allow both bootstrapping and test suite to complete successfully, this is still a work in progress. The TODO items are listed as follows:
riscv-abi.sh) to validate the RISC-V ABI.arm.mkandriscv.mkinto a common build logic (e.g.: configureRUNNER_LD_PREFIX).Summary by cubic
Implements RV32 dynamic linking using RELA with an ABI‑compliant PLT/GOT and a
__libc_start_mainentry, and addsELF_MACHINE_ARM32/ELF_MACHINE_RV32to drive REL (Arm) vs RELA (RISC‑V). CI now validates static and dynamic builds for botharmandriscv, and a new ABI suite checks RV32 calling, stack, and external call behavior.New Features
.rela.plt; PLT0=32B, stubs=16B; per‑archRESERVED_GOT_NUM(RV32=2, ARM=3);DYN_LINKER/lib/ld-linux-riscv32-ilp32d.so.1;.gotinit honors reserved entries; correct dynamic tags for RELA/REL (DT_RELA*,DT_REL*,DT_PLTREL,DT_PLTRELSZ,DT_JMPREL).__libc_start_mainand returns via savedra; syscall trampoline retained for static; 16‑byte stack alignment.hello,fib); GitHub Actions runs static+dynamic forarmandriscv.Dependencies
mk/common.mkwith per‑archTOOLCHAIN_CANDIDATESand autoRUNNER_LD_PREFIX; CI installs a riscv32 glibc toolchain and exports/opt/riscv/bintoPATH.Written for commit 14220c1. Summary will update on new commits.