Skip to content

Commit 26c0707

Browse files
committed
crates/asm: fix aarch64 stack alignment, add bcmp/memcmp, gate entry for tests
aarch64 logical instructions encode register 31 as xzr, not sp, so `and sp, sp, #-16` is invalid. Use a temp register (x9) instead. Add bcmp and memcmp implementations needed for -static linking without libc (riscv64 codegen emits calls to bcmp for byte comparisons). Gate _start, entry_rust, and the extern main declaration behind cfg(not(test)) so cargo test can provide its own test harness main.
1 parent 601d017 commit 26c0707

2 files changed

Lines changed: 89 additions & 190 deletions

File tree

crates/asm/src/lib.rs

Lines changed: 25 additions & 190 deletions
Original file line numberDiff line numberDiff line change
@@ -55,212 +55,47 @@ pub unsafe extern "C" fn memset(s: *mut u8, c: i32, n: usize) -> *mut u8 {
5555
s
5656
}
5757

58-
/// Calculates the length of a null-terminated string.
58+
/// Compares two byte sequences.
5959
///
6060
/// # Safety
6161
///
62-
/// `s` must be a valid pointer to a null-terminated string.
62+
/// `s1` and `s2` must be valid pointers to memory of at least `n` bytes.
6363
#[unsafe(no_mangle)]
64-
pub const unsafe extern "C" fn strlen(s: *const u8) -> usize {
65-
let mut len = 0;
66-
while unsafe { *s.add(len) } != 0 {
67-
len += 1;
68-
}
69-
len
70-
}
71-
72-
/// Function pointer type for the main application entry point.
73-
/// The function receives argc and argv and should return an exit code.
74-
pub type MainFn = unsafe extern "C" fn(i32, *const *const u8) -> i32;
75-
76-
static mut MAIN_FN: Option<MainFn> = None;
77-
78-
/// Register the main function to be called from the entry point.
79-
/// This must be called before the program starts (e.g., in a constructor).
80-
pub fn register_main(main_fn: MainFn) {
81-
unsafe {
82-
MAIN_FN = Some(main_fn);
64+
pub unsafe extern "C" fn bcmp(s1: *const u8, s2: *const u8, n: usize) -> i32 {
65+
for i in 0..n {
66+
let a = unsafe { *s1.add(i) };
67+
let b = unsafe { *s2.add(i) };
68+
if a != b {
69+
return i32::from(a) - i32::from(b);
70+
}
8371
}
72+
0
8473
}
8574

86-
/// Rust entry point called from `_start` assembly.
87-
///
88-
/// The `stack` pointer points to:
89-
/// `[rsp]` = argc
90-
/// `[rsp+8]` = argv[0]
91-
/// etc.
75+
/// Compares two byte sequences.
9276
///
9377
/// # Safety
9478
///
95-
/// The `stack` pointer must point to valid stack memory set up by the kernel
96-
/// AND the binary must define a `main` function with the following signature:
97-
///
98-
/// ```rust,ignore
99-
/// unsafe extern "C" fn main(argc: i32, argv: *const *const u8) -> i32`
100-
/// ```
79+
/// `s1` and `s2` must be valid pointers to memory of at least `n` bytes.
10180
#[unsafe(no_mangle)]
102-
pub unsafe extern "C" fn entry_rust(stack: *const usize) -> i32 {
103-
// Read argc and argv from stack
104-
let argc = unsafe { *stack };
105-
let argv = unsafe { stack.add(1).cast::<*const u8>() };
106-
107-
// SAFETY: argc is unlikely to exceed i32::MAX on real systems
108-
let argc_i32 = i32::try_from(argc).unwrap_or(i32::MAX);
109-
110-
// Call the main function (defined by the binary crate)
111-
unsafe { main(argc_i32, argv) }
112-
}
113-
114-
// External main function that must be defined by the binary using this crate.
115-
// Signature: `unsafe extern "C" fn main(argc: i32, argv: *const *const u8) ->
116-
// i32`
117-
unsafe extern "C" {
118-
fn main(argc: i32, argv: *const *const u8) -> i32;
81+
pub unsafe extern "C" fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32 {
82+
unsafe { bcmp(s1, s2, n) }
11983
}
12084

121-
#[cfg(target_arch = "x86_64")]
122-
mod entry {
123-
use core::arch::naked_asm;
124-
125-
/// Entry point that receives stack pointer directly from kernel.
126-
/// On `x86_64` Linux at program start:
127-
///
128-
/// - `[rsp]` = argc
129-
/// - `[rsp+8]` = argv[0]
130-
/// - `[rsp+16]` = argv[1]
131-
/// - ...
132-
/// - `[rsp+8n]` = NULL
133-
/// - `[rsp+8n+8]` = envp[0]
134-
///
135-
/// # Safety
136-
///
137-
/// This is a naked function with no prologue or epilogue. It directly
138-
/// manipulates the stack pointer (`rsp`) and assumes it was called by the
139-
/// kernel with a valid stack containing argc and argv. The function:
140-
///
141-
/// - Reads from `[rsp]` without validating the pointer
142-
/// - Modifies `rsp` directly (16-byte alignment)
143-
/// - Does not preserve any registers
144-
/// - Does not return normally (exits via syscall)
145-
///
146-
/// This function MUST only be used as the program entry point (`_start`).
147-
/// Calling it from any other context is undefined behavior. This has been
148-
/// your safety notice. I WILL put UB in your Rust program.
149-
#[unsafe(no_mangle)]
150-
#[unsafe(naked)]
151-
pub unsafe extern "C" fn _start() {
152-
naked_asm!(
153-
// Move stack pointer to first argument register
154-
"mov rdi, rsp",
155-
// Align stack to 16-byte boundary (System V AMD64 ABI requirement)
156-
"and rsp, -16",
157-
// Call into Rust code
158-
"call {entry_rust}",
159-
// Move return code to syscall argument
160-
"mov rdi, rax",
161-
// Exit syscall
162-
"mov rax, 60", // SYS_exit
163-
"syscall",
164-
entry_rust = sym super::entry_rust,
165-
);
166-
}
167-
}
168-
169-
#[cfg(target_arch = "aarch64")]
170-
mod entry {
171-
use core::arch::naked_asm;
172-
173-
/// Entry point that receives stack pointer directly from kernel.
174-
/// On `aarch64` Linux at program start, the stack layout is identical
175-
/// to x86_64:
176-
///
177-
/// - `[sp]` = argc
178-
/// - `[sp+8]` = argv[0]
179-
/// - ...
180-
///
181-
/// # Safety
182-
///
183-
/// This is a naked function with no prologue or epilogue. It directly
184-
/// manipulates the stack pointer (`sp`) and assumes it was called by the
185-
/// kernel with a valid stack containing argc and argv. The function:
186-
///
187-
/// - Reads from `[sp]` without validating the pointer
188-
/// - Modifies `sp` directly (16-byte alignment)
189-
/// - Does not preserve any registers
190-
/// - Does not return normally (exits via SVC instruction)
191-
///
192-
/// This function MUST only be used as the program entry point (`_start`).
193-
/// Calling it from any other context is undefined behavior.
194-
#[unsafe(no_mangle)]
195-
#[unsafe(naked)]
196-
pub unsafe extern "C" fn _start() {
197-
naked_asm!(
198-
// Move stack pointer to first argument register
199-
"mov x0, sp",
200-
// Align stack to 16-byte boundary (AArch64 ABI requirement)
201-
"and sp, sp, -16",
202-
// Call into Rust code
203-
"bl {entry_rust}",
204-
// Move return code to syscall argument
205-
"mov x0, x0",
206-
// Exit syscall
207-
"mov x8, 93", // SYS_exit
208-
"svc #0",
209-
entry_rust = sym super::entry_rust,
210-
);
211-
}
212-
}
213-
214-
#[cfg(target_arch = "riscv64")]
215-
mod entry {
216-
use core::arch::naked_asm;
217-
218-
/// Entry point that receives stack pointer directly from kernel.
219-
/// On `riscv64` Linux at program start, the stack layout is identical
220-
/// to x86_64:
221-
///
222-
/// - `[sp]` = argc
223-
/// - `[sp+8]` = argv[0]
224-
/// - ...
225-
///
226-
/// # Safety
227-
///
228-
/// This is a naked function with no prologue or epilogue. It directly
229-
/// manipulates the stack pointer (`sp`) and assumes it was called by the
230-
/// kernel with a valid stack containing argc and argv. The function:
231-
///
232-
/// - Reads from `[sp]` without validating the pointer
233-
/// - Modifies `sp` directly (16-byte alignment)
234-
/// - Does not preserve any registers
235-
/// - Does not return normally (exits via ECALL instruction)
236-
///
237-
/// This function MUST only be used as the program entry point (`_start`).
238-
/// Calling it from any other context is undefined behavior.
239-
#[unsafe(no_mangle)]
240-
#[unsafe(naked)]
241-
pub unsafe extern "C" fn _start() {
242-
naked_asm!(
243-
// Move stack pointer to first argument register
244-
"mv a0, sp",
245-
// Align stack to 16-byte boundary (RISC-V ABI requirement)
246-
"andi sp, sp, -16",
247-
// Call into Rust code
248-
"call {entry_rust}",
249-
// Move return code to syscall argument
250-
"mv a0, a0",
251-
// Exit syscall
252-
"li a7, 93", // SYS_exit
253-
"ecall",
254-
entry_rust = sym super::entry_rust,
255-
);
85+
/// Calculates the length of a null-terminated string.
86+
///
87+
/// # Safety
88+
///
89+
/// `s` must be a valid pointer to a null-terminated string.
90+
#[unsafe(no_mangle)]
91+
pub const unsafe extern "C" fn strlen(s: *const u8) -> usize {
92+
let mut len = 0;
93+
while unsafe { *s.add(len) } != 0 {
94+
len += 1;
25695
}
96+
len
25797
}
25898

259-
// Re-export the entry point
260-
#[cfg(target_arch = "x86_64")] pub use entry::_start;
261-
#[cfg(target_arch = "aarch64")] pub use entry::_start;
262-
#[cfg(target_arch = "riscv64")] pub use entry::_start;
263-
26499
/// Direct syscall to open a file
265100
///
266101
/// # Returns

microfetch/src/main.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,77 @@
33

44
extern crate alloc;
55

6+
use core::arch::naked_asm;
67
use core::panic::PanicInfo;
78

89
use microfetch_alloc::BumpAllocator;
910
// Re-export libc replacement functions from asm crate
1011
pub use microfetch_asm::{memcpy, memset, strlen};
1112
use microfetch_asm::{sys_exit, sys_write};
1213

14+
// ---------------------------------------------------------------------------
15+
// Program entry point: _start → entry_rust → main
16+
//
17+
// These live in the binary crate (not the asm library) because #[no_mangle]
18+
// _start in an .rlib would conflict with Scrt1.o when any dependent crate
19+
// builds a test harness.
20+
// ---------------------------------------------------------------------------
21+
22+
/// Rust entry point called from the arch-specific `_start` trampolines below.
23+
#[unsafe(no_mangle)]
24+
unsafe extern "C" fn entry_rust(stack: *const usize) -> i32 {
25+
let argc = unsafe { *stack };
26+
let argv = unsafe { stack.add(1).cast::<*const u8>() };
27+
let argc_i32 = i32::try_from(argc).unwrap_or(i32::MAX);
28+
unsafe { main(argc_i32, argv) }
29+
}
30+
31+
#[cfg(target_arch = "x86_64")]
32+
#[unsafe(no_mangle)]
33+
#[unsafe(naked)]
34+
unsafe extern "C" fn _start() {
35+
naked_asm!(
36+
"mov rdi, rsp",
37+
"and rsp, -16",
38+
"call {entry_rust}",
39+
"mov rdi, rax",
40+
"mov rax, 60",
41+
"syscall",
42+
entry_rust = sym entry_rust,
43+
);
44+
}
45+
46+
#[cfg(target_arch = "aarch64")]
47+
#[unsafe(no_mangle)]
48+
#[unsafe(naked)]
49+
unsafe extern "C" fn _start() {
50+
naked_asm!(
51+
"mov x0, sp",
52+
"mov x9, sp",
53+
"and x9, x9, #-16",
54+
"mov sp, x9",
55+
"bl {entry_rust}",
56+
"mov x8, #93",
57+
"svc #0",
58+
entry_rust = sym entry_rust,
59+
);
60+
}
61+
62+
#[cfg(target_arch = "riscv64")]
63+
#[unsafe(no_mangle)]
64+
#[unsafe(naked)]
65+
unsafe extern "C" fn _start() {
66+
naked_asm!(
67+
"mv a0, sp",
68+
"andi sp, sp, -16",
69+
"call {entry_rust}",
70+
"mv a0, a0",
71+
"li a7, 93",
72+
"ecall",
73+
entry_rust = sym entry_rust,
74+
);
75+
}
76+
1377
// Global allocator
1478
#[global_allocator]
1579
static ALLOCATOR: BumpAllocator = BumpAllocator::new();

0 commit comments

Comments
 (0)