blob: 465def26486832b6fb136e45e657ac4ba460841e [file] [log] [blame]
Willy Tarreau4e2b6462019-05-16 17:44:30 +02001/*
2 * Process debugging functions.
3 *
4 * Copyright 2000-2019 Willy Tarreau <willy@haproxy.org>.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
10 *
11 */
12
Willy Tarreauf5b4e062020-03-03 15:40:23 +010013
Willy Tarreau368bff42019-12-06 17:18:28 +010014#include <fcntl.h>
Willy Tarreau4e2b6462019-05-16 17:44:30 +020015#include <signal.h>
16#include <time.h>
17#include <stdio.h>
Willy Tarreau6bdf3e92019-05-20 14:25:05 +020018#include <stdlib.h>
Willy Tarreauaeed4a82020-06-04 22:01:04 +020019#include <syslog.h>
Willy Tarreau368bff42019-12-06 17:18:28 +010020#include <sys/types.h>
21#include <sys/wait.h>
Willy Tarreau4e2b6462019-05-16 17:44:30 +020022
Willy Tarreau4c7e4b72020-05-27 12:58:42 +020023#include <haproxy/api.h>
Willy Tarreau8dabda72020-05-27 17:22:10 +020024#include <haproxy/buf.h>
Willy Tarreau83487a82020-06-04 20:19:54 +020025#include <haproxy/cli.h>
Willy Tarreau2a83d602020-05-27 16:58:08 +020026#include <haproxy/debug.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020027#include <haproxy/fd.h>
28#include <haproxy/global.h>
Willy Tarreau86416052020-06-04 09:20:54 +020029#include <haproxy/hlua.h>
Willy Tarreauaeed4a82020-06-04 22:01:04 +020030#include <haproxy/log.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020031#include <haproxy/net_helper.h>
Willy Tarreau5e539c92020-06-04 20:45:39 +020032#include <haproxy/stream_interface.h>
Willy Tarreaucea0e1b2020-06-04 17:25:40 +020033#include <haproxy/task.h>
Willy Tarreau3f567e42020-05-28 15:29:19 +020034#include <haproxy/thread.h>
Willy Tarreau48fbcae2020-06-03 18:09:46 +020035#include <haproxy/tools.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020036#include <import/ist.h>
Willy Tarreau4e2b6462019-05-16 17:44:30 +020037
Willy Tarreau4e2b6462019-05-16 17:44:30 +020038
Willy Tarreaua37cb182019-07-31 19:20:39 +020039/* mask of threads still having to dump, used to respect ordering. Only used
40 * when USE_THREAD_DUMP is set.
41 */
42volatile unsigned long threads_to_dump = 0;
Willy Tarreau9b013702019-10-24 18:18:02 +020043unsigned int debug_commands_issued = 0;
Willy Tarreaua37cb182019-07-31 19:20:39 +020044
Willy Tarreau8a069eb2020-11-30 16:17:33 +010045/* Xorshift RNGs from http://www.jstatsoft.org/v08/i14/paper */
Willy Tarreauc7ead072020-12-18 16:26:36 +010046static THREAD_LOCAL unsigned int y = 2463534242U;
Willy Tarreau8a069eb2020-11-30 16:17:33 +010047static unsigned int debug_prng()
48{
Willy Tarreau8a069eb2020-11-30 16:17:33 +010049 y ^= y << 13;
50 y ^= y >> 17;
51 y ^= y << 5;
52 return y;
53}
54
Willy Tarreau123fc972021-01-22 13:52:41 +010055/* dumps a backtrace of the current thread that is appended to buffer <buf>.
56 * Lines are prefixed with the string <prefix> which may be empty (used for
57 * indenting). It is recommended to use this at a function's tail so that
58 * the function does not appear in the call stack.
59 */
60void ha_dump_backtrace(struct buffer *buf, const char *prefix)
61{
62 struct buffer bak;
63 char pfx2[100];
64 void *callers[100];
65 int j, nptrs;
66 const void *addr;
67 int dump = 0;
68
69 nptrs = my_backtrace(callers, sizeof(callers)/sizeof(*callers));
70 if (!nptrs)
71 return;
72
73 if (snprintf(pfx2, sizeof(pfx2), "%s| ", prefix) > sizeof(pfx2))
74 pfx2[0] = 0;
75
76 /* The call backtrace_symbols_fd(callers, nptrs, STDOUT_FILENO would
77 * produce similar output to the following:
78 */
79 chunk_appendf(buf, "%scall trace(%d):\n", prefix, nptrs);
80 for (j = 0; (j < nptrs || dump < 2); j++) {
81 if (j == nptrs && !dump) {
82 /* we failed to spot the starting point of the
83 * dump, let's start over dumping everything we
84 * have.
85 */
86 dump = 2;
87 j = 0;
88 }
89 bak = *buf;
90 dump_addr_and_bytes(buf, pfx2, callers[j], 8);
91 addr = resolve_sym_name(buf, ": ", callers[j]);
92 if (dump == 0) {
93 /* dump not started, will start *after*
94 * ha_thread_dump_all_to_trash and ha_panic
95 */
96 if (addr == ha_thread_dump_all_to_trash || addr == ha_panic)
97 dump = 1;
98 *buf = bak;
99 continue;
100 }
101
102 if (dump == 1) {
103 /* starting */
104 if (addr == ha_thread_dump_all_to_trash || addr == ha_panic) {
105 *buf = bak;
106 continue;
107 }
108 dump = 2;
109 }
110
111 if (dump == 2) {
112 /* dumping */
113 if (addr == run_poll_loop || addr == main || addr == run_tasks_from_lists) {
114 dump = 3;
115 *buf = bak;
116 break;
117 }
118 }
119 /* OK, line dumped */
120 chunk_appendf(buf, "\n");
121 }
122}
123
Willy Tarreau5cf64dd2019-05-17 10:36:08 +0200124/* Dumps to the buffer some known information for the desired thread, and
125 * optionally extra info for the current thread. The dump will be appended to
126 * the buffer, so the caller is responsible for preliminary initializing it.
127 * The calling thread ID needs to be passed in <calling_tid> to display a star
Willy Tarreaue6a02fa2019-05-22 07:06:44 +0200128 * in front of the calling thread's line (usually it's tid). Any stuck thread
129 * is also prefixed with a '>'.
Willy Tarreau4e2b6462019-05-16 17:44:30 +0200130 */
Willy Tarreau5cf64dd2019-05-17 10:36:08 +0200131void ha_thread_dump(struct buffer *buf, int thr, int calling_tid)
Willy Tarreau4e2b6462019-05-16 17:44:30 +0200132{
133 unsigned long thr_bit = 1UL << thr;
David Carliera92c5ce2019-09-13 05:03:12 +0100134 unsigned long long p = ha_thread_info[thr].prev_cpu_time;
135 unsigned long long n = now_cpu_time_thread(&ha_thread_info[thr]);
136 int stuck = !!(ha_thread_info[thr].flags & TI_FL_STUCK);
Willy Tarreau4e2b6462019-05-16 17:44:30 +0200137
Willy Tarreau5cf64dd2019-05-17 10:36:08 +0200138 chunk_appendf(buf,
Willy Tarreauf0e5da22020-05-01 12:26:03 +0200139 "%c%cThread %-2u: id=0x%llx act=%d glob=%d wq=%d rq=%d tl=%d tlsz=%d rqsz=%d\n"
Olivier Houchard305d5ab2019-07-24 18:07:06 +0200140 " stuck=%d prof=%d",
Willy Tarreaue6a02fa2019-05-22 07:06:44 +0200141 (thr == calling_tid) ? '*' : ' ', stuck ? '>' : ' ', thr + 1,
Willy Tarreauff64d3b2020-05-01 11:28:49 +0200142 ha_get_pthread_id(thr),
Olivier Houchardcfbb3e62019-05-29 19:22:43 +0200143 thread_has_tasks(),
Willy Tarreau4e2b6462019-05-16 17:44:30 +0200144 !!(global_tasks_mask & thr_bit),
145 !eb_is_empty(&task_per_thread[thr].timers),
146 !eb_is_empty(&task_per_thread[thr].rqueue),
Willy Tarreaua62917b2020-01-30 18:37:28 +0100147 !(LIST_ISEMPTY(&task_per_thread[thr].tasklets[TL_URGENT]) &&
148 LIST_ISEMPTY(&task_per_thread[thr].tasklets[TL_NORMAL]) &&
149 LIST_ISEMPTY(&task_per_thread[thr].tasklets[TL_BULK]) &&
150 MT_LIST_ISEMPTY(&task_per_thread[thr].shared_tasklet_list)),
Willy Tarreau4e2b6462019-05-16 17:44:30 +0200151 task_per_thread[thr].task_list_size,
152 task_per_thread[thr].rqueue_size,
Willy Tarreaue6a02fa2019-05-22 07:06:44 +0200153 stuck,
Willy Tarreau4e2b6462019-05-16 17:44:30 +0200154 !!(task_profiling_mask & thr_bit));
155
Willy Tarreau5cf64dd2019-05-17 10:36:08 +0200156 chunk_appendf(buf,
Willy Tarreau4e2b6462019-05-16 17:44:30 +0200157 " harmless=%d wantrdv=%d",
158 !!(threads_harmless_mask & thr_bit),
159 !!(threads_want_rdv_mask & thr_bit));
Willy Tarreau4e2b6462019-05-16 17:44:30 +0200160
Willy Tarreau5cf64dd2019-05-17 10:36:08 +0200161 chunk_appendf(buf, "\n");
Willy Tarreau9c8800a2019-05-20 20:52:20 +0200162 chunk_appendf(buf, " cpu_ns: poll=%llu now=%llu diff=%llu\n", p, n, n-p);
Willy Tarreau4e2b6462019-05-16 17:44:30 +0200163
164 /* this is the end of what we can dump from outside the thread */
165
166 if (thr != tid)
167 return;
168
Willy Tarreau5cf64dd2019-05-17 10:36:08 +0200169 chunk_appendf(buf, " curr_task=");
Willy Tarreaud022e9c2019-09-24 08:25:15 +0200170 ha_task_dump(buf, sched->current, " ");
Willy Tarreauf5b4e062020-03-03 15:40:23 +0100171
Willy Tarreauf5b4e062020-03-03 15:40:23 +0100172 if (stuck) {
173 /* We only emit the backtrace for stuck threads in order not to
174 * waste precious output buffer space with non-interesting data.
Willy Tarreau123fc972021-01-22 13:52:41 +0100175 * Please leave this as the last instruction in this function
176 * so that the compiler uses tail merging and the current
177 * function does not appear in the stack.
Willy Tarreauf5b4e062020-03-03 15:40:23 +0100178 */
Willy Tarreau123fc972021-01-22 13:52:41 +0100179 ha_dump_backtrace(buf, " ");
Willy Tarreauf5b4e062020-03-03 15:40:23 +0100180 }
Willy Tarreau4e2b6462019-05-16 17:44:30 +0200181}
182
183
Willy Tarreau5cf64dd2019-05-17 10:36:08 +0200184/* dumps into the buffer some information related to task <task> (which may
Willy Tarreau4e2b6462019-05-16 17:44:30 +0200185 * either be a task or a tasklet, and prepend each line except the first one
Willy Tarreau5cf64dd2019-05-17 10:36:08 +0200186 * with <pfx>. The buffer is only appended and the first output starts by the
187 * pointer itself. The caller is responsible for making sure the task is not
188 * going to vanish during the dump.
Willy Tarreau4e2b6462019-05-16 17:44:30 +0200189 */
Willy Tarreau5cf64dd2019-05-17 10:36:08 +0200190void ha_task_dump(struct buffer *buf, const struct task *task, const char *pfx)
Willy Tarreau4e2b6462019-05-16 17:44:30 +0200191{
Willy Tarreau578ea8b2019-05-22 09:43:09 +0200192 const struct stream *s = NULL;
Willy Tarreaua512b022019-08-21 14:12:19 +0200193 const struct appctx __maybe_unused *appctx = NULL;
Willy Tarreau78a7cb62019-08-21 14:16:02 +0200194 struct hlua __maybe_unused *hlua = NULL;
Willy Tarreau578ea8b2019-05-22 09:43:09 +0200195
Willy Tarreau14a1ab72019-05-17 10:34:25 +0200196 if (!task) {
Willy Tarreau5cf64dd2019-05-17 10:36:08 +0200197 chunk_appendf(buf, "0\n");
Willy Tarreau231ec392019-05-17 10:39:47 +0200198 return;
199 }
200
Willy Tarreau20db9112019-05-17 14:14:35 +0200201 if (TASK_IS_TASKLET(task))
202 chunk_appendf(buf,
203 "%p (tasklet) calls=%u\n",
204 task,
205 task->calls);
206 else
207 chunk_appendf(buf,
208 "%p (task) calls=%u last=%llu%s\n",
209 task,
210 task->calls,
211 task->call_date ? (unsigned long long)(now_mono_time() - task->call_date) : 0,
212 task->call_date ? " ns ago" : "");
Willy Tarreau4e2b6462019-05-16 17:44:30 +0200213
Willy Tarreau2e89b092020-03-03 17:13:02 +0100214 chunk_appendf(buf, "%s fct=%p(", pfx, task->process);
215 resolve_sym_name(buf, NULL, task->process);
216 chunk_appendf(buf,") ctx=%p", task->context);
Willy Tarreau578ea8b2019-05-22 09:43:09 +0200217
Willy Tarreaua512b022019-08-21 14:12:19 +0200218 if (task->process == task_run_applet && (appctx = task->context))
219 chunk_appendf(buf, "(%s)\n", appctx->applet->name);
220 else
221 chunk_appendf(buf, "\n");
222
Willy Tarreau578ea8b2019-05-22 09:43:09 +0200223 if (task->process == process_stream && task->context)
224 s = (struct stream *)task->context;
225 else if (task->process == task_run_applet && task->context)
226 s = si_strm(((struct appctx *)task->context)->owner);
227 else if (task->process == si_cs_io_cb && task->context)
228 s = si_strm((struct stream_interface *)task->context);
229
230 if (s)
231 stream_dump(buf, s, pfx, '\n');
Willy Tarreau78a7cb62019-08-21 14:16:02 +0200232
233#ifdef USE_LUA
234 hlua = NULL;
235 if (s && (hlua = s->hlua)) {
236 chunk_appendf(buf, "%sCurrent executing Lua from a stream analyser -- ", pfx);
237 }
238 else if (task->process == hlua_process_task && (hlua = task->context)) {
239 chunk_appendf(buf, "%sCurrent executing a Lua task -- ", pfx);
240 }
241 else if (task->process == task_run_applet && (appctx = task->context) &&
242 (appctx->applet->fct == hlua_applet_tcp_fct && (hlua = appctx->ctx.hlua_apptcp.hlua))) {
243 chunk_appendf(buf, "%sCurrent executing a Lua TCP service -- ", pfx);
244 }
245 else if (task->process == task_run_applet && (appctx = task->context) &&
246 (appctx->applet->fct == hlua_applet_http_fct && (hlua = appctx->ctx.hlua_apphttp.hlua))) {
247 chunk_appendf(buf, "%sCurrent executing a Lua HTTP service -- ", pfx);
248 }
249
Christopher Faulet471425f2020-07-24 19:08:05 +0200250 if (hlua && hlua->T) {
Willy Tarreau78a7cb62019-08-21 14:16:02 +0200251 luaL_traceback(hlua->T, hlua->T, NULL, 0);
252 if (!append_prefixed_str(buf, lua_tostring(hlua->T, -1), pfx, '\n', 1))
253 b_putchr(buf, '\n');
254 }
Christopher Faulet471425f2020-07-24 19:08:05 +0200255 else
256 b_putchr(buf, '\n');
Willy Tarreau78a7cb62019-08-21 14:16:02 +0200257#endif
Willy Tarreau4e2b6462019-05-16 17:44:30 +0200258}
259
Willy Tarreau4e2b6462019-05-16 17:44:30 +0200260/* This function dumps all profiling settings. It returns 0 if the output
261 * buffer is full and it needs to be called again, otherwise non-zero.
262 */
263static int cli_io_handler_show_threads(struct appctx *appctx)
264{
265 struct stream_interface *si = appctx->owner;
266 int thr;
267
268 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
269 return 1;
270
271 if (appctx->st0)
272 thr = appctx->st1;
273 else
274 thr = 0;
275
Willy Tarreau5cf64dd2019-05-17 10:36:08 +0200276 chunk_reset(&trash);
Willy Tarreauc7091d82019-05-17 10:08:49 +0200277 ha_thread_dump_all_to_trash();
Willy Tarreau5cf64dd2019-05-17 10:36:08 +0200278
279 if (ci_putchk(si_ic(si), &trash) == -1) {
280 /* failed, try again */
281 si_rx_room_blk(si);
282 appctx->st1 = thr;
283 return 0;
284 }
Willy Tarreau4e2b6462019-05-16 17:44:30 +0200285 return 1;
286}
287
Willy Tarreau56131ca2019-05-20 13:48:29 +0200288/* dumps a state of all threads into the trash and on fd #2, then aborts. */
289void ha_panic()
290{
291 chunk_reset(&trash);
Willy Tarreaua9f9fc92019-05-20 17:45:35 +0200292 chunk_appendf(&trash, "Thread %u is about to kill the process.\n", tid + 1);
Willy Tarreau56131ca2019-05-20 13:48:29 +0200293 ha_thread_dump_all_to_trash();
Willy Tarreau2e8ab6b2020-03-14 11:03:20 +0100294 DISGUISE(write(2, trash.area, trash.data));
Willy Tarreau56131ca2019-05-20 13:48:29 +0200295 for (;;)
296 abort();
297}
298
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200299/* parse a "debug dev exit" command. It always returns 1, though it should never return. */
300static int debug_parse_cli_exit(char **args, char *payload, struct appctx *appctx, void *private)
301{
302 int code = atoi(args[3]);
303
304 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
305 return 1;
306
Willy Tarreau9b013702019-10-24 18:18:02 +0200307 _HA_ATOMIC_ADD(&debug_commands_issued, 1);
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200308 exit(code);
309 return 1;
310}
311
312/* parse a "debug dev close" command. It always returns 1. */
313static int debug_parse_cli_close(char **args, char *payload, struct appctx *appctx, void *private)
314{
315 int fd;
316
317 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
318 return 1;
319
Willy Tarreau9d008692019-08-09 11:21:01 +0200320 if (!*args[3])
321 return cli_err(appctx, "Missing file descriptor number.\n");
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200322
323 fd = atoi(args[3]);
Willy Tarreau9d008692019-08-09 11:21:01 +0200324 if (fd < 0 || fd >= global.maxsock)
325 return cli_err(appctx, "File descriptor out of range.\n");
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200326
Willy Tarreau9d008692019-08-09 11:21:01 +0200327 if (!fdtab[fd].owner)
328 return cli_msg(appctx, LOG_INFO, "File descriptor was already closed.\n");
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200329
Willy Tarreau9b013702019-10-24 18:18:02 +0200330 _HA_ATOMIC_ADD(&debug_commands_issued, 1);
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200331 fd_delete(fd);
332 return 1;
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200333}
334
335/* parse a "debug dev delay" command. It always returns 1. */
336static int debug_parse_cli_delay(char **args, char *payload, struct appctx *appctx, void *private)
337{
338 int delay = atoi(args[3]);
339
340 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
341 return 1;
342
Willy Tarreau9b013702019-10-24 18:18:02 +0200343 _HA_ATOMIC_ADD(&debug_commands_issued, 1);
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200344 usleep((long)delay * 1000);
345 return 1;
346}
347
348/* parse a "debug dev log" command. It always returns 1. */
349static int debug_parse_cli_log(char **args, char *payload, struct appctx *appctx, void *private)
350{
351 int arg;
352
353 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
354 return 1;
355
Willy Tarreau9b013702019-10-24 18:18:02 +0200356 _HA_ATOMIC_ADD(&debug_commands_issued, 1);
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200357 chunk_reset(&trash);
358 for (arg = 3; *args[arg]; arg++) {
359 if (arg > 3)
360 chunk_strcat(&trash, " ");
361 chunk_strcat(&trash, args[arg]);
362 }
363
364 send_log(NULL, LOG_INFO, "%s\n", trash.area);
365 return 1;
366}
367
368/* parse a "debug dev loop" command. It always returns 1. */
369static int debug_parse_cli_loop(char **args, char *payload, struct appctx *appctx, void *private)
370{
371 struct timeval deadline, curr;
372 int loop = atoi(args[3]);
373
374 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
375 return 1;
376
Willy Tarreau9b013702019-10-24 18:18:02 +0200377 _HA_ATOMIC_ADD(&debug_commands_issued, 1);
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200378 gettimeofday(&curr, NULL);
379 tv_ms_add(&deadline, &curr, loop);
380
381 while (tv_ms_cmp(&curr, &deadline) < 0)
382 gettimeofday(&curr, NULL);
383
384 return 1;
385}
386
387/* parse a "debug dev panic" command. It always returns 1, though it should never return. */
388static int debug_parse_cli_panic(char **args, char *payload, struct appctx *appctx, void *private)
389{
390 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
391 return 1;
392
Willy Tarreau9b013702019-10-24 18:18:02 +0200393 _HA_ATOMIC_ADD(&debug_commands_issued, 1);
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200394 ha_panic();
395 return 1;
396}
397
398/* parse a "debug dev exec" command. It always returns 1. */
Willy Tarreaub24ab222019-10-24 18:03:39 +0200399#if defined(DEBUG_DEV)
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200400static int debug_parse_cli_exec(char **args, char *payload, struct appctx *appctx, void *private)
401{
Willy Tarreau368bff42019-12-06 17:18:28 +0100402 int pipefd[2];
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200403 int arg;
Willy Tarreau368bff42019-12-06 17:18:28 +0100404 int pid;
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200405
406 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
407 return 1;
408
Willy Tarreau9b013702019-10-24 18:18:02 +0200409 _HA_ATOMIC_ADD(&debug_commands_issued, 1);
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200410 chunk_reset(&trash);
411 for (arg = 3; *args[arg]; arg++) {
412 if (arg > 3)
413 chunk_strcat(&trash, " ");
414 chunk_strcat(&trash, args[arg]);
415 }
416
Willy Tarreau368bff42019-12-06 17:18:28 +0100417 thread_isolate();
418 if (pipe(pipefd) < 0)
419 goto fail_pipe;
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200420
Willy Tarreau368bff42019-12-06 17:18:28 +0100421 if (fcntl(pipefd[0], F_SETFD, fcntl(pipefd[0], F_GETFD, FD_CLOEXEC) | FD_CLOEXEC) == -1)
422 goto fail_fcntl;
423
424 if (fcntl(pipefd[1], F_SETFD, fcntl(pipefd[1], F_GETFD, FD_CLOEXEC) | FD_CLOEXEC) == -1)
425 goto fail_fcntl;
426
427 pid = fork();
428
429 if (pid < 0)
430 goto fail_fork;
431 else if (pid == 0) {
432 /* child */
433 char *cmd[4] = { "/bin/sh", "-c", 0, 0 };
434
435 close(0);
436 dup2(pipefd[1], 1);
437 dup2(pipefd[1], 2);
438
439 cmd[2] = trash.area;
440 execvp(cmd[0], cmd);
441 printf("execvp() failed\n");
442 exit(1);
443 }
444
445 /* parent */
446 thread_release();
447 close(pipefd[1]);
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200448 chunk_reset(&trash);
449 while (1) {
Willy Tarreau368bff42019-12-06 17:18:28 +0100450 size_t ret = read(pipefd[0], trash.area + trash.data, trash.size - 20 - trash.data);
451 if (ret <= 0)
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200452 break;
453 trash.data += ret;
454 if (trash.data + 20 == trash.size) {
455 chunk_strcat(&trash, "\n[[[TRUNCATED]]]\n");
456 break;
457 }
458 }
Willy Tarreau368bff42019-12-06 17:18:28 +0100459 close(pipefd[0]);
460 waitpid(pid, NULL, WNOHANG);
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200461 trash.area[trash.data] = 0;
Willy Tarreau9d008692019-08-09 11:21:01 +0200462 return cli_msg(appctx, LOG_INFO, trash.area);
Willy Tarreau368bff42019-12-06 17:18:28 +0100463
464 fail_fork:
465 fail_fcntl:
466 close(pipefd[0]);
467 close(pipefd[1]);
468 fail_pipe:
469 thread_release();
470 return cli_err(appctx, "Failed to execute command.\n");
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200471}
Willy Tarreaub24ab222019-10-24 18:03:39 +0200472#endif
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200473
474/* parse a "debug dev hex" command. It always returns 1. */
475static int debug_parse_cli_hex(char **args, char *payload, struct appctx *appctx, void *private)
476{
477 unsigned long start, len;
478
479 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
480 return 1;
481
Willy Tarreau9d008692019-08-09 11:21:01 +0200482 if (!*args[3])
483 return cli_err(appctx, "Missing memory address to dump from.\n");
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200484
485 start = strtoul(args[3], NULL, 0);
Willy Tarreau9d008692019-08-09 11:21:01 +0200486 if (!start)
487 return cli_err(appctx, "Will not dump from NULL address.\n");
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200488
Willy Tarreau9b013702019-10-24 18:18:02 +0200489 _HA_ATOMIC_ADD(&debug_commands_issued, 1);
490
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200491 /* by default, dump ~128 till next block of 16 */
492 len = strtoul(args[4], NULL, 0);
493 if (!len)
494 len = ((start + 128) & -16) - start;
495
496 chunk_reset(&trash);
Willy Tarreau37101052019-05-20 16:48:20 +0200497 dump_hex(&trash, " ", (const void *)start, len, 1);
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200498 trash.area[trash.data] = 0;
Willy Tarreau9d008692019-08-09 11:21:01 +0200499 return cli_msg(appctx, LOG_INFO, trash.area);
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200500}
501
502/* parse a "debug dev tkill" command. It always returns 1. */
503static int debug_parse_cli_tkill(char **args, char *payload, struct appctx *appctx, void *private)
504{
505 int thr = 0;
506 int sig = SIGABRT;
507
508 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
509 return 1;
510
511 if (*args[3])
512 thr = atoi(args[3]);
513
Willy Tarreau9d008692019-08-09 11:21:01 +0200514 if (thr < 0 || thr > global.nbthread)
515 return cli_err(appctx, "Thread number out of range (use 0 for current).\n");
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200516
517 if (*args[4])
518 sig = atoi(args[4]);
519
Willy Tarreau9b013702019-10-24 18:18:02 +0200520 _HA_ATOMIC_ADD(&debug_commands_issued, 1);
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200521 if (thr)
Willy Tarreaufade80d2019-05-22 08:46:59 +0200522 ha_tkill(thr - 1, sig);
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200523 else
Willy Tarreau6bdf3e92019-05-20 14:25:05 +0200524 raise(sig);
525 return 1;
526}
527
Willy Tarreau6cbe62b2020-03-05 17:16:24 +0100528/* parse a "debug dev write" command. It always returns 1. */
529static int debug_parse_cli_write(char **args, char *payload, struct appctx *appctx, void *private)
530{
531 unsigned long len;
532
533 if (!*args[3])
534 return cli_err(appctx, "Missing output size.\n");
535
536 len = strtoul(args[3], NULL, 0);
537 if (len >= trash.size)
538 return cli_err(appctx, "Output too large, must be <tune.bufsize.\n");
539
540 _HA_ATOMIC_ADD(&debug_commands_issued, 1);
541
542 chunk_reset(&trash);
543 trash.data = len;
544 memset(trash.area, '.', trash.data);
545 trash.area[trash.data] = 0;
546 for (len = 64; len < trash.data; len += 64)
547 trash.area[len] = '\n';
548 return cli_msg(appctx, LOG_INFO, trash.area);
549}
550
Willy Tarreau68680bb2019-10-23 17:23:25 +0200551/* parse a "debug dev stream" command */
552/*
553 * debug dev stream [strm=<ptr>] [strm.f[{+-=}<flags>]] [txn.f[{+-=}<flags>]] \
554 * [req.f[{+-=}<flags>]] [res.f[{+-=}<flags>]] \
555 * [sif.f[{+-=<flags>]] [sib.f[{+-=<flags>]] \
556 * [sif.s[=<state>]] [sib.s[=<state>]]
557 */
558static int debug_parse_cli_stream(char **args, char *payload, struct appctx *appctx, void *private)
559{
560 struct stream *s = si_strm(appctx->owner);
561 int arg;
562 void *ptr;
563 int size;
564 const char *word, *end;
565 struct ist name;
566 char *msg = NULL;
567 char *endarg;
568 unsigned long long old, new;
569
570 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
571 return 1;
572
573 ptr = NULL; size = 0;
574
575 if (!*args[3]) {
576 return cli_err(appctx,
577 "Usage: debug dev stream { <obj> <op> <value> | wake }*\n"
578 " <obj> = {strm | strm.f | sif.f | sif.s | sif.x | sib.f | sib.s | sib.x |\n"
579 " txn.f | req.f | req.r | req.w | res.f | res.r | res.w}\n"
580 " <op> = {'' (show) | '=' (assign) | '^' (xor) | '+' (or) | '-' (andnot)}\n"
581 " <value> = 'now' | 64-bit dec/hex integer (0x prefix supported)\n"
582 " 'wake' wakes the stream asssigned to 'strm' (default: current)\n"
583 );
584 }
585
Willy Tarreau9b013702019-10-24 18:18:02 +0200586 _HA_ATOMIC_ADD(&debug_commands_issued, 1);
Willy Tarreau68680bb2019-10-23 17:23:25 +0200587 for (arg = 3; *args[arg]; arg++) {
588 old = 0;
589 end = word = args[arg];
590 while (*end && *end != '=' && *end != '^' && *end != '+' && *end != '-')
591 end++;
592 name = ist2(word, end - word);
593 if (isteq(name, ist("strm"))) {
Willy Tarreaub2fee042019-10-25 10:06:55 +0200594 ptr = (!s || !may_access(s)) ? NULL : &s; size = sizeof(s);
Willy Tarreau68680bb2019-10-23 17:23:25 +0200595 } else if (isteq(name, ist("strm.f"))) {
Willy Tarreaub2fee042019-10-25 10:06:55 +0200596 ptr = (!s || !may_access(s)) ? NULL : &s->flags; size = sizeof(s->flags);
Willy Tarreau68680bb2019-10-23 17:23:25 +0200597 } else if (isteq(name, ist("txn.f"))) {
Willy Tarreaub2fee042019-10-25 10:06:55 +0200598 ptr = (!s || !may_access(s)) ? NULL : &s->txn->flags; size = sizeof(s->txn->flags);
Willy Tarreau68680bb2019-10-23 17:23:25 +0200599 } else if (isteq(name, ist("req.f"))) {
Willy Tarreaub2fee042019-10-25 10:06:55 +0200600 ptr = (!s || !may_access(s)) ? NULL : &s->req.flags; size = sizeof(s->req.flags);
Willy Tarreau68680bb2019-10-23 17:23:25 +0200601 } else if (isteq(name, ist("res.f"))) {
Willy Tarreaub2fee042019-10-25 10:06:55 +0200602 ptr = (!s || !may_access(s)) ? NULL : &s->res.flags; size = sizeof(s->res.flags);
Willy Tarreau68680bb2019-10-23 17:23:25 +0200603 } else if (isteq(name, ist("req.r"))) {
Willy Tarreaub2fee042019-10-25 10:06:55 +0200604 ptr = (!s || !may_access(s)) ? NULL : &s->req.rex; size = sizeof(s->req.rex);
Willy Tarreau68680bb2019-10-23 17:23:25 +0200605 } else if (isteq(name, ist("res.r"))) {
Willy Tarreaub2fee042019-10-25 10:06:55 +0200606 ptr = (!s || !may_access(s)) ? NULL : &s->res.rex; size = sizeof(s->res.rex);
Willy Tarreau68680bb2019-10-23 17:23:25 +0200607 } else if (isteq(name, ist("req.w"))) {
Willy Tarreaub2fee042019-10-25 10:06:55 +0200608 ptr = (!s || !may_access(s)) ? NULL : &s->req.wex; size = sizeof(s->req.wex);
Willy Tarreau68680bb2019-10-23 17:23:25 +0200609 } else if (isteq(name, ist("res.w"))) {
Willy Tarreaub2fee042019-10-25 10:06:55 +0200610 ptr = (!s || !may_access(s)) ? NULL : &s->res.wex; size = sizeof(s->res.wex);
Willy Tarreau68680bb2019-10-23 17:23:25 +0200611 } else if (isteq(name, ist("sif.f"))) {
Willy Tarreaub2fee042019-10-25 10:06:55 +0200612 ptr = (!s || !may_access(s)) ? NULL : &s->si[0].flags; size = sizeof(s->si[0].flags);
Willy Tarreau68680bb2019-10-23 17:23:25 +0200613 } else if (isteq(name, ist("sib.f"))) {
Willy Tarreaub2fee042019-10-25 10:06:55 +0200614 ptr = (!s || !may_access(s)) ? NULL : &s->si[1].flags; size = sizeof(s->si[1].flags);
Willy Tarreau68680bb2019-10-23 17:23:25 +0200615 } else if (isteq(name, ist("sif.x"))) {
Willy Tarreaub2fee042019-10-25 10:06:55 +0200616 ptr = (!s || !may_access(s)) ? NULL : &s->si[0].exp; size = sizeof(s->si[0].exp);
Willy Tarreau68680bb2019-10-23 17:23:25 +0200617 } else if (isteq(name, ist("sib.x"))) {
Willy Tarreaub2fee042019-10-25 10:06:55 +0200618 ptr = (!s || !may_access(s)) ? NULL : &s->si[1].exp; size = sizeof(s->si[1].exp);
Willy Tarreau68680bb2019-10-23 17:23:25 +0200619 } else if (isteq(name, ist("sif.s"))) {
Willy Tarreaub2fee042019-10-25 10:06:55 +0200620 ptr = (!s || !may_access(s)) ? NULL : &s->si[0].state; size = sizeof(s->si[0].state);
Willy Tarreau68680bb2019-10-23 17:23:25 +0200621 } else if (isteq(name, ist("sib.s"))) {
Willy Tarreaub2fee042019-10-25 10:06:55 +0200622 ptr = (!s || !may_access(s)) ? NULL : &s->si[1].state; size = sizeof(s->si[1].state);
Willy Tarreau68680bb2019-10-23 17:23:25 +0200623 } else if (isteq(name, ist("wake"))) {
Willy Tarreau2b5520d2019-10-24 18:28:23 +0200624 if (s && may_access(s) && may_access((void *)s + sizeof(*s) - 1))
Willy Tarreau68680bb2019-10-23 17:23:25 +0200625 task_wakeup(s->task, TASK_WOKEN_TIMER|TASK_WOKEN_IO|TASK_WOKEN_MSG);
626 continue;
627 } else
628 return cli_dynerr(appctx, memprintf(&msg, "Unsupported field name: '%s'.\n", word));
629
630 /* read previous value */
Willy Tarreau2b5520d2019-10-24 18:28:23 +0200631 if ((s || ptr == &s) && ptr && may_access(ptr) && may_access(ptr + size - 1)) {
Willy Tarreau68680bb2019-10-23 17:23:25 +0200632 if (size == 8)
633 old = read_u64(ptr);
634 else if (size == 4)
635 old = read_u32(ptr);
636 else if (size == 2)
637 old = read_u16(ptr);
638 else
639 old = *(const uint8_t *)ptr;
Willy Tarreau2b5520d2019-10-24 18:28:23 +0200640 } else {
641 memprintf(&msg,
642 "%sSkipping inaccessible pointer %p for field '%.*s'.\n",
643 msg ? msg : "", ptr, (int)(end - word), word);
644 continue;
Willy Tarreau68680bb2019-10-23 17:23:25 +0200645 }
646
647 /* parse the new value . */
648 new = strtoll(end + 1, &endarg, 0);
649 if (end[1] && *endarg) {
650 if (strcmp(end + 1, "now") == 0)
651 new = now_ms;
652 else {
653 memprintf(&msg,
654 "%sIgnoring unparsable value '%s' for field '%.*s'.\n",
655 msg ? msg : "", end + 1, (int)(end - word), word);
656 continue;
657 }
658 }
659
660 switch (*end) {
661 case '\0': /* show */
662 memprintf(&msg, "%s%.*s=%#llx ", msg ? msg : "", (int)(end - word), word, old);
663 new = old; // do not change the value
664 break;
665
666 case '=': /* set */
667 break;
668
669 case '^': /* XOR */
670 new = old ^ new;
671 break;
672
673 case '+': /* OR */
674 new = old | new;
675 break;
676
677 case '-': /* AND NOT */
678 new = old & ~new;
679 break;
680
681 default:
682 break;
683 }
684
685 /* write the new value */
Willy Tarreau2b5520d2019-10-24 18:28:23 +0200686 if (new != old) {
Willy Tarreau68680bb2019-10-23 17:23:25 +0200687 if (size == 8)
688 write_u64(ptr, new);
689 else if (size == 4)
690 write_u32(ptr, new);
691 else if (size == 2)
692 write_u16(ptr, new);
693 else
694 *(uint8_t *)ptr = new;
695 }
696 }
697
698 if (msg && *msg)
699 return cli_dynmsg(appctx, LOG_INFO, msg);
700 return 1;
701}
702
Willy Tarreaua5a44792020-11-29 17:12:15 +0100703static struct task *debug_task_handler(struct task *t, void *ctx, unsigned short state)
704{
705 unsigned long *tctx = ctx; // [0] = #tasks, [1] = inter, [2+] = { tl | (tsk+1) }
706 unsigned long inter = tctx[1];
707 unsigned long rnd;
708
709 t->expire = tick_add(now_ms, inter);
710
711 /* half of the calls will wake up another entry */
Willy Tarreau8a069eb2020-11-30 16:17:33 +0100712 rnd = debug_prng();
Willy Tarreaua5a44792020-11-29 17:12:15 +0100713 if (rnd & 1) {
714 rnd >>= 1;
715 rnd %= tctx[0];
716 rnd = tctx[rnd + 2];
717
718 if (rnd & 1)
719 task_wakeup((struct task *)(rnd - 1), TASK_WOKEN_MSG);
720 else
721 tasklet_wakeup((struct tasklet *)rnd);
722 }
723 return t;
724}
725
726static struct task *debug_tasklet_handler(struct task *t, void *ctx, unsigned short state)
727{
728 unsigned long *tctx = ctx; // [0] = #tasks, [1] = inter, [2+] = { tl | (tsk+1) }
729 unsigned long rnd;
730 int i;
731
732 /* wake up two random entries */
733 for (i = 0; i < 2; i++) {
Willy Tarreau8a069eb2020-11-30 16:17:33 +0100734 rnd = debug_prng() % tctx[0];
Willy Tarreaua5a44792020-11-29 17:12:15 +0100735 rnd = tctx[rnd + 2];
736
737 if (rnd & 1)
738 task_wakeup((struct task *)(rnd - 1), TASK_WOKEN_MSG);
739 else
740 tasklet_wakeup((struct tasklet *)rnd);
741 }
742 return t;
743}
744
745/* parse a "debug dev sched" command
746 * debug dev sched {task|tasklet} [count=<count>] [mask=<mask>] [single=<single>] [inter=<inter>]
747 */
748static int debug_parse_cli_sched(char **args, char *payload, struct appctx *appctx, void *private)
749{
750 int arg;
751 void *ptr;
752 int size;
753 const char *word, *end;
754 struct ist name;
755 char *msg = NULL;
756 char *endarg;
757 unsigned long long new;
758 unsigned long count = 0;
759 unsigned long thrid = 0;
760 unsigned int inter = 0;
761 unsigned long mask, tmask;
762 unsigned long i;
763 int mode = 0; // 0 = tasklet; 1 = task
764 int single = 0;
765 unsigned long *tctx; // [0] = #tasks, [1] = inter, [2+] = { tl | (tsk+1) }
766
767 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
768 return 1;
769
770 ptr = NULL; size = 0;
771 mask = all_threads_mask;
772
773 if (strcmp(args[3], "task") != 0 && strcmp(args[3], "tasklet") != 0) {
774 return cli_err(appctx,
775 "Usage: debug dev sched {task|tasklet} { <obj> = <value> }*\n"
776 " <obj> = {count | mask | inter | single }\n"
777 " <value> = 64-bit dec/hex integer (0x prefix supported)\n"
778 );
779 }
780
781 mode = strcmp(args[3], "task") == 0;
782
783 _HA_ATOMIC_ADD(&debug_commands_issued, 1);
784 for (arg = 4; *args[arg]; arg++) {
785 end = word = args[arg];
786 while (*end && *end != '=' && *end != '^' && *end != '+' && *end != '-')
787 end++;
788 name = ist2(word, end - word);
789 if (isteq(name, ist("count"))) {
790 ptr = &count; size = sizeof(count);
791 } else if (isteq(name, ist("mask"))) {
792 ptr = &mask; size = sizeof(mask);
793 } else if (isteq(name, ist("tid"))) {
794 ptr = &thrid; size = sizeof(thrid);
795 } else if (isteq(name, ist("inter"))) {
796 ptr = &inter; size = sizeof(inter);
797 } else if (isteq(name, ist("single"))) {
798 ptr = &single; size = sizeof(single);
799 } else
800 return cli_dynerr(appctx, memprintf(&msg, "Unsupported setting: '%s'.\n", word));
801
802 /* parse the new value . */
803 new = strtoll(end + 1, &endarg, 0);
804 if (end[1] && *endarg) {
805 memprintf(&msg,
806 "%sIgnoring unparsable value '%s' for field '%.*s'.\n",
807 msg ? msg : "", end + 1, (int)(end - word), word);
808 continue;
809 }
810
811 /* write the new value */
812 if (size == 8)
813 write_u64(ptr, new);
814 else if (size == 4)
815 write_u32(ptr, new);
816 else if (size == 2)
817 write_u16(ptr, new);
818 else
819 *(uint8_t *)ptr = new;
820 }
821
822 tctx = calloc(sizeof(*tctx), count + 2);
823 if (!tctx)
824 goto fail;
825
826 tctx[0] = (unsigned long)count;
827 tctx[1] = (unsigned long)inter;
828
829 mask &= all_threads_mask;
830 if (!mask)
831 mask = tid_bit;
832
833 tmask = 0;
834 for (i = 0; i < count; i++) {
835 if (single || mode == 0) {
836 /* look for next bit matching a bit in mask or loop back to zero */
837 for (tmask <<= 1; !(mask & tmask); ) {
838 if (!(mask & -tmask))
839 tmask = 1;
840 else
841 tmask <<= 1;
842 }
843 } else {
844 /* multi-threaded task */
845 tmask = mask;
846 }
847
848 /* now, if poly or mask was set, tmask corresponds to the
849 * valid thread mask to use, otherwise it remains zero.
850 */
851 //printf("%lu: mode=%d mask=%#lx\n", i, mode, tmask);
852 if (mode == 0) {
853 struct tasklet *tl = tasklet_new();
854
855 if (!tl)
856 goto fail;
857
858 if (tmask)
859 tl->tid = my_ffsl(tmask) - 1;
860 tl->process = debug_tasklet_handler;
861 tl->context = tctx;
862 tctx[i + 2] = (unsigned long)tl;
863 } else {
864 struct task *task = task_new(tmask ? tmask : tid_bit);
865
866 if (!task)
867 goto fail;
868
869 task->process = debug_task_handler;
870 task->context = tctx;
871 tctx[i + 2] = (unsigned long)task + 1;
872 }
873 }
874
875 /* start the tasks and tasklets */
876 for (i = 0; i < count; i++) {
877 unsigned long ctx = tctx[i + 2];
878
879 if (ctx & 1)
880 task_wakeup((struct task *)(ctx - 1), TASK_WOKEN_INIT);
881 else
882 tasklet_wakeup((struct tasklet *)ctx);
883 }
884
885 if (msg && *msg)
886 return cli_dynmsg(appctx, LOG_INFO, msg);
887 return 1;
888
889 fail:
890 /* free partially allocated entries */
891 for (i = 0; tctx && i < count; i++) {
892 unsigned long ctx = tctx[i + 2];
893
894 if (!ctx)
895 break;
896
897 if (ctx & 1)
898 task_destroy((struct task *)(ctx - 1));
899 else
900 tasklet_free((struct tasklet *)ctx);
901 }
902
903 free(tctx);
904 return cli_err(appctx, "Not enough memory");
905}
906
Willy Tarreaua6026a02020-07-02 09:14:48 +0200907#if defined(DEBUG_MEM_STATS)
908/* CLI parser for the "debug dev memstats" command */
909static int debug_parse_cli_memstats(char **args, char *payload, struct appctx *appctx, void *private)
910{
911 extern __attribute__((__weak__)) struct mem_stats __start_mem_stats;
912 extern __attribute__((__weak__)) struct mem_stats __stop_mem_stats;
913
914 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
915 return 1;
916
917 if (strcmp(args[3], "reset") == 0) {
918 struct mem_stats *ptr;
919
920 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
921 return 1;
922
923 for (ptr = &__start_mem_stats; ptr < &__stop_mem_stats; ptr++) {
924 _HA_ATOMIC_STORE(&ptr->calls, 0);
925 _HA_ATOMIC_STORE(&ptr->size, 0);
926 }
927 return 1;
928 }
929
930 if (strcmp(args[3], "all") == 0)
931 appctx->ctx.cli.i0 = 1;
932
933 /* otherwise proceed with the dump from p0 to p1 */
934 appctx->ctx.cli.p0 = &__start_mem_stats;
935 appctx->ctx.cli.p1 = &__stop_mem_stats;
936 return 0;
937}
938
939/* CLI I/O handler for the "debug dev memstats" command. Dumps all mem_stats
940 * structs referenced by pointers located between p0 and p1. Dumps all entries
941 * if i0 > 0, otherwise only non-zero calls.
942 */
943static int debug_iohandler_memstats(struct appctx *appctx)
944{
945 struct stream_interface *si = appctx->owner;
946 struct mem_stats *ptr = appctx->ctx.cli.p0;
947 int ret = 1;
948
949 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
950 goto end;
951
952 chunk_reset(&trash);
953
954 /* we have two inner loops here, one for the proxy, the other one for
955 * the buffer.
956 */
957 for (ptr = appctx->ctx.cli.p0; ptr != appctx->ctx.cli.p1; ptr++) {
958 const char *type;
959 const char *name;
960 const char *p;
961
962 if (!ptr->size && !ptr->calls && !appctx->ctx.cli.i0)
963 continue;
964
965 /* basename only */
966 for (p = name = ptr->file; *p; p++) {
967 if (*p == '/')
968 name = p + 1;
969 }
970
971 switch (ptr->type) {
972 case MEM_STATS_TYPE_CALLOC: type = "CALLOC"; break;
973 case MEM_STATS_TYPE_FREE: type = "FREE"; break;
974 case MEM_STATS_TYPE_MALLOC: type = "MALLOC"; break;
975 case MEM_STATS_TYPE_REALLOC: type = "REALLOC"; break;
976 case MEM_STATS_TYPE_STRDUP: type = "STRDUP"; break;
977 default: type = "UNSET"; break;
978 }
979
980 //chunk_printf(&trash,
981 // "%20s:%-5d %7s size: %12lu calls: %9lu size/call: %6lu\n",
982 // name, ptr->line, type,
983 // (unsigned long)ptr->size, (unsigned long)ptr->calls,
984 // (unsigned long)(ptr->calls ? (ptr->size / ptr->calls) : 0));
985
986 chunk_printf(&trash, "%s:%d", name, ptr->line);
987 while (trash.data < 25)
988 trash.area[trash.data++] = ' ';
989 chunk_appendf(&trash, "%7s size: %12lu calls: %9lu size/call: %6lu\n",
990 type,
991 (unsigned long)ptr->size, (unsigned long)ptr->calls,
992 (unsigned long)(ptr->calls ? (ptr->size / ptr->calls) : 0));
993
994 if (ci_putchk(si_ic(si), &trash) == -1) {
995 si_rx_room_blk(si);
996 appctx->ctx.cli.p0 = ptr;
997 ret = 0;
998 break;
999 }
1000 }
1001
1002 end:
1003 return ret;
1004}
1005
1006#endif
1007
Willy Tarreauc7091d82019-05-17 10:08:49 +02001008#ifndef USE_THREAD_DUMP
1009
1010/* This function dumps all threads' state to the trash. This version is the
1011 * most basic one, which doesn't inspect other threads.
1012 */
1013void ha_thread_dump_all_to_trash()
1014{
1015 unsigned int thr;
1016
1017 for (thr = 0; thr < global.nbthread; thr++)
1018 ha_thread_dump(&trash, thr, tid);
1019}
1020
1021#else /* below USE_THREAD_DUMP is set */
1022
Willy Tarreauc7091d82019-05-17 10:08:49 +02001023/* ID of the thread requesting the dump */
1024static unsigned int thread_dump_tid;
1025
1026/* points to the buffer where the dump functions should write. It must
1027 * have already been initialized by the requester. Nothing is done if
1028 * it's NULL.
1029 */
1030struct buffer *thread_dump_buffer = NULL;
1031
1032void ha_thread_dump_all_to_trash()
1033{
Willy Tarreauc7091d82019-05-17 10:08:49 +02001034 unsigned long old;
1035
1036 while (1) {
1037 old = 0;
1038 if (HA_ATOMIC_CAS(&threads_to_dump, &old, all_threads_mask))
1039 break;
1040 ha_thread_relax();
1041 }
1042
1043 thread_dump_buffer = &trash;
1044 thread_dump_tid = tid;
Willy Tarreaufade80d2019-05-22 08:46:59 +02001045 ha_tkillall(DEBUGSIG);
Willy Tarreauc7091d82019-05-17 10:08:49 +02001046}
1047
1048/* handles DEBUGSIG to dump the state of the thread it's working on */
1049void debug_handler(int sig, siginfo_t *si, void *arg)
1050{
Willy Tarreau82aafc42020-03-03 08:31:34 +01001051 /* first, let's check it's really for us and that we didn't just get
1052 * a spurious DEBUGSIG.
1053 */
1054 if (!(threads_to_dump & tid_bit))
1055 return;
1056
Willy Tarreauc7091d82019-05-17 10:08:49 +02001057 /* There are 4 phases in the dump process:
1058 * 1- wait for our turn, i.e. when all lower bits are gone.
1059 * 2- perform the action if our bit is set
1060 * 3- remove our bit to let the next one go, unless we're
Willy Tarreauc0773622019-07-31 19:15:45 +02001061 * the last one and have to put them all as a signal
1062 * 4- wait out bit to re-appear, then clear it and quit.
Willy Tarreauc7091d82019-05-17 10:08:49 +02001063 */
1064
1065 /* wait for all previous threads to finish first */
1066 while (threads_to_dump & (tid_bit - 1))
1067 ha_thread_relax();
1068
1069 /* dump if needed */
1070 if (threads_to_dump & tid_bit) {
1071 if (thread_dump_buffer)
1072 ha_thread_dump(thread_dump_buffer, tid, thread_dump_tid);
1073 if ((threads_to_dump & all_threads_mask) == tid_bit) {
1074 /* last one */
Willy Tarreauc0773622019-07-31 19:15:45 +02001075 HA_ATOMIC_STORE(&threads_to_dump, all_threads_mask);
Willy Tarreauc7091d82019-05-17 10:08:49 +02001076 thread_dump_buffer = NULL;
1077 }
1078 else
1079 HA_ATOMIC_AND(&threads_to_dump, ~tid_bit);
1080 }
1081
1082 /* now wait for all others to finish dumping. The last one will set all
Willy Tarreauc0773622019-07-31 19:15:45 +02001083 * bits again to broadcast the leaving condition so we'll see ourselves
1084 * present again. This way the threads_to_dump variable never passes to
1085 * zero until all visitors have stopped waiting.
Willy Tarreauc7091d82019-05-17 10:08:49 +02001086 */
Willy Tarreauc0773622019-07-31 19:15:45 +02001087 while (!(threads_to_dump & tid_bit))
1088 ha_thread_relax();
1089 HA_ATOMIC_AND(&threads_to_dump, ~tid_bit);
Willy Tarreaue6a02fa2019-05-22 07:06:44 +02001090
1091 /* mark the current thread as stuck to detect it upon next invocation
1092 * if it didn't move.
1093 */
1094 if (!((threads_harmless_mask|sleeping_thread_mask) & tid_bit))
1095 ti->flags |= TI_FL_STUCK;
Willy Tarreauc7091d82019-05-17 10:08:49 +02001096}
1097
1098static int init_debug_per_thread()
1099{
1100 sigset_t set;
1101
1102 /* unblock the DEBUGSIG signal we intend to use */
1103 sigemptyset(&set);
1104 sigaddset(&set, DEBUGSIG);
1105 ha_sigmask(SIG_UNBLOCK, &set, NULL);
1106 return 1;
1107}
1108
1109static int init_debug()
1110{
1111 struct sigaction sa;
Willy Tarreau2f1227e2021-01-22 12:12:29 +01001112 void *callers[1];
Willy Tarreauc7091d82019-05-17 10:08:49 +02001113
Willy Tarreau0214b452020-03-04 06:01:40 +01001114 /* calling backtrace() will access libgcc at runtime. We don't want to
1115 * do it after the chroot, so let's perform a first call to have it
1116 * ready in memory for later use.
1117 */
Willy Tarreau13faf162020-03-04 07:44:06 +01001118 my_backtrace(callers, sizeof(callers)/sizeof(*callers));
Willy Tarreauc7091d82019-05-17 10:08:49 +02001119 sa.sa_handler = NULL;
1120 sa.sa_sigaction = debug_handler;
1121 sigemptyset(&sa.sa_mask);
1122 sa.sa_flags = SA_SIGINFO;
1123 sigaction(DEBUGSIG, &sa, NULL);
Christopher Fauletfc633b62020-11-06 15:24:23 +01001124 return ERR_NONE;
Willy Tarreauc7091d82019-05-17 10:08:49 +02001125}
1126
1127REGISTER_POST_CHECK(init_debug);
1128REGISTER_PER_THREAD_INIT(init_debug_per_thread);
1129
1130#endif /* USE_THREAD_DUMP */
1131
Willy Tarreau4e2b6462019-05-16 17:44:30 +02001132/* register cli keywords */
1133static struct cli_kw_list cli_kws = {{ },{
Willy Tarreaub24ab222019-10-24 18:03:39 +02001134 {{ "debug", "dev", "close", NULL }, "debug dev close <fd> : close this file descriptor", debug_parse_cli_close, NULL, NULL, NULL, ACCESS_EXPERT },
1135 {{ "debug", "dev", "delay", NULL }, "debug dev delay [ms] : sleep this long", debug_parse_cli_delay, NULL, NULL, NULL, ACCESS_EXPERT },
Willy Tarreau6bdf3e92019-05-20 14:25:05 +02001136#if defined(DEBUG_DEV)
Willy Tarreaub24ab222019-10-24 18:03:39 +02001137 {{ "debug", "dev", "exec", NULL }, "debug dev exec [cmd] ... : show this command's output", debug_parse_cli_exec, NULL, NULL, NULL, ACCESS_EXPERT },
Willy Tarreau6bdf3e92019-05-20 14:25:05 +02001138#endif
Willy Tarreaub24ab222019-10-24 18:03:39 +02001139 {{ "debug", "dev", "exit", NULL }, "debug dev exit [code] : immediately exit the process", debug_parse_cli_exit, NULL, NULL, NULL, ACCESS_EXPERT },
1140 {{ "debug", "dev", "hex", NULL }, "debug dev hex <addr> [len]: dump a memory area", debug_parse_cli_hex, NULL, NULL, NULL, ACCESS_EXPERT },
1141 {{ "debug", "dev", "log", NULL }, "debug dev log [msg] ... : send this msg to global logs", debug_parse_cli_log, NULL, NULL, NULL, ACCESS_EXPERT },
1142 {{ "debug", "dev", "loop", NULL }, "debug dev loop [ms] : loop this long", debug_parse_cli_loop, NULL, NULL, NULL, ACCESS_EXPERT },
Willy Tarreaua6026a02020-07-02 09:14:48 +02001143#if defined(DEBUG_MEM_STATS)
1144 {{ "debug", "dev", "memstats", NULL }, "debug dev memstats [reset|all] : dump/reset memory statistics", debug_parse_cli_memstats, debug_iohandler_memstats, NULL, NULL, ACCESS_EXPERT },
1145#endif
Willy Tarreaub24ab222019-10-24 18:03:39 +02001146 {{ "debug", "dev", "panic", NULL }, "debug dev panic : immediately trigger a panic", debug_parse_cli_panic, NULL, NULL, NULL, ACCESS_EXPERT },
Willy Tarreaua5a44792020-11-29 17:12:15 +01001147 {{ "debug", "dev", "sched", NULL }, "debug dev sched ... : stress the scheduler", debug_parse_cli_sched, NULL, NULL, NULL, ACCESS_EXPERT },
Willy Tarreaub24ab222019-10-24 18:03:39 +02001148 {{ "debug", "dev", "stream",NULL }, "debug dev stream ... : show/manipulate stream flags", debug_parse_cli_stream,NULL, NULL, NULL, ACCESS_EXPERT },
1149 {{ "debug", "dev", "tkill", NULL }, "debug dev tkill [thr] [sig] : send signal to thread", debug_parse_cli_tkill, NULL, NULL, NULL, ACCESS_EXPERT },
Willy Tarreau6cbe62b2020-03-05 17:16:24 +01001150 {{ "debug", "dev", "write", NULL }, "debug dev write [size] : write that many bytes", debug_parse_cli_write, NULL, NULL, NULL, ACCESS_EXPERT },
Willy Tarreaub24ab222019-10-24 18:03:39 +02001151 {{ "show", "threads", NULL, NULL }, "show threads : show some threads debugging information", NULL, cli_io_handler_show_threads, NULL },
Willy Tarreau4e2b6462019-05-16 17:44:30 +02001152 {{},}
1153}};
1154
1155INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);