From 9271bbd6b8767bae47000b5c998c18410a4e1ced Mon Sep 17 00:00:00 2001 From: Jay Satiro Date: Sun, 19 Nov 2023 03:19:16 -0500 Subject: [PATCH] build: support Windows CRT heap memory tracking - Support Windows CRT heap memory tracking (_CRTDBG_MAP_ALLOC defined) as an alternative to libcurl heap memory tracking (CURLDEBUG defined). This commit is to demonstrate how it's possible to use Windows CRT heap memory tracking to detect leaks in libcurl *and* the application, with filenames and line numbers recorded for the leaks. This is not intended to go upstream, it was written to further the discussion. See winbuild\README_HEAP_DEBUG.md for instructions. Ref: https://github.com/curl/curl/issues/12327 Ref: TBD --- docs/examples/simple.c | 28 ++++++++++++++ lib/curl_memory.h | 12 +++++- lib/easy.c | 23 +++++++++++ src/tool_main.c | 19 +++++++++ winbuild/README_HEAP_DEBUG.md | 73 +++++++++++++++++++++++++++++++++++ 5 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 winbuild/README_HEAP_DEBUG.md diff --git a/docs/examples/simple.c b/docs/examples/simple.c index 8579b0ba54cc95..47dd698b35cc8f 100644 --- a/docs/examples/simple.c +++ b/docs/examples/simple.c @@ -33,6 +33,34 @@ int main(void) CURL *curl; CURLcode res; +#if defined(CURLDEBUG) && defined(_CRTDBG_MAP_ALLOC) +#error "CURLDEBUG and _CRTDBG_MAP_ALLOC both defined, pick one" +#elif defined(CURLDEBUG) + /* libcurl heap memory tracking. run tests/memanalyze.pl */ + { + const char *env = getenv("CURL_MEMDEBUG"); + if(env) + curl_dbg_memdebug(env); + } +#elif defined(_CRTDBG_MAP_ALLOC) +#ifndef _INC_CRTDBG +#error "missing crtdbg.h forced include: cl /FIcrtdbg.h /D_CRTDBG_MAP_ALLOC" +#endif + /* Windows CRT heap memory tracking. on exit it prints heap memory leaks. + if libcurl was not built with _CRTDBG_MAP_ALLOC as well then there are no + filename or line numbers for libcurl allocations in the leak report. */ + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif + +#ifdef CURL_FORCEMEMLEAK + /* MEMORY LEAK TEST. See winbuild\README_HEAP_DEBUG.md. */ + malloc(5); +#endif + + curl_global_init(CURL_GLOBAL_DEFAULT); + curl = curl_easy_init(); if(curl) { curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); diff --git a/lib/curl_memory.h b/lib/curl_memory.h index b8c46d79395f50..629d700c02a483 100644 --- a/lib/curl_memory.h +++ b/lib/curl_memory.h @@ -138,7 +138,15 @@ extern curl_calloc_callback Curl_ccalloc; extern curl_wcsdup_callback Curl_cwcsdup; #endif -#ifndef CURLDEBUG +#if defined(_CRTDBG_MAP_ALLOC) && defined(CURLDEBUG) +#error "CURLDEBUG and _CRTDBG_MAP_ALLOC both defined, pick one" +#endif + +#if defined(_CRTDBG_MAP_ALLOC) && !defined(_INC_CRTDBG) +#error "missing crtdbg.h forced include: cl /FIcrtdbg.h /D_CRTDBG_MAP_ALLOC" +#endif + +#if !defined(_CRTDBG_MAP_ALLOC) && !defined(CURLDEBUG) /* * libcurl's 'memory tracking' system defines strdup, malloc, calloc, @@ -174,5 +182,5 @@ extern curl_wcsdup_callback Curl_cwcsdup; # endif #endif -#endif /* CURLDEBUG */ +#endif /* !_CRTDBG_MAP_ALLOC && !CURLDEBUG */ #endif /* HEADER_CURL_MEMORY_H */ diff --git a/lib/easy.c b/lib/easy.c index cf254ee555d785..405d4aabf6d470 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -143,9 +143,32 @@ static char *leakpointer; */ static CURLcode global_init(long flags, bool memoryfuncs) { + /* If _CRTDBG_MAP_ALLOC (Windows CRT heap debugging) is defined then assume + we're using malloc -> _malloc_dbg, etc, from CRT's crtdbg.h instead of + libcurl's Curl_cmalloc etc. + If the user has set memory functions already, they do nothing. Permafail + to make it impossible to ignore. */ +#ifdef _CRTDBG_MAP_ALLOC + static bool permafail; + if(!memoryfuncs) + permafail = true; + if(permafail) { + fputs("Error: Use curl_global_init instead of curl_global_init_mem to " + "initialize libcurl. libcurl was built with _CRTDBG_MAP_ALLOC which " + "allows the Windows CRT to debug the heap if crtdbg.h was also " + "force-included. Custom memory functions can't be set.\n", stderr); + return CURLE_FAILED_INIT; + } +#endif /* _CRTDBG_MAP_ALLOC */ + if(initialized++) return CURLE_OK; +#ifdef CURL_FORCEMEMLEAK + /* MEMORY LEAK TEST. See winbuild\README_HEAP_DEBUG.md. */ + malloc(7); +#endif + if(memoryfuncs) { /* Setup the default memory functions here (again) */ Curl_cmalloc = (curl_malloc_callback)malloc; diff --git a/src/tool_main.c b/src/tool_main.c index 2f132e2d29c524..59a9de96b14860 100644 --- a/src/tool_main.c +++ b/src/tool_main.c @@ -261,8 +261,27 @@ int main(int argc, char *argv[]) (void)signal(SIGPIPE, SIG_IGN); #endif +#if defined(CURLDEBUG) && defined(_CRTDBG_MAP_ALLOC) +#error "CURLDEBUG and _CRTDBG_MAP_ALLOC both defined, pick one" +#elif defined(CURLDEBUG) /* Initialize memory tracking */ memory_tracking_init(); +#elif defined(_CRTDBG_MAP_ALLOC) +#ifndef _INC_CRTDBG +#error "missing crtdbg.h forced include: cl /FIcrtdbg.h /D_CRTDBG_MAP_ALLOC" +#endif + /* Windows CRT heap memory tracking. on exit it prints heap memory leaks. + if libcurl was not built with _CRTDBG_MAP_ALLOC as well then there are no + filename or line numbers for libcurl allocations in the leak report. */ + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif + +#ifdef CURL_FORCEMEMLEAK + /* MEMORY LEAK TEST. See winbuild\README_HEAP_DEBUG.md. */ + malloc(5); +#endif /* Initialize the curl library - do not call any libcurl functions before this point */ diff --git a/winbuild/README_HEAP_DEBUG.md b/winbuild/README_HEAP_DEBUG.md new file mode 100644 index 00000000000000..d29fc826669c8e --- /dev/null +++ b/winbuild/README_HEAP_DEBUG.md @@ -0,0 +1,73 @@ +Ref: https://github.com/curl/curl/issues/12327 + +This document shows how to build libcurl with CURLDEBUG (libcurl heap memory +tracking) or CRTDBG (Windows CRT heap memory tracking). The latter will track +libcurl and the application. + +The example commands use `%CL_CRTDBG%` for CRTDBG, but can be changed to +`%CL_CURLDEBUG%` for CURLDEBUG. + +To force a memory leak in both libcurl and the application (curl.exe or +simple.exe) add `/DCURL_FORCEMEMLEAK` to the CL options. This is useful to see +what the output of a memory leak looks like. It should cause 7 bytes to leak +from libcurl and 5 bytes to leak from the application. + +Open a Visual Studio command prompt and build curl: +~~~ +set "CURL_SRCPATH=x:\j\curl\curl" +set "CL_CRTDBG=cl /DCURL_FORCEMEMLEAK /FC /FIcrtdbg.h /D_CRTDBG_MAP_ALLOC /D_CRT_SECURE_NO_WARNINGS /D_CRT_NONSTDC_NO_DEPRECATE" +set "CL_CURLDEBUG=cl /DCURL_FORCEMEMLEAK /FC /DCURLDEBUG /DMEMDEBUG_LOG_SYNC" + +cd /d "%CURL_SRCPATH%\winbuild" +if not exist ..\src\tool_hugehelp.c (..\buildconf.bat) +nmake /f Makefile.vc mode=dll VC=10 DEBUG=yes clean +nmake /f Makefile.vc mode=dll VC=10 DEBUG=yes CC="%CL_CRTDBG%" +~~~ + +The build output will show an object directory ending in -obj-curl. Remove that +suffix and switch to the bin output subdir. For example if the object directory +is `libcurl-vc10-x86-debug-dll-ipv6-sspi-schannel-obj-curl`: +~~~ +cd ..\builds\libcurl-vc10-x86-debug-dll-ipv6-sspi-schannel\bin +~~~ + +If the curl tool (curl.exe) is not the application then build the application: +~~~ +%CL_CRTDBG% /MDd /I..\include "%CURL_SRCPATH%\docs\examples\simple.c" /link /LIBPATH:..\lib libcurl_debug.lib +~~~ + +**(CRTDBG)** Run the application. Heap memory allocations in the CRT are logged +internally. Heap memory leaks are printed to stderr on exit. +~~~ +simple +~~~ + +**(CRTDBG)** Examine the output. If `CURL_FORCEMEMLEAK` was defined then the +last lines of output should show a memory leak in libcurl and the application: +~~~ +Detected memory leaks! +Dumping objects -> +x:\j\curl\curl\lib\easy.c(169) : {88} normal block at 0x006A8E38, 7 bytes long. + Data: < > CD CD CD CD CD CD CD +x:\j\curl\curl\docs\examples\simple.c(59) : {87} normal block at 0x006E8FB0, 5 bytes long. + Data: < > CD CD CD CD CD +Object dump complete. +~~~ + +**(CURLDEBUG)** Run the application. Heap memory allocations in libcurl are +logged to curldbg.log. Heap memory leaks can be printed by running +memanalyze.pl. +~~~ +set "CURL_MEMDEBUG=curldbg.log" +simple +if not exist "%CURL_MEMDEBUG%" echo problem: missing "%CURL_MEMDEBUG%" +..\..\..\tests\memanalyze.pl curldbg.log +~~~ + +**(CURLDEBUG)** Examine the output. If `CURL_FORCEMEMLEAK` was defined then +memanalyze should show a memory leak in libcurl: +~~~ +Leak detected: memory still allocated: 7 bytes +At 4e7a58, there's 7 bytes. + allocated by x:\j\curl\curl\lib\easy.c:169 +~~~