malloc() replacement in lib kilipili - Megatokio/kilipili GitHub Wiki

Lib kilipili contains a replacement for the stdclib malloc implementation.

Why?

When i wrote a function to map out the available free space in the heap, i found that malloc() sometimes did not return memory despite it was there! I can only guess the reasons why but probably it is optimized for desktop and server-size hosts.

In my function i try to allocate the largest available block, then free it and return it's size. To find the next largest block i have to allocate this block again which often failed. I tried to eliminate all possible other reasons without success.

The final proof was that my malloc() replacement does not exhibit the same problem.

In addition the malloc() replacement provides additional tools to help with debugging and a better integration with lib kilipili.

Command line / cmake options

PICO_CXX_DISABLE_ALLOCATION_OVERRIDES=0|1

If disabled (the default) then new et.al. are replaced by own implementations. The main differences:

  • new() throws an Error which is a const char* as is done throughout lib kilipili.
  • new(size_t, std::nothrow_t) does not call the throwing new() and then catch the exception, which is stupid. B-)

MALLOC_EXTENDED_VALIDATION=1

this enabled more thorough checking of the heap linking to fail on heap corruption early. This also initializes the heap with 0xE5 and free() clears the free'd memory with 0xA5 so that use-after-free is more likely to fail. MALLOC_EXTENDED_VALIDATION is implicitely enabled in DEBUG builds.

MALLOC_EXTENDED_LOGGING=1

this enables logging of all malloc() and free() to stdio.

MALLOC_SPINLOCK_NUMBER

set the number for the spinlock used to make malloc() reentrant. The default is to use PICO_SPINLOCK_ID_OS2, one of the spinlocks reserved for OS use.

Though it is held for the shortest possible time, much shorter than a mutex around the entire call, this may still be considered 'long' in some context.

free() is completely lock-free. malloc() blocks the spinlock only for the search-and-claim loop, and it does not block interrupts while waiting for a blocked spinlock, making it much more real-time friendly.

Utility functions

const char* check_heap()

A quick function to check the heap integrity. Returns a nullptr or a string describing the corruption.

void dump_heap()

Dump the heap to stdout. The first 256 bytes of each block are printed.

void dump_heap_to_fu(dump_heap_print_fu*, void* data)

Dump the heap to a user-provided function.

size_t heap_total_size()

Return the total size of the heap. Nice to see how it decays as your project evolves. B-)

size_t heap_largest_free_block()

Return the size of the largest available block.

bool heap_cut_exception_block()

If exceptions are enabled (as is done with lib kilipili) then the c++ boot code reserves a block of 1088 bytes during the statics initialization phase. This block is rarely ever used but reduces the amount of available memory for the application. If you are desperate for memory then you can call malloc_cut_exception_block(), best before allocating any own memory.

WARNING: I don't know what i'm doing here!

  • This function truncates the "well known" spare block for the c++ exception handler
  • and changes it's content to reflect the new size, because otherwise the exception handler will deallocate any exception which happens to be allocated within the range of the original block differently and corrupt the heap.
  • I expect a program will crash without proper indication if an exception cannot be allocated on the heap. This can mostly be only an OUT_OF_MEMORY exception, but in rare cases any other exception as well.
  • The size of the block c++ allocates for any exception thrown is at least 132 bytes. (at the time of writing)

panic(...)

kio::panic(...) in "utilities/" checks the heap and dumps it along with the panic error message.