Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ jobs:
working-directory: pr-${{ env.PR_NUMBER }}/obj
run: ccache --evict-older-than 7200s --dir=$CCACHE_DIR

- name: Install dependencies
shell: bash
run: |
sudo apt update
sudo apt install -y libsdl2-2.0-0
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to use the qemu system emulator. the machine that runs the CI workflow is missing a library. Shakti suggested adding it to the workflow but clearly that did not work. If you have any suggestions about how to address this let me know.


- name: Run tests
if: steps.file-check.outputs.skip_build == 'false'
working-directory: pr-${{ env.PR_NUMBER }}/obj
Expand Down
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,17 +127,17 @@ Configure the project with `-DELD_ENABLE_RUN_TESTS=On` and set the
following environment variables for each target architecture you want
to enable run tests on:

| Architecture | C Compiler | C++ Compiler | Sysroot | Emulator |
|--------------|------------|--------------|---------|----------|
| AArch64 | `ELD_AARCH64_C_COMPILER` | `ELD_AARCH64_CXX_COMPILER` | `ELD_AARCH64_SYSROOT` | `ELD_AARCH64_EMULATOR` |
| ARM | `ELD_ARM_C_COMPILER` | `ELD_ARM_CXX_COMPILER` | `ELD_ARM_SYSROOT` | `ELD_ARM_EMULATOR` |
| RISCV32 | `ELD_RISCV32_C_COMPILER` | `ELD_RISCV32_CXX_COMPILER` | `ELD_RISCV32_SYSROOT` | `ELD_RISCV32_EMULATOR` |
| RISCV64 | `ELD_RISCV64_C_COMPILER` | `ELD_RISCV64_CXX_COMPILER` | `ELD_RISCV64_SYSROOT` | `ELD_RISCV64_EMULATOR` |
| X86_64 | `ELD_X86_64_C_COMPILER` | `ELD_X86_64_CXX_COMPILER` | `ELD_X86_64_SYSROOT` | `ELD_X86_64_EMULATOR` |
| Architecture | C Compiler | C++ Compiler | Sysroot | Emulator | Emulator System|
|--------------|------------|--------------|---------|----------|----------------|
| AArch64 | `ELD_AARCH64_C_COMPILER` | `ELD_AARCH64_CXX_COMPILER` | `ELD_AARCH64_SYSROOT` | `ELD_AARCH64_EMULATOR` | `ELD_AARCH64_EMULATOR_SYSTEM` |
| ARM | `ELD_ARM_C_COMPILER` | `ELD_ARM_CXX_COMPILER` | `ELD_ARM_SYSROOT` | `ELD_ARM_EMULATOR` | `ELD_ARM_EMULATOR_SYSTEM` |
| RISCV32 | `ELD_RISCV32_C_COMPILER` | `ELD_RISCV32_CXX_COMPILER` | `ELD_RISCV32_SYSROOT` | `ELD_RISCV32_EMULATOR` | `ELD_RISCV32_EMULATOR_SYSTEM` |
| RISCV64 | `ELD_RISCV64_C_COMPILER` | `ELD_RISCV64_CXX_COMPILER` | `ELD_RISCV64_SYSROOT` | `ELD_RISCV64_EMULATOR` | `ELD_RISCV64_EMULATOR_SYSTEM` |
| X86_64 | `ELD_X86_64_C_COMPILER` | `ELD_X86_64_CXX_COMPILER` | `ELD_X86_64_SYSROOT` | `ELD_X86_64_EMULATOR` | `ELD_X86_64_EMULATOR_SYSTEM` |

The `run_test` lit feature is enabled when the C compiler, sysroot, and emulator are all configured for a target. The `run_cxx_test` feature additionally requires the C++ compiler.

For `<TARGET>_C_COMPILER`, `<TARGET>_CXX_COMPILER`, and `<TARGET>_EMULATOR`,
For `<TARGET>_C_COMPILER`, `<TARGET>_CXX_COMPILER`, `<TARGET>_EMULATOR`, and `<TARGET>_EMULATOR_SYSTEM`,
you can provide either a full path or just the tool name
(which will be resolved via `PATH`).

