Skip to content

Commit

Permalink
build: support Windows CRT heap memory tracking
Browse files Browse the repository at this point in the history
- 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: curl#12327
Ref: https://github.com/jay/curl/blob/crtdbg/winbuild/README_HEAP_DEBUG.md
  • Loading branch information
jay committed Nov 19, 2023
1 parent 8261800 commit d943b54
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 2 deletions.
28 changes: 28 additions & 0 deletions docs/examples/simple.c
Expand Up @@ -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 <logfile> */
{
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");
Expand Down
12 changes: 10 additions & 2 deletions lib/curl_memory.h
Expand Up @@ -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,
Expand Down Expand Up @@ -174,5 +182,5 @@ extern curl_wcsdup_callback Curl_cwcsdup;
# endif
#endif

#endif /* CURLDEBUG */
#endif /* !_CRTDBG_MAP_ALLOC && !CURLDEBUG */
#endif /* HEADER_CURL_MEMORY_H */
23 changes: 23 additions & 0 deletions lib/easy.c
Expand Up @@ -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;
Expand Down
19 changes: 19 additions & 0 deletions src/tool_main.c
Expand Up @@ -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 */
Expand Down
73 changes: 73 additions & 0 deletions 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:
~~~cmd
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`:
~~~cmd
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:
~~~cmd
%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.
~~~cmd
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.
~~~cmd
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
~~~

0 comments on commit d943b54

Please sign in to comment.