Coverage Report

Created: 2026-06-01 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/memdebug.c
Line
Count
Source
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9
 *
10
 * This software is licensed as described in the file COPYING, which
11
 * you should have received as part of this distribution. The terms
12
 * are also available at https://curl.se/docs/copyright.html.
13
 *
14
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
 * copies of the Software, and permit persons to whom the Software is
16
 * furnished to do so, under the terms of the COPYING file.
17
 *
18
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
 * KIND, either express or implied.
20
 *
21
 * SPDX-License-Identifier: curl
22
 *
23
 ***************************************************************************/
24
#include "curl_setup.h"
25
26
#ifdef CURL_MEMDEBUG
27
28
#include <stddef.h>  /* for offsetof() */
29
30
#include "urldata.h"
31
#include "curl_threads.h"
32
#include "curlx/fopen.h"  /* for CURLX_FOPEN_LOW(), CURLX_FREOPEN_LOW() */
33
34
#ifdef USE_BACKTRACE
35
#include <backtrace.h>
36
#endif
37
38
struct memdebug {
39
  size_t size;
40
  union {
41
    curl_off_t o;
42
    double d;
43
    void *p;
44
  } mem[1];
45
  /* I am hoping this is the thing with the strictest alignment
46
   * requirements. That also means we waste some space :-( */
47
};
48
49
/*
50
 * Note that these debug functions are simple and they are meant to remain so.
51
 * For advanced analysis, record a log file and write perl scripts to analyze
52
 * them!
53
 *
54
 * Do not use these with multi-threaded test programs!
55
 */
56
57
FILE *curl_dbg_logfile = NULL;
58
static bool registered_cleanup = FALSE; /* atexit registered cleanup */
59
static bool memlimit = FALSE; /* enable memory limit */
60
static long memsize = 0;  /* set number of mallocs allowed */
61
#ifdef USE_BACKTRACE
62
static struct backtrace_state *btstate;
63
#endif
64
65
static char membuf[10000];
66
static size_t memwidx = 0; /* write index */
67
68
#ifdef USE_MUTEX
69
static bool dbg_mutex_init = 0;
70
static curl_mutex_t dbg_mutex;
71
#endif
72
73
static bool curl_dbg_lock(void)
74
8.55M
{
75
8.55M
#ifdef USE_MUTEX
76
8.55M
  if(dbg_mutex_init) {
77
0
    Curl_mutex_acquire(&dbg_mutex);
78
0
    return TRUE;
79
0
  }
80
8.55M
#endif
81
8.55M
  return FALSE;
82
8.55M
}
83
84
static void curl_dbg_unlock(bool was_locked)
85
8.55M
{
86
8.55M
#ifdef USE_MUTEX
87
8.55M
  if(was_locked)
88
0
    Curl_mutex_release(&dbg_mutex);
89
#else
90
  (void)was_locked;
91
#endif
92
8.55M
}
93
94
static void curl_dbg_log_locked(const char *format, ...) CURL_PRINTF(1, 2);
95
96
/* LeakSantizier (LSAN) calls _exit() instead of exit() when a leak is detected
97
   on exit so the logfile must be closed explicitly or data could be lost.
98
   Though _exit() does not call atexit handlers such as this, LSAN's call to
99
   _exit() comes after the atexit handlers are called. curl/curl#6620 */
100
static void curl_dbg_cleanup(void)
101
0
{
102
0
  bool locked = curl_dbg_lock();
103
0
  if(curl_dbg_logfile &&
104
0
     curl_dbg_logfile != stderr &&
105
0
     curl_dbg_logfile != stdout) {
106
0
    if(memwidx)
107
0
      fwrite(membuf, 1, memwidx, curl_dbg_logfile);
108
    /* !checksrc! disable BANNEDFUNC 1 */
109
0
    fclose(curl_dbg_logfile);
110
0
  }
111
0
  curl_dbg_logfile = NULL;
112
0
  curl_dbg_unlock(locked);
113
0
#ifdef USE_MUTEX
114
0
  if(dbg_mutex_init) {
115
0
    Curl_mutex_destroy(&dbg_mutex);
116
0
    dbg_mutex_init = FALSE;
117
0
  }
118
0
#endif
119
0
}
120
121
#ifdef USE_BACKTRACE
122
static void error_bt_callback(void *data, const char *message,
123
                              int error_number)
