Add memory allocation wrappers
Provides a consistent interface to allocate, resize and free heap memory.
This commit is contained in:
109
src/common/memory.c
Normal file
109
src/common/memory.c
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
#include "memory.h"
|
||||||
|
#include "error.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
constexpr uint32_t static_canary_value = 0xFF000A00;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes a block of memory and its meta data. The user will only ever see
|
||||||
|
* the data pointer.
|
||||||
|
*
|
||||||
|
* FIXME: It might be possible to ensure alignment of data here so that assuming
|
||||||
|
* the underlying malloc() call is aligned to 16 bytes we also guarantee
|
||||||
|
* alignment to 16 bytes. This is not currently implemented.
|
||||||
|
*/
|
||||||
|
typedef struct memory {
|
||||||
|
uint32_t canary;
|
||||||
|
size_t size;
|
||||||
|
uint8_t data[];
|
||||||
|
} mem_t;
|
||||||
|
|
||||||
|
static bool would_overflow_mul(size_t a, size_t b) {
|
||||||
|
if (a == 0 || b == 0)
|
||||||
|
return false;
|
||||||
|
return a > SIZE_MAX / b;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool would_overflow_add(size_t a, size_t b) {
|
||||||
|
return a > SIZE_MAX - b;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline mem_t *mem_get_metadata(void *user_ptr) {
|
||||||
|
return (mem_t *)((char *)user_ptr - sizeof(mem_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] void die_with_ptr_msg(void *ptr, uint32_t canary) {
|
||||||
|
fprintf(stderr, "*** HEAP CORRUPTION at %p ***\n", ptr);
|
||||||
|
fprintf(stderr, "Unexpected heap canary value %08x\n", canary);
|
||||||
|
fflush(stderr);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t *mem_alloc(void **ptr, size_t n, size_t size) {
|
||||||
|
if (ptr == nullptr)
|
||||||
|
return err_invalid_parameters;
|
||||||
|
*ptr = nullptr;
|
||||||
|
if (n == 0 || size == 0)
|
||||||
|
return err_invalid_parameters;
|
||||||
|
if (would_overflow_mul(n, size))
|
||||||
|
return err_integer_overflow;
|
||||||
|
size_t total = n * size;
|
||||||
|
if (would_overflow_add(total, sizeof(mem_t)))
|
||||||
|
return err_integer_overflow;
|
||||||
|
|
||||||
|
mem_t *mem = malloc(total + sizeof(mem_t));
|
||||||
|
if (mem == nullptr)
|
||||||
|
return err_allocation_failed;
|
||||||
|
|
||||||
|
mem->canary = static_canary_value;
|
||||||
|
mem->size = total;
|
||||||
|
memset(&mem->data[0], 0, total);
|
||||||
|
|
||||||
|
*ptr = &mem->data[0];
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t *mem_resize(void **ptr, size_t n, size_t size) {
|
||||||
|
if (ptr == nullptr || *ptr == nullptr)
|
||||||
|
return err_invalid_parameters;
|
||||||
|
if (n == 0 || size == 0)
|
||||||
|
return err_invalid_parameters;
|
||||||
|
|
||||||
|
mem_t *mem = mem_get_metadata(*ptr);
|
||||||
|
if (mem->canary != static_canary_value)
|
||||||
|
die_with_ptr_msg(&mem->data[0], mem->canary);
|
||||||
|
|
||||||
|
if (would_overflow_mul(n, size))
|
||||||
|
return err_integer_overflow;
|
||||||
|
size_t total = n * size;
|
||||||
|
if (would_overflow_add(total, sizeof(mem_t)))
|
||||||
|
return err_integer_overflow;
|
||||||
|
|
||||||
|
mem_t *new = realloc(mem, total + sizeof(mem_t));
|
||||||
|
if (new == nullptr)
|
||||||
|
return err_allocation_failed;
|
||||||
|
if (new != mem) {
|
||||||
|
*ptr = &new->data[0];
|
||||||
|
}
|
||||||
|
if (total > new->size)
|
||||||
|
memset(&new->data[new->size], 0, total - new->size);
|
||||||
|
new->size = total;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mem_free(void **ptr) {
|
||||||
|
if (ptr == nullptr || *ptr == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mem_t *mem = mem_get_metadata(*ptr);
|
||||||
|
if (mem->canary != static_canary_value)
|
||||||
|
die_with_ptr_msg(&mem->data[0], mem->canary);
|
||||||
|
|
||||||
|
mem->canary = 0;
|
||||||
|
free(mem);
|
||||||
|
*ptr = nullptr;
|
||||||
|
}
|
||||||
68
src/common/memory.h
Normal file
68
src/common/memory.h
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#ifndef INCLUDE_COMMON_MEMORY_H_
|
||||||
|
#define INCLUDE_COMMON_MEMORY_H_
|
||||||
|
|
||||||
|
#include "error.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates n*size bytes of memory and initializes them to 0.
|
||||||
|
*
|
||||||
|
* Note that the allocated memory is not guaranteed to be 16 byte aligned.
|
||||||
|
*
|
||||||
|
* ptr (out):
|
||||||
|
* If successful, *ptr will contain the pointer to the allocated memory. Must
|
||||||
|
* be a valid pointer and specifically must not be nullptr.
|
||||||
|
* n:
|
||||||
|
* The amount of consecutive objects to allocate. Must not be 0.
|
||||||
|
* size:
|
||||||
|
* The size of objects to allocate. Must not be 0.
|
||||||
|
* return:
|
||||||
|
* - nullptr on success
|
||||||
|
* - err_allocation_failed when the memory cannot be allocated.
|
||||||
|
* - err_invalid_parameter when either n or size are 0 or ptr is nullptr.
|
||||||
|
* - err_integer_overflow when n*size overflows size_t.
|
||||||
|
*/
|
||||||
|
error_t *mem_alloc(void **ptr, size_t n, size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resizes previously allocated memory to n*size bytes.
|
||||||
|
*
|
||||||
|
* When resizing existing data is preserved but may be truncated if the new size
|
||||||
|
* is smaller. If the memory grows the newly allocated memory is initialized to
|
||||||
|
* 0. Note that the allocated memory is not guaranteed to be 16 byte aligned.
|
||||||
|
*
|
||||||
|
* If memory overflows happened or if invalid pointers are passed then this
|
||||||
|
* function may exit with abort()
|
||||||
|
*
|
||||||
|
* ptr (in, out):
|
||||||
|
* Pointer to the memory to resize. If successful, *ptr will contain the
|
||||||
|
* pointer to the resized memory (which may differ from the original).
|
||||||
|
* *ptr must have been allocated with mem_alloc or mem_resize.
|
||||||
|
* If the new size is larger, the additional bytes are initialized to 0.
|
||||||
|
* If this function returns an error, *ptr remains unchanged and points to
|
||||||
|
* the original, valid allocation.
|
||||||
|
* n:
|
||||||
|
* The amount of consecutive objects to allocate. Must not be 0.
|
||||||
|
* size:
|
||||||
|
* The size of objects to allocate. Must not be 0.
|
||||||
|
* return:
|
||||||
|
* - nullptr on success
|
||||||
|
* - err_allocation_failed when the memory cannot be allocated.
|
||||||
|
* - err_invalid_parameter when either n or size are 0, or *ptr is nullptr.
|
||||||
|
* - err_integer_overflow when n*size overflows size_t.
|
||||||
|
*/
|
||||||
|
error_t *mem_resize(void **ptr, size_t n, size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees memory previously allocated with mem_alloc or mem_resize.
|
||||||
|
*
|
||||||
|
* If memory overflows happened or if invalid pointers are passed then this
|
||||||
|
* function may exit with abort()
|
||||||
|
*
|
||||||
|
* ptr (in, out)
|
||||||
|
* Pointer to the memory to free. If ptr and *ptr are not nullptr, *ptr will
|
||||||
|
* be set to nullptr after freeing. Both ptr and *ptr can be nullptr (no-op).
|
||||||
|
* *ptr must have been allocated with mem_alloc or mem_resize.
|
||||||
|
*/
|
||||||
|
void mem_free(void **ptr);
|
||||||
|
|
||||||
|
#endif // INCLUDE_COMMON_MEMORY_H_
|
||||||
Reference in New Issue
Block a user