Skip to content
Open
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
15 changes: 15 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[target.x86_64-unknown-linux-gnu]
linker = "x86_64-linux-gnu-gcc"
rustflags = [
"-Clink-args=-nostartfiles",
"-Clink-args=-Wl,-n,-N,--no-dynamic-linker,--build-id=none",
"-Crelocation-model=static"
]

[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
rustflags = [
"-Clink-args=-nostartfiles",
"-Clink-args=-Wl,-n,-N,--no-dynamic-linker,--build-id=none",
"-Crelocation-model=static"
]
8 changes: 8 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM mcr.microsoft.com/devcontainers/rust:1-bookworm

RUN apt-get update \
&& apt-get install -y \
binutils-aarch64-linux-gnu binutils-x86-64-linux-gnu \
gcc-aarch64-linux-gnu gcc-x86-64-linux-gnu \
make nasm qemu-user-static \
&& rm -rf /var/lib/apt/lists/*
31 changes: 31 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/rust
{
"name": "Rust",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"dockerFile": "Dockerfile",

// Use 'mounts' to make the cargo cache persistent in a Docker Volume.
// "mounts": [
// {
// "source": "devcontainer-cargo-cache-${devcontainerId}",
// "target": "/usr/local/cargo",
// "type": "volume"
// }
// ]

// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},

// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],

// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "rustc --version"

// Configure tool-specific properties.
// "customizations": {},

// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
12 changes: 12 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for more information:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
# https://containers.dev/guide/dependabot

version: 2
updates:
- package-ecosystem: "devcontainers"
directory: "/"
schedule:
interval: weekly
40 changes: 12 additions & 28 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -1,41 +1,25 @@
name: Build & Test
on:
- push
push:
branches: [ main, rust ]
pull_request:
branches: [ main ]

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout Repo
uses: actions/checkout@v2

- name: Install dependencies
run: |
sudo apt-get update -y
sudo apt-get install -y make nasm binutils binutils-aarch64-linux-gnu
uses: actions/checkout@v5

- name: Build via Makefile
run: make ci_build
- name: Build and run Dev Container task
uses: devcontainers/ci@v0.3
with:
runCmd: make -s ci_tests

- name: Public artifact
uses: actions/upload-artifact@v1
- name: Publish artifact
uses: actions/upload-artifact@v4
with:
name: Build Artifact
path: out/

test:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout Repo
uses: actions/checkout@v2

- name: Install dependencies
run: |
sudo apt-get update -y
sudo apt-get install -y make nasm binutils binutils-aarch64-linux-gnu
sudo wget https://github.com/multiarch/qemu-user-static/releases/download/v7.1.0-2/qemu-aarch64-static -O /usr/sbin/qemu-aarch64-static
sudo chmod +x /usr/sbin/qemu-aarch64-static

- name: Run Tests
run: make ci_tests
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.idea
out/
target/
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[workspace]
resolver = "2"
members = ["rs-nap"]

[profile.release]
strip = true
opt-level = "z"
codegen-units = 1
panic = "abort"
lto = true
15 changes: 3 additions & 12 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,12 @@ ci_build:
x86_64-linux-gnu-ld -m elf_x86_64 -z noseparate-code -z noexecstack --strip-all -o out/nap nap.o && rm nap.o
aarch64-linux-gnu-as nap-aarch64.s -o nap-aarch64.o
aarch64-linux-gnu-ld -z noseparate-code -z noexecstack --strip-all -o out/nap-aarch64 nap-aarch64.o && rm nap-aarch64.o
cargo build --release --target-dir target --target x86_64-unknown-linux-gnu && cp target/x86_64-unknown-linux-gnu/release/rs-nap out/rs-nap-x86_64
cargo build --release --target-dir target --target aarch64-unknown-linux-gnu && cp target/aarch64-unknown-linux-gnu/release/rs-nap out/rs-nap-aarch64
ls -la out/

ci_tests: ci_build
if [ $$(arch) = "x86_64" ]; then \
echo "[x86_64] Testing 1s nap"; \
timeout 3s out/nap 1 2>&1 > /dev/null; \
echo "[x86_64] Testing 10s default/bad input nap"; \
timeout 12s out/nap bad_arg ; if [ $$? = "1" ]; then true; else false; fi; \
else \
echo "x86_64 testing not available on aarch64 platform"; \
fi
echo "[aarch64] Testing 1s nap"
timeout 3s qemu-aarch64-static out/nap-aarch64 1 2>&1 > /dev/null
echo "[aarch64] Testing 10s default/bad input nap"
timeout 12s qemu-aarch64-static out/nap-aarch64 bad_arg ; if [ $$? = "1" ]; then true; else false; fi
@bash run-tests.sh

tests: install
if [ $$(arch) = "x86_64" ]; then \
Expand Down
7 changes: 7 additions & 0 deletions rs-nap/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions rs-nap/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "rs-nap"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
60 changes: 60 additions & 0 deletions rs-nap/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Rust rewrite

## Goals

### Feature parity

- [x] Maintain roughly the same binary size
- [x] Zero requirements other than the linux kernel
- [x] Increased readability over assembly
- [x] Shared code base for all architectures

### Architecture support

- [x] x86_64 implementation
- [x] aarch64 implementation

#### Architecture specific implementation notes

For each new architecture:

1. Add a file to the `support` subfolder which implements
- The startup code (`_start`) to get arguments from the stack
- and the following linux kernel service calls
- `sys_exit` to call `exit`
- `sys_write` to call `write` to stdout 1
- and `sys_sleep`
2. Add a platform specific entry to `support.rs` in the form of a `cfg_attr`
3. Add a platform target to `.cargo/config.toml`
4. Add a toolchain target to `rust-toolchain.toml`
5. Update this `README.md` with the new platform build and run commands
6. Update any CI/CD (future) to build the binary for the target platform

## Run on a native processor

```sh
# Build
cargo build --release
# Display file size
ls -lah ./target/release/rs-nap
# Run for 3 seconds
./target/release/rs-nap 3
```

## Run on an emulated processor

```sh
# Install cross compiler and emulation layer

# Debian-based distros
sudo apt -y install gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu qemu-user-static
```

```sh
# Build
cargo build --target aarch64-unknown-linux-gnu --release
# Display file size
ls -lah target/aarch64-unknown-linux-gnu/release/rs-nap
# Run for 3 seconds
qemu-aarch64-static target/aarch64-unknown-linux-gnu/release/rs-nap 3
```
52 changes: 52 additions & 0 deletions rs-nap/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#![no_std]
#![no_main]

mod support;
use support::*;
use core::slice::from_raw_parts as mkslice;

#[no_mangle]
pub unsafe fn nap(args: &[*const u8]) -> ! {
let mut sleep_time:usize = 10;
let mut good_input:bool = false;

if args.len() > 1 {
(sleep_time, good_input) = get_sleep_time(args[1]);
}

sleep(sleep_time, good_input);

print_str(b"Done!\n");
sys_exit(0)
}

pub unsafe fn sleep(mut sleep_time: usize, good_input: bool) {
if good_input == false {
Copy link

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

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

[nitpick] Comparing boolean values with == false is not idiomatic in Rust. Use !good_input instead.

Suggested change
if good_input == false {
if !good_input {

Copilot uses AI. Check for mistakes.
sleep_time = 10;
print_str(b"Bad input. ");
}

print_str(b"Sleeping for ");
print_num(sleep_time);
print_str(b" seconds...\n");

sys_sleep(sleep_time);
}

unsafe fn get_sleep_time(arg: *const u8) -> (usize, bool) {
let (seconds,_) = from_radix_10(mkslice(arg, strlen(arg)));
(seconds, seconds > 0 && seconds < 1000000000)
}

#[no_mangle]
unsafe fn get_args(stack_top: *const u8) {
let argc = *(stack_top as *const usize);
let argv = stack_top.add(8) as *const *const u8;
let args = mkslice(argv, argc as usize);
nap(args)
}

#[panic_handler]
unsafe fn my_panic(_info: &core::panic::PanicInfo) -> ! {
sys_exit(255)
}
17 changes: 17 additions & 0 deletions rs-nap/src/support.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#[cfg(all(target_arch="x86_64", target_os="linux"))]
#[path="support/x86_64.rs"]
mod arch_support;

#[cfg(all(target_arch="aarch64", target_os="linux"))]
#[path="support/aarch64.rs"]
mod arch_support;

#[cfg(not(target_os="linux"))]
#[path="support/generic.rs"]
mod arch_support;

pub use arch_support::*;

#[path="support/noarch.rs"]
mod noarch;
pub use noarch::*;
47 changes: 47 additions & 0 deletions rs-nap/src/support/aarch64.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use core::arch::{asm,naked_asm};

mod interop;
use interop::timespec;

#[no_mangle]
#[unsafe(naked)]
unsafe extern "C" fn _start() {
// Move the stack pointer before it gets clobbered
naked_asm!(
"mov fp, sp",
"mov x0, fp",
"bl get_args"
)
}

pub unsafe fn sys_exit(exit_code:usize) -> ! {
asm!("svc 0",
in("w8") 93,
in("x0") exit_code,
options(nostack, noreturn)
)
}

pub unsafe fn sys_write(buffer: *const u8, count: usize) {
asm!("svc #0",
Copy link

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

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

[nitpick] Inconsistent syscall instruction format: this uses svc #0 while line 41 uses svc 0 (without #). Consider standardizing the format for consistency.

Suggested change
asm!("svc #0",
asm!("svc 0",

Copilot uses AI. Check for mistakes.
inout("x0") 1 => _,
inout("x1") buffer => _,
inout("x2") count => _,
inout("x8") 64 => _,
options(nostack)
)
}

pub unsafe fn sys_sleep(seconds: usize) {
let sleep_time = timespec {
tv_sec: seconds as isize,
tv_nsec: 0
};

asm!("svc 0",
in("x0") &sleep_time,
in("x1") 0,
in("x8") 101,
options(nostack, preserves_flags)
)
}
Loading