124
{
125
  (void)data;
126
  if(error_number == -1)
127
    curl_dbg_log("compile with -g\n\n");
128
  else
129
    curl_dbg_log("Backtrace error %d: %s\n", error_number, message);
130
}
131
132
static int full_callback(void *data, uintptr_t pc, const char *pathname,
133
                         int line_number, const char *function)
134
{
135
  (void)data;
136
  (void)pc;
137
  if(pathname || function || line_number)
138
    curl_dbg_log("BT %s:%d -- %s\n", pathname, line_number, function);
139
  return 0;
140
}
141
142
static void dump_bt(void)
143
{
144
  backtrace_full(btstate, 0, full_callback, error_bt_callback, NULL);
145
}
146
#else
147
#define dump_bt() /* nothing to do */
148
#endif
149
150
/* this sets the log filename */
151
void curl_dbg_memdebug(const char *logname)
152
0
{
153
0
  if(!curl_dbg_logfile) {
154
0
    if(logname && *logname)
155
0
      curl_dbg_logfile = CURLX_FOPEN_LOW(logname, FOPEN_WRITETEXT);
156
#ifdef MEMDEBUG_LOG_SYNC
157
    /* Flush the log file after every line so the log is not lost in a crash */
158
    if(curl_dbg_logfile)
159
      setbuf(curl_dbg_logfile, (char *)NULL);
160
#endif
161
0
  }
162
0
#ifdef USE_MUTEX
163
0
  if(!dbg_mutex_init) {
164
0
    dbg_mutex_init = TRUE;
165
0
    Curl_mutex_init(&dbg_mutex);
166
0
  }
167
0
#endif
168
#ifdef USE_BACKTRACE
169
  btstate = backtrace_create_state(NULL, 0, error_bt_callback, NULL);
170
#endif
171
0
  if(!registered_cleanup)
172
0
    registered_cleanup = !atexit(curl_dbg_cleanup);
173
0
}
174
175
/* This function sets the number of malloc() calls that should return
176
   successfully! */
177
void curl_dbg_memlimit(long limit)
178
0
{
179
0
  if(!memlimit) {
180
0
    memlimit = TRUE;
181
0
    memsize = limit;
182
0
  }
183
0
}
184
185
/* returns TRUE if this is not allowed! */
186
static bool countcheck(const char *func, int line, const char *source)
187
38.2M
{
188
  /* if source is NULL, then the call is made internally and this check
189
     should not be made */
190
38.2M
  if(memlimit && source) {
191
0
    if(!memsize) {
192
      /* log to file */
193
0
      curl_dbg_log("LIMIT %s:%d %s reached memlimit\n", source, line, func);
194
      /* log to stderr also */
195
0
      curl_mfprintf(stderr, "LIMIT %s:%d %s reached memlimit\n",
196
0
                    source, line, func);
197
0
      dump_bt();
198
0
      fflush(curl_dbg_logfile); /* because it might crash now */
199
      /* !checksrc! disable ERRNOVAR 1 */
200
0
      errno = ENOMEM;
201
0
      return TRUE; /* RETURN ERROR! */
202
0
    }
203
0
    else
204
0
      memsize--; /* countdown */
205
0
  }
206
207
38.2M
  return FALSE; /* allow this */
208
38.2M
}
209
210
ALLOC_FUNC
211
void *curl_dbg_malloc(size_t wantedsize, int line, const char *source)
212
14.4M
{
213
14.4M
  struct memdebug *mem;
214
14.4M
  size_t size;
215
216
14.4M
  DEBUGASSERT(wantedsize != 0);
217
218
14.4M
  if(countcheck("malloc", line, source))
219
0
    return NULL;
220
221
  /* alloc at least 64 bytes */
222
14.4M
  size = sizeof(struct memdebug) + wantedsize;
223
224
14.4M
  mem = Curl_cmalloc(size);
225
14.4M
  if(mem) {
226
14.4M
    mem->size = wantedsize;
227
14.4M
  }
228
229
14.4M
  if(source)
230
9.39M
    curl_dbg_log("MEM %s:%d malloc(%zu) = %p\n",
231
9.39M
                 source, line, wantedsize,
232
9.39M
                 mem ? (void *)mem->mem : (void *)0);
233
234
14.4M
  return mem ? mem->mem : NULL;
235
14.4M
}
236
237
ALLOC_FUNC
238
void *curl_dbg_calloc(size_t wanted_elements, size_t wanted_size,
239
                      int line, const char *source)
