memdebug.c 15 KB

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