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 +~~~