240
10.0M
{
241
10.0M
  struct memdebug *mem;
242
10.0M
  size_t size, user_size;
243
244
10.0M
  DEBUGASSERT(wanted_elements != 0);
245
10.0M
  DEBUGASSERT(wanted_size != 0);
246
247
10.0M
  if(countcheck("calloc", line, source))
248
0
    return NULL;
249
250
  /* alloc at least 64 bytes */
251
10.0M
  user_size = wanted_size * wanted_elements;
252
10.0M
  size = sizeof(struct memdebug) + user_size;
253
254
10.0M
  mem = Curl_ccalloc(1, size);
255
10.0M
  if(mem)
256
10.0M
    mem->size = user_size;
257
258
10.0M
  if(source)
259
10.0M
    curl_dbg_log("MEM %s:%d calloc(%zu,%zu) = %p\n",
260
10.0M
                 source, line, wanted_elements, wanted_size,
261
10.0M
                 mem ? (void *)mem->mem : (void *)0);
262
263
10.0M
  return mem ? mem->mem : NULL;
264
10.0M
}
265
266
ALLOC_FUNC
267
char *curl_dbg_strdup(const char *str, int line, const char *source)
268
5.01M
{
269
5.01M
  char *mem;
270
5.01M
  size_t len;
271
272
5.01M
  DEBUGASSERT(str != NULL);
273
274
5.01M
  if(countcheck("strdup", line, source))
275
0
    return NULL;
276
277
5.01M
  len = strlen(str) + 1;
278
279
5.01M
  mem = curl_dbg_malloc(len, 0, NULL); /* NULL prevents logging */
280
5.01M
  if(mem)
281
5.01M
    memcpy(mem, str, len);
282
283
5.01M
  if(source)
284
5.01M
    curl_dbg_log("MEM %s:%d strdup(%p) (%zu) = %p\n",
285
5.01M
                 source, line, (const void *)str, len, (const void *)mem);
286
287
5.01M
  return mem;
288
5.01M
}
289
290
#if defined(_WIN32) && defined(UNICODE)
291
ALLOC_FUNC
292
wchar_t *curl_dbg_wcsdup(const wchar_t *str, int line, const char *source)
293
{
294
  wchar_t *mem;
295
  size_t wsiz, bsiz;
296
297
  DEBUGASSERT(str != NULL);
298
299
  if(countcheck("wcsdup", line, source))
300
    return NULL;
301
302
  wsiz = wcslen(str) + 1;
303
  bsiz = wsiz * sizeof(wchar_t);
304
305
  mem = curl_dbg_malloc(bsiz, 0, NULL); /* NULL prevents logging */
306
  if(mem)
307
    memcpy(mem, str, bsiz);
308
309
  if(source)
310
    curl_dbg_log("MEM %s:%d wcsdup(%p) (%zu) = %p\n",
311
                source, line, (const void *)str, bsiz, (void *)mem);
312
313
  return mem;
314
}
315
#endif
316
317
/* We provide a realloc() that accepts a NULL as pointer, which then
318
   performs a malloc(). In order to work with ares. */
319
void *curl_dbg_realloc(void *ptr, size_t wantedsize,
320
                       int line, const char *source)
321
8.55M
{
322
8.55M
  struct memdebug *mem = NULL;
323
8.55M
  bool was_locked;
324
325
8.55M
  size_t size = sizeof(struct memdebug) + wantedsize;
326
327
8.55M
  DEBUGASSERT(wantedsize != 0);
328
329
8.55M
  if(countcheck("realloc", line, source))
330
0
    return NULL;
331
332
  /* need to realloc under lock, as we get out-of-order log
333
   * entries otherwise, since another thread might alloc the
334
   * memory released by realloc() before otherwise would log it. */
335
8.55M
  was_locked = curl_dbg_lock();
336
#ifdef __INTEL_COMPILER
337
#  pragma warning(push)
338
#  pragma warning(disable:1684)
339
   /* 1684: conversion from pointer to same-sized integral type */
340
#endif
341
342
8.55M
  if(ptr)
343
1.40M
    mem = (void *)((char *)ptr - offsetof(struct memdebug, mem));
344
345
#ifdef __INTEL_COMPILER
346
#  pragma warning(pop)
347
#endif
348
349
8.55M
  mem = Curl_crealloc(mem, size);
350
8.55M
  if(source)
351
8.55M
    curl_dbg_log_locked("MEM %s:%d realloc(%p, %zu) = %p\n",
352
8.55M
                        source, line, (void *)ptr, wantedsize,
353
8.55M
                        mem ? (void *)mem->mem : (void *)0);
354
355
8.55M
  curl_dbg_unlock(was_locked);
356
8.55M
  if(mem) {
357
8.55M
    mem->size = wantedsize;
358
8.55M
    return mem->mem;
359
8.55M
  }
360
361
0
  return NULL;
362
8.55M
}
363
364
void curl_dbg_free(void *ptr, int line, const char *source)
365
151M
{
366
151M
  if(ptr) {
367
31.6M
    struct memdebug *mem;
368
369
31.6M
    if(source)
370
31.6M
      curl_dbg_log("MEM %s:%d free(%p)\n", source, line, (void *)ptr);
371
372
#ifdef __INTEL_COMPILER
373
#  pragma warning(push)
374
#  pragma warning(disable:1684)
375
   /* 1684: conversion from pointer to same-sized integral type */
376
#endif
377
378
31.6M
    mem = (void *)((char *)ptr - offsetof(struct memdebug, mem));
379
380
#ifdef __INTEL_COMPILER
381
#  pragma warning(pop)
382
#endif
383
384
    /* free for real */
385
31.6M
    Curl_cfree(mem);
386
31.6M
  }
387
151M
}
388
389
curl_socket_t curl_dbg_socket(int domain, int type, int protocol,
390
                              int line, const char *source)
