diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a49e4d7..e37efcb0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,19 +4,22 @@ project(libmimalloc C CXX) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) -option(MI_OVERRIDE "Override the standard malloc interface" ON) -option(MI_DEBUG_FULL "Use full internal heap invariant checking in DEBUG mode" OFF) option(MI_SECURE "Use full security mitigations (like guard pages, allocation randomization, double-free mitigation, and free-list corruption detection)" OFF) -option(MI_USE_CXX "Use the C++ compiler to compile the library" OFF) +option(MI_DEBUG_FULL "Use full internal heap invariant checking in DEBUG mode (expensive)" OFF) +option(MI_PADDING "Enable padding to detect heap block overflow (used only in DEBUG mode)" ON) +option(MI_OVERRIDE "Override the standard malloc interface (e.g. define entry points for malloc() etc)" ON) +option(MI_XMALLOC "Enable abort() call on memory allocation failure by default" OFF) +option(MI_SHOW_ERRORS "Show error and warning messages by default (only enabled by default in DEBUG mode)" OFF) +option(MI_USE_CXX "Use the C++ compiler to compile the library (instead of the C compiler)" OFF) option(MI_SEE_ASM "Generate assembly files" OFF) option(MI_INTERPOSE "Use interpose to override standard malloc on macOS" ON) option(MI_OSX_ZONE "Use malloc zone to override standard malloc on macOS" OFF) # enables interpose as well option(MI_LOCAL_DYNAMIC_TLS "Use slightly slower, dlopen-compatible TLS mechanism (Unix)" OFF) +option(MI_BUILD_SHARED "Build shared library" ON) +option(MI_BUILD_STATIC "Build static library" ON) +option(MI_BUILD_OBJECT "Build object library" ON) option(MI_BUILD_TESTS "Build test executables" ON) option(MI_CHECK_FULL "Use full internal invariant checking in DEBUG mode (deprecated, use MI_DEBUG_FULL instead)" OFF) -option(MI_PADDING "Enable padding to detect heap block overflow (only in debug mode)" ON) -option(MI_XMALLOC "Enable abort() call on memory allocation failure by default" OFF) -option(MI_SHOW_ERRORS "Show error and warning messages by default" OFF) include("cmake/mimalloc-config-version.cmake") @@ -182,10 +185,23 @@ string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LC) if(NOT(CMAKE_BUILD_TYPE_LC MATCHES "^(release|relwithdebinfo|minsizerel)$")) set(mi_basename "${mi_basename}-${CMAKE_BUILD_TYPE_LC}") #append build type (e.g. -debug) if not a release version endif() +if(MI_BUILD_SHARED) + list(APPEND mi_build_targets "shared") +endif() +if(MI_BUILD_STATIC) + list(APPEND mi_build_targets "static") +endif() +if(MI_BUILD_OBJECT) + list(APPEND mi_build_targets "object") +endif() +if(MI_BUILD_TESTS) + list(APPEND mi_build_targets "tests") +endif() message(STATUS "") message(STATUS "Library base name: ${mi_basename}") message(STATUS "Build type : ${CMAKE_BUILD_TYPE_LC}") message(STATUS "Install directory: ${mi_install_dir}") +message(STATUS "Build targets : ${mi_build_targets}") message(STATUS "") # ----------------------------------------------------------------------------- @@ -193,53 +209,59 @@ message(STATUS "") # ----------------------------------------------------------------------------- # shared library -add_library(mimalloc SHARED ${mi_sources}) -set_target_properties(mimalloc PROPERTIES VERSION ${mi_version} OUTPUT_NAME ${mi_basename} ) -target_compile_definitions(mimalloc PRIVATE ${mi_defines} MI_SHARED_LIB MI_SHARED_LIB_EXPORT) -target_compile_options(mimalloc PRIVATE ${mi_cflags}) -target_link_libraries(mimalloc PUBLIC ${mi_libraries}) -target_include_directories(mimalloc PUBLIC - $ - $ -) -if(WIN32) - # On windows copy the mimalloc redirection dll too. - target_link_libraries(mimalloc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/bin/mimalloc-redirect.lib) - add_custom_command(TARGET mimalloc POST_BUILD - COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/bin/mimalloc-redirect.dll" $ - COMMENT "Copy mimalloc-redirect.dll to output directory") +if(MI_BUILD_SHARED) + add_library(mimalloc SHARED ${mi_sources}) + set_target_properties(mimalloc PROPERTIES VERSION ${mi_version} OUTPUT_NAME ${mi_basename} ) + target_compile_definitions(mimalloc PRIVATE ${mi_defines} MI_SHARED_LIB MI_SHARED_LIB_EXPORT) + target_compile_options(mimalloc PRIVATE ${mi_cflags}) + target_link_libraries(mimalloc PUBLIC ${mi_libraries}) + target_include_directories(mimalloc PUBLIC + $ + $ + ) + if(WIN32) + # On windows copy the mimalloc redirection dll too. + target_link_libraries(mimalloc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/bin/mimalloc-redirect.lib) + add_custom_command(TARGET mimalloc POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/bin/mimalloc-redirect.dll" $ + COMMENT "Copy mimalloc-redirect.dll to output directory") + endif() + + install(TARGETS mimalloc EXPORT mimalloc DESTINATION ${mi_install_dir} LIBRARY) + install(EXPORT mimalloc DESTINATION ${mi_install_dir}/cmake) endif() # static library -add_library(mimalloc-static STATIC ${mi_sources}) -set_property(TARGET mimalloc-static PROPERTY POSITION_INDEPENDENT_CODE ON) -target_compile_definitions(mimalloc-static PRIVATE ${mi_defines} MI_STATIC_LIB) -target_compile_options(mimalloc-static PRIVATE ${mi_cflags}) -target_link_libraries(mimalloc-static PUBLIC ${mi_libraries}) -target_include_directories(mimalloc-static PUBLIC - $ - $ -) -if(WIN32) - # When building both static and shared libraries on Windows, a static library should use a - # different output name to avoid the conflict with the import library of a shared one. - string(REPLACE "mimalloc" "mimalloc-static" mi_output_name ${mi_basename}) - set_target_properties(mimalloc-static PROPERTIES OUTPUT_NAME ${mi_output_name}) -else() - set_target_properties(mimalloc-static PROPERTIES OUTPUT_NAME ${mi_basename}) +if (MI_BUILD_STATIC) + add_library(mimalloc-static STATIC ${mi_sources}) + set_property(TARGET mimalloc-static PROPERTY POSITION_INDEPENDENT_CODE ON) + target_compile_definitions(mimalloc-static PRIVATE ${mi_defines} MI_STATIC_LIB) + target_compile_options(mimalloc-static PRIVATE ${mi_cflags}) + target_link_libraries(mimalloc-static PUBLIC ${mi_libraries}) + target_include_directories(mimalloc-static PUBLIC + $ + $ + ) + if(WIN32) + # When building both static and shared libraries on Windows, a static library should use a + # different output name to avoid the conflict with the import library of a shared one. + string(REPLACE "mimalloc" "mimalloc-static" mi_output_name ${mi_basename}) + set_target_properties(mimalloc-static PROPERTIES OUTPUT_NAME ${mi_output_name}) + else() + set_target_properties(mimalloc-static PROPERTIES OUTPUT_NAME ${mi_basename}) + endif() + + install(TARGETS mimalloc-static EXPORT mimalloc DESTINATION ${mi_install_dir}) endif() -# install static and shared library, and the include files -install(TARGETS mimalloc EXPORT mimalloc DESTINATION ${mi_install_dir} LIBRARY) -install(TARGETS mimalloc-static EXPORT mimalloc DESTINATION ${mi_install_dir}) +# install include files install(FILES include/mimalloc.h DESTINATION ${mi_install_dir}/include) install(FILES include/mimalloc-override.h DESTINATION ${mi_install_dir}/include) install(FILES include/mimalloc-new-delete.h DESTINATION ${mi_install_dir}/include) install(FILES cmake/mimalloc-config.cmake DESTINATION ${mi_install_dir}/cmake) install(FILES cmake/mimalloc-config-version.cmake DESTINATION ${mi_install_dir}/cmake) -install(EXPORT mimalloc DESTINATION ${mi_install_dir}/cmake) -if(NOT WIN32) +if(NOT WIN32 AND MI_BUILD_SHARED) # install a symlink in the /usr/local/lib to the versioned library set(mi_symlink "${CMAKE_SHARED_MODULE_PREFIX}${mi_basename}${CMAKE_SHARED_LIBRARY_SUFFIX}") set(mi_soname "mimalloc-${mi_version}/${mi_symlink}.${mi_version}") @@ -248,23 +270,25 @@ if(NOT WIN32) endif() # single object file for more predictable static overriding -add_library(mimalloc-obj OBJECT src/static.c) -set_property(TARGET mimalloc-obj PROPERTY POSITION_INDEPENDENT_CODE ON) -target_compile_definitions(mimalloc-obj PRIVATE ${mi_defines}) -target_compile_options(mimalloc-obj PRIVATE ${mi_cflags}) -target_include_directories(mimalloc-obj PUBLIC - $ - $ -) +if (MI_BUILD_OBJECT) + add_library(mimalloc-obj OBJECT src/static.c) + set_property(TARGET mimalloc-obj PROPERTY POSITION_INDEPENDENT_CODE ON) + target_compile_definitions(mimalloc-obj PRIVATE ${mi_defines}) + target_compile_options(mimalloc-obj PRIVATE ${mi_cflags}) + target_include_directories(mimalloc-obj PUBLIC + $ + $ + ) -# the following seems to lead to cmake warnings/errors on some systems, disable for now :-( -# install(TARGETS mimalloc-obj EXPORT mimalloc DESTINATION ${mi_install_dir}) + # the following seems to lead to cmake warnings/errors on some systems, disable for now :-( + # install(TARGETS mimalloc-obj EXPORT mimalloc DESTINATION ${mi_install_dir}) -# the FILES expression can also be: $ -# but that fails cmake versions less than 3.10 so we leave it as is for now -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/mimalloc-obj.dir/src/static.c${CMAKE_C_OUTPUT_EXTENSION} - DESTINATION ${mi_install_dir} - RENAME ${mi_basename}${CMAKE_C_OUTPUT_EXTENSION} ) + # the FILES expression can also be: $ + # but that fails cmake versions less than 3.10 so we leave it as is for now + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/mimalloc-obj.dir/src/static.c${CMAKE_C_OUTPUT_EXTENSION} + DESTINATION ${mi_install_dir} + RENAME ${mi_basename}${CMAKE_C_OUTPUT_EXTENSION} ) +endif() # ----------------------------------------------------------------------------- # API surface testing @@ -292,10 +316,16 @@ endif() # Set override properties # ----------------------------------------------------------------------------- if (MI_OVERRIDE MATCHES "ON") - target_compile_definitions(mimalloc PRIVATE MI_MALLOC_OVERRIDE) + if (MI_BUILD_SHARED) + target_compile_definitions(mimalloc PRIVATE MI_MALLOC_OVERRIDE) + endif() if(NOT WIN32) # It is only possible to override malloc on Windows when building as a DLL. - target_compile_definitions(mimalloc-static PRIVATE MI_MALLOC_OVERRIDE) - target_compile_definitions(mimalloc-obj PRIVATE MI_MALLOC_OVERRIDE) + if (MI_BUILD_STATIC) + target_compile_definitions(mimalloc-static PRIVATE MI_MALLOC_OVERRIDE) + endif() + if (MI_BUILD_OBJECT) + target_compile_definitions(mimalloc-obj PRIVATE MI_MALLOC_OVERRIDE) + endif() endif() endif() diff --git a/include/mimalloc.h b/include/mimalloc.h index 4c0f31b4..c7a65e7a 100644 --- a/include/mimalloc.h +++ b/include/mimalloc.h @@ -8,7 +8,7 @@ terms of the MIT license. A copy of the license can be found in the file #ifndef MIMALLOC_H #define MIMALLOC_H -#define MI_MALLOC_VERSION 163 // major + 2 digits minor +#define MI_MALLOC_VERSION 164 // major + 2 digits minor // ------------------------------------------------------ // Compiler specific attributes diff --git a/readme.md b/readme.md index fd600763..7ec20d72 100644 --- a/readme.md +++ b/readme.md @@ -11,7 +11,7 @@ mimalloc (pronounced "me-malloc") is a general purpose allocator with excellent [performance](#performance) characteristics. Initially developed by Daan Leijen for the run-time systems of the [Koka](https://github.com/koka-lang/koka) and [Lean](https://github.com/leanprover/lean) languages. -Latest release:`v1.6.2` (2020-04-20). +Latest release:`v1.6.3` (2020-05-05). It is a drop-in replacement for `malloc` and can be used in other programs without code changes, for example, on dynamically linked ELF-based systems (Linux, BSD, etc.) you can use it as: @@ -57,6 +57,8 @@ Enjoy! ### Releases +* 2020-05-05, `v1.6.3`: stable release 1.6: improved behavior in out-of-memory situations, improved malloc zones on macOS, + build PIC static libraries by default, add option to abort on out-of-memory, line buffered statistics. * 2020-04-20, `v1.6.2`: stable release 1.6: fix compilation on Android, MingW, Raspberry, and Conda, stability fix for Windows 7, fix multiple mimalloc instances in one executable, fix `strnlen` overload, fix aligned debug padding. @@ -262,19 +264,19 @@ to make mimalloc more robust against exploits. In particular: - All internal mimalloc pages are surrounded by guard pages and the heap metadata is behind a guard page as well (so a buffer overflow exploit cannot reach into the metadata), -- All free list pointers are - [encoded](https://github.com/microsoft/mimalloc/blob/783e3377f79ee82af43a0793910a9f2d01ac7863/include/mimalloc-internal.h#L396) +- All free list pointers are + [encoded](https://github.com/microsoft/mimalloc/blob/783e3377f79ee82af43a0793910a9f2d01ac7863/include/mimalloc-internal.h#L396) with per-page keys which is used both to prevent overwrites with a known pointer, as well as to detect heap corruption, - Double free's are detected (and ignored), -- The free lists are initialized in a random order and allocation randomly chooses between extension and reuse within a page to - mitigate against attacks that rely on a predicable allocation order. Similarly, the larger heap blocks allocated by mimalloc +- The free lists are initialized in a random order and allocation randomly chooses between extension and reuse within a page to + mitigate against attacks that rely on a predicable allocation order. Similarly, the larger heap blocks allocated by mimalloc from the OS are also address randomized. As always, evaluate with care as part of an overall security strategy as all of the above are mitigations but not guarantees. ## Debug Mode -When _mimalloc_ is built using debug mode, various checks are done at runtime to catch development errors. +When _mimalloc_ is built using debug mode, various checks are done at runtime to catch development errors. - Statistics are maintained in detail for each object size. They can be shown using `MIMALLOC_SHOW_STATS=1` at runtime. - All objects have padding at the end to detect (byte precise) heap block overflows. diff --git a/src/init.c b/src/init.c index b4c0e757..2513e3a4 100644 --- a/src/init.c +++ b/src/init.c @@ -220,7 +220,6 @@ static bool _mi_heap_done(mi_heap_t* heap) { heap = heap->tld->heap_backing; if (!mi_heap_is_initialized(heap)) return false; - // delete all non-backing heaps in this thread mi_heap_t* curr = heap->tld->heaps; while (curr != NULL) { @@ -238,13 +237,14 @@ static bool _mi_heap_done(mi_heap_t* heap) { if (heap != &_mi_heap_main) { _mi_heap_collect_abandon(heap); } + // merge stats _mi_stats_done(&heap->tld->stats); // free if not the main thread if (heap != &_mi_heap_main) { - mi_assert_internal(heap->tld->segments.count == 0); + mi_assert_internal(heap->tld->segments.count == 0 || heap->thread_id != _mi_thread_id()); _mi_os_free(heap, sizeof(mi_thread_data_t), &_mi_stats_main); } #if 0 @@ -359,12 +359,16 @@ void mi_thread_done(void) mi_attr_noexcept { } static void _mi_thread_done(mi_heap_t* heap) { + // check thread-id as on Windows shutdown with FLS the main (exit) thread may call this on thread-local heaps... + if (heap->thread_id != _mi_thread_id()) return; + // stats if (!_mi_is_main_thread() && mi_heap_is_initialized(heap)) { _mi_stat_decrease(&heap->tld->stats.threads, 1); } + // abandon the thread local heap - if (_mi_heap_done(heap)) return; // returns true if already ran + if (_mi_heap_done(heap)) return; // returns true if already ran } void _mi_heap_set_default_direct(mi_heap_t* heap) { diff --git a/src/page.c b/src/page.c index 3bc75a3f..a2189e00 100644 --- a/src/page.c +++ b/src/page.c @@ -785,8 +785,29 @@ static mi_page_t* mi_huge_page_alloc(mi_heap_t* heap, size_t size) { } +// Allocate a page +// Note: in debug mode the size includes extra padding size and might have overflowed. +static mi_page_t* mi_find_page(mi_heap_t* heap, size_t size) mi_attr_noexcept { + // huge allocation? + const size_t req_size = size - mi_extra_padding(heap); // correct for padding_size in case of an overflow on `size` + if (mi_unlikely(req_size > (MI_LARGE_OBJ_SIZE_MAX - mi_extra_padding(heap)))) { + if (mi_unlikely(req_size > PTRDIFF_MAX)) { // we don't allocate more than PTRDIFF_MAX (see ) + _mi_error_message(EOVERFLOW, "allocation request is too large (%zu b requested)\n", req_size); + return NULL; + } + else { + return mi_huge_page_alloc(heap, size); + } + } + else { + // otherwise find a page with free blocks in our size segregated queues + mi_assert_internal(size >= mi_extra_padding(heap)); + return mi_find_free_page(heap, size); + } +} + // Generic allocation routine if the fast path (`alloc.c:mi_page_malloc`) does not succeed. -// Note: in debug mode the size includes MI_PADDING_SIZE and might have overflowed. +// Note: in debug mode the size includes extra padding size and might have overflowed. void* _mi_malloc_generic(mi_heap_t* heap, size_t size MI_SOURCE_XPARAM) mi_attr_noexcept { mi_assert_internal(heap != NULL); @@ -804,23 +825,13 @@ void* _mi_malloc_generic(mi_heap_t* heap, size_t size MI_SOURCE_XPARAM) mi_attr // free delayed frees from other threads _mi_heap_delayed_free(heap); - // huge allocation? - mi_page_t* page; - const size_t req_size = size - mi_extra_padding(heap); // correct for padding_size in case of an overflow on `size` - if (mi_unlikely(req_size > (MI_LARGE_OBJ_SIZE_MAX - mi_extra_padding(heap)) )) { - if (mi_unlikely(req_size > PTRDIFF_MAX)) { // we don't allocate more than PTRDIFF_MAX (see ) - _mi_error_message(EOVERFLOW, "allocation request is too large (%zu b requested)\n", req_size); - return NULL; - } - else { - page = mi_huge_page_alloc(heap,size); - } - } - else { - // otherwise find a page with free blocks in our size segregated queues - mi_assert_internal(size >= mi_extra_padding(heap)); - page = mi_find_free_page(heap,size); + // find (or allocate) a page of the right size + mi_page_t* page = mi_find_page(heap, size); + if (mi_unlikely(page == NULL)) { // first time out of memory, try to collect and retry the allocation once more + mi_heap_collect(heap, true /* force */); + page = mi_find_page(heap, size); } + if (mi_unlikely(page == NULL)) { // out of memory _mi_error_message(ENOMEM, "cannot allocate memory (%zu bytes requested)\n", size); return NULL; diff --git a/test/main-override.cpp b/test/main-override.cpp index 922c73ab..7cb80838 100644 --- a/test/main-override.cpp +++ b/test/main-override.cpp @@ -3,9 +3,13 @@ #include #include #include + #include #include +#include +#include #include + #include #include #include @@ -23,14 +27,16 @@ void heap_no_delete(); // issue #202 void heap_late_free(); // issue #204 void padding_shrink(); // issue #209 void various_tests(); +void test_mt_shutdown(); int main() { mi_stats_reset(); // ignore earlier allocations heap_thread_free_large(); - heap_no_delete(); - heap_late_free(); - padding_shrink(); + heap_no_delete(); + heap_late_free(); + padding_shrink(); various_tests(); + //test_mt_shutdown(); mi_stats_print(NULL); return 0; } @@ -133,7 +139,7 @@ bool test_stl_allocator2() { // Issue #202 static void heap_no_delete_worker() { mi_heap_t* heap = mi_heap_new(); - void* q = mi_heap_malloc(heap,1024); + void* q = mi_heap_malloc(heap, 1024); // mi_heap_delete(heap); // uncomment to prevent assertion } @@ -189,3 +195,29 @@ void heap_thread_free_large() { t1.join(); } } + + + +void test_mt_shutdown() +{ + const int threads = 5; + std::vector< std::future< std::vector< char* > > > ts; + + auto fn = [&]() + { + std::vector< char* > ps; + ps.reserve(1000); + for (int i = 0; i < 1000; i++) + ps.emplace_back(new char[1]); + return ps; + }; + + for (int i = 0; i < threads; i++) + ts.emplace_back(std::async(std::launch::async, fn)); + + for (auto& f : ts) + for (auto& p : f.get()) + delete[] p; + + std::cout << "done" << std::endl; +}