#memory-allocator #allocator #safe #calloc #no-std #heap-allocation

no-std bin+lib alloc-stdlib

A dynamic allocator example that may be used with the stdlib

4 releases

Uses old Rust 2015

0.2.2 Sep 12, 2022
0.2.1 Oct 30, 2018
0.2.0 Oct 30, 2018
0.1.0 Oct 28, 2018

#266 in Memory management

Download history 636434/week @ 2024-07-21 645914/week @ 2024-07-28 652332/week @ 2024-08-04 668669/week @ 2024-08-11 660742/week @ 2024-08-18 675418/week @ 2024-08-25 634749/week @ 2024-09-01 666615/week @ 2024-09-08 598394/week @ 2024-09-15 675182/week @ 2024-09-22 683562/week @ 2024-09-29 707498/week @ 2024-10-06 713853/week @ 2024-10-13 729343/week @ 2024-10-20 722504/week @ 2024-10-27 701922/week @ 2024-11-03

2,909,865 downloads per month
Used in 366 crates (2 directly)

BSD-3-Clause

63KB
1K SLoC

Framework for allocating memory in #![no_std] modules.

Build Status

Requirements

  • Rust 1.6

Documentation

Currently there is no standard way to allocate memory from within a module that is no_std. This provides a mechanism to allocate memory using the stdlib-independent memory allocation system described by rust-alloc-no-stdlib describe a memory allocation that can be satisfied entirely on the stack, by unsafely linking to calloc, or by unsafely referencing a mutable global variable. This library currently will leak memory if free_cell isn't specifically invoked on memory.

However, if linked by a library that actually can depend on the stdlib then that library can simply pass in a few allocators and use the standard Box allocation and will free automatically.

This library should also make it possible to entirely jail a rust application that needs dynamic allocations by preallocating a maximum limit of data upfront using calloc and using seccomp to disallow future syscalls.

Usage

There are 3 modes for allocating memory using the stdlib, each with advantages and disadvantages

On the heap

This uses the standard Box facilities to allocate memory and assumeds a default constructor for the given type

let mut halloc = StandardAlloc::new(0);
for _i in 1..10 { // heap test
    let mut x = <StandardAlloc as Allocator<u8>>::alloc_cell(&mut halloc, 100000)
    x[0] = 4;
    let mut y = <StandardAlloc as Allocator<u8>>::alloc_cell(&mut halloc, 100000)
    y[0] = 5;
    let mut z = <StandardAlloc as Allocator<u8>>::alloc_cell(&mut halloc, 100000)
    z[0] = 6;
    assert_eq!(y[0], 5);
    halloc.free_cell(y);
    assert_eq!(x[0], 4);
    assert_eq!(x[9], 0);
    assert_eq!(z[0], 6);
}

On the heap

This uses the standard Box facilities to allocate memory but assuming a default user-provided value

let mut halloc = HeapAlloc::<u8>::new(8);
for _i in 1..10 { // heap test
    let mut x = halloc.alloc_cell(100000);
    x[0] = 4;
    let mut y = halloc.alloc_cell(110000);
    y[0] = 5;
    let mut z = halloc.alloc_cell(120000);
    z[0] = 6;
    assert_eq!(y[0], 5);
    halloc.free_cell(y);
    assert_eq!(x[0], 4);
    assert_eq!(x[9], 8);
    assert_eq!(z[0], 6);
}

On the heap, but uninitialized

This does allocate data every time it is requested, but it does not allocate the memory, so naturally it is unsafe. The caller must initialize the memory properly

let mut halloc = unsafe{HeapAllocUninitialized::<u8>::new()};
{ // heap test
    let mut x = halloc.alloc_cell(100000);
    x[0] = 4;
    let mut y = halloc.alloc_cell(110000);
    y[0] = 5;
    let mut z = halloc.alloc_cell(120000);
    z[0] = 6;
    assert_eq!(y[0], 5);
    halloc.free_cell(y);
    assert_eq!(x[0], 4);
    assert_eq!(x[9], 0);
    assert_eq!(z[0], 6);
    ...
}