391
181k
{
392
181k
  curl_socket_t sockfd;
393
394
181k
  if(countcheck("socket", line, source))
395
0
    return CURL_SOCKET_BAD;
396
397
  /* !checksrc! disable BANNEDFUNC 1 */
398
181k
  sockfd = socket(domain, type, protocol);
399
400
181k
  if(source && (sockfd != CURL_SOCKET_BAD))
401
181k
    curl_dbg_log("FD %s:%d socket() = %" FMT_SOCKET_T "\n",
402
181k
                 source, line, sockfd);
403
404
181k
  return sockfd;
405
181k
}
406
407
#ifdef HAVE_SOCKETPAIR
408
int curl_dbg_socketpair(int domain, int type, int protocol,
409
                        curl_socket_t socket_vector[2],
410
                        int line, const char *source)
411
0
{
412
  /* !checksrc! disable BANNEDFUNC 1 */
413
0
  int res = socketpair(domain, type, protocol, socket_vector);
414
415
0
  if(source && (res == 0))
416
0
    curl_dbg_log("FD %s:%d socketpair() = "
417
0
                 "%" FMT_SOCKET_T " %" FMT_SOCKET_T "\n",
418
0
                 source, line, socket_vector[0], socket_vector[1]);
419
420
0
  return res;
421
0
}
422
#endif
423
424
curl_socket_t curl_dbg_accept(curl_socket_t s, void *saddr, void *saddrlen,
425
                              int line, const char *source)
426
0
{
427
0
  struct sockaddr *addr = (struct sockaddr *)saddr;
428
0
  curl_socklen_t *addrlen = (curl_socklen_t *)saddrlen;
429
430
  /* !checksrc! disable BANNEDFUNC 1 */
431
0
  curl_socket_t sockfd = accept(s, addr, addrlen);
432
433
0
  if(source && (sockfd != CURL_SOCKET_BAD))
434
0
    curl_dbg_log("FD %s:%d accept() = %" FMT_SOCKET_T "\n",
435
0
                 source, line, sockfd);
436
437
0
  return sockfd;
438
0
}
439
440
#ifdef HAVE_ACCEPT4
441
curl_socket_t curl_dbg_accept4(curl_socket_t s, void *saddr, void *saddrlen,
442
                               int flags,
443
                               int line, const char *source)
444
0
{
445
0
  struct sockaddr *addr = (struct sockaddr *)saddr;
446
0
  curl_socklen_t *addrlen = (curl_socklen_t *)saddrlen;
447
448
  /* !checksrc! disable BANNEDFUNC 1 */
449
0
  curl_socket_t sockfd = accept4(s, addr, addrlen, flags);
450
451
0
  if(source && (sockfd != CURL_SOCKET_BAD))
452
0
    curl_dbg_log("FD %s:%d accept() = %" FMT_SOCKET_T "\n",
453
0
                 source, line, sockfd);
454
455
0
  return sockfd;
456
0
}
457
#endif
458
459
/* separate function to allow libcurl to mark a "faked" close */
460
void curl_dbg_mark_sclose(curl_socket_t sockfd, int line, const char *source)
461
291k
{
462
291k
  if(source)
463
291k
    curl_dbg_log("FD %s:%d sclose(%" FMT_SOCKET_T ")\n",
464
291k
                 source, line, sockfd);
465
291k
}
466
467
/* this is our own defined way to close sockets on *ALL* platforms */
468
int curl_dbg_sclose(curl_socket_t sockfd, int line, const char *source)
469
291k
{
470
291k
  curl_dbg_mark_sclose(sockfd, line, source);
471
291k
  return CURL_SCLOSE(sockfd);
472
291k
}
473
474
ALLOC_FUNC
475
FILE *curl_dbg_fopen(const char *file, const char *mode,
476
                     int line, const char *source)
