1/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2021, 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 ***************************************************************************/
22
23#include "curl_setup.h"
24
25#ifdef CURLDEBUG
26
27#include <curl/curl.h>
28
29#include "urldata.h"
30
31#define MEMDEBUG_NODEFINES /* don't redefine the standard functions */
32
33/* The last 3 #include files should be in this order */
34#include "curl_printf.h"
35#include "curl_memory.h"
36#include "memdebug.h"
37
38struct memdebug {
39 size_t size;
40 union {
41 curl_off_t o;
42 double d;
43 void *p;
44 } mem[1];
45 /* I'm 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 very simple and they are meant to
51 * remain so. For advanced analysis, record a log file and write perl scripts
52 * to analyze them!
53 *
54 * Don't use these with multithreaded test programs!
55 */
56
57FILE *curl_dbg_logfile = NULL;
58static bool registered_cleanup = FALSE; /* atexit registered cleanup */
59static bool memlimit = FALSE; /* enable memory limit */
60static long memsize = 0; /* set number of mallocs allowed */
61
62/* LeakSantizier (LSAN) calls _exit() instead of exit() when a leak is detected
63 on exit so the logfile must be closed explicitly or data could be lost.
64 Though _exit() does not call atexit handlers such as this, LSAN's call to
65 _exit() comes after the atexit handlers are called. curl/curl#6620 */
66static void curl_dbg_cleanup(void)
67{
68 if(curl_dbg_logfile &&
69 curl_dbg_logfile != stderr &&
70 curl_dbg_logfile != stdout) {
71 fclose(curl_dbg_logfile);
72 }
73 curl_dbg_logfile = NULL;
74}
75
76/* this sets the log file name */
77void curl_dbg_memdebug(const char *logname)
78{
79 if(!curl_dbg_logfile) {
80 if(logname && *logname)
81 curl_dbg_logfile = fopen(logname, FOPEN_WRITETEXT);
82 else
83 curl_dbg_logfile = stderr;
84#ifdef MEMDEBUG_LOG_SYNC
85 /* Flush the log file after every line so the log isn't lost in a crash */
86 if(curl_dbg_logfile)
87 setbuf(curl_dbg_logfile, (char *)NULL);
88#endif
89 }
90 if(!registered_cleanup)
91 registered_cleanup = !atexit(curl_dbg_cleanup);
92}
93
94/* This function sets the number of malloc() calls that should return
95 successfully! */
96void curl_dbg_memlimit(long limit)
97{
98 if(!memlimit) {
99 memlimit = TRUE;
100 memsize = limit;
101 }
102}
103
104/* returns TRUE if this isn't allowed! */
105static bool countcheck(const char *func, int line, const char *source)
106{
107 /* if source is NULL, then the call is made internally and this check
108 should not be made */
109 if(memlimit && source) {
110 if(!memsize) {
111 /* log to file */
112 curl_dbg_log("LIMIT %s:%d %s reached memlimit\n",
113 source, line, func);
114 /* log to stderr also */
115 fprintf(stderr, "LIMIT %s:%d %s reached memlimit\n",
116 source, line, func);
117 fflush(curl_dbg_logfile); /* because it might crash now */
118 errno = ENOMEM;
119 return TRUE; /* RETURN ERROR! */
120 }
121 else
122 memsize--; /* countdown */
123
124
125 }
126
127 return FALSE; /* allow this */
128}
129
130void *curl_dbg_malloc(size_t wantedsize, int line, const char *source)
131{
132 struct memdebug *mem;
133 size_t size;
134
135 DEBUGASSERT(wantedsize != 0);
136
137 if(countcheck("malloc", line, source))
138 return NULL;
139
140 /* alloc at least 64 bytes */
141 size = sizeof(struct memdebug) + wantedsize;
142
143 mem = (Curl_cmalloc)(size);
144 if(mem) {
145 mem->size = wantedsize;
146 }
147
148 if(source)
149 curl_dbg_log("MEM %s:%d malloc(%zu) = %p\n",
150 source, line, wantedsize,
151 mem ? (void *)mem->mem : (void *)0);
152
153 return (mem ? mem->mem : NULL);
154}
155
156void *curl_dbg_calloc(size_t wanted_elements, size_t wanted_size,
157 int line, const char *source)
158{
159 struct memdebug *mem;
160 size_t size, user_size;
161
162 DEBUGASSERT(wanted_elements != 0);
163 DEBUGASSERT(wanted_size != 0);
164
165 if(countcheck("calloc", line, source))
166 return NULL;
167
168 /* alloc at least 64 bytes */
169 user_size = wanted_size * wanted_elements;
170 size = sizeof(struct memdebug) + user_size;
171
172 mem = (Curl_ccalloc)(1, size);
173 if(mem)
174 mem->size = user_size;
175
176 if(source)
177 curl_dbg_log("MEM %s:%d calloc(%zu,%zu) = %p\n",
178 source, line, wanted_elements, wanted_size,
179 mem ? (void *)mem->mem : (void *)0);
180
181 return (mem ? mem->mem : NULL);
182}
183
184char *curl_dbg_strdup(const char *str, int line, const char *source)
185{
186 char *mem;
187 size_t len;
188
189 DEBUGASSERT(str != NULL);
190
191 if(countcheck("strdup", line, source))
192 return NULL;
193
194 len = strlen(str) + 1;
195
196 mem = curl_dbg_malloc(len, 0, NULL); /* NULL prevents logging */
197 if(mem)
198 memcpy(mem, str, len);
199
200 if(source)
201 curl_dbg_log("MEM %s:%d strdup(%p) (%zu) = %p\n",
202 source, line, (const void *)str, len, (const void *)mem);
203
204 return mem;
205}
206
207#if defined(WIN32) && defined(UNICODE)
208wchar_t *curl_dbg_wcsdup(const wchar_t *str, int line, const char *source)
209{
210 wchar_t *mem;
211 size_t wsiz, bsiz;
212
213 DEBUGASSERT(str != NULL);
214
215 if(countcheck("wcsdup", line, source))
216 return NULL;
217
218 wsiz = wcslen(str) + 1;
219 bsiz = wsiz * sizeof(wchar_t);
220
221 mem = curl_dbg_malloc(bsiz, 0, NULL); /* NULL prevents logging */
222 if(mem)
223 memcpy(mem, str, bsiz);
224
225 if(source)
226 curl_dbg_log("MEM %s:%d wcsdup(%p) (%zu) = %p\n",
227 source, line, (void *)str, bsiz, (void *)mem);
228
229 return mem;
230}
231#endif
232
233/* We provide a realloc() that accepts a NULL as pointer, which then
234 performs a malloc(). In order to work with ares. */
235void *curl_dbg_realloc(void *ptr, size_t wantedsize,
236 int line, const char *source)
237{
238 struct memdebug *mem = NULL;
239
240 size_t size = sizeof(struct memdebug) + wantedsize;
241
242 DEBUGASSERT(wantedsize != 0);
243
244 if(countcheck("realloc", line, source))
245 return NULL;
246
247#ifdef __INTEL_COMPILER
248# pragma warning(push)
249# pragma warning(disable:1684)
250 /* 1684: conversion from pointer to same-sized integral type */
251#endif
252
253 if(ptr)
254 mem = (void *)((char *)ptr - offsetof(struct memdebug, mem));
255
256#ifdef __INTEL_COMPILER
257# pragma warning(pop)
258#endif
259
260 mem = (Curl_crealloc)(mem, size);
261 if(source)
262 curl_dbg_log("MEM %s:%d realloc(%p, %zu) = %p\n",
263 source, line, (void *)ptr, wantedsize,
264 mem ? (void *)mem->mem : (void *)0);
265
266 if(mem) {
267 mem->size = wantedsize;
268 return mem->mem;
269 }
270
271 return NULL;
272}
273
274void curl_dbg_free(void *ptr, int line, const char *source)
275{
276 if(ptr) {
277 struct memdebug *mem;
278
279#ifdef __INTEL_COMPILER
280# pragma warning(push)
281# pragma warning(disable:1684)
282 /* 1684: conversion from pointer to same-sized integral type */
283#endif
284
285 mem = (void *)((char *)ptr - offsetof(struct memdebug, mem));
286
287#ifdef __INTEL_COMPILER
288# pragma warning(pop)
289#endif
290
291 /* free for real */
292 (Curl_cfree)(mem);
293 }
294
295 if(source && ptr)
296 curl_dbg_log("MEM %s:%d free(%p)\n", source, line, (void *)ptr);
297}
298
299curl_socket_t curl_dbg_socket(int domain, int type, int protocol,
300 int line, const char *source)
301{
302 const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ?
303 "FD %s:%d socket() = %d\n" :
304 (sizeof(curl_socket_t) == sizeof(long)) ?
305 "FD %s:%d socket() = %ld\n" :
306 "FD %s:%d socket() = %zd\n";
307
308 curl_socket_t sockfd;
309
310 if(countcheck("socket", line, source))
311 return CURL_SOCKET_BAD;
312
313 sockfd = socket(domain, type, protocol);
314
315 if(source && (sockfd != CURL_SOCKET_BAD))
316 curl_dbg_log(fmt, source, line, sockfd);
317
318 return sockfd;
319}
320
321SEND_TYPE_RETV curl_dbg_send(SEND_TYPE_ARG1 sockfd,
322 SEND_QUAL_ARG2 SEND_TYPE_ARG2 buf,
323 SEND_TYPE_ARG3 len, SEND_TYPE_ARG4 flags, int line,
324 const char *source)
325{
326 SEND_TYPE_RETV rc;
327 if(countcheck("send", line, source))
328 return -1;
329 rc = send(sockfd, buf, len, flags);
330 if(source)
331 curl_dbg_log("SEND %s:%d send(%lu) = %ld\n",
332 source, line, (unsigned long)len, (long)rc);
333 return rc;
334}
335
336RECV_TYPE_RETV curl_dbg_recv(RECV_TYPE_ARG1 sockfd, RECV_TYPE_ARG2 buf,
337 RECV_TYPE_ARG3 len, RECV_TYPE_ARG4 flags, int line,
338 const char *source)
339{
340 RECV_TYPE_RETV rc;
341 if(countcheck("recv", line, source))
342 return -1;
343 rc = recv(sockfd, buf, len, flags);
344 if(source)
345 curl_dbg_log("RECV %s:%d recv(%lu) = %ld\n",
346 source, line, (unsigned long)len, (long)rc);
347 return rc;
348}
349
350#ifdef HAVE_SOCKETPAIR
351int curl_dbg_socketpair(int domain, int type, int protocol,
352 curl_socket_t socket_vector[2],
353 int line, const char *source)
354{
355 const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ?
356 "FD %s:%d socketpair() = %d %d\n" :
357 (sizeof(curl_socket_t) == sizeof(long)) ?
358 "FD %s:%d socketpair() = %ld %ld\n" :
359 "FD %s:%d socketpair() = %zd %zd\n";
360
361 int res = socketpair(domain, type, protocol, socket_vector);
362
363 if(source && (0 == res))
364 curl_dbg_log(fmt, source, line, socket_vector[0], socket_vector[1]);
365
366 return res;
367}
368#endif
369
370curl_socket_t curl_dbg_accept(curl_socket_t s, void *saddr, void *saddrlen,
371 int line, const char *source)
372{
373 const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ?
374 "FD %s:%d accept() = %d\n" :
375 (sizeof(curl_socket_t) == sizeof(long)) ?
376 "FD %s:%d accept() = %ld\n" :
377 "FD %s:%d accept() = %zd\n";
378
379 struct sockaddr *addr = (struct sockaddr *)saddr;
380 curl_socklen_t *addrlen = (curl_socklen_t *)saddrlen;
381
382 curl_socket_t sockfd = accept(s, addr, addrlen);
383
384 if(source && (sockfd != CURL_SOCKET_BAD))
385 curl_dbg_log(fmt, source, line, sockfd);
386
387 return sockfd;
388}
389
390/* separate function to allow libcurl to mark a "faked" close */
391void curl_dbg_mark_sclose(curl_socket_t sockfd, int line, const char *source)
392{
393 const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ?
394 "FD %s:%d sclose(%d)\n":
395 (sizeof(curl_socket_t) == sizeof(long)) ?
396 "FD %s:%d sclose(%ld)\n":
397 "FD %s:%d sclose(%zd)\n";
398
399 if(source)
400 curl_dbg_log(fmt, source, line, sockfd);
401}
402
403/* this is our own defined way to close sockets on *ALL* platforms */
404int curl_dbg_sclose(curl_socket_t sockfd, int line, const char *source)
405{
406 int res = sclose(sockfd);
407 curl_dbg_mark_sclose(sockfd, line, source);
408 return res;
409}
410
411FILE *curl_dbg_fopen(const char *file, const char *mode,
412 int line, const char *source)
413{
414 FILE *res = fopen(file, mode);
415
416 if(source)
417 curl_dbg_log("FILE %s:%d fopen(\"%s\",\"%s\") = %p\n",
418 source, line, file, mode, (void *)res);
419
420 return res;
421}
422
423FILE *curl_dbg_fdopen(int filedes, const char *mode,
424 int line, const char *source)
425{
426 FILE *res = fdopen(filedes, mode);
427 if(source)
428 curl_dbg_log("FILE %s:%d fdopen(\"%d\",\"%s\") = %p\n",
429 source, line, filedes, mode, (void *)res);
430 return res;
431}
432
433int curl_dbg_fclose(FILE *file, int line, const char *source)
434{
435 int res;
436
437 DEBUGASSERT(file != NULL);
438
439 if(source)
440 curl_dbg_log("FILE %s:%d fclose(%p)\n",
441 source, line, (void *)file);
442
443 res = fclose(file);
444
445 return res;
446}
447
448#define LOGLINE_BUFSIZE 1024
449
450/* this does the writing to the memory tracking log file */
451void curl_dbg_log(const char *format, ...)
452{
453 char *buf;
454 int nchars;
455 va_list ap;
456
457 if(!curl_dbg_logfile)
458 return;
459
460 buf = (Curl_cmalloc)(LOGLINE_BUFSIZE);
461 if(!buf)
462 return;
463
464 va_start(ap, format);
465 nchars = mvsnprintf(buf, LOGLINE_BUFSIZE, format, ap);
466 va_end(ap);
467
468 if(nchars > LOGLINE_BUFSIZE - 1)
469 nchars = LOGLINE_BUFSIZE - 1;
470
471 if(nchars > 0)
472 fwrite(buf, 1, (size_t)nchars, curl_dbg_logfile);
473
474 (Curl_cfree)(buf);
475}
476
477#endif /* CURLDEBUG */
478