This project provides x86-64 assembly implementations of common C standard
library functions, including read, write, strlen, strcmp, strcpy, and strdup.
It adheres as much as possible to the x86-64 calling conventions
and the System V ABI.
It uses libcriterion as a testing framework, so you can see not only
what I've done, but how I've tested it!
.
├── Makefile # Build automation
├── Dockerfile # Container setup for testing
├── README.md # This file
├── src/ # Assembly source files
│ ├── read.s # Implementation of `read`
│ ├── strcmp.s # Implementation of `strcmp`
│ ├── strcpy.s # Implementation of `strcpy`
│ ├── strdup.s # Implementation of `strdup`
│ ├── strlen.s # Implementation of `strlen`
│ └── write.s # Implementation of `write`
├── main.c # The tests for all functions and calling in C.
└── objects/ # Compiled object filesAll functions follow their glibc counterparts.
Want to know how to call one of my functions?
Just man <function_name> and that's it!
read: Reads n bytes from a file descriptor into a buffer.strcmp: Compares two strings.strcpy: Copies a string to a pre allocated buffer.strdup: Duplicates a string, giving a newly allocated copy.strlen: Computes the length of a string.write: Writes n bytes from a buffer into a file descriptor.
Since the function's implementations adhere to
the System V ABI, calling one of
the assembly functions is as simple as including and calling.
For example, to call asm_strcmp in a C file:
// include the function like this:
extern int asm_strcmp(const char *s1, const char *s2);
// or just include all my asm functions like this:
#include "libasm.h"
// Don't worry about double inclusion, there's an IFNDEF in there ;)
// and use it!
int result = asm_strcmp("hello", "world");We also have Rust bindings to the asm functions via a ✨ C FFI layer ✨. So if you're feeling spicy and want to call raw x86 System V ABI compliant assembly from safe Rust, you're in luck.
- The assembly functions are exposed to C via a standard .h header.
- Rust uses a build.rs and hand-written extern definitions to call the C API.
- Safe Rust wrappers are provided to ensure memory safety and ergonomic usage.
Example:
pub fn safe_strlen(string: &str) -> Result<usize, LibasmErrorKind> {
let len: usize;
unsafe {
let ptr = CString::from_str(string)
.map_err(|val| LibasmErrorKind::GenericError(val.to_string()))?;
if ptr.is_empty() {
Err(LibasmErrorKind::GenericError(
"the string would segfault".to_string(),
))?;
}
len = asm_strlen(ptr.as_ptr());
}
Ok(len)
}This allows you to use the asm functions in Rust through a safe interface!
A Dockerfile is provided to containerize the testing environment,
so you don't have to install libcriterion locally.
The included Makefile simplifies building, testing, and cleaning the project,
so I don't have to write a lot of documentation and you don't have to copy-paste
a bunch of commands on the CLI.
Just run make or make help and you'll see the available rules!
- NASM (Netwide Assembler)
- GCC (GNU Compiler Collection)
- Make
libcriterion(for testing)
- Docker (what else did you expect? xD)
Build the Project:
make buildClean Build Artifacts:
make cleanJust compile your C project with the libasm.a and you're good to go!
Build the Docker Image:
docker image build --tag libasm .Run Tests in the Container:
docker container run --rm libasmThe project uses libcriterion (an absolute banger of a framework) for testing the C part (the happy paths, and the not so happy paths).
All C tests are in the same file, divided by each function. And all Rust tests are in the lib.rs file.
To run both C and Rust tests just go for:
make run-testsOr, if you've built the docker image, just run the image. The image's default entrypoint is the test suite
[----] tests/test_strcmp.c:25: test_strcmp_equal
[OK] test_strcmp_equal
[----] tests/test_strcmp.c:35: test_strcmp_not_equal
[OK] test_strcmp_not_equal
...
[====] Synthesis: Tested: 6 | Passing: 6 | Failing: 0 | Crashing: 0Main blog post
Calling convention for 64:
On A.2.1: Calling Conventions
Nasm instructions in depth documentation
Documentation on each instruction
Intel in depth docs
Q: Where can I find which integers map to which syscall on my linux os?
A: Go to /usr/include/x86_64-linux-gnu/asm/unistd_<64||32>.h
Q: How do I know where each function parameter in C is on my asm code?
A: See Calling Conventions,
chapter A.2.1: Calling Conventions
Q: In which register should I store the return value of a function?
A: See Calling Conventions,
end of chapter 3.2.3, on the "Returning of Values".
Q: Which register should I use to store a counter?
A: rcx is commonly the one used. It is implied by the loop section on the intel
documentation.
- Add a function prelude to all functions (reference)
- Add memcpy
- Add memset
libcriterion: For providing a robust testing framework.NASM: For making x86-64 assembly development accessible.Docker: For simplifying environment setup and testing.