diff --git a/ide/vs2017/mimalloc-override-test.vcxproj b/ide/vs2017/mimalloc-override-test.vcxproj index dd84927a..c50f80dc 100644 --- a/ide/vs2017/mimalloc-override-test.vcxproj +++ b/ide/vs2017/mimalloc-override-test.vcxproj @@ -30,27 +30,23 @@ Application true v141 - MultiByte Application false v141 true - MultiByte Application true v141 - MultiByte Application false v141 true - MultiByte @@ -152,6 +148,7 @@ Console + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) @@ -165,4 +162,4 @@ - + \ No newline at end of file diff --git a/ide/vs2017/mimalloc-override.vcxproj b/ide/vs2017/mimalloc-override.vcxproj index 7ebde940..c6d89be0 100644 --- a/ide/vs2017/mimalloc-override.vcxproj +++ b/ide/vs2017/mimalloc-override.vcxproj @@ -30,27 +30,23 @@ DynamicLibrary true v141 - MultiByte DynamicLibrary false v141 true - MultiByte DynamicLibrary true v141 - MultiByte DynamicLibrary false v141 true - MultiByte diff --git a/ide/vs2017/mimalloc-test.vcxproj b/ide/vs2017/mimalloc-test.vcxproj index 27d887d9..675d6acb 100644 --- a/ide/vs2017/mimalloc-test.vcxproj +++ b/ide/vs2017/mimalloc-test.vcxproj @@ -30,27 +30,23 @@ Application true v141 - MultiByte Application false v141 true - MultiByte Application true v141 - MultiByte Application false v141 true - MultiByte diff --git a/ide/vs2017/mimalloc.vcxproj b/ide/vs2017/mimalloc.vcxproj index 14529db0..c5e86acc 100644 --- a/ide/vs2017/mimalloc.vcxproj +++ b/ide/vs2017/mimalloc.vcxproj @@ -30,27 +30,23 @@ StaticLibrary true v141 - MultiByte StaticLibrary false v141 true - MultiByte StaticLibrary true v141 - MultiByte StaticLibrary false v141 true - MultiByte diff --git a/include/mimalloc-internal.h b/include/mimalloc-internal.h index 3e0df1ee..b2526379 100644 --- a/include/mimalloc-internal.h +++ b/include/mimalloc-internal.h @@ -14,12 +14,19 @@ terms of the MIT license. A copy of the license can be found in the file #define MI_TLS_RECURSE_GUARD #endif +#if (MI_DEBUG>0) +#define mi_trace_message(...) _mi_trace_message(__VA_ARGS__) +#else +#define mi_trace_message(...) +#endif + // "options.c" void _mi_fprintf(FILE* out, const char* fmt, ...); void _mi_error_message(const char* fmt, ...); void _mi_warning_message(const char* fmt, ...); void _mi_verbose_message(const char* fmt, ...); +void _mi_trace_message(const char* fmt, ...); // "init.c" extern mi_stats_t _mi_stats_main; @@ -119,6 +126,11 @@ bool _mi_page_is_valid(mi_page_t* page); Inlined definitions ----------------------------------------------------------- */ #define UNUSED(x) (void)(x) +#if (MI_DEBUG>0) +#define UNUSED_RELEASE(x) +#else +#define UNUSED_RELEASE(x) UNUSED(x) +#endif #define MI_INIT4(x) x(),x(),x(),x() #define MI_INIT8(x) MI_INIT4(x),MI_INIT4(x) diff --git a/src/alloc-override-win.c b/src/alloc-override-win.c index cd1953c8..3ac499b8 100644 --- a/src/alloc-override-win.c +++ b/src/alloc-override-win.c @@ -261,14 +261,18 @@ static int mi_register_atexit(exit_list_t* list, cbfun_t* fn) { } // Register a global `atexit` function -static int mi__crt_atexit(cbfun_t* fn) { +static int mi_atexit(cbfun_t* fn) { return mi_register_atexit(&atexit_list,fn); } -static int mi__crt_at_quick_exit(cbfun_t* fn) { +static int mi_at_quick_exit(cbfun_t* fn) { return mi_register_atexit(&at_quick_exit_list,fn); } +static int mi_register_onexit(void* table, cbfun_t* fn) { + // TODO: how can we distinguish a quick_exit from atexit? + return mi_atexit(fn); +} // Execute exit functions in a list static void mi_execute_exit_list(exit_list_t* list) { @@ -375,47 +379,57 @@ typedef enum patch_apply_e { PATCH_TARGET_TERM } patch_apply_t; +#define MAX_ENTRIES 4 // maximum number of patched entry points (like `malloc` in ucrtbase and msvcrt) + typedef struct mi_patch_s { - const char* name; // name of the function to patch - int priority; // priority to patch this one (used to prioritize over multiple entries in various dll's) - void* original; // the resolved address of the function (or NULL) - void* target; // the address of the new target (never NULL) - void* target_term;// the address of the target during termination (or NULL) - patch_apply_t applied; // what target has been applied? - mi_jump_t save; // the saved instructions in case it was applied + const char* name; // name of the function to patch + void* target; // the address of the new target (never NULL) + void* target_term; // the address of the target during termination (or NULL) + patch_apply_t applied; // what target has been applied? + void* originals[MAX_ENTRIES]; // the resolved addresses of the function (or NULLs) + mi_jump_t saves[MAX_ENTRIES]; // the saved instructions in case it was applied } mi_patch_t; -#define MI_PATCH_NAME3(name,target,term) { name, 0, NULL, &target, &term, PATCH_NONE } -#define MI_PATCH_NAME2(name,target) { name, 0, NULL, &target, NULL, PATCH_NONE } +#define MI_PATCH_NAME3(name,target,term) { name, &target, &term, PATCH_NONE, {NULL,NULL,NULL,NULL} } +#define MI_PATCH_NAME2(name,target) { name, &target, NULL, PATCH_NONE, {NULL,NULL,NULL,NULL} } #define MI_PATCH3(name,target,term) MI_PATCH_NAME3(#name, target, term) #define MI_PATCH2(name,target) MI_PATCH_NAME2(#name, target) #define MI_PATCH1(name) MI_PATCH2(name,mi_##name) static mi_patch_t patches[] = { // we implement our own global exit handler (as the CRT versions do a realloc internally) - MI_PATCH2(_crt_atexit, mi__crt_atexit), - MI_PATCH2(_crt_at_quick_exit, mi__crt_at_quick_exit), + //MI_PATCH2(_crt_atexit, mi_atexit), + //MI_PATCH2(_crt_at_quick_exit, mi_at_quick_exit), MI_PATCH2(_setmaxstdio, mi_setmaxstdio), + MI_PATCH2(_register_onexit_function, mi_register_onexit), - // base versions + // override higher level atexit functions so we can implement at_quick_exit correcty + MI_PATCH2(atexit, mi_atexit), + MI_PATCH2(at_quick_exit, mi_at_quick_exit), + + // regular entries + MI_PATCH2(malloc, mi_malloc), + MI_PATCH2(calloc, mi_calloc), + MI_PATCH3(realloc, mi_realloc,mi_realloc_term), + MI_PATCH3(free, mi_free,mi_free_term), + + // extended api + MI_PATCH2(_strdup, mi_strdup), + MI_PATCH2(_strndup, mi_strndup), + MI_PATCH3(_expand, mi__expand,mi__expand_term), + MI_PATCH3(_recalloc, mi_recalloc,mi__recalloc_term), + MI_PATCH3(_msize, mi_usable_size,mi__msize_term), + + // base versions MI_PATCH2(_malloc_base, mi_malloc), MI_PATCH2(_calloc_base, mi_calloc), MI_PATCH3(_realloc_base, mi_realloc,mi_realloc_term), MI_PATCH3(_free_base, mi_free,mi_free_term), - // regular entries - MI_PATCH3(_expand, mi__expand,mi__expand_term), - MI_PATCH3(_recalloc, mi_recalloc,mi__recalloc_term), - MI_PATCH3(_msize, mi_usable_size,mi__msize_term), - // these base versions are in the crt but without import records MI_PATCH_NAME3("_recalloc_base", mi_recalloc,mi__recalloc_term), MI_PATCH_NAME3("_msize_base", mi_usable_size,mi__msize_term), - // utility - MI_PATCH2(_strdup, mi_strdup), - MI_PATCH2(_strndup, mi_strndup), - // debug MI_PATCH2(_malloc_dbg, mi__malloc_dbg), MI_PATCH2(_realloc_dbg, mi__realloc_dbg), @@ -426,6 +440,8 @@ static mi_patch_t patches[] = { MI_PATCH3(_recalloc_dbg, mi__recalloc_dbg, mi__recalloc_dbg_term), MI_PATCH3(_msize_dbg, mi__msize_dbg, mi__msize_dbg_term), +#if 0 + // override new/delete variants for efficiency (?) #ifdef _WIN64 // 64 bit new/delete MI_PATCH_NAME2("??2@YAPEAX_K@Z", mi_malloc), @@ -447,30 +463,35 @@ static mi_patch_t patches[] = { MI_PATCH_NAME3("??3@YAXPAXABUnothrow_t@std@@@Z", mi_free, mi_free_term), MI_PATCH_NAME3("??_V@YAXPAXABUnothrow_t@std@@@Z", mi_free, mi_free_term), #endif - - { NULL, 0, NULL, NULL, NULL, PATCH_NONE } +#endif + { NULL, NULL, NULL, PATCH_NONE, {NULL,NULL,NULL,NULL} } }; // Apply a patch static bool mi_patch_apply(mi_patch_t* patch, patch_apply_t apply) { - if (patch->original == NULL) return true; // unresolved + if (patch->originals[0] == NULL) return true; // unresolved if (apply == PATCH_TARGET_TERM && patch->target_term == NULL) apply = PATCH_TARGET; // avoid re-applying non-term variants if (patch->applied == apply) return false; - DWORD protect = PAGE_READWRITE; - if (!VirtualProtect(patch->original, MI_JUMP_SIZE, PAGE_EXECUTE_READWRITE, &protect)) return false; - if (apply == PATCH_NONE) { - mi_jump_restore(patch->original, &patch->save); - } - else { - void* target = (apply == PATCH_TARGET ? patch->target : patch->target_term); - mi_assert_internal(target!=NULL); - if (target != NULL) mi_jump_write(patch->original, target, &patch->save); + for (int i = 0; i < MAX_ENTRIES; i++) { + void* original = patch->originals[i]; + if (original == NULL) break; // no more + + DWORD protect = PAGE_READWRITE; + if (!VirtualProtect(original, MI_JUMP_SIZE, PAGE_EXECUTE_READWRITE, &protect)) return false; + if (apply == PATCH_NONE) { + mi_jump_restore(original, &patch->saves[i]); + } + else { + void* target = (apply == PATCH_TARGET ? patch->target : patch->target_term); + mi_assert_internal(target != NULL); + if (target != NULL) mi_jump_write(original, target, &patch->saves[i]); + } + VirtualProtect(original, MI_JUMP_SIZE, protect, &protect); } patch->applied = apply; - VirtualProtect(patch->original, MI_JUMP_SIZE, protect, &protect); return true; } @@ -523,23 +544,29 @@ static int __cdecl mi_setmaxstdio(int newmax) { // ------------------------------------------------------ // Try to resolve patches for a given module (DLL) -static void mi_module_resolve(HMODULE mod, int priority) { +static void mi_module_resolve(const char* fname, HMODULE mod, int priority) { // see if any patches apply for (size_t i = 0; patches[i].name != NULL; i++) { mi_patch_t* patch = &patches[i]; - if (!patch->applied && patch->priority < priority) { - void* addr = GetProcAddress(mod, patch->name); - if (addr != NULL) { - // found it! set the address - patch->original = addr; - patch->priority = priority; + if (patch->applied == PATCH_NONE) { + // find an available entry + int i = 0; + while (i < MAX_ENTRIES && patch->originals[i] != NULL) i++; + if (i < MAX_ENTRIES) { + void* addr = GetProcAddress(mod, patch->name); + if (addr != NULL) { + // found it! set the address + patch->originals[i] = addr; + _mi_trace_message(" override %s at %s!%p (entry %i)\n", patch->name, fname, addr, i); + } } } } } -#define MIMALLOC_NAME "mimalloc-override" -#define UCRTBASE_NAME "ucrtbase" +#define MIMALLOC_NAME "mimalloc-override.dll" +#define UCRTBASE_NAME "ucrtbase.dll" +#define UCRTBASED_NAME "ucrtbased.dll" // Resolve addresses of all patches by inspecting the loaded modules static atexit_fun_t* crt_atexit = NULL; @@ -553,14 +580,13 @@ static bool mi_patches_resolve(void) { HMODULE modules[400]; // try to stay under 4k to not trigger the guard page EnumProcessModules(process, modules, sizeof(modules), &needed); if (needed == 0) return false; - size_t count = needed / sizeof(HMODULE); - size_t ucrtbase_index = 0; - size_t mimalloc_index = 0; - // iterate through the loaded modules; do this from the end so we prefer the - // first loaded DLL as sometimes both "msvcr" and "ucrt" are both loaded and we should - // override "ucrt" in that situation. - for (size_t i = count; i > 0; i--) { - HMODULE mod = modules[i-1]; + int count = needed / sizeof(HMODULE); + int ucrtbase_index = 0; + int mimalloc_index = 0; + // iterate through the loaded modules + _mi_trace_message("overriding malloc dynamically...\n"); + for (int i = 0; i < count; i++) { + HMODULE mod = modules[i]; char filename[MAX_PATH] = { 0 }; DWORD slen = GetModuleFileName(mod, filename, MAX_PATH); if (slen > 0 && slen < MAX_PATH) { @@ -568,19 +594,23 @@ static bool mi_patches_resolve(void) { filename[slen] = 0; const char* lastsep = strrchr(filename, '\\'); const char* basename = (lastsep==NULL ? filename : lastsep+1); + _mi_trace_message(" %i: dynamic module %s\n", i, filename); + // remember indices so we can check load order (in debug mode) + if (_stricmp(basename, MIMALLOC_NAME) == 0) mimalloc_index = i; + if (_stricmp(basename, UCRTBASE_NAME) == 0) ucrtbase_index = i; + if (_stricmp(basename, UCRTBASED_NAME) == 0) ucrtbase_index = i; + + // see if we potentially patch in this module int priority = 0; if (i == 0) priority = 2; // main module to allow static crt linking else if (_strnicmp(basename, "ucrt", 4) == 0) priority = 3; // new ucrtbase.dll in windows 10 - else if (_strnicmp(basename, "msvcr", 5) == 0) priority = 1; // older runtimes + // NOTE: don't override msvcr -- leads to crashes in setlocale (needs more testing) + // else if (_strnicmp(basename, "msvcr", 5) == 0) priority = 1; // older runtimes if (priority > 0) { - // remember indices so we can check load order (in debug mode) - if (_stricmp(basename, MIMALLOC_NAME) == 0) mimalloc_index = i; - if (_stricmp(basename, UCRTBASE_NAME) == 0) ucrtbase_index = i; - // probably found a crt module, try to patch it - mi_module_resolve(mod,priority); + mi_module_resolve(basename,mod,priority); // try to find the atexit functions for the main process (in `ucrtbase.dll`) if (crt_atexit==NULL) crt_atexit = (atexit_fun_t*)GetProcAddress(mod, "_crt_atexit"); @@ -588,13 +618,11 @@ static bool mi_patches_resolve(void) { } } } -#if (MI_DEBUG) - size_t diff = (mimalloc_index > ucrtbase_index ? mimalloc_index - ucrtbase_index : ucrtbase_index - mimalloc_index); - if ((mimalloc_index > 0 || ucrtbase_index > 0) && (diff != 1)) { - _mi_warning_message("warning: the \"mimalloc-override\" DLL seems not to load right before or after the C runtime (\"ucrtbase\").\n" - " Try to fix this by changing the linking order."); + int diff = mimalloc_index - ucrtbase_index; + if (diff > 1) { + _mi_warning_message("warning: the \"mimalloc-override\" DLL seems not to load before or right after the C runtime (\"ucrtbase\").\n" + " Try to fix this by changing the linking order.\n"); } -#endif return true; } diff --git a/src/init.c b/src/init.c index 9b7e60ce..0c77a09b 100644 --- a/src/init.c +++ b/src/init.c @@ -333,7 +333,9 @@ void mi_thread_init(void) mi_attr_noexcept pthread_setspecific(mi_pthread_key, (void*)(_mi_thread_id()|1)); // set to a dummy value so that `mi_pthread_done` is called #endif + #if (MI_DEBUG>0) // not in release mode as that leads to crashes on Windows dynamic override _mi_verbose_message("thread init: 0x%zx\n", _mi_thread_id()); + #endif } void mi_thread_done(void) mi_attr_noexcept { @@ -346,9 +348,11 @@ void mi_thread_done(void) mi_attr_noexcept { // abandon the thread local heap if (_mi_heap_done()) return; // returns true if already ran + #if (MI_DEBUG>0) if (!_mi_is_main_thread()) { _mi_verbose_message("thread done: 0x%zx\n", _mi_thread_id()); } + #endif } diff --git a/src/options.c b/src/options.c index a1170386..5393b3df 100644 --- a/src/options.c +++ b/src/options.c @@ -39,7 +39,7 @@ static mi_option_desc_t options[_mi_option_last] = { #endif { 0, UNINIT, "show_stats" }, { MI_DEBUG, UNINIT, "show_errors" }, - { MI_DEBUG, UNINIT, "verbose" } + { 0, UNINIT, "verbose" } }; static void mi_option_init(mi_option_desc_t* desc); @@ -105,6 +105,14 @@ void _mi_fprintf( FILE* out, const char* fmt, ... ) { va_end(args); } +void _mi_trace_message(const char* fmt, ...) { + if (mi_option_get(mi_option_verbose) <= 1) return; // only with verbose level 2 or higher + va_list args; + va_start(args, fmt); + mi_vfprintf(stderr, "mimalloc: ", fmt, args); + va_end(args); +} + void _mi_verbose_message(const char* fmt, ...) { if (!mi_option_is_enabled(mi_option_verbose)) return; va_list args; diff --git a/test/main-override.cpp b/test/main-override.cpp index e24ed6d3..c42884be 100644 --- a/test/main-override.cpp +++ b/test/main-override.cpp @@ -12,6 +12,14 @@ void free_p() { return; } +class Test { +private: + int i; +public: + Test(int x) { i = x; } + ~Test() { } +}; + int main() { mi_stats_reset(); atexit(free_p); @@ -26,8 +34,10 @@ int main() { free(p1); free(p2); free(s); + Test* t = new Test(42); + delete t; mi_collect(true); - mi_stats_print(NULL); + // mi_stats_print(NULL); // MIMALLOC_VERBOSE env is set to 2 return 0; }