477
968k
{
478
968k
  FILE *res = CURLX_FOPEN_LOW(file, mode);
479
968k
  if(source)
480
968k
    curl_dbg_log("FILE %s:%d fopen(\"%s\",\"%s\") = %p\n",
481
968k
                 source, line, file, mode, (void *)res);
482
483
968k
  return res;
484
968k
}
485
486
ALLOC_FUNC
487
FILE *curl_dbg_freopen(const char *file, const char *mode, FILE *fh,
488
                       int line, const char *source)
489
0
{
490
0
  FILE *res = CURLX_FREOPEN_LOW(file, mode, fh);
491
0
  if(source)
492
0
    curl_dbg_log("FILE %s:%d freopen(\"%s\",\"%s\",%p) = %p\n",
493
0
                 source, line, file, mode, (void *)fh, (void *)res);
494
495
0
  return res;
496
0
}
497
498
ALLOC_FUNC
499
FILE *curl_dbg_fdopen(int filedes, const char *mode,
500
                      int line, const char *source)
501
0
{
502
0
  FILE *res = CURLX_FDOPEN_LOW(filedes, mode);
503
0
  if(source)
504
0
    curl_dbg_log("FILE %s:%d fdopen(\"%d\",\"%s\") = %p\n",
505
0
                 source, line, filedes, mode, (void *)res);
506
0
  return res;
507
0
}
508
509
int curl_dbg_fclose(FILE *file, int line, const char *source)
510
968k
{
511
968k
  int res;
512
513
968k
  DEBUGASSERT(file != NULL);
514
515
968k
  if(source)
516
968k
    curl_dbg_log("FILE %s:%d fclose(%p)\n", source, line, (void *)file);
517
518
  /* !checksrc! disable BANNEDFUNC 1 */
519
968k
  res = fclose(file);
520
521
968k
  return res;
522
968k
}
523
524
static void curl_dbg_vlog(const char * const fmt,
525
                          va_list ap) CURL_PRINTF(1, 0);
526
527
static void curl_dbg_vlog(const char * const fmt, va_list ap)
528
0
{
529
0
  char buf[1024];
530
0
  size_t nchars = curl_mvsnprintf(buf, sizeof(buf), fmt, ap);
531
532
0
  if(nchars > (int)sizeof(buf) - 1)
533
0
    nchars = (int)sizeof(buf) - 1;
534
535
0
  if(nchars > 0) {
536
0
    if(sizeof(membuf) - nchars < memwidx) {
537
      /* flush */
538
0
      fwrite(membuf, 1, memwidx, curl_dbg_logfile);
539
0
      fflush(curl_dbg_logfile);
540
0
      memwidx = 0;
541
0
    }
542
0
    if(memwidx) {
543
      /* the previous line ends with a newline */
544
0
      DEBUGASSERT(membuf[memwidx - 1] == '\n');
545
0
    }
546
0
    memcpy(&membuf[memwidx], buf, nchars);
547
0
    memwidx += nchars;
548
0
  }
549
0
}
550
551
static void curl_dbg_log_locked(const char *format, ...)
552
8.55M
{
553
8.55M
  va_list ap;
554
555
8.55M
  if(!curl_dbg_logfile)
556
8.55M
    return;
557
558
8.55M
  va_start(ap, format);
559
0
  curl_dbg_vlog(format, ap);
560
0
  va_end(ap);
561
0
}
562
563
/* this does the writing to the memory tracking log file */
564
void curl_dbg_log(const char *format, ...)
565
58.5M
{
566
58.5M
  bool was_locked;
567
58.5M
  va_list ap;
568
569
58.5M
  if(!curl_dbg_logfile)
570
58.5M
    return;
571
572
1
  was_locked = curl_dbg_lock();
573
1
  va_start(ap, format);
574
1
  curl_dbg_vlog(format, ap);
575
  va_end(ap);
576
1
  curl_dbg_unlock(was_locked);
577
1
}
578
579
#endif /* CURL_MEMDEBUG */