Skip to content

Grsaiago/libasm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Libasm

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!

📂 Project Structure

.
├── 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 files

Features

Assembly Implementations

All 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.

Interoperability with C

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");

🦀Rust Bindings

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.

How It Works

  1. The assembly functions are exposed to C via a standard .h header.
  2. Rust uses a build.rs and hand-written extern definitions to call the C API.
  3. 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!

Easy Setup

A Dockerfile is provided to containerize the testing environment, so you don't have to install libcriterion locally.

Makefile Automation

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!

Setup

Prerequisites

For Local Development

  • NASM (Netwide Assembler)
  • GCC (GNU Compiler Collection)
  • Make
  • libcriterion (for testing)

For Docker

  • Docker (what else did you expect? xD)

How to Run?

Locally

Build the Project:

make build

Clean Build Artifacts:

make clean

Just compile your C project with the libasm.a and you're good to go!

Using Docker

Build the Docker Image:

docker image build --tag libasm .

Run Tests in the Container:

docker container run --rm libasm

🧪 Testing

The 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-tests

Or, if you've built the docker image, just run the image. The image's default entrypoint is the test suite

Example Test Output

[----] 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: 0

References

Main 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

FAQ

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.

TODOS

  • Add a function prelude to all functions (reference)
  • Add memcpy
  • Add memset

🙏 Acknowledgments

  • libcriterion: For providing a robust testing framework.
  • NASM: For making x86-64 assembly development accessible.
  • Docker: For simplifying environment setup and testing.

About

Recreating a bunch of glibc functions in x86 asm and exporting them in C and in Rust

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published