Expand All @@ -149,6 +149,7 @@ export ELD_AARCH64_C_COMPILER=/usr/bin/aarch64-linux-gnu-gcc
export ELD_AARCH64_CXX_COMPILER=/usr/bin/aarch64-linux-gnu-g++
export ELD_AARCH64_SYSROOT=/usr/aarch64-linux-gnu
export ELD_AARCH64_EMULATOR=qemu-aarch64
export ELD_AARCH64_EMULATOR_SYSTEM=qemu-system-aarch64
```


Expand Down
6 changes: 6 additions & 0 deletions test/ARM/RunTests/R_ARM_CALL/Inputs/distant_callee.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Distant callee function placed in a far section
// This will require a trampoline for ARM BL instruction

__attribute__((section(".text.distant"))) int distant_callee(void) {
return 42;
}
22 changes: 22 additions & 0 deletions test/ARM/RunTests/R_ARM_CALL/Inputs/hidden_callee.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Hidden symbol: non-preemptible, direct relocation, no PLT entry.
#define UART0_DR ((volatile unsigned int *)0x10009000)
Comment thread
Steven6798 marked this conversation as resolved.
#define UART0_FR ((volatile unsigned int *)0x10009018)

__attribute__((noinline, visibility("hidden"))) int hidden_callee() {
return 7;
}

int main(void) {
if (hidden_callee() != 7) {
return 1;
}
const char *s = "success\n";

while (*s) {
while (*UART0_FR & (1 << 5))
;

*UART0_DR = *s++;
}
return 0;
}
13 changes: 13 additions & 0 deletions test/ARM/RunTests/R_ARM_CALL/Inputs/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#define UART0_DR ((volatile unsigned int *)0x10009000)
#define UART0_FR ((volatile unsigned int *)0x10009018)

int main(void) {
const char *s = "success\n";

while (*s) {
while (*UART0_FR & (1 << 5))
;
*UART0_DR = *s++;
}
return 0;
}
12 changes: 12 additions & 0 deletions test/ARM/RunTests/R_ARM_CALL/Inputs/script.ld
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
ENTRY(_start)

SECTIONS
{
. = 0x60010000;

.text : { *(.text) }

. = ALIGN(8);
. = . + 0x1000; /* 4KB stack */
stack_top = .;
}
16 changes: 16 additions & 0 deletions test/ARM/RunTests/R_ARM_CALL/Inputs/script_trampoline.ld
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
ENTRY(_start)

SECTIONS
{
. = 0x60010000;

.text : { *(.text) }

. = ALIGN(8);
. = . + 0x1000; /* 4KB stack */
stack_top = .;

/* Place distant callee in a far section for trampoline testing */
. = 0x62020000;
.text.distant : { *(.text.distant) }
}
3 changes: 3 additions & 0 deletions test/ARM/RunTests/R_ARM_CALL/Inputs/shared_callee.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Preemptible symbol in a shared library: R_ARM_CALL goes through PLT.

__attribute__((noinline)) int shared_callee() { return 3; }
11 changes: 11 additions & 0 deletions test/ARM/RunTests/R_ARM_CALL/Inputs/shared_caller.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Caller that uses a preemptible symbol resolved via PLT at runtime.
#include <stdio.h>
extern int shared_callee();

int main() {
if (shared_callee() != 3) {
return 1;
}
puts("success");
return 0;
}
4 changes: 4 additions & 0 deletions test/ARM/RunTests/R_ARM_CALL/Inputs/startup.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.global _start
_start:
ldr sp, =stack_top
bl main
20 changes: 20 additions & 0 deletions test/ARM/RunTests/R_ARM_CALL/Inputs/thumb_callee.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Thumb target: R_ARM_CALL from ARM caller triggers BL->BLX rewrite.
#define UART0_DR ((volatile unsigned int *)0x10009000)
#define UART0_FR ((volatile unsigned int *)0x10009018)

__attribute__((noinline, target("thumb"))) int thumb_callee() { return 5; }

__attribute__((target("arm"))) int main() {
if (thumb_callee() != 5) {
return 1;
}
const char *s = "success\n";

while (*s) {
while (*UART0_FR & (1 << 5))
;

*UART0_DR = *s++;
}
return 0;
}
22 changes: 22 additions & 0 deletions test/ARM/RunTests/R_ARM_CALL/Inputs/trampoline_caller.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Caller that invokes a distant function
// This requires a trampoline to be created by the linker

#define UART0_DR ((volatile unsigned int *)0x10009000)
#define UART0_FR ((volatile unsigned int *)0x10009018)

extern int distant_callee(void);

int main(void) {
int result = distant_callee();

// Print success if we got here (trampoline worked)
const char *s = "success\n";

while (*s) {
while (*UART0_FR & (1 << 5))
;

*UART0_DR = *s++;
}
return result;
}
19 changes: 19 additions & 0 deletions test/ARM/RunTests/R_ARM_CALL/Inputs/weak.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Weak undefined symbol: linker rewrites BL to NOP.
// The call must be silently skipped and execution must continue normally.
#define UART0_DR ((volatile unsigned int *)0x10009000)
#define UART0_FR ((volatile unsigned int *)0x10009018)

__attribute__((weak)) void weak_undef_func();

int main(void) {
weak_undef_func(); // must not crash - linker converts BL to NOP
const char *s = "success\n";

while (*s) {
while (*UART0_FR & (1 << 5))
;

*UART0_DR = *s++;
}
return 0;
}
54 changes: 54 additions & 0 deletions test/ARM/RunTests/R_ARM_CALL/R_ARM_CALL.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
REQUIRES: run_test
#---R_ARM_CALL.test--------------------- Executable------------------#
#BEGIN_COMMENT
# Test the ARM R_ARM_CALL relocation.
# Covers: basic ARM-to-ARM call (static/PIE/dynamic), local and hidden
# (non-preemptible) symbols, weak undefined symbol NOP conversion,
# Thumb target BL->BLX rewrite, PLT call via shared library, and
# trampoline creation for out-of-range branches.
#END_COMMENT
#START_TEST
## --- Basic ARM-to-ARM call: local and global callee ---
#RUN: %run_cc -nostdlib -c -o %t.startup.o %p/Inputs/startup.s
#RUN: %run_cc -nostdlib -c -o %t.main.o %p/Inputs/main.c
# Static
#RUN: %link -T %p/Inputs/script.ld -static -o %t.static.out %t.startup.o %t.main.o
#RUN: %not timeout .1 %run_sys -M vexpress-a9 -m 32M -kernel %t.static.out | %filecheck %s
# PIE
#RUN: %link -T %p/Inputs/script.ld -pie -o %t.pie.out %t.startup.o %t.main.o
#RUN: %not timeout .1 %run_sys -M vexpress-a9 -m 32M -kernel %t.pie.out | %filecheck %s
# Dynamic
#RUN: %link -T %p/Inputs/script.ld -Wl,-dy -o %t.dyn.out %t.startup.o %t.main.o
#RUN: %not timeout .1 %run_sys -M vexpress-a9 -m 32M -kernel %t.dyn.out | %filecheck %s

## --- Weak undefined symbol: BL rewritten to NOP, execution continues ---
#RUN: %run_cc -nostdlib -c -o %t.weak.o %p/Inputs/weak.c
#RUN: %link -T %p/Inputs/script.ld -static -o %t.weak.out %t.startup.o %t.weak.o
#RUN: %not timeout .1 %run_sys -M vexpress-a9 -m 32M -kernel %t.weak.out | %filecheck %s

## --- Hidden (non-preemptible) symbol: direct call, no PLT ---
#RUN: %run_cc -nostdlib -c -o %t.hidden.o %p/Inputs/hidden_callee.c
#RUN: %link -T %p/Inputs/script.ld -static -o %t.hidden.out %t.startup.o %t.hidden.o
#RUN: %not timeout .1 %run_sys -M vexpress-a9 -m 32M -kernel %t.hidden.out | %filecheck %s

## --- Thumb target: BL rewritten to BLX at link time ---
#RUN: %run_cc -nostdlib -c -o %t.thumb.o %p/Inputs/thumb_callee.c
#RUN: %link -T %p/Inputs/script.ld -static -o %t.thumb.out %t.startup.o %t.thumb.o
#RUN: %not timeout .1 %run_sys -M vexpress-a9 -m 32M -kernel %t.thumb.out | %filecheck %s

## --- Preemptible symbol: call goes through PLT at runtime ---
#RUN: %run_cc -fPIC -o %t.shared_callee.o -c %p/Inputs/shared_callee.c
#RUN: %run_cc -fPIC -o %t.shared_caller.o -c %p/Inputs/shared_caller.c
#RUN: %run_cc -shared -o %t.callee.so %t.shared_callee.o
#RUN: %run_cc -o %t.shared.out %t.shared_caller.o %t.callee.so -Wl,-dy
#RUN: %run %t.shared.out

## --- Trampoline: out-of-range branch requires trampoline creation ---
#RUN: %run_cc -nostdlib -c -o %t.distant_callee.o %p/Inputs/distant_callee.c
#RUN: %run_cc -nostdlib -c -o %t.trampoline_caller.o %p/Inputs/trampoline_caller.c
#RUN: %link -T %p/Inputs/script_trampoline.ld -static -o %t.trampoline.out %t.startup.o %t.trampoline_caller.o %t.distant_callee.o
#RUN: %not timeout .1 %run_sys -M vexpress-a9 -m 64M -kernel %t.trampoline.out | %filecheck %s

#CHECK: success

#END_TEST
10 changes: 9 additions & 1 deletion test/lit.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -214,10 +214,12 @@ dirname = 'dirname'
datalayout = ''
link_path = which(link)
run=""
run_sys=""
run_cc = ''
run_cxx = ''
sysroot = ''
emulator = ''
emulator_system = ''
Copy link
Copy Markdown
Contributor

@parth-07 parth-07 Jun 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please explain why do we need system emulator? For which cases linux emulator is not enough?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shankar suggested using the system emulator to isolate the testing as much as possible. For example, when static linking a lot gets included in the binary, with this approach, only a minimal amount of code is included. What do you think?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@quic-seaswara Hi Shankar, I am concerned that writing baremetal code and using system emulator requires more boilerplate and makes testing more difficult without providing any apparent benefit.

to isolate the testing as much as possible

We are only removing the libc dependency -- all other dependency are still there, and we are unlikely to encounter bugs/corner-cases in libc when doing extended testing of different relocations computations because we won't be using any advanced feature of libc.

For example, when static linking a lot gets included in the binary, with this approach, only a minimal amount of code is included.

I agree with this, but I am not sure if less size is worth the benefit here. We can always delete the test artifacts after the testing.


# Control RISC-V Compression extension option, on by default
clangnorvcopts = ''
Expand Down Expand Up @@ -540,16 +542,19 @@ if enable_run_tests:
run_cxx = resolve_env_tool(f'ELD_{arch_env}_CXX_COMPILER')
sysroot = os.environ.get(f'ELD_{arch_env}_SYSROOT', '')
fallback_emulator = "qemu-" + arch_env.lower()
fallback_emulator_system = "qemu-system-" + arch_env.lower()
emulator = resolve_env_tool(f'ELD_{arch_env}_EMULATOR') or which(fallback_emulator)
emulator_system = resolve_env_tool(f'ELD_{arch_env}_EMULATOR_SYSTEM') or which(fallback_emulator_system)

if run_cc and sysroot and emulator:
if run_cc and sysroot and emulator and emulator_system:
config.available_features.add('run_test')
run_cc += f" --ld-path={link_path} --sysroot={sysroot}"
# This is required to ensure that libc.so from sysroot is loaded by
# the runtime loader. dynamic linker searches both the standard paths
# and the paths within the syroot to find libc. If the host sytem
# contains a valid libc.so, then the dynamic linker can select that.
run = f'{emulator} -E LD_LIBRARY_PATH={sysroot}/lib -L {sysroot}'
run_sys = f'{emulator_system} -nographic -audio none -display none'
if run_cxx:
run_cxx += f" --ld-path={link_path} --sysroot={sysroot}"
config.available_features.add('run_cxx_test')
Expand Down Expand Up @@ -612,7 +617,9 @@ else:
lit_config.note('using run_cc: {}'.format(run_cc))
lit_config.note('using run_cxx: {}'.format(run_cxx))
lit_config.note('using sysroot: {}'.format(sysroot))
lit_config.note('using emulator_system: {}'.format(emulator_system))
lit_config.note('using emulator: {}'.format(emulator))
lit_config.note('using run_sys: {}'.format(run_sys))
lit_config.note('using run: {}'.format(run))
# Add substitutions.

Expand Down Expand Up @@ -710,6 +717,7 @@ config.substitutions.append( ("%emulation","".join(config.emulation)) )
config.substitutions.append( ("%run_cc", run_cc) )
config.substitutions.append( ("%run_cxx", run_cxx) )
config.substitutions.append( ("%sysroot", sysroot) )
config.substitutions.append( ("%run_sys","".join(run_sys)) )
config.substitutions.append( ("%run","".join(run)) )
if muslclang is not None:
config.substitutions.append( ("%musl-clang","".join(muslclang)) )
Loading