On the heap in a single pool allocation

This does a single big allocation on the heap, after which no further usage of the stdlib will happen. This can be useful for a jailed application that wishes to restrict syscalls at this point

use alloc_no_stdlib::HeapPrealloc;
...
let mut heap_global_buffer = define_allocator_memory_pool!(4096, u8, [0; 6 * 1024 * 1024], heap);
let mut ags = HeapPrealloc::<u8>::new_allocator(4096, &mut heap_global_buffer, uninitialized);
{
    let mut x = ags.alloc_cell(9999);
    x.slice_mut()[0] = 4;
    let mut y = ags.alloc_cell(4);
    y[0] = 5;
    ags.free_cell(y);

    //y.mem[0] = 6; // <-- this is an error (use after free)
}

On the heap, uninitialized

This does a single big allocation on the heap, after which no further usage of the stdlib will happen. This can be useful for a jailed application that wishes to restrict syscalls at this point. This option keep does not set the memory to a valid value, so it is necessarily marked unsafe

use alloc_no_stdlib::HeapPrealloc;
...
let mut heap_global_buffer = unsafe{HeapPrealloc::<u8>::new_uninitialized_memory_pool(6 * 1024 * 1024)};
let mut ags = HeapPrealloc::<u8>::new_allocator(4096, &mut heap_global_buffer, uninitialized);
{
    let mut x = ags.alloc_cell(9999);
    x.slice_mut()[0] = 4;
    let mut y = ags.alloc_cell(4);
    y[0] = 5;
    ags.free_cell(y);

    //y.mem[0] = 6; // <-- this is an error (use after free)
}

With calloc

This is the most efficient way to get a zero'd dynamically sized buffer without the stdlib It does invoke the C calloc function and hence must invoke unsafe code. In this version, the number of cells are fixed to the parameter specified in the struct definition (4096 in this example)

extern {
    fn calloc(n_elem : usize, el_size : usize) -> *mut u8;
    fn malloc(len : usize) -> *mut u8;
    fn free(item : *mut u8);
}

declare_stack_allocator_struct!(CallocAllocatedFreelist4096, 4096, calloc);
...

// the buffer is defined with 200 megs of zero'd memory from calloc
let mut calloc_global_buffer = unsafe {define_allocator_memory_pool!(4096, u8, [0; 200 * 1024 * 1024], calloc)};
// and assigned to a new_allocator
let mut ags = CallocAllocatedFreelist4096::<u8>::new_allocator(&mut calloc_global_buffer.data, bzero);
{
    let mut x = ags.alloc_cell(9999);
    x.slice_mut()[0] = 4;
    let mut y = ags.alloc_cell(4);
    y[0] = 5;
    ags.free_cell(y);
    //y.mem[0] = 6; // <-- this is an error (use after free)
}

With a static, mutable buffer

If a single buffer of data is needed for the entire span of the application Then the simplest way to do so without a zero operation on the memory and without using the stdlib is to simply have a global allocated structure. Accessing mutable static variables requires unsafe code; however, so this code will invoke an unsafe block.

Make sure to only reference global_buffer in a single place, at a single time in the code If it is used from two places or at different times, undefined behavior may result, since multiple allocators may get access to global_buffer.

declare_stack_allocator_struct!(GlobalAllocatedFreelist, 16, global);
define_allocator_memory_pool!(16, u8, [0; 1024 * 1024 * 100], global, global_buffer);

...
// this references a global buffer
let mut ags = GlobalAllocatedFreelist::<u8>::new_allocator(bzero);
unsafe {
    bind_global_buffers_to_allocator!(ags, global_buffer, u8);
}
{
    let mut x = ags.alloc_cell(9999);
    x.slice_mut()[0] = 4;
    let mut y = ags.alloc_cell(4);
    y[0] = 5;
    ags.free_cell(y);

    //y.mem[0] = 6; // <-- this is an error (use after free)
}

Contributors

  • Daniel Reiter Horn

Dependencies