Coverage Report

Created: 2026-01-17 06:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/haproxy/src/debug.c
Line
Count
Source
1
/*
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
13
14
#include <errno.h>
15
#include <fcntl.h>
16
#include <signal.h>
17
#include <time.h>
18
#include <stdio.h>
19
#include <stdlib.h>
20
#include <syslog.h>
21
#include <sys/resource.h>
22
#include <sys/stat.h>
23
#include <sys/types.h>
24
#include <sys/utsname.h>
25
#include <sys/wait.h>
26
#include <unistd.h>
27
#ifdef USE_EPOLL
28
#include <sys/epoll.h>
29
#endif
30
31
#include <haproxy/api.h>
32
#include <haproxy/applet.h>
33
#include <haproxy/buf.h>
34
#include <haproxy/cfgparse.h>
35
#include <haproxy/cli.h>
36
#include <haproxy/clock.h>
37
#ifdef USE_CPU_AFFINITY
38
#include <haproxy/cpu_topo.h>
39
#endif
40
#include <haproxy/debug.h>
41
#include <haproxy/fd.h>
42
#include <haproxy/global.h>
43
#include <haproxy/hlua.h>
44
#include <haproxy/http_ana.h>
45
#include <haproxy/limits.h>
46
#if defined(USE_LINUX_CAP)
47
#include <haproxy/linuxcap.h>
48
#endif
49
#include <haproxy/log.h>
50
#include <haproxy/net_helper.h>
51
#include <haproxy/sc_strm.h>
52
#include <haproxy/proxy.h>
53
#include <haproxy/stconn.h>
54
#include <haproxy/task.h>
55
#include <haproxy/thread.h>
56
#include <haproxy/time.h>
57
#include <haproxy/tools.h>
58
#include <haproxy/trace.h>
59
#include <haproxy/version.h>
60
#include <import/ist.h>
61
62
63
/* The dump state is made of:
64
 *   - num_thread on the lowest 15 bits
65
 *   - a SYNC flag on bit 15 (waiting for sync start)
66
 *   - number of participating threads on bits 16-30
67
 * Initiating a dump consists in setting it to SYNC and incrementing the
68
 * num_thread part when entering the function. The first thread periodically
69
 * recounts active threads and compares it to the ready ones, and clears SYNC
70
 * and sets the number of participants to the value found, which serves as a
71
 * start signal. A thread finished dumping looks up the TID of the next active
72
 * thread after it and writes it in the lowest part. If there's none, it sets
73
 * the thread counter to the number of participants and resets that part,
74
 * which serves as an end-of-dump signal. All threads decrement the num_thread
75
 * part. Then all threads wait for the value to reach zero. Only used when
76
 * USE_THREAD_DUMP is set.
77
 */
78
#define THREAD_DUMP_TMASK     0x00007FFFU
79
#define THREAD_DUMP_FSYNC     0x00008000U
80
#define THREAD_DUMP_PMASK     0x7FFF0000U
81
82
/* Description of a component with name, version, path, build options etc. E.g.
83
 * one of them is haproxy. Others might be some clearly identified shared libs.
84
 * They're intentionally self-contained and to be placed into an array to make
85
 * it easier to find them in a core. The important fields (name and version)
86
 * are locally allocated, other ones are dynamic.
87
 */
88
struct post_mortem_component {
89
  char name[32];           // symbolic short name
90
  char version[32];        // exact version
91
  char *toolchain;         // compiler and version (e.g. gcc-11.4.0)
92
  char *toolchain_opts;    // optims, arch-specific options (e.g. CFLAGS)
93
  char *build_settings;    // build options (e.g. USE_*, TARGET, etc)
94
  char *path;              // path if known.
95
};
96
97
/* This is a collection of information that are centralized to help with core
98
 * dump analysis. It must be used with a public variable and gather elements
99
 * as much as possible without dereferences so that even when identified in a
100
 * core dump it's possible to get the most out of it even if the core file is
101
 * not much exploitable. It's aligned to 256 so that it's easy to spot, given
102
 * that being that large it will not change its size much.
103
 */
104
struct post_mortem {
105
  /* platform-specific information */
106
  char post_mortem_magic[32];     // "POST-MORTEM STARTS HERE+7654321\0"
107
  struct {
108
    struct utsname utsname; // OS name+ver+arch+hostname
109
    char distro[64];  // Distro name and version from os-release file if exists
110
    char hw_vendor[64];     // hardware/hypervisor vendor when known
111
    char hw_family[64];     // hardware/hypervisor product family when known
112
    char hw_model[64];      // hardware/hypervisor product/model when known
113
    char brd_vendor[64];    // mainboard vendor when known
114
    char brd_model[64];     // mainboard model when known
115
    char soc_vendor[64];    // SoC/CPU vendor from cpuinfo
116
    char soc_model[64];     // SoC model when known and relevant
117
    char cpu_model[64];     // CPU model when different from SoC
118
    char virt_techno[16];   // when provided by cpuid
119
    char cont_techno[16];   // empty, "no", "yes", "docker" or others
120
  } platform;
121
122
  /* process-specific information */
123
  struct {
124
    pid_t pid;
125
    uid_t boot_uid;
126
    gid_t boot_gid;
127
    uid_t run_uid;
128
    gid_t run_gid;
129
#if defined(USE_LINUX_CAP)
130
    struct {
131
      // initial process capabilities
132
      struct __user_cap_data_struct boot[_LINUX_CAPABILITY_U32S_3];
133
      int err_boot; // errno, if capget() syscall fails at boot
134
      // runtime process capabilities
135
      struct __user_cap_data_struct run[_LINUX_CAPABILITY_U32S_3];
136
      int err_run; // errno, if capget() syscall fails at runtime
137
    } caps;
138
#endif
139
    struct rlimit boot_lim_fd;  // RLIMIT_NOFILE at startup
140
    struct rlimit boot_lim_ram; // RLIMIT_DATA at startup
141
    struct rlimit run_lim_fd;  // RLIMIT_NOFILE just before enter in polling loop
142
    struct rlimit run_lim_ram; // RLIMIT_DATA just before enter in polling loop
143
    char **argv;
144
    unsigned char argc;
145
  } process;
146
147
#if defined(HA_HAVE_DUMP_LIBS)
148
  /* information about dynamic shared libraries involved */
149
  char *libs;                      // dump of one addr / path per line, or NULL
150
#endif
151
  struct tgroup_info *tgroup_info; // pointer to ha_tgroup_info
152
  struct thread_info *thread_info; // pointer to ha_thread_info
153
  struct tgroup_ctx  *tgroup_ctx;  // pointer to ha_tgroup_ctx
154
  struct thread_ctx  *thread_ctx;  // pointer to ha_thread_ctx
155
  struct list *pools;              // pointer to the head of the pools list
156
  struct proxy **proxies;          // pointer to the head of the proxies list
157
  struct global *global;           // pointer to the struct global
158
  struct fdtab **fdtab;            // pointer to the fdtab array
159
  struct activity *activity;       // pointer to the activity[] per-thread array
160
161
  /* info about identified distinct components (executable, shared libs, etc).
162
   * These can be all listed at once in gdb using:
163
   *    p *post_mortem.components@post_mortem.nb_components
164
   */
165
  uint nb_components;              // # of components below
166
  struct post_mortem_component *components; // NULL or array
167
} post_mortem ALIGNED(256) HA_SECTION("_post_mortem") = { };
168
169
unsigned int debug_commands_issued = 0;
170
unsigned int warn_blocked_issued = 0;
171
unsigned int debug_enable_counters = (DEBUG_COUNTERS >= 2);
172
173
/* dumps a backtrace of the current thread that is appended to buffer <buf>.
174
 * Lines are prefixed with the string <prefix> which may be empty (used for
175
 * indenting). It is recommended to use this at a function's tail so that
176
 * the function does not appear in the call stack. The <dump> argument
177
 * indicates what dump state to start from, and should usually be zero. It
178
 * may be among the following values:
179
 *   - 0: search usual callers before step 1, or directly jump to 2
180
 *   - 1: skip usual callers before step 2
181
 *   - 2: dump until polling loop, scheduler, or main() (excluded)
182
 *   - 3: end
183
 *   - 4-7: like 0 but stops *after* main.
184
 */
185
void ha_dump_backtrace(struct buffer *buf, const char *prefix, int dump)
186
0
{
187
0
  sigset_t new_mask, old_mask;
188
0
  struct buffer bak;
189
0
  char pfx2[100];
190
0
  void *callers[100];
191
0
  int j, nptrs;
192
0
  const void *addr;
193
194
  /* make sure we don't re-enter from debug coming from other threads,
195
   * as some libc's backtrace() are not re-entrant. We'll block these
196
   * sensitive signals while possibly dumping a backtrace.
197
   */
198
0
  sigemptyset(&new_mask);
199
#ifdef WDTSIG
200
  sigaddset(&new_mask, WDTSIG);
201
#endif
202
#ifdef DEBUGSIG
203
  sigaddset(&new_mask, DEBUGSIG);
204
#endif
205
0
  ha_sigmask(SIG_BLOCK, &new_mask, &old_mask);
206
207
0
  nptrs = my_backtrace(callers, sizeof(callers)/sizeof(*callers));
208
0
  if (!nptrs)
209
0
    goto leave;
210
211
0
  if (snprintf(pfx2, sizeof(pfx2), "%s| ", prefix) > sizeof(pfx2))
212
0
    pfx2[0] = 0;
213
214
  /* The call backtrace_symbols_fd(callers, nptrs, STDOUT_FILENO would
215
   * produce similar output to the following:
216
   */
217
0
  chunk_appendf(buf, "%scall trace(%d):\n", prefix, nptrs);
218
0
  for (j = 0; (j < nptrs || (dump & 3) < 2); j++) {
219
0
    if (j == nptrs && !(dump & 3)) {
220
      /* we failed to spot the starting point of the
221
       * dump, let's start over dumping everything we
222
       * have.
223
       */
224
0
      dump += 2;
225
0
      j = 0;
226
0
    }
227
0
    bak = *buf;
228
0
    dump_addr_and_bytes(buf, pfx2, callers[j], -8);
229
0
    addr = resolve_sym_name(buf, ": ", callers[j]);
230
231
0
#if defined(__i386__) || defined(__x86_64__)
232
    /* Try to decode a relative call (0xe8 + 32-bit signed ofs) */
233
0
    if (may_access(callers[j] - 5) && may_access(callers[j] - 1) &&
234
0
        *((uchar*)(callers[j] - 5)) == 0xe8) {
235
0
      int ofs = *((int *)(callers[j] - 4));
236
0
      const void *addr2 = callers[j] + ofs;
237
0
      resolve_sym_name(buf, " > ", addr2);
238
0
    }
239
#elif defined(__aarch64__)
240
    /* Try to decode a relative call (0x9X + 26-bit signed ofs) */
241
    if (may_access(callers[j] - 4) && may_access(callers[j] - 1) &&
242
        (*((int*)(callers[j] - 4)) & 0xFC000000) == 0x94000000) {
243
      int ofs = (*((int *)(callers[j] - 4)) << 6) >> 4; // 26-bit signed immed*4
244
      const void *addr2 = callers[j] - 4 + ofs;
245
      resolve_sym_name(buf, " > ", addr2);
246
    }
247
#endif
248
0
    if ((dump & 3) == 0) {
249
      /* dump not started, will start *after* ha_thread_dump_one(),
250
       * ha_panic and ha_backtrace_to_stderr
251
       */
252
0
      if (addr == ha_panic ||
253
0
          addr == ha_backtrace_to_stderr || addr == ha_thread_dump_one)
254
0
        dump++;
255
0
      *buf = bak;
256
0
      continue;
257
0
    }
258
259
0
    if ((dump & 3) == 1) {
260
      /* starting */
261
0
      if (addr == ha_panic ||
262
0
          addr == ha_backtrace_to_stderr || addr == ha_thread_dump_one) {
263
0
        *buf = bak;
264
0
        continue;
265
0
      }
266
0
      dump++;
267
0
    }
268
269
0
    if ((dump & 3) == 2) {
270
      /* still dumping */
271
0
      if (dump == 6) {
272
        /* we only stop *after* main and we must send the LF */
273
0
        if (addr == main) {
274
0
          j = nptrs;
275
0
          dump++;
276
0
        }
277
0
      }
278
0
      else if (addr == run_poll_loop || addr == main || addr == run_tasks_from_lists) {
279
0
        dump++;
280
0
        *buf = bak;
281
0
        break;
282
0
      }
283
0
    }
284
    /* OK, line dumped */
285
0
    chunk_appendf(buf, "\n");
286
0
  }
287
0
 leave:
288
  /* unblock temporarily blocked signals */
289
0
  ha_sigmask(SIG_SETMASK, &old_mask, NULL);
290
0
}
291
292
/* dump a backtrace of current thread's stack to stderr. */
293
void ha_backtrace_to_stderr(void)
294
0
{
295
0
  char area[8192];
296
0
  struct buffer b = b_make(area, sizeof(area), 0, 0);
297
298
0
  ha_dump_backtrace(&b, "  ", 4);
299
0
  if (b.data)
300
0
    DISGUISE(write(2, b.area, b.data));
301
0
}
302
303
/* Dumps some known information about the current thread into its dump buffer,
304
 * and optionally extra info when it's considered safe to do so. The dump will
305
 * be appended to the buffer, so the caller is responsible for preliminary
306
 * initializing it. The <is_caller> argument will indicate if the thread is the
307
 * one requesting the dump (e.g. watchdog, panic etc), in order to display a
308
 * star ('*') in front of the thread to indicate the requesting one. Any stuck
309
 * thread is also prefixed with a '>'. The caller is responsible for atomically
310
 * setting up the thread's dump buffer to point to a valid buffer with enough
311
 * room. Output will be truncated if it does not fit. When the dump is complete
312
 * the dump buffer will have bit 0 set to 1 to tell the caller it's done, and
313
 * the caller will then change that value to indicate it's done once the
314
 * contents are collected.
315
 */
316
void ha_thread_dump_one(struct buffer *buf, int is_caller)
317
0
{
318
0
  unsigned long long p = th_ctx->prev_cpu_time;
319
0
  unsigned long long n = now_cpu_time();
320
0
  int stuck = !!(th_ctx->flags & TH_FL_STUCK);
321
322
  /* keep a copy of the dump pointer for post-mortem analysis */
323
0
  HA_ATOMIC_STORE(&th_ctx->last_dump_buffer, buf);
324
325
0
  chunk_appendf(buf,
326
0
                "%c%cThread %-2u: id=0x%llx act=%d glob=%d wq=%d rq=%d tl=%d tlsz=%d rqsz=%d\n"
327
0
                "     %2u/%-2u   loops=%u ctxsw=%u stuck=%d prof=%d",
328
0
                (is_caller) ? '*' : ' ', stuck ? '>' : ' ', tid + 1,
329
0
          ha_get_pthread_id(tid),
330
0
          thread_has_tasks(),
331
0
                !eb_is_empty(&th_ctx->rqueue_shared),
332
0
                !eb_is_empty(&th_ctx->timers),
333
0
                !eb_is_empty(&th_ctx->rqueue),
334
0
                !(LIST_ISEMPTY(&th_ctx->tasklets[TL_URGENT]) &&
335
0
      LIST_ISEMPTY(&th_ctx->tasklets[TL_NORMAL]) &&
336
0
      LIST_ISEMPTY(&th_ctx->tasklets[TL_BULK]) &&
337
0
      MT_LIST_ISEMPTY(&th_ctx->shared_tasklet_list)),
338
0
                th_ctx->tasks_in_list,
339
0
                th_ctx->rq_total,
340
0
          ti->tgid, ti->ltid + 1,
341
0
          activity[tid].loops, activity[tid].ctxsw,
342
0
                stuck,
343
0
                !!(th_ctx->flags & TH_FL_TASK_PROFILING));
344
345
#if defined(USE_THREAD)
346
  chunk_appendf(buf,
347
                " harmless=%d isolated=%d",
348
                !!(_HA_ATOMIC_LOAD(&tg_ctx->threads_harmless) & ti->ltid_bit),
349
          isolated_thread == tid);
350
#endif
351
#if (DEBUG_THREAD > 0) || defined(DEBUG_FULL)
352
  chunk_appendf(buf, " locks=%d", th_ctx->lock_level);
353
#endif
354
355
0
  chunk_appendf(buf, "\n");
356
0
  chunk_appendf(buf, "             cpu_ns: poll=%llu now=%llu diff=%llu\n", p, n, n-p);
357
358
  /* also try to indicate for how long we've entered the current task.
359
   * Note that the task's wake date only contains the 32 lower bits of
360
   * the current time.
361
   */
362
0
  if (th_ctx->current && tick_isset(th_ctx->sched_wake_date)) {
363
0
    unsigned long long now = now_mono_time();
364
365
0
    chunk_appendf(buf, "             current call: wake=%u ns ago, call=%llu ns ago\n",
366
0
            (uint)(now - th_ctx->sched_wake_date),
367
0
            (now - th_ctx->sched_call_date));
368
0
  }
369
370
  /* this is the end of what we can dump from outside the current thread */
371
372
0
  chunk_appendf(buf, "             curr_task=");
373
0
  ha_task_dump(buf, th_ctx->current, "             ");
374
375
#if defined(USE_THREAD) && ((DEBUG_THREAD > 0) || defined(DEBUG_FULL))
376
  /* List the lock history */
377
  if (th_ctx->lock_history) {
378
    int lkh, lkl, lbl;
379
    int done;
380
381
    chunk_appendf(buf, "             lock_hist:");
382
    for (lkl = 7; lkl >= 0; lkl--) {
383
      lkh = (th_ctx->lock_history >> (lkl * 8)) & 0xff;
384
      if (!lkh)
385
        continue;
386
      chunk_appendf(buf, " %c:%s",
387
              "URSW"[lkh & 3], lock_label((lkh >> 2) - 1));
388
    }
389
390
    /* now rescan the list to only show those that remain */
391
    done = 0;
392
    for (lbl = 0; lbl < LOCK_LABELS; lbl++) {
393
      /* find the latest occurrence of each label */
394
      for (lkl = 0; lkl < 8; lkl++) {
395
        lkh = (th_ctx->lock_history >> (lkl * 8)) & 0xff;
396
        if (!lkh)
397
          continue;
398
        if ((lkh >> 2) == lbl)
399
          break;
400
      }
401
      if (lkl == 8) // not found
402
        continue;
403
      if ((lkh & 3) == _LK_UN)
404
        continue;
405
      if (!done)
406
        chunk_appendf(buf, " locked:");
407
      chunk_appendf(buf, " %s(%c)",
408
              lock_label((lkh >> 2) - 1),
409
              "URSW"[lkh & 3]);
410
      done++;
411
    }
412
    chunk_appendf(buf, "\n");
413
  }
414
#endif
415
416
0
  if (!(HA_ATOMIC_LOAD(&tg_ctx->threads_idle) & ti->ltid_bit)) {
417
    /* only dump the stack of active threads */
418
#ifdef USE_LUA
419
    if (th_ctx->current &&
420
        th_ctx->current->process == process_stream && th_ctx->current->context) {
421
      const struct stream *s = (const struct stream *)th_ctx->current->context;
422
      struct hlua *hlua = NULL;
423
424
      if (s) {
425
        if (s->hlua[0] && HLUA_IS_BUSY(s->hlua[0]))
426
          hlua = s->hlua[0];
427
        else if (s->hlua[1] && HLUA_IS_BUSY(s->hlua[1]))
428
          hlua = s->hlua[1];
429
      }
430
      if (hlua) {
431
        mark_tainted(TAINTED_LUA_STUCK);
432
        if (hlua->state_id == 0)
433
          mark_tainted(TAINTED_LUA_STUCK_SHARED);
434
      }
435
    }
436
#endif
437
438
0
    if (HA_ATOMIC_LOAD(&pool_trim_in_progress))
439
0
      mark_tainted(TAINTED_MEM_TRIMMING_STUCK);
440
441
0
    ha_dump_backtrace(buf, "             ", 0);
442
0
  }
443
0
 leave:
444
0
  return;
445
0
}
446
447
/* Triggers a thread dump from thread <thr>, either directly if it's the
448
 * current thread or if thread dump signals are not implemented, or by sending
449
 * a signal if it's a remote one and the feature is supported. The buffer <buf>
450
 * will get the dump appended, and the caller is responsible for making sure
451
 * there is enough room otherwise some contents will be truncated. The function
452
 * waits for the called thread to fill the buffer before returning (or cancelling
453
 * by reporting NULL). It does not release the called thread yet, unless it's the
454
 * current one, which in this case is always available. It returns a pointer to
455
 * the buffer used if the dump was done, otherwise NULL. When the dump starts, it
456
 * marks the current thread as dumping, which will only be released via a failure
457
 * (returns NULL) or via a call to ha_dump_thread_done().
458
 */
459
struct buffer *ha_thread_dump_fill(struct buffer *buf, int thr)
460
0
{
461
#ifdef USE_THREAD_DUMP
462
  /* silence bogus warning in gcc 11 without threads */
463
  ASSUME(0 <= thr && thr < MAX_THREADS);
464
465
  if (thr != tid) {
466
    struct buffer *old = NULL;
467
468
    /* try to impose our dump buffer and to reserve the target thread's
469
     * next dump for us.
470
     */
471
    do {
472
      if (old)
473
        ha_thread_relax();
474
      old = NULL;
475
    } while (!HA_ATOMIC_CAS(&ha_thread_ctx[thr].thread_dump_buffer, &old, buf));
476
477
    /* asking the remote thread to dump itself allows to get more details
478
     * including a backtrace.
479
     */
480
    ha_tkill(thr, DEBUGSIG);
481
482
    /* now wait for the dump to be done (or cancelled) */
483
    while (1) {
484
      buf = HA_ATOMIC_LOAD(&ha_thread_ctx[thr].thread_dump_buffer);
485
      if ((ulong)buf & 0x1)
486
        break;
487
      if (!buf)
488
        return buf;
489
      ha_thread_relax();
490
    }
491
  }
492
  else
493
    ha_thread_dump_one(buf, 1);
494
495
#else /* !USE_THREAD_DUMP below, we're on the target thread */
496
  /* when thread-dump is not supported, we can only dump our own thread */
497
0
  if (thr != tid)
498
0
    return NULL;
499
500
  /* the buffer might not be valid in case of a panic, since we
501
   * have to allocate it ourselves in this case.
502
   */
503
0
  if ((ulong)buf == 0x2UL)
504
0
    buf = get_trash_chunk();
505
0
  HA_ATOMIC_STORE(&th_ctx->thread_dump_buffer, buf);
506
0
  ha_thread_dump_one(buf, 1);
507
0
#endif
508
0
  return (struct buffer *)((ulong)buf & ~0x1UL);
509
0
}
510
511
/* Indicates to the called thread that the dumped data are collected by
512
 * clearing the thread_dump_buffer pointer. It waits for the dump to be
513
 * completed if it was not the case, and can also leave if the pointer
514
 * is already NULL (e.g. if a thread has aborted).
515
 */
516
void ha_thread_dump_done(int thr)
517
0
{
518
0
  struct buffer *old;
519
520
  /* silence bogus warning in gcc 11 without threads */
521
0
  ASSUME(0 <= thr && thr < MAX_THREADS);
522
523
  /* now wait for the dump to be done or cancelled, and release it */
524
0
  do {
525
0
    if (thr == tid)
526
0
      break;
527
0
    old = HA_ATOMIC_LOAD(&ha_thread_ctx[thr].thread_dump_buffer);
528
0
    if (!((ulong)old & 0x1)) {
529
0
      if (!old)
530
0
        break;
531
0
      ha_thread_relax();
532
0
      continue;
533
0
    }
534
0
  } while (!HA_ATOMIC_CAS(&ha_thread_ctx[thr].thread_dump_buffer, &old, NULL));
535
0
}
536
537
/* dumps into the buffer some information related to task <task> (which may
538
 * either be a task or a tasklet, and prepend each line except the first one
539
 * with <pfx>. The buffer is only appended and the first output starts by the
540
 * pointer itself. The caller is responsible for making sure the task is not
541
 * going to vanish during the dump.
542
 */
543
void ha_task_dump(struct buffer *buf, const struct task *task, const char *pfx)
544
0
{
545
0
  const struct stream *s = NULL;
546
0
  const struct appctx __maybe_unused *appctx = NULL;
547
0
  struct hlua __maybe_unused *hlua = NULL;
548
0
  const struct stconn *sc;
549
550
0
  if (!task) {
551
0
    chunk_appendf(buf, "0\n");
552
0
    return;
553
0
  }
554
555
0
  if (TASK_IS_TASKLET(task))
556
0
    chunk_appendf(buf,
557
0
                  "%p (tasklet) calls=%u\n",
558
0
                  task,
559
0
                  task->calls);
560
0
  else
561
0
    chunk_appendf(buf,
562
0
                  "%p (task) calls=%u last=%llu%s\n",
563
0
                  task,
564
0
                  task->calls,
565
0
                  task->wake_date ? (unsigned long long)(now_mono_time() - task->wake_date) : 0,
566
0
                  task->wake_date ? " ns ago" : "");
567
568
0
  chunk_appendf(buf, "%s  fct=%p(", pfx, task->process);
569
0
  resolve_sym_name(buf, NULL, task->process);
570
0
  chunk_appendf(buf,") ctx=%p", task->context);
571
572
0
  if (task->process == task_run_applet && (appctx = task->context))
573
0
    chunk_appendf(buf, "(%s)\n", appctx->applet->name);
574
0
  else
575
0
    chunk_appendf(buf, "\n");
576
577
0
  if (task->process == process_stream && task->context)
578
0
    s = (struct stream *)task->context;
579
0
  else if (task->process == task_run_applet && task->context && (sc = appctx_sc((struct appctx *)task->context)))
580
0
    s = sc_strm(sc);
581
0
  else if (task->process == sc_conn_io_cb && task->context)
582
0
    s = sc_strm(((struct stconn *)task->context));
583
584
0
  if (s) {
585
0
    chunk_appendf(buf, "%sstream=", pfx);
586
0
    strm_dump_to_buffer(buf, s, pfx, HA_ATOMIC_LOAD(&global.anon_key));
587
0
  }
588
589
#ifdef USE_LUA
590
  hlua = NULL;
591
  if (s && ((s->hlua[0] && HLUA_IS_BUSY(s->hlua[0])) ||
592
      (s->hlua[1] && HLUA_IS_BUSY(s->hlua[1])))) {
593
    hlua = (s->hlua[0] && HLUA_IS_BUSY(s->hlua[0])) ? s->hlua[0] : s->hlua[1];
594
    chunk_appendf(buf, "%sCurrent executing Lua from a stream analyser -- ", pfx);
595
  }
596
  else if (task->process == hlua_process_task && (hlua = task->context)) {
597
    chunk_appendf(buf, "%sCurrent executing a Lua task -- ", pfx);
598
  }
599
  else if (task->process == task_run_applet && (appctx = task->context) &&
600
     (appctx->applet->fct == hlua_applet_tcp_fct)) {
601
    chunk_appendf(buf, "%sCurrent executing a Lua TCP service -- ", pfx);
602
  }
603
  else if (task->process == task_run_applet && (appctx = task->context) &&
604
     (appctx->applet->fct == hlua_applet_http_fct)) {
605
    chunk_appendf(buf, "%sCurrent executing a Lua HTTP service -- ", pfx);
606
  }
607
608
  if (hlua && hlua->T) {
609
    chunk_appendf(buf, "stack traceback:\n    ");
610
    append_prefixed_str(buf, hlua_traceback(hlua->T, "\n    "), pfx, '\n', 0);
611
  }
612
613
  /* we may need to terminate the current line */
614
  if (*b_peek(buf, b_data(buf)-1) != '\n')
615
    b_putchr(buf, '\n');
616
#endif
617
0
}
618
619
/* This function dumps all profiling settings. It returns 0 if the output
620
 * buffer is full and it needs to be called again, otherwise non-zero.
621
 * Note: to not statify this one, it's hard to spot in backtraces!
622
 */
623
int cli_io_handler_show_threads(struct appctx *appctx)
624
0
{
625
0
  int *thr = appctx->svcctx;
626
627
0
  if (!thr)
628
0
    thr = applet_reserve_svcctx(appctx, sizeof(*thr));
629
630
0
  do {
631
0
    chunk_reset(&trash);
632
0
    if (ha_thread_dump_fill(&trash, *thr)) {
633
0
      ha_thread_dump_done(*thr);
634
0
      if (applet_putchk(appctx, &trash) == -1) {
635
        /* failed, try again */
636
0
        return 0;
637
0
      }
638
0
    }
639
0
    (*thr)++;
640
0
  } while (*thr < global.nbthread);
641
642
0
  return 1;
643
0
}
644
645
#if defined(HA_HAVE_DUMP_LIBS)
646
/* parse a "show libs" command. It returns 1 if it emits anything otherwise zero. */
647
static int debug_parse_cli_show_libs(char **args, char *payload, struct appctx *appctx, void *private)
648
{
649
  if (!cli_has_level(appctx, ACCESS_LVL_OPER))
650
    return 1;
651
652
  chunk_reset(&trash);
653
  if (dump_libs(&trash, 1))
654
    return cli_msg(appctx, LOG_INFO, trash.area);
655
  else
656
    return 0;
657
}
658
#endif
659
660
/* parse a "show dev" command. It returns 1 if it emits anything otherwise zero. */
661
static int debug_parse_cli_show_dev(char **args, char *payload, struct appctx *appctx, void *private)
662
0
{
663
0
  const char **build_opt;
664
0
  char *err = NULL;
665
0
  int i;
666
667
0
  if (*args[2])
668
0
    return cli_err(appctx, "This command takes no argument.\n");
669
670
0
  chunk_reset(&trash);
671
672
0
  chunk_appendf(&trash, "HAProxy version %s\n", haproxy_version);
673
0
  chunk_appendf(&trash, "Features:\n  %s\n", build_features);
674
675
0
  chunk_appendf(&trash, "Build options:\n");
676
0
  for (build_opt = NULL; (build_opt = hap_get_next_build_opt(build_opt)); )
677
0
    if (append_prefixed_str(&trash, *build_opt, "  ", '\n', 0) == 0)
678
0
      chunk_strcat(&trash, "\n");
679
680
0
  chunk_appendf(&trash, "Platform info:\n");
681
0
  if (*post_mortem.platform.hw_vendor)
682
0
    chunk_appendf(&trash, "  machine vendor: %s\n", post_mortem.platform.hw_vendor);
683
0
  if (*post_mortem.platform.hw_family)
684
0
    chunk_appendf(&trash, "  machine family: %s\n", post_mortem.platform.hw_family);
685
0
  if (*post_mortem.platform.hw_model)
686
0
    chunk_appendf(&trash, "  machine model: %s\n", post_mortem.platform.hw_model);
687
0
  if (*post_mortem.platform.brd_vendor)
688
0
    chunk_appendf(&trash, "  board vendor: %s\n", post_mortem.platform.brd_vendor);
689
0
  if (*post_mortem.platform.brd_model)
690
0
    chunk_appendf(&trash, "  board model: %s\n", post_mortem.platform.brd_model);
691
0
  if (*post_mortem.platform.soc_vendor)
692
0
    chunk_appendf(&trash, "  soc vendor: %s\n", post_mortem.platform.soc_vendor);
693
0
  if (*post_mortem.platform.soc_model)
694
0
    chunk_appendf(&trash, "  soc model: %s\n", post_mortem.platform.soc_model);
695
0
  if (*post_mortem.platform.cpu_model)
696
0
    chunk_appendf(&trash, "  cpu model: %s\n", post_mortem.platform.cpu_model);
697
0
  if (*post_mortem.platform.virt_techno)
698
0
    chunk_appendf(&trash, "  virtual machine: %s\n", post_mortem.platform.virt_techno);
699
0
  if (*post_mortem.platform.cont_techno)
700
0
    chunk_appendf(&trash, "  container: %s\n", post_mortem.platform.cont_techno);
701
0
  if (*post_mortem.platform.utsname.sysname)
702
0
    chunk_appendf(&trash, "  OS name: %s\n", post_mortem.platform.utsname.sysname);
703
0
  if (*post_mortem.platform.utsname.release)
704
0
    chunk_appendf(&trash, "  OS release: %s\n", post_mortem.platform.utsname.release);
705
0
  if (*post_mortem.platform.utsname.version)
706
0
    chunk_appendf(&trash, "  OS version: %s\n", post_mortem.platform.utsname.version);
707
0
  if (*post_mortem.platform.utsname.machine)
708
0
    chunk_appendf(&trash, "  OS architecture: %s\n", post_mortem.platform.utsname.machine);
709
0
  if (*post_mortem.platform.utsname.nodename)
710
0
    chunk_appendf(&trash, "  node name: %s\n", HA_ANON_CLI(post_mortem.platform.utsname.nodename));
711
0
  if (*post_mortem.platform.distro)
712
0
    chunk_appendf(&trash, "  distro pretty name: %s\n", HA_ANON_CLI(post_mortem.platform.distro));
713
714
0
  chunk_appendf(&trash, "Process info:\n");
715
0
  chunk_appendf(&trash, "  pid: %d\n", post_mortem.process.pid);
716
0
  chunk_appendf(&trash, "  cmdline: ");
717
0
  for (i = 0; i < post_mortem.process.argc; i++)
718
0
    chunk_appendf(&trash, "%s ", post_mortem.process.argv[i]);
719
0
  chunk_appendf(&trash, "\n");
720
#if defined(USE_LINUX_CAP)
721
  /* let's dump saved in feed_post_mortem() initial capabilities sets */
722
  if(!post_mortem.process.caps.err_boot) {
723
    chunk_appendf(&trash, "  boot capabilities:\n");
724
    chunk_appendf(&trash, "  \tCapEff: 0x%016llx\n",
725
            CAPS_TO_ULLONG(post_mortem.process.caps.boot[0].effective,
726
               post_mortem.process.caps.boot[1].effective));
727
    chunk_appendf(&trash, "  \tCapPrm: 0x%016llx\n",
728
            CAPS_TO_ULLONG(post_mortem.process.caps.boot[0].permitted,
729
               post_mortem.process.caps.boot[1].permitted));
730
    chunk_appendf(&trash, "  \tCapInh: 0x%016llx\n",
731
            CAPS_TO_ULLONG(post_mortem.process.caps.boot[0].inheritable,
732
               post_mortem.process.caps.boot[1].inheritable));
733
  } else
734
    chunk_appendf(&trash, "  capget() failed at boot with: %s.\n",
735
            errname(post_mortem.process.caps.err_boot, &err));
736
737
  /* let's print actual capabilities sets, could be useful in order to compare */
738
  if (!post_mortem.process.caps.err_run) {
739
    chunk_appendf(&trash, "  runtime capabilities:\n");
740
    chunk_appendf(&trash, "  \tCapEff: 0x%016llx\n",
741
            CAPS_TO_ULLONG(post_mortem.process.caps.run[0].effective,
742
               post_mortem.process.caps.run[1].effective));
743
    chunk_appendf(&trash, "  \tCapPrm: 0x%016llx\n",
744
            CAPS_TO_ULLONG(post_mortem.process.caps.run[0].permitted,
745
               post_mortem.process.caps.run[1].permitted));
746
    chunk_appendf(&trash, "  \tCapInh: 0x%016llx\n",
747
            CAPS_TO_ULLONG(post_mortem.process.caps.run[0].inheritable,
748
               post_mortem.process.caps.run[1].inheritable));
749
  } else
750
    chunk_appendf(&trash, "  capget() failed at runtime with: %s.\n",
751
            errname(post_mortem.process.caps.err_run, &err));
752
#endif
753
754
0
  chunk_appendf(&trash, "  %-22s  %-11s  %-11s \n", "identity:", "-boot-", "-runtime-");
755
0
  chunk_appendf(&trash, "  %-22s  %-11d  %-11d \n", "    uid:", post_mortem.process.boot_uid,
756
0
                                                          post_mortem.process.run_uid);
757
0
  chunk_appendf(&trash, "  %-22s  %-11d  %-11d \n", "    gid:", post_mortem.process.boot_gid,
758
0
                                                          post_mortem.process.run_gid);
759
0
  chunk_appendf(&trash, "  %-22s  %-11s  %-11s \n", "limits:", "-boot-", "-runtime-");
760
0
  chunk_appendf(&trash, "  %-22s  %-11s  %-11s \n", "    fd limit (soft):",
761
0
    LIM2A(normalize_rlim((ulong)post_mortem.process.boot_lim_fd.rlim_cur), "unlimited"),
762
0
    LIM2A(normalize_rlim((ulong)post_mortem.process.run_lim_fd.rlim_cur), "unlimited"));
763
0
  chunk_appendf(&trash, "  %-22s  %-11s  %-11s \n", "    fd limit (hard):",
764
0
    LIM2A(normalize_rlim((ulong)post_mortem.process.boot_lim_fd.rlim_max), "unlimited"),
765
0
    LIM2A(normalize_rlim((ulong)post_mortem.process.run_lim_fd.rlim_max), "unlimited"));
766
0
  chunk_appendf(&trash, "  %-22s  %-11s  %-11s \n", "    ram limit (soft):",
767
0
    LIM2A(normalize_rlim((ulong)post_mortem.process.boot_lim_ram.rlim_cur), "unlimited"),
768
0
    LIM2A(normalize_rlim((ulong)post_mortem.process.run_lim_ram.rlim_cur), "unlimited"));
769
0
  chunk_appendf(&trash, "  %-22s  %-11s  %-11s \n", "    ram limit (hard):",
770
0
    LIM2A(normalize_rlim((ulong)post_mortem.process.boot_lim_ram.rlim_max), "unlimited"),
771
0
    LIM2A(normalize_rlim((ulong)post_mortem.process.run_lim_ram.rlim_max), "unlimited"));
772
#ifdef USE_CPU_AFFINITY
773
  cpu_topo_dump_summary(ha_cpu_topo, &trash);
774
#endif
775
776
0
  ha_free(&err);
777
778
0
  return cli_msg(appctx, LOG_INFO, trash.area);
779
0
}
780
781
/* Dumps a state of all threads into the trash and on fd #2, then aborts. */
782
void ha_panic()
783
0
{
784
0
  struct buffer *buf;
785
0
  unsigned int thr;
786
787
0
  if (mark_tainted(TAINTED_PANIC) & TAINTED_PANIC) {
788
    /* a panic dump is already in progress, let's not disturb it,
789
     * we'll be called via signal DEBUGSIG. By returning we may be
790
     * able to leave a current signal handler (e.g. WDT) so that
791
     * this will ensure more reliable signal delivery.
792
     */
793
0
    return;
794
0
  }
795
796
0
  chunk_printf(&trash, "\nPANIC! Thread %u is about to kill the process (pid %d).\n", tid + 1, pid);
797
798
  /* dump a few of the post-mortem info */
799
0
  chunk_appendf(&trash, "\nHAProxy info:\n  version: %s\n  features: %s\n",
800
0
          haproxy_version, build_features);
801
802
0
  chunk_appendf(&trash, "\nOperating system info:\n");
803
0
  if (*post_mortem.platform.virt_techno)
804
0
    chunk_appendf(&trash, "  virtual machine: %s\n", post_mortem.platform.virt_techno);
805
0
  if (*post_mortem.platform.cont_techno)
806
0
    chunk_appendf(&trash, "  container: %s\n", post_mortem.platform.cont_techno);
807
0
  if (*post_mortem.platform.utsname.sysname || *post_mortem.platform.utsname.release ||
808
0
      *post_mortem.platform.utsname.version || *post_mortem.platform.utsname.machine)
809
0
    chunk_appendf(&trash, "  kernel: %s %s %s %s\n",
810
0
            post_mortem.platform.utsname.sysname, post_mortem.platform.utsname.release,
811
0
            post_mortem.platform.utsname.version, post_mortem.platform.utsname.machine);
812
0
  if (*post_mortem.platform.distro)
813
0
    chunk_appendf(&trash, "  userland: %s\n", post_mortem.platform.distro);
814
815
0
  chunk_appendf(&trash, "\n");
816
0
  DISGUISE(write(2, trash.area, trash.data));
817
818
0
  for (thr = 0; thr < global.nbthread; thr++) {
819
0
    if (thr == tid)
820
0
      buf = get_trash_chunk();
821
0
    else
822
0
      buf = (void *)0x2UL; // let the target thread allocate it
823
824
0
    buf = ha_thread_dump_fill(buf, thr);
825
0
    if (!buf)
826
0
      continue;
827
828
0
    DISGUISE(write(2, buf->area, buf->data));
829
    /* restore the thread's dump pointer for easier post-mortem analysis */
830
0
    ha_thread_dump_done(thr);
831
0
  }
832
833
#ifdef USE_LUA
834
  if (get_tainted() & TAINTED_LUA_STUCK_SHARED && global.nbthread > 1) {
835
    chunk_printf(&trash,
836
           "### Note: at least one thread was stuck in a Lua context loaded using the\n"
837
           "          'lua-load' directive, which is known for causing heavy contention\n"
838
           "          when used with threads. Please consider using 'lua-load-per-thread'\n"
839
           "          instead if your code is safe to run in parallel on multiple threads.\n");
840
    DISGUISE(write(2, trash.area, trash.data));
841
  }
842
  else if (get_tainted() & TAINTED_LUA_STUCK) {
843
    chunk_printf(&trash,
844
           "### Note: at least one thread was stuck in a Lua context in a way that suggests\n"
845
           "          heavy processing inside a dependency or a long loop that can't yield.\n"
846
           "          Please make sure any external code you may rely on is safe for use in\n"
847
           "          an event-driven engine.\n");
848
    DISGUISE(write(2, trash.area, trash.data));
849
  }
850
#endif
851
0
  if (get_tainted() & TAINTED_MEM_TRIMMING_STUCK) {
852
0
    chunk_printf(&trash,
853
0
           "### Note: one thread was found stuck under malloc_trim(), which can run for a\n"
854
0
           "          very long time on large memory systems. You way want to disable this\n"
855
0
           "          memory reclaiming feature by setting 'no-memory-trimming' in the\n"
856
0
           "          'global' section of your configuration to avoid this in the future.\n");
857
0
    DISGUISE(write(2, trash.area, trash.data));
858
0
  }
859
860
0
  chunk_printf(&trash,
861
0
               "\n"
862
0
               "Hint: when reporting this bug to developers, please check if a core file was\n"
863
0
               "      produced, open it with 'gdb', issue 't a a bt full', check that the\n"
864
0
               "      output does not contain sensitive data, then join it with the bug report.\n"
865
0
               "      For more info, please see https://github.com/haproxy/haproxy/issues/2374\n");
866
867
0
  DISGUISE(write(2, trash.area, trash.data));
868
869
0
  for (;;)
870
0
    abort();
871
0
}
872
873
/* Dumps a state of the current thread on fd #2 and returns. It takes a great
874
 * care about not using any global state variable so as to gracefully recover.
875
 * It is designed to be called exclusively from the watchdog signal handler,
876
 * and takes care of not touching thread_dump_buffer so as not to interfere
877
 * with any other parallel dump that could have been started.
878
 */
879
void ha_stuck_warning(void)
880
0
{
881
0
  char msg_buf[8192];
882
0
  struct buffer buf;
883
0
  ullong n, p;
884
885
0
  if (mark_tainted(TAINTED_WARN_BLOCKED_TRAFFIC) & TAINTED_PANIC) {
886
    /* a panic dump is already in progress, let's not disturb it,
887
     * we'll be called via signal DEBUGSIG. By returning we may be
888
     * able to leave a current signal handler (e.g. WDT) so that
889
     * this will ensure more reliable signal delivery.
890
     */
891
0
    return;
892
0
  }
893
894
0
  HA_ATOMIC_INC(&warn_blocked_issued);
895
896
0
  buf = b_make(msg_buf, sizeof(msg_buf), 0, 0);
897
898
0
  p = HA_ATOMIC_LOAD(&th_ctx->prev_cpu_time);
899
0
  n = now_cpu_time();
900
901
0
  chunk_appendf(&buf,
902
0
         "\nWARNING! thread %u has stopped processing traffic for %llu milliseconds\n"
903
0
         "    with %d streams currently blocked, prevented from making any progress.\n"
904
0
         "    While this may occasionally happen with inefficient configurations\n"
905
0
         "    involving excess of regular expressions, map_reg, or heavy Lua processing,\n"
906
0
         "    this must remain exceptional because the system's stability is now at risk.\n"
907
0
         "    Timers in logs may be reported incorrectly, spurious timeouts may happen,\n"
908
0
         "    some incoming connections may silently be dropped, health checks may\n"
909
0
         "    randomly fail, and accesses to the CLI may block the whole process. The\n"
910
0
         "    blocking delay before emitting this warning may be adjusted via the global\n"
911
0
         "    'warn-blocked-traffic-after' directive. Please check the trace below for\n"
912
0
         "    any clues about configuration elements that need to be corrected:\n\n",
913
0
         tid + 1, (n - p) / 1000000ULL,
914
0
         HA_ATOMIC_LOAD(&ha_thread_ctx[tid].stream_cnt));
915
916
0
  ha_thread_dump_one(&buf, 1);
917
918
#ifdef USE_LUA
919
  if (get_tainted() & TAINTED_LUA_STUCK_SHARED && global.nbthread > 1) {
920
    chunk_appendf(&buf,
921
           "### Note: at least one thread was stuck in a Lua context loaded using the\n"
922
           "          'lua-load' directive, which is known for causing heavy contention\n"
923
           "          when used with threads. Please consider using 'lua-load-per-thread'\n"
924
           "          instead if your code is safe to run in parallel on multiple threads.\n");
925
  }
926
  else if (get_tainted() & TAINTED_LUA_STUCK) {
927
    chunk_appendf(&buf,
928
           "### Note: at least one thread was stuck in a Lua context in a way that suggests\n"
929
           "          heavy processing inside a dependency or a long loop that can't yield.\n"
930
           "          Please make sure any external code you may rely on is safe for use in\n"
931
           "          an event-driven engine.\n");
932
  }
933
#endif
934
0
  if (get_tainted() & TAINTED_MEM_TRIMMING_STUCK) {
935
0
    chunk_appendf(&buf,
936
0
           "### Note: one thread was found stuck under malloc_trim(), which can run for a\n"
937
0
           "          very long time on large memory systems. You way want to disable this\n"
938
0
           "          memory reclaiming feature by setting 'no-memory-trimming' in the\n"
939
0
           "          'global' section of your configuration to avoid this in the future.\n");
940
0
  }
941
942
0
  chunk_appendf(&buf, " => Trying to gracefully recover now (pid %d).\n", pid);
943
944
  /* Note: it's important to dump the whole buffer at once to avoid
945
   * interleaved outputs from multiple threads dumping in parallel.
946
   */
947
0
  DISGUISE(write(2, buf.area, buf.data));
948
0
}
949
950
/* Complain with message <msg> on stderr. If <counter> is not NULL, it is
951
 * atomically incremented, and the message is only printed when the counter
952
 * was zero, so that the message is only printed once. <taint> is only checked
953
 * on bit 1, and will taint the process either for a bug (2) or warn (0).
954
 */
955
void complain(int *counter, const char *msg, int taint)
956
0
{
957
0
  if (counter && _HA_ATOMIC_FETCH_ADD(counter, 1))
958
0
    return;
959
0
  DISGUISE(write(2, msg, strlen(msg)));
960
0
  if (taint & 2)
961
0
    mark_tainted(TAINTED_BUG);
962
0
  else
963
0
    mark_tainted(TAINTED_WARN);
964
0
}
965
966
/* parse a "debug dev exit" command. It always returns 1, though it should never return. */
967
static int debug_parse_cli_exit(char **args, char *payload, struct appctx *appctx, void *private)
968
0
{
969
0
  int code = atoi(args[3]);
970
971
0
  if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
972
0
    return 1;
973
974
0
  _HA_ATOMIC_INC(&debug_commands_issued);
975
0
  exit(code);
976
0
  return 1;
977
0
}
978
979
/* parse a "debug dev bug" command. It always returns 1, though it should never return.
980
 * Note: we make sure not to make the function static so that it appears in the trace.
981
 */
982
int debug_parse_cli_bug(char **args, char *payload, struct appctx *appctx, void *private)
983
0
{
984
0
  if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
985
0
    return 1;
986
987
0
  _HA_ATOMIC_INC(&debug_commands_issued);
988
0
  BUG_ON(one > zero, "This was triggered on purpose from the CLI 'debug dev bug' command.");
989
0
  return 1;
990
0
}
991
992
/* parse a "debug dev warn" command. It always returns 1.
993
 * Note: we make sure not to make the function static so that it appears in the trace.
994
 */
995
int debug_parse_cli_warn(char **args, char *payload, struct appctx *appctx, void *private)
996
0
{
997
0
  if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
998
0
    return 1;
999
1000
0
  _HA_ATOMIC_INC(&debug_commands_issued);
1001
0
  WARN_ON(one > zero, "This was triggered on purpose from the CLI 'debug dev warn' command.");
1002
0
  return 1;
1003
0
}
1004
1005
/* parse a "debug dev check" command. It always returns 1.
1006
 * Note: we make sure not to make the function static so that it appears in the trace.
1007
 */
1008
int debug_parse_cli_check(char **args, char *payload, struct appctx *appctx, void *private)
1009
0
{
1010
0
  if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1011
0
    return 1;
1012
1013
0
  _HA_ATOMIC_INC(&debug_commands_issued);
1014
0
  CHECK_IF(one > zero, "This was triggered on purpose from the CLI 'debug dev check' command.");
1015
0
  return 1;
1016
0
}
1017
1018
/* parse a "debug dev close" command. It always returns 1. */
1019
static int debug_parse_cli_close(char **args, char *payload, struct appctx *appctx, void *private)
1020
0
{
1021
0
  int fd;
1022
1023
0
  if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1024
0
    return 1;
1025
1026
0
  if (!*args[3])
1027
0
    return cli_err(appctx, "Missing file descriptor number (optionally followed by 'hard').\n");
1028
1029
0
  fd = atoi(args[3]);
1030
0
  if (fd < 0 || fd >= global.maxsock)
1031
0
    return cli_err(appctx, "File descriptor out of range.\n");
1032
1033
0
  if (strcmp(args[4], "hard") == 0) {
1034
    /* hard silent close, even for unknown FDs */
1035
0
    close(fd);
1036
0
    goto done;
1037
0
  }
1038
0
  if (!fdtab[fd].owner)
1039
0
    return cli_msg(appctx, LOG_INFO, "File descriptor was already closed.\n");
1040
1041
0
  fd_delete(fd);
1042
0
 done:
1043
0
  _HA_ATOMIC_INC(&debug_commands_issued);
1044
0
  return 1;
1045
0
}
1046
1047
/* this is meant to cause a deadlock when more than one task is running it or when run twice */
1048
struct task *debug_run_cli_deadlock(struct task *task, void *ctx, unsigned int state)
1049
0
{
1050
0
  static HA_SPINLOCK_T lock __maybe_unused;
1051
1052
0
  HA_SPIN_LOCK(OTHER_LOCK, &lock);
1053
0
  return NULL;
1054
0
}
1055
1056
/* parse a "debug dev deadlock" command. It always returns 1. */
1057
static int debug_parse_cli_deadlock(char **args, char *payload, struct appctx *appctx, void *private)
1058
0
{
1059
0
  int tasks;
1060
1061
0
  if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1062
0
    return 1;
1063
1064
0
  _HA_ATOMIC_INC(&debug_commands_issued);
1065
0
  for (tasks = atoi(args[3]); tasks > 0; tasks--) {
1066
0
    struct task *t = task_new_on(tasks % global.nbthread);
1067
0
    if (!t)
1068
0
      continue;
1069
0
    t->process = debug_run_cli_deadlock;
1070
0
    t->context = NULL;
1071
0
    task_wakeup(t, TASK_WOKEN_INIT);
1072
0
  }
1073
1074
0
  return 1;
1075
0
}
1076
1077
/* parse a "debug dev delay" command. It always returns 1. */
1078
static int debug_parse_cli_delay(char **args, char *payload, struct appctx *appctx, void *private)
1079
0
{
1080
0
  int delay = atoi(args[3]);
1081
1082
0
  if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1083
0
    return 1;
1084
1085
0
  _HA_ATOMIC_INC(&debug_commands_issued);
1086
0
  usleep((long)delay * 1000);
1087
0
  return 1;
1088
0
}
1089
1090
/* parse a "debug dev log" command. It always returns 1. */
1091
static int debug_parse_cli_log(char **args, char *payload, struct appctx *appctx, void *private)
1092
0
{
1093
0
  int arg;
1094
1095
0
  if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1096
0
    return 1;
1097
1098
0
  _HA_ATOMIC_INC(&debug_commands_issued);
1099
0
  chunk_reset(&trash);
1100
0
  for (arg = 3; *args[arg]; arg++) {
1101
0
    if (arg > 3)
1102
0
      chunk_strcat(&trash, " ");
1103
0
    chunk_strcat(&trash, args[arg]);
1104
0
  }
1105
1106
0
  send_log(NULL, LOG_INFO, "%s\n", trash.area);
1107
0
  return 1;
1108
0
}
1109
1110
/* parse a "debug dev loop" command. It always returns 1. */
1111
int debug_parse_cli_loop(char **args, char *payload, struct appctx *appctx, void *private)
1112
0
{
1113
0
  struct timeval deadline, curr;
1114
0
  int loop = atoi(args[3]);
1115
0
  int isolate;
1116
0
  int warn;
1117
1118
0
  if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1119
0
    return 1;
1120
1121
0
  isolate = strcmp(args[4], "isolated") == 0;
1122
0
  warn    = strcmp(args[4], "warn") == 0;
1123
1124
0
  _HA_ATOMIC_INC(&debug_commands_issued);
1125
0
  gettimeofday(&curr, NULL);
1126
0
  tv_ms_add(&deadline, &curr, loop);
1127
1128
0
  if (isolate)
1129
0
    thread_isolate();
1130
1131
0
  while (tv_ms_cmp(&curr, &deadline) < 0) {
1132
0
    if (warn)
1133
0
      _HA_ATOMIC_AND(&th_ctx->flags, ~TH_FL_STUCK);
1134
0
    gettimeofday(&curr, NULL);
1135
0
  }
1136
1137
0
  if (isolate)
1138
0
    thread_release();
1139
1140
0
  return 1;
1141
0
}
1142
1143
/* parse a "debug dev panic" command. It always returns 1, though it should never return. */
1144
int debug_parse_cli_panic(char **args, char *payload, struct appctx *appctx, void *private)
1145
0
{
1146
0
  if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1147
0
    return 1;
1148
1149
0
  _HA_ATOMIC_INC(&debug_commands_issued);
1150
0
  ha_panic();
1151
0
  return 1;
1152
0
}
1153
1154
/* parse a "debug dev exec" command. It always returns 1. */
1155
#if defined(DEBUG_DEV)
1156
static int debug_parse_cli_exec(char **args, char *payload, struct appctx *appctx, void *private)
1157
{
1158
  int pipefd[2];
1159
  int arg;
1160
  int pid;
1161
1162
  if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1163
    return 1;
1164
1165
  _HA_ATOMIC_INC(&debug_commands_issued);
1166
  chunk_reset(&trash);
1167
  for (arg = 3; *args[arg]; arg++) {
1168
    if (arg > 3)
1169
      chunk_strcat(&trash, " ");
1170
    chunk_strcat(&trash, args[arg]);
1171
  }
1172
1173
  thread_isolate();
1174
  if (pipe(pipefd) < 0)
1175
    goto fail_pipe;
1176
1177
  if (fd_set_cloexec(pipefd[0]) == -1)
1178
    goto fail_fcntl;
1179
1180
  if (fd_set_cloexec(pipefd[1]) == -1)
1181
    goto fail_fcntl;
1182
1183
  pid = fork();
1184
1185
  if (pid < 0)
1186
    goto fail_fork;
1187
  else if (pid == 0) {
1188
    /* child */
1189
    char *cmd[4] = { "/bin/sh", "-c", 0, 0 };
1190
1191
    close(0);
1192
    dup2(pipefd[1], 1);
1193
    dup2(pipefd[1], 2);
1194
1195
    cmd[2] = trash.area;
1196
    execvp(cmd[0], cmd);
1197
    printf("execvp() failed\n");
1198
    exit(1);
1199
  }
1200
1201
  /* parent */
1202
  thread_release();
1203
  close(pipefd[1]);
1204
  chunk_reset(&trash);
1205
  while (1) {
1206
    size_t ret = read(pipefd[0], trash.area + trash.data, trash.size - 20 - trash.data);
1207
    if (ret <= 0)
1208
      break;
1209
    trash.data += ret;
1210
    if (trash.data + 20 == trash.size) {
1211
      chunk_strcat(&trash, "\n[[[TRUNCATED]]]\n");
1212
      break;
1213
    }
1214
  }
1215
  close(pipefd[0]);
1216
  waitpid(pid, NULL, WNOHANG);
1217
  trash.area[trash.data] = 0;
1218
  return cli_msg(appctx, LOG_INFO, trash.area);
1219
1220
 fail_fork:
1221
 fail_fcntl:
1222
  close(pipefd[0]);
1223
  close(pipefd[1]);
1224
 fail_pipe:
1225
  thread_release();
1226
  return cli_err(appctx, "Failed to execute command.\n");
1227
}
1228
1229
/* handles SIGRTMAX to inject random delays on the receiving thread in order
1230
 * to try to increase the likelihood to reproduce inter-thread races. The
1231
 * signal is periodically sent by a task initiated by "debug dev delay-inj".
1232
 */
1233
void debug_delay_inj_sighandler(int sig, siginfo_t *si, void *arg)
1234
{
1235
  volatile int i = statistical_prng_range(10000);
1236
1237
  while (i--)
1238
    __ha_cpu_relax();
1239
}
1240
#endif
1241
1242
/* parse a "debug dev hex" command. It always returns 1. */
1243
static int debug_parse_cli_hex(char **args, char *payload, struct appctx *appctx, void *private)
1244
0
{
1245
0
  unsigned long start, len;
1246
1247
0
  if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1248
0
    return 1;
1249
1250
0
  if (!*args[3])
1251
0
    return cli_err(appctx, "Missing memory address to dump from.\n");
1252
1253
0
  start = strtoul(args[3], NULL, 0);
1254
0
  if (!start)
1255
0
    return cli_err(appctx, "Will not dump from NULL address.\n");
1256
1257
0
  _HA_ATOMIC_INC(&debug_commands_issued);
1258
1259
  /* by default, dump ~128 till next block of 16 */
1260
0
  len = strtoul(args[4], NULL, 0);
1261
0
  if (!len)
1262
0
    len = ((start + 128) & -16) - start;
1263
1264
0
  chunk_reset(&trash);
1265
0
  dump_hex(&trash, "  ", (const void *)start, len, 1);
1266
0
  trash.area[trash.data] = 0;
1267
0
  return cli_msg(appctx, LOG_INFO, trash.area);
1268
0
}
1269
1270
/* parse a "debug dev sym <addr>" command. It always returns 1. */
1271
static int debug_parse_cli_sym(char **args, char *payload, struct appctx *appctx, void *private)
1272
0
{
1273
0
  unsigned long addr;
1274
1275
0
  if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1276
0
    return 1;
1277
1278
0
  if (!*args[3])
1279
0
    return cli_err(appctx, "Missing memory address to be resolved.\n");
1280
1281
0
  _HA_ATOMIC_INC(&debug_commands_issued);
1282
1283
0
  addr = strtoul(args[3], NULL, 0);
1284
0
  chunk_printf(&trash, "%#lx resolves to ", addr);
1285
0
  resolve_sym_name(&trash, NULL, (const void *)addr);
1286
0
  chunk_appendf(&trash, "\n");
1287
1288
0
  return cli_msg(appctx, LOG_INFO, trash.area);
1289
0
}
1290
1291
/* parse a "debug dev tkill" command. It always returns 1. */
1292
static int debug_parse_cli_tkill(char **args, char *payload, struct appctx *appctx, void *private)
1293
0
{
1294
0
  int thr = 0;
1295
0
  int sig = SIGABRT;
1296
1297
0
  if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1298
0
    return 1;
1299
1300
0
  if (*args[3])
1301
0
    thr = atoi(args[3]);
1302
1303
0
  if (thr < 0 || thr > global.nbthread)
1304
0
    return cli_err(appctx, "Thread number out of range (use 0 for current).\n");
1305
1306
0
  if (*args[4])
1307
0
    sig = atoi(args[4]);
1308
1309
0
  _HA_ATOMIC_INC(&debug_commands_issued);
1310
0
  if (thr)
1311
0
    ha_tkill(thr - 1, sig);
1312
0
  else
1313
0
    raise(sig);
1314
0
  return 1;
1315
0
}
1316
1317
/* hashes 'word' in "debug dev hash 'word' ". */
1318
static int debug_parse_cli_hash(char **args, char *payload, struct appctx *appctx, void *private)
1319
0
{
1320
0
  char *msg = NULL;
1321
1322
0
  cli_dynmsg(appctx, LOG_INFO, memprintf(&msg, "%s\n", HA_ANON_CLI(args[3])));
1323
0
  return 1;
1324
0
}
1325
1326
/* parse a "debug dev write" command. It always returns 1. */
1327
static int debug_parse_cli_write(char **args, char *payload, struct appctx *appctx, void *private)
1328
0
{
1329
0
  unsigned long len;
1330
1331
0
  if (!*args[3])
1332
0
    return cli_err(appctx, "Missing output size.\n");
1333
1334
0
  len = strtoul(args[3], NULL, 0);
1335
0
  if (len >= trash.size)
1336
0
    return cli_err(appctx, "Output too large, must be <tune.bufsize.\n");
1337
1338
0
  _HA_ATOMIC_INC(&debug_commands_issued);
1339
1340
0
  chunk_reset(&trash);
1341
0
  trash.data = len;
1342
0
  memset(trash.area, '.', trash.data);
1343
0
  trash.area[trash.data] = 0;
1344
0
  for (len = 64; len < trash.data; len += 64)
1345
0
    trash.area[len] = '\n';
1346
0
  return cli_msg(appctx, LOG_INFO, trash.area);
1347
0
}
1348
1349
/* parse a "debug dev stream" command */
1350
/*
1351
 *  debug dev stream [strm=<ptr>] [strm.f[{+-=}<flags>]] [txn.f[{+-=}<flags>]] \
1352
 *                   [req.f[{+-=}<flags>]] [res.f[{+-=}<flags>]]               \
1353
 *                   [sif.f[{+-=<flags>]] [sib.f[{+-=<flags>]]                 \
1354
 *                   [sif.s[=<state>]] [sib.s[=<state>]]
1355
 */
1356
static int debug_parse_cli_stream(char **args, char *payload, struct appctx *appctx, void *private)
1357
0
{
1358
0
  struct stream *s = appctx_strm(appctx);
1359
0
  int arg;
1360
0
  void *ptr;
1361
0
  int size;
1362
0
  const char *word, *end;
1363
0
  struct ist name;
1364
0
  char *msg = NULL;
1365
0
  char *endarg;
1366
0
  unsigned long long old, new;
1367
1368
0
  if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1369
0
    return 1;
1370
1371
0
  ptr = NULL; size = 0;
1372
1373
0
  if (!*args[3]) {
1374
0
    return cli_err(appctx,
1375
0
             "Usage: debug dev stream [ strm=<ptr> ] { <obj> <op> <value> | wake }*\n"
1376
0
             "     <obj>   = { strm.f | strm.x | scf.s | scb.s | txn.f | req.f | res.f }\n"
1377
0
             "     <op>    = {'' (show) | '=' (assign) | '^' (xor) | '+' (or) | '-' (andnot)}\n"
1378
0
             "     <value> = 'now' | 64-bit dec/hex integer (0x prefix supported)\n"
1379
0
             "     'wake' wakes the stream assigned to 'strm' (default: current)\n"
1380
0
             );
1381
0
  }
1382
1383
0
  _HA_ATOMIC_INC(&debug_commands_issued);
1384
0
  for (arg = 3; *args[arg]; arg++) {
1385
0
    old = 0;
1386
0
    end = word = args[arg];
1387
0
    while (*end && *end != '=' && *end != '^' && *end != '+' && *end != '-')
1388
0
      end++;
1389
0
    name = ist2(word, end - word);
1390
0
    if (isteq(name, ist("strm"))) {
1391
0
      ptr = (!s || !may_access(s)) ? NULL : &s; size = sizeof(s);
1392
0
    } else if (isteq(name, ist("strm.f"))) {
1393
0
      ptr = (!s || !may_access(s)) ? NULL : &s->flags; size = sizeof(s->flags);
1394
0
    } else if (isteq(name, ist("strm.x"))) {
1395
0
      ptr = (!s || !may_access(s)) ? NULL : &s->conn_exp; size = sizeof(s->conn_exp);
1396
0
    } else if (isteq(name, ist("txn.f"))) {
1397
0
      ptr = (!s || !may_access(s)) ? NULL : &s->txn->flags; size = sizeof(s->txn->flags);
1398
0
    } else if (isteq(name, ist("req.f"))) {
1399
0
      ptr = (!s || !may_access(s)) ? NULL : &s->req.flags; size = sizeof(s->req.flags);
1400
0
    } else if (isteq(name, ist("res.f"))) {
1401
0
      ptr = (!s || !may_access(s)) ? NULL : &s->res.flags; size = sizeof(s->res.flags);
1402
0
    } else if (isteq(name, ist("scf.s"))) {
1403
0
      ptr = (!s || !may_access(s)) ? NULL : &s->scf->state; size = sizeof(s->scf->state);
1404
0
    } else if (isteq(name, ist("scb.s"))) {
1405
0
      ptr = (!s || !may_access(s)) ? NULL : &s->scf->state; size = sizeof(s->scb->state);
1406
0
    } else if (isteq(name, ist("wake"))) {
1407
0
      if (s && may_access(s) && may_access((void *)s + sizeof(*s) - 1))
1408
0
        task_wakeup(s->task, TASK_WOKEN_TIMER|TASK_WOKEN_IO|TASK_WOKEN_MSG);
1409
0
      continue;
1410
0
    } else
1411
0
      return cli_dynerr(appctx, memprintf(&msg, "Unsupported field name: '%s'.\n", word));
1412
1413
    /* read previous value */
1414
0
    if ((s || ptr == &s) && ptr && may_access(ptr) && may_access(ptr + size - 1)) {
1415
0
      if (size == 8)
1416
0
        old = read_u64(ptr);
1417
0
      else if (size == 4)
1418
0
        old = read_u32(ptr);
1419
0
      else if (size == 2)
1420
0
        old = read_u16(ptr);
1421
0
      else
1422
0
        old = *(const uint8_t *)ptr;
1423
0
    } else {
1424
0
      memprintf(&msg,
1425
0
          "%sSkipping inaccessible pointer %p for field '%.*s'.\n",
1426
0
          msg ? msg : "", ptr, (int)(end - word), word);
1427
0
      continue;
1428
0
    }
1429
1430
    /* parse the new value . */
1431
0
    new = strtoll(end + 1, &endarg, 0);
1432
0
    if (end[1] && *endarg) {
1433
0
      if (strcmp(end + 1, "now") == 0)
1434
0
        new = now_ms;
1435
0
      else {
1436
0
        memprintf(&msg,
1437
0
            "%sIgnoring unparsable value '%s' for field '%.*s'.\n",
1438
0
            msg ? msg : "", end + 1, (int)(end - word), word);
1439
0
        continue;
1440
0
      }
1441
0
    }
1442
1443
0
    switch (*end) {
1444
0
    case '\0': /* show */
1445
0
      memprintf(&msg, "%s%.*s=%#llx ", msg ? msg : "", (int)(end - word), word, old);
1446
0
      new = old; // do not change the value
1447
0
      break;
1448
1449
0
    case '=': /* set */
1450
0
      break;
1451
1452
0
    case '^': /* XOR */
1453
0
      new = old ^ new;
1454
0
      break;
1455
1456
0
    case '+': /* OR */
1457
0
      new = old | new;
1458
0
      break;
1459
1460
0
    case '-': /* AND NOT */
1461
0
      new = old & ~new;
1462
0
      break;
1463
1464
0
    default:
1465
0
      break;
1466
0
    }
1467
1468
    /* write the new value */
1469
0
    if (new != old) {
1470
0
      if (size == 8)
1471
0
        write_u64(ptr, new);
1472
0
      else if (size == 4)
1473
0
        write_u32(ptr, new);
1474
0
      else if (size == 2)
1475
0
        write_u16(ptr, new);
1476
0
      else
1477
0
        *(uint8_t *)ptr = new;
1478
0
    }
1479
0
  }
1480
1481
0
  if (msg && *msg)
1482
0
    return cli_dynmsg(appctx, LOG_INFO, msg);
1483
0
  return 1;
1484
0
}
1485
1486
/* parse a "debug dev stream" command */
1487
/*
1488
 *  debug dev task <ptr> [ "wake" | "expire" | "kill" ]
1489
 *  Show/change status of a task/tasklet
1490
 */
1491
static int debug_parse_cli_task(char **args, char *payload, struct appctx *appctx, void *private)
1492
0
{
1493
0
  const struct ha_caller *caller;
1494
0
  struct task *t;
1495
0
  char *endarg;
1496
0
  char *msg;
1497
0
  void *ptr;
1498
0
  int ret = 1;
1499
0
  int task_ok;
1500
0
  int arg;
1501
1502
0
  if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1503
0
    return 1;
1504
1505
  /* parse the pointer value */
1506
0
  ptr = (void *)strtoul(args[3], &endarg, 0);
1507
0
  if (!*args[3] || *endarg)
1508
0
    goto usage;
1509
1510
0
  _HA_ATOMIC_INC(&debug_commands_issued);
1511
1512
  /* everything below must run under thread isolation till reaching label "leave" */
1513
0
  thread_isolate();
1514
1515
  /* struct tasklet is smaller than struct task and is sufficient to check
1516
   * the TASK_COMMON part.
1517
   */
1518
0
  if (!may_access(ptr) || !may_access(ptr + sizeof(struct tasklet) - 1) ||
1519
0
      ((const struct tasklet *)ptr)->tid  < -1 ||
1520
0
      ((const struct tasklet *)ptr)->tid  >= (int)MAX_THREADS) {
1521
0
    ret = cli_err(appctx, "The designated memory area doesn't look like a valid task/tasklet\n");
1522
0
    goto leave;
1523
0
  }
1524
1525
0
  t = ptr;
1526
0
  caller = t->caller;
1527
0
  msg = NULL;
1528
0
  task_ok = may_access(ptr + sizeof(*t) - 1);
1529
1530
0
  chunk_reset(&trash);
1531
0
  resolve_sym_name(&trash, NULL, (const void *)t->process);
1532
1533
  /* we need to be careful here because we may dump a freed task that's
1534
   * still in the pool cache, containing garbage in pointers.
1535
   */
1536
0
  if (!*args[4]) {
1537
0
    memprintf(&msg, "%s%p: %s state=%#x tid=%d process=%s ctx=%p calls=%d last=%s:%d intl=%d",
1538
0
        msg ? msg : "", t, (t->state & TASK_F_TASKLET) ? "tasklet" : "task",
1539
0
        t->state, t->tid, trash.area, t->context, t->calls,
1540
0
        caller && may_access(caller) && may_access(caller->func) && isalnum((uchar)*caller->func) ? caller->func : "0",
1541
0
        caller ? t->caller->line : 0,
1542
0
        (t->state & TASK_F_TASKLET) ? LIST_INLIST(&((const struct tasklet *)t)->list) : 0);
1543
1544
0
    if (task_ok && !(t->state & TASK_F_TASKLET))
1545
0
      memprintf(&msg, "%s inrq=%d inwq=%d exp=%d nice=%d",
1546
0
          msg ? msg : "", task_in_rq(t), task_in_wq(t), t->expire, t->nice);
1547
1548
0
    memprintf(&msg, "%s\n", msg ? msg : "");
1549
0
  }
1550
1551
0
  for (arg = 4; *args[arg]; arg++) {
1552
0
    if (strcmp(args[arg], "expire") == 0) {
1553
0
      if (t->state & TASK_F_TASKLET) {
1554
        /* do nothing for tasklets */
1555
0
      }
1556
0
      else if (task_ok) {
1557
        /* unlink task and wake with timer flag */
1558
0
        __task_unlink_wq(t);
1559
0
        t->expire = tick_add(now_ms, 0);
1560
0
        task_wakeup(t, TASK_WOKEN_TIMER);
1561
0
      }
1562
0
    } else if (strcmp(args[arg], "wake") == 0) {
1563
      /* wake with all flags but init / timer */
1564
0
      if (t->state & TASK_F_TASKLET)
1565
0
        tasklet_wakeup((struct tasklet *)t);
1566
0
      else if (task_ok)
1567
0
        task_wakeup(t, TASK_WOKEN_ANY & ~(TASK_WOKEN_INIT|TASK_WOKEN_TIMER));
1568
0
    } else if (strcmp(args[arg], "kill") == 0) {
1569
      /* Kill the task. This is not idempotent! */
1570
0
      if (!(t->state & TASK_KILLED)) {
1571
0
        if (t->state & TASK_F_TASKLET)
1572
0
          tasklet_kill((struct tasklet *)t);
1573
0
        else if (task_ok)
1574
0
          task_kill(t);
1575
0
      }
1576
0
    } else {
1577
0
      thread_release();
1578
0
      goto usage;
1579
0
    }
1580
0
  }
1581
1582
0
  if (msg && *msg)
1583
0
    ret = cli_dynmsg(appctx, LOG_INFO, msg);
1584
0
 leave:
1585
0
  thread_release();
1586
0
  return ret;
1587
0
 usage:
1588
0
  return cli_err(appctx,
1589
0
           "Usage: debug dev task <ptr> [ wake | expire | kill ]\n"
1590
0
           "  By default, dumps some info on task/tasklet <ptr>. 'wake' will wake it up\n"
1591
0
           "  with all conditions flags but init/exp. 'expire' will expire the entry, and\n"
1592
0
           "  'kill' will kill it (warning: may crash since later not idempotent!). All\n"
1593
0
           "  changes may crash the process if performed on a wrong object!\n"
1594
0
           );
1595
0
}
1596
1597
#if defined(DEBUG_DEV)
1598
static struct task *debug_delay_inj_task(struct task *t, void *ctx, unsigned int state)
1599
{
1600
  unsigned long *tctx = ctx; // [0] = interval, [1] = nbwakeups
1601
  unsigned long inter = tctx[0];
1602
  unsigned long count = tctx[1];
1603
  unsigned long rnd;
1604
1605
  if (inter)
1606
    t->expire = tick_add(now_ms, inter);
1607
  else
1608
    task_wakeup(t, TASK_WOKEN_MSG);
1609
1610
  /* wake a random thread */
1611
  while (count--) {
1612
    rnd = statistical_prng_range(global.nbthread);
1613
    ha_tkill(rnd, SIGRTMAX);
1614
  }
1615
  return t;
1616
}
1617
1618
/* parse a "debug dev delay-inj" command
1619
 * debug dev delay-inj <inter> <count>
1620
 */
1621
static int debug_parse_delay_inj(char **args, char *payload, struct appctx *appctx, void *private)
1622
{
1623
  unsigned long *tctx; // [0] = inter, [2] = count
1624
  struct task *task;
1625
1626
  if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1627
    return 1;
1628
1629
  if (!*args[4])
1630
    return cli_err(appctx,  "Usage: debug dev delay-inj <inter_ms> <count>*\n");
1631
1632
  _HA_ATOMIC_INC(&debug_commands_issued);
1633
1634
  tctx = calloc(2, sizeof(*tctx));
1635
  if (!tctx)
1636
    goto fail;
1637
1638
  tctx[0] = atoi(args[3]);
1639
  tctx[1] = atoi(args[4]);
1640
1641
  task = task_new_here/*anywhere*/();
1642
  if (!task)
1643
    goto fail;
1644
1645
  task->process = debug_delay_inj_task;
1646
  task->context = tctx;
1647
  task_wakeup(task, TASK_WOKEN_INIT);
1648
  return 1;
1649
1650
 fail:
1651
  free(tctx);
1652
  return cli_err(appctx, "Not enough memory");
1653
}
1654
#endif // DEBUG_DEV
1655
1656
static struct task *debug_task_handler(struct task *t, void *ctx, unsigned int state)
1657
0
{
1658
0
  unsigned long *tctx = ctx; // [0] = #tasks, [1] = inter, [2+] = { tl | (tsk+1) }
1659
0
  unsigned long inter = tctx[1];
1660
0
  unsigned long rnd;
1661
1662
0
  if (stopping)
1663
0
    return NULL;
1664
1665
0
  t->expire = tick_add(now_ms, inter);
1666
1667
  /* half of the calls will wake up another entry */
1668
0
  rnd = statistical_prng();
1669
0
  if (rnd & 1) {
1670
0
    rnd >>= 1;
1671
0
    rnd %= tctx[0];
1672
0
    rnd = tctx[rnd + 2];
1673
1674
0
    if (rnd & 1)
1675
0
      task_wakeup((struct task *)(rnd - 1), TASK_WOKEN_MSG);
1676
0
    else
1677
0
      tasklet_wakeup((struct tasklet *)rnd);
1678
0
  }
1679
0
  return t;
1680
0
}
1681
1682
static struct task *debug_tasklet_handler(struct task *t, void *ctx, unsigned int state)
1683
0
{
1684
0
  unsigned long *tctx = ctx; // [0] = #tasks, [1] = inter, [2+] = { tl | (tsk+1) }
1685
0
  unsigned long rnd;
1686
0
  int i;
1687
1688
0
  if (stopping)
1689
0
    return NULL;
1690
1691
  /* wake up two random entries */
1692
0
  for (i = 0; i < 2; i++) {
1693
0
    rnd = statistical_prng() % tctx[0];
1694
0
    rnd = tctx[rnd + 2];
1695
1696
0
    if (rnd & 1)
1697
0
      task_wakeup((struct task *)(rnd - 1), TASK_WOKEN_MSG);
1698
0
    else
1699
0
      tasklet_wakeup((struct tasklet *)rnd);
1700
0
  }
1701
0
  return t;
1702
0
}
1703
1704
/* parse a "debug dev sched" command
1705
 * debug dev sched {task|tasklet} [count=<count>] [mask=<mask>] [single=<single>] [inter=<inter>]
1706
 */
1707
static int debug_parse_cli_sched(char **args, char *payload, struct appctx *appctx, void *private)
1708
0
{
1709
0
  int arg;
1710
0
  void *ptr;
1711
0
  int size;
1712
0
  const char *word, *end;
1713
0
  struct ist name;
1714
0
  char *msg = NULL;
1715
0
  char *endarg;
1716
0
  unsigned long long new;
1717
0
  unsigned long count = 0;
1718
0
  unsigned long thrid = tid;
1719
0
  unsigned int inter = 0;
1720
0
  unsigned long i;
1721
0
  int mode = 0; // 0 = tasklet; 1 = task
1722
0
  unsigned long *tctx; // [0] = #tasks, [1] = inter, [2+] = { tl | (tsk+1) }
1723
1724
0
  if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1725
0
    return 1;
1726
1727
0
  ptr = NULL; size = 0;
1728
1729
0
  if (strcmp(args[3], "task") != 0 && strcmp(args[3], "tasklet") != 0) {
1730
0
    return cli_err(appctx,
1731
0
             "Usage: debug dev sched {task|tasklet} { <obj> = <value> }*\n"
1732
0
             "     <obj>   = {count | tid | inter }\n"
1733
0
             "     <value> = 64-bit dec/hex integer (0x prefix supported)\n"
1734
0
             );
1735
0
  }
1736
1737
0
  mode = strcmp(args[3], "task") == 0;
1738
1739
0
  _HA_ATOMIC_INC(&debug_commands_issued);
1740
0
  for (arg = 4; *args[arg]; arg++) {
1741
0
    end = word = args[arg];
1742
0
    while (*end && *end != '=' && *end != '^' && *end != '+' && *end != '-')
1743
0
      end++;
1744
0
    name = ist2(word, end - word);
1745
0
    if (isteq(name, ist("count"))) {
1746
0
      ptr = &count; size = sizeof(count);
1747
0
    } else if (isteq(name, ist("tid"))) {
1748
0
      ptr = &thrid; size = sizeof(thrid);
1749
0
    } else if (isteq(name, ist("inter"))) {
1750
0
      ptr = &inter; size = sizeof(inter);
1751
0
    } else
1752
0
      return cli_dynerr(appctx, memprintf(&msg, "Unsupported setting: '%s'.\n", word));
1753
1754
    /* parse the new value . */
1755
0
    new = strtoll(end + 1, &endarg, 0);
1756
0
    if (end[1] && *endarg) {
1757
0
      memprintf(&msg,
1758
0
                "%sIgnoring unparsable value '%s' for field '%.*s'.\n",
1759
0
                msg ? msg : "", end + 1, (int)(end - word), word);
1760
0
      continue;
1761
0
    }
1762
1763
    /* write the new value */
1764
0
    if (size == 8)
1765
0
      write_u64(ptr, new);
1766
0
    else if (size == 4)
1767
0
      write_u32(ptr, new);
1768
0
    else if (size == 2)
1769
0
      write_u16(ptr, new);
1770
0
    else
1771
0
      *(uint8_t *)ptr = new;
1772
0
  }
1773
1774
0
  tctx = calloc(count + 2, sizeof(*tctx));
1775
0
  if (!tctx)
1776
0
    goto fail;
1777
1778
0
  tctx[0] = (unsigned long)count;
1779
0
  tctx[1] = (unsigned long)inter;
1780
1781
0
  if ((int)thrid >= global.nbthread)
1782
0
    thrid = tid;
1783
1784
0
  for (i = 0; i < count; i++) {
1785
    /* now, if poly or mask was set, tmask corresponds to the
1786
     * valid thread mask to use, otherwise it remains zero.
1787
     */
1788
    //printf("%lu: mode=%d mask=%#lx\n", i, mode, tmask);
1789
0
    if (mode == 0) {
1790
0
      struct tasklet *tl = tasklet_new();
1791
1792
0
      if (!tl)
1793
0
        goto fail;
1794
1795
0
      tl->tid = thrid;
1796
0
      tl->process = debug_tasklet_handler;
1797
0
      tl->context = tctx;
1798
0
      tctx[i + 2] = (unsigned long)tl;
1799
0
    } else {
1800
0
      struct task *task = task_new_on(thrid);
1801
1802
0
      if (!task)
1803
0
        goto fail;
1804
1805
0
      task->process = debug_task_handler;
1806
0
      task->context = tctx;
1807
0
      tctx[i + 2] = (unsigned long)task + 1;
1808
0
    }
1809
0
  }
1810
1811
  /* start the tasks and tasklets */
1812
0
  for (i = 0; i < count; i++) {
1813
0
    unsigned long ctx = tctx[i + 2];
1814
1815
0
    if (ctx & 1)
1816
0
      task_wakeup((struct task *)(ctx - 1), TASK_WOKEN_INIT);
1817
0
    else
1818
0
      tasklet_wakeup((struct tasklet *)ctx);
1819
0
  }
1820
1821
0
  if (msg && *msg)
1822
0
    return cli_dynmsg(appctx, LOG_INFO, msg);
1823
0
  return 1;
1824
1825
0
 fail:
1826
  /* free partially allocated entries */
1827
0
  for (i = 0; tctx && i < count; i++) {
1828
0
    unsigned long ctx = tctx[i + 2];
1829
1830
0
    if (!ctx)
1831
0
      break;
1832
1833
0
    if (ctx & 1)
1834
0
      task_destroy((struct task *)(ctx - 1));
1835
0
    else
1836
0
      tasklet_free((struct tasklet *)ctx);
1837
0
  }
1838
1839
0
  free(tctx);
1840
0
  return cli_err(appctx, "Not enough memory");
1841
0
}
1842
1843
#if defined(DEBUG_DEV)
1844
/* All of this is for "trace dbg" */
1845
1846
static struct trace_source trace_dbg __read_mostly = {
1847
  .name = IST("dbg"),
1848
  .desc = "trace debugger",
1849
  .report_events = ~0,  // report everything by default
1850
};
1851
1852
#define TRACE_SOURCE &trace_dbg
1853
INITCALL1(STG_REGISTER, trace_register_source, TRACE_SOURCE);
1854
1855
/* This is the task handler used to send traces in loops. Note that the task's
1856
 * context contains the number of remaining calls to be done. The task sends 20
1857
 * messages per wakeup.
1858
 */
1859
static struct task *debug_trace_task(struct task *t, void *ctx, unsigned int state)
1860
{
1861
  ulong count;
1862
1863
  /* send 2 traces enter/leave +18 devel = 20 traces total */
1864
  TRACE_ENTER(1);
1865
  TRACE_DEVEL("msg01 has 20 bytes .", 1);
1866
  TRACE_DEVEL("msg02 has 20 bytes .", 1);
1867
  TRACE_DEVEL("msg03 has 20 bytes .", 1);
1868
  TRACE_DEVEL("msg04 has 70 bytes payload: 0123456789 0123456789 0123456789 012345678", 1);
1869
  TRACE_DEVEL("msg05 has 70 bytes payload: 0123456789 0123456789 0123456789 012345678", 1);
1870
  TRACE_DEVEL("msg06 has 70 bytes payload: 0123456789 0123456789 0123456789 012345678", 1);
1871
  TRACE_DEVEL("msg07 has 120 bytes payload: 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 012", 1);
1872
  TRACE_DEVEL("msg08 has 120 bytes payload: 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 012", 1);
1873
  TRACE_DEVEL("msg09 has 120 bytes payload: 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 012", 1);
1874
  TRACE_DEVEL("msg10 has 170 bytes payload: 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 012345678", 1);
1875
  TRACE_DEVEL("msg11 has 170 bytes payload: 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 012345678", 1);
1876
  TRACE_DEVEL("msg12 has 170 bytes payload: 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 012345678", 1);
1877
  TRACE_DEVEL("msg13 has 220 bytes payload: 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123", 1);
1878
  TRACE_DEVEL("msg14 has 220 bytes payload: 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123", 1);
1879
  TRACE_DEVEL("msg15 has 220 bytes payload: 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123", 1);
1880
  TRACE_DEVEL("msg16 has 270 bytes payload: 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789", 1);
1881
  TRACE_DEVEL("msg17 has 270 bytes payload: 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789", 1);
1882
  TRACE_DEVEL("msg18 has 270 bytes payload: 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789", 1);
1883
  TRACE_LEAVE(1);
1884
1885
  count = (ulong)t->context;
1886
  t->context = (void*)count - 1;
1887
1888
  if (count)
1889
    task_wakeup(t, TASK_WOKEN_MSG);
1890
  else {
1891
    task_destroy(t);
1892
    t = NULL;
1893
  }
1894
  return t;
1895
}
1896
1897
/* parse a "debug dev trace" command
1898
 * debug dev trace <nbthr>.
1899
 * It will create as many tasks (one per thread), starting from lowest threads.
1900
 * The traces will stop after 1M wakeups or 20M messages ~= 4GB of data.
1901
 */
1902
static int debug_parse_cli_trace(char **args, char *payload, struct appctx *appctx, void *private)
1903
{
1904
  unsigned long count = 1;
1905
  unsigned long i;
1906
  char *msg = NULL;
1907
  char *endarg;
1908
1909
  if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1910
    return 1;
1911
1912
  _HA_ATOMIC_INC(&debug_commands_issued);
1913
1914
  if (!args[3][0]) {
1915
    memprintf(&msg, "Need a thread count. Note that 20M msg will be sent per thread.\n");
1916
    goto fail;
1917
  }
1918
1919
  /* parse the new value . */
1920
  count = strtoll(args[3], &endarg, 0);
1921
  if (args[3][1] && *endarg) {
1922
    memprintf(&msg, "Ignoring unparsable thread number '%s'.\n", args[3]);
1923
    goto fail;
1924
  }
1925
1926
  if (count >= global.nbthread)
1927
    count = global.nbthread;
1928
1929
  for (i = 0; i < count; i++) {
1930
    struct task *task = task_new_on(i);
1931
1932
    if (!task)
1933
      goto fail;
1934
1935
    task->process = debug_trace_task;
1936
    task->context = (void*)(ulong)1000000; // 1M wakeups = 20M messages
1937
    task_wakeup(task, TASK_WOKEN_INIT);
1938
  }
1939
1940
  if (msg && *msg)
1941
    return cli_dynmsg(appctx, LOG_INFO, msg);
1942
  return 1;
1943
1944
 fail:
1945
  return cli_dynmsg(appctx, LOG_ERR, msg);
1946
}
1947
#endif /* DEBUG_DEV */
1948
1949
/* CLI state for "debug dev fd" */
1950
struct dev_fd_ctx {
1951
  int start_fd;
1952
};
1953
1954
/* CLI parser for the "debug dev fd" command. The current FD to restart from is
1955
 * stored in a struct dev_fd_ctx pointed to by svcctx.
1956
 */
1957
static int debug_parse_cli_fd(char **args, char *payload, struct appctx *appctx, void *private)
1958
0
{
1959
0
  struct dev_fd_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
1960
1961
0
  if (!cli_has_level(appctx, ACCESS_LVL_OPER))
1962
0
    return 1;
1963
1964
  /* start at fd #0 */
1965
0
  ctx->start_fd = 0;
1966
0
  return 0;
1967
0
}
1968
1969
/* CLI I/O handler for the "debug dev fd" command. Dumps all FDs that are
1970
 * accessible from the process but not known from fdtab. The FD number to
1971
 * restart from is stored in a struct dev_fd_ctx pointed to by svcctx.
1972
 */
1973
static int debug_iohandler_fd(struct appctx *appctx)
1974
0
{
1975
0
  struct dev_fd_ctx *ctx = appctx->svcctx;
1976
0
  struct sockaddr_storage sa;
1977
0
  struct stat statbuf;
1978
0
  socklen_t salen, vlen;
1979
0
  int ret1, ret2, port;
1980
0
  char *addrstr;
1981
0
  int ret = 1;
1982
0
  int i, fd;
1983
1984
0
  chunk_reset(&trash);
1985
1986
0
  thread_isolate();
1987
1988
  /* we have two inner loops here, one for the proxy, the other one for
1989
   * the buffer.
1990
   */
1991
0
  for (fd = ctx->start_fd; fd < global.maxsock; fd++) {
1992
    /* check for FD's existence */
1993
0
    ret1 = fcntl(fd, F_GETFD, 0);
1994
0
    if (ret1 == -1)
1995
0
      continue; // not known to the process
1996
0
    if (fdtab[fd].owner)
1997
0
      continue; // well-known
1998
1999
    /* OK we're seeing an orphan let's try to retrieve as much
2000
     * information as possible about it.
2001
     */
2002
0
    chunk_printf(&trash, "%5d", fd);
2003
2004
0
    if (fstat(fd, &statbuf) != -1) {
2005
0
      chunk_appendf(&trash, " type=%s mod=%04o dev=%#llx siz=%#llx uid=%lld gid=%lld fs=%#llx ino=%#llx",
2006
0
              isatty(fd)                ? "tty.":
2007
0
              S_ISREG(statbuf.st_mode)  ? "file":
2008
0
              S_ISDIR(statbuf.st_mode)  ? "dir.":
2009
0
              S_ISCHR(statbuf.st_mode)  ? "chr.":
2010
0
              S_ISBLK(statbuf.st_mode)  ? "blk.":
2011
0
              S_ISFIFO(statbuf.st_mode) ? "pipe":
2012
0
              S_ISLNK(statbuf.st_mode)  ? "link":
2013
0
              S_ISSOCK(statbuf.st_mode) ? "sock":
2014
#ifdef USE_EPOLL
2015
              /* trick: epoll_ctl() will return -ENOENT when trying
2016
               * to remove from a valid epoll FD an FD that was not
2017
               * registered against it. But we don't want to risk
2018
               * disabling a random FD. Instead we'll create a new
2019
               * one by duplicating 0 (it should be valid since
2020
               * pointing to a terminal or /dev/null), and try to
2021
               * remove it.
2022
               */
2023
              ({
2024
                int fd2 = dup(0);
2025
                int ret = fd2;
2026
                if (ret >= 0) {
2027
                  ret = epoll_ctl(fd, EPOLL_CTL_DEL, fd2, NULL);
2028
                  if (ret == -1 && errno == ENOENT)
2029
                    ret = 0; // that's a real epoll
2030
                  else
2031
                    ret = -1; // it's something else
2032
                  close(fd2);
2033
                }
2034
                ret;
2035
              }) == 0 ? "epol" :
2036
#endif
2037
0
              "????",
2038
0
              (uint)statbuf.st_mode & 07777,
2039
2040
0
              (ullong)statbuf.st_rdev,
2041
0
              (ullong)statbuf.st_size,
2042
0
              (ullong)statbuf.st_uid,
2043
0
              (ullong)statbuf.st_gid,
2044
2045
0
              (ullong)statbuf.st_dev,
2046
0
              (ullong)statbuf.st_ino);
2047
0
    }
2048
2049
0
    chunk_appendf(&trash, " getfd=%s+%#x",
2050
0
           (ret1 & FD_CLOEXEC) ? "cloex" : "",
2051
0
           ret1 &~ FD_CLOEXEC);
2052
2053
    /* FD options */
2054
0
    ret2 = fcntl(fd, F_GETFL, 0);
2055
0
    if (ret2) {
2056
0
      chunk_appendf(&trash, " getfl=%s",
2057
0
              (ret1 & 3) >= 2 ? "O_RDWR" :
2058
0
              (ret1 & 1) ? "O_WRONLY" : "O_RDONLY");
2059
2060
0
      for (i = 2; i < 32; i++) {
2061
0
        if (!(ret2 & (1UL << i)))
2062
0
          continue;
2063
0
        switch (1UL << i) {
2064
0
        case O_CREAT:   chunk_appendf(&trash, ",O_CREAT");   break;
2065
0
        case O_EXCL:    chunk_appendf(&trash, ",O_EXCL");    break;
2066
0
        case O_NOCTTY:  chunk_appendf(&trash, ",O_NOCTTY");  break;
2067
0
        case O_TRUNC:   chunk_appendf(&trash, ",O_TRUNC");   break;
2068
0
        case O_APPEND:  chunk_appendf(&trash, ",O_APPEND");  break;
2069
0
#ifdef O_ASYNC
2070
0
        case O_ASYNC:   chunk_appendf(&trash, ",O_ASYNC");   break;
2071
0
#endif
2072
#ifdef O_DIRECT
2073
        case O_DIRECT:  chunk_appendf(&trash, ",O_DIRECT");  break;
2074
#endif
2075
#ifdef O_NOATIME
2076
        case O_NOATIME: chunk_appendf(&trash, ",O_NOATIME"); break;
2077
#endif
2078
0
        }
2079
0
      }
2080
0
    }
2081
2082
0
    vlen = sizeof(ret2);
2083
0
    ret1 = getsockopt(fd, SOL_SOCKET, SO_TYPE, &ret2, &vlen);
2084
0
    if (ret1 != -1)
2085
0
      chunk_appendf(&trash, " so_type=%d", ret2);
2086
2087
0
    vlen = sizeof(ret2);
2088
0
    ret1 = getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &ret2, &vlen);
2089
0
    if (ret1 != -1)
2090
0
      chunk_appendf(&trash, " so_accept=%d", ret2);
2091
2092
0
    vlen = sizeof(ret2);
2093
0
    ret1 = getsockopt(fd, SOL_SOCKET, SO_ERROR, &ret2, &vlen);
2094
0
    if (ret1 != -1)
2095
0
      chunk_appendf(&trash, " so_error=%d", ret2);
2096
2097
0
    salen = sizeof(sa);
2098
0
    if (getsockname(fd, (struct sockaddr *)&sa, &salen) != -1) {
2099
0
      int i;
2100
2101
0
      if (sa.ss_family == AF_INET)
2102
0
        port = ntohs(((const struct sockaddr_in *)&sa)->sin_port);
2103
0
      else if (sa.ss_family == AF_INET6)
2104
0
        port = ntohs(((const struct sockaddr_in6 *)&sa)->sin6_port);
2105
0
      else
2106
0
        port = 0;
2107
0
      addrstr = sa2str(&sa, port, 0);
2108
      /* cleanup the output */
2109
0
      for  (i = 0; i < strlen(addrstr); i++) {
2110
0
        if (iscntrl((unsigned char)addrstr[i]) || !isprint((unsigned char)addrstr[i]))
2111
0
          addrstr[i] = '.';
2112
0
      }
2113
2114
0
      chunk_appendf(&trash, " laddr=%s", addrstr);
2115
0
      free(addrstr);
2116
0
    }
2117
2118
0
    salen = sizeof(sa);
2119
0
    if (getpeername(fd, (struct sockaddr *)&sa, &salen) != -1) {
2120
0
      if (sa.ss_family == AF_INET)
2121
0
        port = ntohs(((const struct sockaddr_in *)&sa)->sin_port);
2122
0
      else if (sa.ss_family == AF_INET6)
2123
0
        port = ntohs(((const struct sockaddr_in6 *)&sa)->sin6_port);
2124
0
      else
2125
0
        port = 0;
2126
0
      addrstr = sa2str(&sa, port, 0);
2127
      /* cleanup the output */
2128
0
      for  (i = 0; i < strlen(addrstr); i++) {
2129
0
        if ((iscntrl((unsigned char)addrstr[i])) || !isprint((unsigned char)addrstr[i]))
2130
0
          addrstr[i] = '.';
2131
0
      }
2132
0
      chunk_appendf(&trash, " raddr=%s", addrstr);
2133
0
      free(addrstr);
2134
0
    }
2135
2136
0
    chunk_appendf(&trash, "\n");
2137
2138
0
    if (applet_putchk(appctx, &trash) == -1) {
2139
0
      ctx->start_fd = fd;
2140
0
      ret = 0;
2141
0
      break;
2142
0
    }
2143
0
  }
2144
2145
0
  thread_release();
2146
0
  return ret;
2147
0
}
2148
2149
#if defined(DEBUG_MEM_STATS)
2150
2151
/* CLI state for "debug dev memstats" */
2152
struct dev_mem_ctx {
2153
  struct mem_stats *start, *stop; /* begin/end of dump */
2154
  char *match;                    /* non-null if a name prefix is specified */
2155
  int show_all;                   /* show all entries if non-null */
2156
  int width;                      /* 1st column width */
2157
  long tot_size;                  /* sum of alloc-free */
2158
  ulong tot_calls;                /* sum of calls */
2159
};
2160
2161
/* CLI parser for the "debug dev memstats" command. Sets a dev_mem_ctx shown above. */
2162
static int debug_parse_cli_memstats(char **args, char *payload, struct appctx *appctx, void *private)
2163
{
2164
  struct dev_mem_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
2165
  int arg;
2166
2167
  extern __attribute__((__weak__)) struct mem_stats __start_mem_stats;
2168
  extern __attribute__((__weak__)) struct mem_stats __stop_mem_stats;
2169
2170
  if (!cli_has_level(appctx, ACCESS_LVL_OPER))
2171
    return 1;
2172
2173
  for (arg = 3; *args[arg]; arg++) {
2174
    if (strcmp(args[arg], "reset") == 0) {
2175
      struct mem_stats *ptr;
2176
2177
      if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2178
        return 1;
2179
2180
      for (ptr = &__start_mem_stats; ptr < &__stop_mem_stats; ptr++) {
2181
        _HA_ATOMIC_STORE(&ptr->calls, 0);
2182
        _HA_ATOMIC_STORE(&ptr->size, 0);
2183
      }
2184
      return 1;
2185
    }
2186
    else if (strcmp(args[arg], "all") == 0) {
2187
      ctx->show_all = 1;
2188
      continue;
2189
    }
2190
    else if (strcmp(args[arg], "match") == 0 && *args[arg + 1]) {
2191
      ha_free(&ctx->match);
2192
      ctx->match = strdup(args[arg + 1]);
2193
      if (!ctx->match)
2194
        return cli_err(appctx, "Out of memory.\n");
2195
      arg++;
2196
      continue;
2197
    }
2198
    else
2199
      return cli_err(appctx, "Expects either 'reset', 'all', or 'match <pfx>'.\n");
2200
  }
2201
2202
  /* otherwise proceed with the dump from p0 to p1 */
2203
  ctx->start = &__start_mem_stats;
2204
  ctx->stop  = &__stop_mem_stats;
2205
  ctx->width = 0;
2206
  return 0;
2207
}
2208
2209
/* CLI I/O handler for the "debug dev memstats" command using a dev_mem_ctx
2210
 * found in appctx->svcctx. Dumps all mem_stats structs referenced by pointers
2211
 * located between ->start and ->stop. Dumps all entries if ->show_all != 0,
2212
 * otherwise only non-zero calls.
2213
 */
2214
static int debug_iohandler_memstats(struct appctx *appctx)
2215
{
2216
  struct dev_mem_ctx *ctx = appctx->svcctx;
2217
  struct mem_stats *ptr;
2218
  const char *pfx = ctx->match;
2219
  int ret = 1;
2220
2221
  if (!ctx->width) {
2222
    /* we don't know the first column's width, let's compute it
2223
     * now based on a first pass on printable entries and their
2224
     * expected width (approximated).
2225
     */
2226
    for (ptr = ctx->start; ptr != ctx->stop; ptr++) {
2227
      const char *p, *name;
2228
      int w = 0;
2229
      char tmp;
2230
2231
      if (!ptr->size && !ptr->calls && !ctx->show_all)
2232
        continue;
2233
2234
      for (p = name = ptr->caller.file; *p; p++) {
2235
        if (*p == '/')
2236
          name = p + 1;
2237
      }
2238
2239
      if (ctx->show_all)
2240
        w = snprintf(&tmp, 0, "%s(%s:%d) ", ptr->caller.func, name, ptr->caller.line);
2241
      else
2242
        w = snprintf(&tmp, 0, "%s:%d ", name, ptr->caller.line);
2243
2244
      if (w > ctx->width)
2245
        ctx->width = w;
2246
    }
2247
  }
2248
2249
  /* we have two inner loops here, one for the proxy, the other one for
2250
   * the buffer.
2251
   */
2252
  for (ptr = ctx->start; ptr != ctx->stop; ptr++) {
2253
    const char *type;
2254
    const char *name;
2255
    const char *p;
2256
    const char *info = NULL;
2257
    const char *func = NULL;
2258
    int direction = 0; // neither alloc nor free (e.g. realloc)
2259
2260
    if (!ptr->size && !ptr->calls && !ctx->show_all)
2261
      continue;
2262
2263
    /* basename only */
2264
    for (p = name = ptr->caller.file; *p; p++) {
2265
      if (*p == '/')
2266
        name = p + 1;
2267
    }
2268
2269
    func = ptr->caller.func;
2270
2271
    switch (ptr->caller.what) {
2272
    case MEM_STATS_TYPE_CALLOC:  type = "CALLOC";  direction =  1; if (ptr->extra) info = (const char *)ptr->extra; break;
2273
    case MEM_STATS_TYPE_FREE:    type = "FREE";    direction = -1; break;
2274
    case MEM_STATS_TYPE_MALLOC:  type = "MALLOC";  direction =  1; if (ptr->extra) info = (const char *)ptr->extra; break;
2275
    case MEM_STATS_TYPE_REALLOC: type = "REALLOC"; break;
2276
    case MEM_STATS_TYPE_STRDUP:  type = "STRDUP";  direction =  1; break;
2277
    case MEM_STATS_TYPE_P_ALLOC: type = "P_ALLOC"; direction =  1; if (ptr->extra) info = ((const struct pool_head *)ptr->extra)->name; break;
2278
    case MEM_STATS_TYPE_P_FREE:  type = "P_FREE";  direction = -1; if (ptr->extra) info = ((const struct pool_head *)ptr->extra)->name; break;
2279
    default:                     type = "UNSET";   break;
2280
    }
2281
2282
    //chunk_printf(&trash,
2283
    //       "%20s:%-5d %7s size: %12lu calls: %9lu size/call: %6lu\n",
2284
    //       name, ptr->line, type,
2285
    //       (unsigned long)ptr->size, (unsigned long)ptr->calls,
2286
    //       (unsigned long)(ptr->calls ? (ptr->size / ptr->calls) : 0));
2287
2288
    /* only match requested prefixes */
2289
    if (pfx && (!info || strncmp(info, pfx, strlen(pfx)) != 0))
2290
      continue;
2291
2292
    chunk_reset(&trash);
2293
    if (ctx->show_all)
2294
      chunk_appendf(&trash, "%s(", func);
2295
2296
    chunk_appendf(&trash, "%s:%d", name, ptr->caller.line);
2297
2298
    if (ctx->show_all)
2299
      chunk_appendf(&trash, ")");
2300
2301
    while (trash.data < ctx->width)
2302
      trash.area[trash.data++] = ' ';
2303
2304
    chunk_appendf(&trash, "%7s  size: %12lu  calls: %9lu  size/call: %6lu %s\n",
2305
           type,
2306
           (unsigned long)ptr->size, (unsigned long)ptr->calls,
2307
                 (unsigned long)(ptr->calls ? (ptr->size / ptr->calls) : 0),
2308
           info ? info : "");
2309
2310
    if (applet_putchk(appctx, &trash) == -1) {
2311
      ctx->start = ptr;
2312
      ret = 0;
2313
      goto end;
2314
    }
2315
    if (direction > 0) {
2316
      ctx->tot_size  += (ulong)ptr->size;
2317
      ctx->tot_calls += (ulong)ptr->calls;
2318
    }
2319
    else if (direction < 0) {
2320
      ctx->tot_size  -= (ulong)ptr->size;
2321
      ctx->tot_calls += (ulong)ptr->calls;
2322
    }
2323
  }
2324
2325
  /* now dump a summary */
2326
  chunk_reset(&trash);
2327
  chunk_appendf(&trash, "Total");
2328
  while (trash.data < ctx->width)
2329
    trash.area[trash.data++] = ' ';
2330
2331
  chunk_appendf(&trash, "%7s  size: %12ld  calls: %9lu  size/call: %6ld %s\n",
2332
          "BALANCE",
2333
          ctx->tot_size, ctx->tot_calls,
2334
          (long)(ctx->tot_calls ? (ctx->tot_size / ctx->tot_calls) : 0),
2335
          "(excl. realloc)");
2336
2337
  if (applet_putchk(appctx, &trash) == -1) {
2338
    ctx->start = ptr;
2339
    ret = 0;
2340
    goto end;
2341
  }
2342
 end:
2343
  return ret;
2344
}
2345
2346
/* release the "show pools" context */
2347
static void debug_release_memstats(struct appctx *appctx)
2348
{
2349
  struct dev_mem_ctx *ctx = appctx->svcctx;
2350
2351
  ha_free(&ctx->match);
2352
}
2353
#endif
2354
2355
#if !defined(USE_OBSOLETE_LINKER)
2356
2357
/* CLI state for "debug counters" */
2358
struct deb_cnt_ctx {
2359
  struct debug_count *start, *stop; /* begin/end of dump */
2360
  int types;                        /* OR mask of 1<<type */
2361
  int show_all;                     /* show all entries if non-null */
2362
};
2363
2364
/* CLI parser for the "debug counters" command. Sets a deb_cnt_ctx shown above. */
2365
static int debug_parse_cli_counters(char **args, char *payload, struct appctx *appctx, void *private)
2366
0
{
2367
0
  struct deb_cnt_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
2368
0
  int action;
2369
0
  int arg;
2370
2371
0
  if (!cli_has_level(appctx, ACCESS_LVL_OPER))
2372
0
    return 1;
2373
2374
0
  action = 0; // 0=show, 1=reset
2375
0
  for (arg = 2; *args[arg]; arg++) {
2376
0
    if (strcmp(args[arg], "reset") == 0) {
2377
0
      action = 1;
2378
0
      continue;
2379
0
    }
2380
0
    else if (strcmp(args[arg], "off") == 0) {
2381
0
      action = 2;
2382
0
      continue;
2383
0
    }
2384
0
    else if (strcmp(args[arg], "on") == 0) {
2385
0
      action = 3;
2386
0
      continue;
2387
0
    }
2388
0
    else if (strcmp(args[arg], "all") == 0) {
2389
0
      ctx->show_all = 1;
2390
0
      continue;
2391
0
    }
2392
0
    else if (strcmp(args[arg], "show") == 0) {
2393
0
      action = 0;
2394
0
      continue;
2395
0
    }
2396
0
    else if (strcmp(args[arg], "bug") == 0) {
2397
0
      ctx->types |= 1 << DBG_BUG;
2398
0
      continue;
2399
0
    }
2400
0
    else if (strcmp(args[arg], "chk") == 0) {
2401
0
      ctx->types |= 1 << DBG_BUG_ONCE;
2402
0
      continue;
2403
0
    }
2404
0
    else if (strcmp(args[arg], "cnt") == 0) {
2405
0
      ctx->types |= 1 << DBG_COUNT_IF;
2406
0
      continue;
2407
0
    }
2408
0
    else if (strcmp(args[arg], "glt") == 0) {
2409
0
      ctx->types |= 1 << DBG_GLITCH;
2410
0
      continue;
2411
0
    }
2412
0
    else
2413
0
      return cli_err(appctx, "Expects an optional action ('reset','show','on','off'), optional types ('bug','chk','cnt','glt') and optionally 'all' to even dump null counters.\n");
2414
0
  }
2415
2416
0
#if (DEBUG_STRICT > 0) || (DEBUG_COUNTERS > 0)
2417
0
  ctx->start = &__start_dbg_cnt;
2418
0
  ctx->stop  = &__stop_dbg_cnt;
2419
0
#endif
2420
0
  if (action == 1) { // reset
2421
0
    struct debug_count *ptr;
2422
2423
0
    if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2424
0
      return 1;
2425
2426
0
    for (ptr = ctx->start; ptr < ctx->stop; ptr++) {
2427
0
      if (ctx->types && !(ctx->types & (1 << ptr->type)))
2428
0
        continue;
2429
0
      _HA_ATOMIC_STORE(&ptr->count, 0);
2430
0
    }
2431
0
    return 1;
2432
0
  }
2433
0
  else if (action == 2 || action == 3) { // off/on
2434
0
    if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2435
0
      return 1;
2436
0
    HA_ATOMIC_STORE(&debug_enable_counters, action == 3);
2437
0
    return 0;
2438
0
  }
2439
2440
  /* OK it's a show, let's dump relevant counters */
2441
0
  return 0;
2442
0
}
2443
2444
/* CLI I/O handler for the "debug counters" command using a deb_cnt_ctx
2445
 * found in appctx->svcctx. Dumps all mem_stats structs referenced by pointers
2446
 * located between ->start and ->stop. Dumps all entries if ->show_all != 0,
2447
 * otherwise only non-zero calls.
2448
 */
2449
static int debug_iohandler_counters(struct appctx *appctx)
2450
0
{
2451
0
  const char *bug_type[DBG_COUNTER_TYPES] = {
2452
0
    [DBG_BUG]      = "BUG",
2453
0
    [DBG_BUG_ONCE] = "CHK",
2454
0
    [DBG_COUNT_IF] = "CNT",
2455
0
    [DBG_GLITCH]   = "GLT",
2456
0
  };
2457
0
  struct deb_cnt_ctx *ctx = appctx->svcctx;
2458
0
  struct debug_count *ptr;
2459
0
  int ret = 1;
2460
2461
  /* we have two inner loops here, one for the proxy, the other one for
2462
   * the buffer.
2463
   */
2464
0
  chunk_printf(&trash, "Count     Type Location function(): \"condition\" [comment]\n");
2465
0
  for (ptr = ctx->start; ptr != ctx->stop; ptr++) {
2466
0
    const char *p, *name;
2467
2468
0
    if (ctx->types && !(ctx->types & (1 << ptr->type)))
2469
0
      continue;
2470
2471
0
    if (!ptr->count && !ctx->show_all)
2472
0
      continue;
2473
2474
0
    for (p = name = ptr->file; *p; p++) {
2475
0
      if (*p == '/')
2476
0
        name = p + 1;
2477
0
    }
2478
2479
0
    if (ptr->type < DBG_COUNTER_TYPES)
2480
0
      chunk_appendf(&trash, "%-10u %3s %s:%d %s()%s%s%s\n",
2481
0
              ptr->count, bug_type[ptr->type],
2482
0
              name, ptr->line, ptr->func,
2483
0
              *ptr->desc ? ": " : "", ptr->desc,
2484
0
              (ptr->type == DBG_COUNT_IF && !debug_enable_counters) ? " (stopped)" : "");
2485
2486
0
    if (applet_putchk(appctx, &trash) == -1) {
2487
0
      ctx->start = ptr;
2488
0
      ret = 0;
2489
0
      goto end;
2490
0
    }
2491
0
  }
2492
2493
  /* we could even dump a summary here if needed, returning ret=0 */
2494
0
 end:
2495
0
  return ret;
2496
0
}
2497
#endif /* USE_OBSOLETE_LINKER */
2498
2499
#ifdef USE_THREAD_DUMP
2500
2501
/* handles DEBUGSIG to dump the state of the thread it's working on. This is
2502
 * appended at the end of thread_dump_buffer which must be protected against
2503
 * reentrance from different threads (a thread-local buffer works fine). If
2504
 * the buffer pointer is equal to 0x2, then it's a panic. The thread allocates
2505
 * the buffer from its own trash chunks so that the contents remain visible in
2506
 * the core, and it never returns.
2507
 */
2508
void debug_handler(int sig, siginfo_t *si, void *arg)
2509
{
2510
  struct buffer *buf = HA_ATOMIC_LOAD(&th_ctx->thread_dump_buffer);
2511
  int no_return = 0;
2512
2513
  /* first, let's check it's really for us and that we didn't just get
2514
   * a spurious DEBUGSIG.
2515
   */
2516
  if (!buf || (ulong)buf & 0x1UL)
2517
    return;
2518
2519
  /* inform callees to be careful, we're in a signal handler! */
2520
  _HA_ATOMIC_OR(&th_ctx->flags, TH_FL_IN_DBG_HANDLER);
2521
2522
  /* Special value 0x2 is used during panics and requires that the thread
2523
   * allocates its own dump buffer among its own trash buffers. The goal
2524
   * is that all threads keep a copy of their own dump.
2525
   */
2526
  if ((ulong)buf == 0x2UL) {
2527
    no_return = 1;
2528
    buf = get_trash_chunk();
2529
    HA_ATOMIC_STORE(&th_ctx->thread_dump_buffer, buf);
2530
  }
2531
2532
  /* Extra work might have been queued in the mean time via 0x2 */
2533
  if (((ulong)buf & 0x2UL)) {
2534
    buf = (void *)((ulong)buf & ~0x2);
2535
  }
2536
2537
  /* now dump the current state into the designated buffer, and indicate
2538
   * we come from a sig handler.
2539
   */
2540
  ha_thread_dump_one(buf, 0);
2541
2542
  /* end of dump, setting the buffer to 0x1 will tell the caller we're done */
2543
  HA_ATOMIC_OR((ulong*)DISGUISE(&th_ctx->thread_dump_buffer), 0x1UL);
2544
2545
  /* in case of panic, no return is planned so that we don't destroy
2546
   * the buffer's contents and we make sure not to trigger in loops.
2547
   */
2548
  while (no_return)
2549
    wait(NULL);
2550
2551
  _HA_ATOMIC_AND(&th_ctx->flags, ~TH_FL_IN_DBG_HANDLER);
2552
}
2553
2554
static int init_debug_per_thread()
2555
{
2556
  sigset_t set;
2557
2558
  /* unblock the DEBUGSIG signal we intend to use */
2559
  sigemptyset(&set);
2560
  sigaddset(&set, DEBUGSIG);
2561
#if defined(DEBUG_DEV)
2562
  sigaddset(&set, SIGRTMAX);
2563
#endif
2564
  ha_sigmask(SIG_UNBLOCK, &set, NULL);
2565
  return 1;
2566
}
2567
2568
static int init_debug()
2569
{
2570
  struct sigaction sa;
2571
  void *callers[1];
2572
  int ret = ERR_NONE;
2573
2574
  /* calling backtrace() will access libgcc at runtime. We don't want to
2575
   * do it after the chroot, so let's perform a first call to have it
2576
   * ready in memory for later use.
2577
   */
2578
  my_backtrace(callers, sizeof(callers)/sizeof(*callers));
2579
  sa.sa_handler = NULL;
2580
  sa.sa_sigaction = debug_handler;
2581
  sigemptyset(&sa.sa_mask);
2582
#ifdef WDTSIG
2583
  sigaddset(&sa.sa_mask, WDTSIG);
2584
#endif
2585
  sigaddset(&sa.sa_mask, DEBUGSIG);
2586
#if defined(DEBUG_DEV)
2587
  sigaddset(&sa.sa_mask, SIGRTMAX);
2588
#endif
2589
  sa.sa_flags = SA_SIGINFO;
2590
  sigaction(DEBUGSIG, &sa, NULL);
2591
2592
#if defined(DEBUG_DEV)
2593
  sa.sa_handler = NULL;
2594
  sa.sa_sigaction = debug_delay_inj_sighandler;
2595
  sigemptyset(&sa.sa_mask);
2596
  sa.sa_flags = SA_SIGINFO;
2597
  sigaction(SIGRTMAX, &sa, NULL);
2598
#endif
2599
2600
#if !defined(USE_OBSOLETE_LINKER) && ((DEBUG_STRICT > 0) || (DEBUG_COUNTERS > 0))
2601
  if (&__start_dbg_cnt) {
2602
    const struct debug_count *ptr;
2603
    const char *p;
2604
2605
    for (ptr = &__start_dbg_cnt; ptr < &__stop_dbg_cnt; ptr++) {
2606
      for (p = ptr->desc; *p; p++) {
2607
        if (*p < 0x20 || *p >= 0x7f) {
2608
          ha_warning("Invalid character 0x%02x at position %d in description string at %s:%d %s()\n",
2609
               (uchar)*p, (int)(p - ptr->desc), ptr->file, ptr->line, ptr->func);
2610
          ret = ERR_WARN;
2611
          break;
2612
        }
2613
      }
2614
    }
2615
  }
2616
#endif
2617
  return ret;
2618
}
2619
2620
REGISTER_POST_CHECK(init_debug);
2621
REGISTER_PER_THREAD_INIT(init_debug_per_thread);
2622
2623
#endif /* USE_THREAD_DUMP */
2624
2625
2626
static void feed_post_mortem_linux()
2627
0
{
2628
0
#if defined(__linux__)
2629
0
  struct stat statbuf;
2630
0
  FILE *file;
2631
2632
  /* DMI reports either HW or hypervisor, this allows to detect most VMs.
2633
   * On ARM the device-tree is often more precise for the model. Since many
2634
   * boards present "to be filled by OEM" or so in many fields, we dedup
2635
   * them as much as possible.
2636
   */
2637
0
  if (read_line_to_trash("/sys/class/dmi/id/sys_vendor") > 0)
2638
0
    strlcpy2(post_mortem.platform.hw_vendor, trash.area, sizeof(post_mortem.platform.hw_vendor));
2639
2640
0
  if (read_line_to_trash("/sys/class/dmi/id/product_family") > 0 &&
2641
0
      strcmp(trash.area, post_mortem.platform.hw_vendor) != 0)
2642
0
    strlcpy2(post_mortem.platform.hw_family, trash.area, sizeof(post_mortem.platform.hw_family));
2643
2644
0
  if ((read_line_to_trash("/sys/class/dmi/id/product_name") > 0 &&
2645
0
       strcmp(trash.area, post_mortem.platform.hw_vendor) != 0 &&
2646
0
       strcmp(trash.area, post_mortem.platform.hw_family) != 0))
2647
0
    strlcpy2(post_mortem.platform.hw_model, trash.area, sizeof(post_mortem.platform.hw_model));
2648
2649
0
  if ((read_line_to_trash("/sys/class/dmi/id/board_vendor") > 0 &&
2650
0
       strcmp(trash.area, post_mortem.platform.hw_vendor) != 0))
2651
0
    strlcpy2(post_mortem.platform.brd_vendor, trash.area, sizeof(post_mortem.platform.brd_vendor));
2652
2653
0
  if ((read_line_to_trash("/sys/firmware/devicetree/base/model") > 0 &&
2654
0
       strcmp(trash.area, post_mortem.platform.brd_vendor) != 0 &&
2655
0
       strcmp(trash.area, post_mortem.platform.hw_vendor) != 0 &&
2656
0
       strcmp(trash.area, post_mortem.platform.hw_family) != 0 &&
2657
0
       strcmp(trash.area, post_mortem.platform.hw_model) != 0) ||
2658
0
      (read_line_to_trash("/sys/class/dmi/id/board_name") > 0 &&
2659
0
       strcmp(trash.area, post_mortem.platform.brd_vendor) != 0 &&
2660
0
       strcmp(trash.area, post_mortem.platform.hw_vendor) != 0 &&
2661
0
       strcmp(trash.area, post_mortem.platform.hw_family) != 0 &&
2662
0
       strcmp(trash.area, post_mortem.platform.hw_model) != 0))
2663
0
    strlcpy2(post_mortem.platform.brd_model, trash.area, sizeof(post_mortem.platform.brd_model));
2664
2665
  /* Check for containers. In a container on linux we don't see keventd (2.4) kthreadd (2.6+) on pid 2 */
2666
0
  if (read_line_to_trash("/proc/2/status") <= 0 ||
2667
0
      (strcmp(trash.area, "Name:\tkthreadd") != 0 &&
2668
0
       strcmp(trash.area, "Name:\tkeventd") != 0)) {
2669
    /* OK we're in a container. Docker often has /.dockerenv */
2670
0
    const char *tech = "yes";
2671
2672
0
    if (stat("/.dockerenv", &statbuf) == 0)
2673
0
      tech = "docker";
2674
0
    strlcpy2(post_mortem.platform.cont_techno, tech, sizeof(post_mortem.platform.cont_techno));
2675
0
  }
2676
0
  else {
2677
0
    strlcpy2(post_mortem.platform.cont_techno, "no", sizeof(post_mortem.platform.cont_techno));
2678
0
  }
2679
2680
0
  file = fopen("/proc/cpuinfo", "r");
2681
0
  if (file) {
2682
0
    uint cpu_implem = 0, cpu_arch = 0, cpu_variant = 0, cpu_part = 0, cpu_rev = 0; // arm
2683
0
    uint cpu_family = 0, model = 0, stepping = 0;                                  // x86
2684
0
    char vendor_id[64] = "", model_name[64] = "";                                  // x86
2685
0
    char machine[64] = "", system_type[64] = "", cpu_model[64] = "";               // mips
2686
0
    const char *virt = "no";
2687
0
    char *p, *e, *v, *lf;
2688
2689
    /* let's figure what CPU we're working with */
2690
0
    while ((p = fgets(trash.area, trash.size, file)) != NULL) {
2691
0
      lf = strchr(p, '\n');
2692
0
      if (lf)
2693
0
        *lf = 0;
2694
2695
      /* stop at first line break */
2696
0
      if (!*p)
2697
0
        break;
2698
2699
      /* skip colon and spaces and trim spaces after name */
2700
0
      v = e = strchr(p, ':');
2701
0
      if (!e)
2702
0
        continue;
2703
2704
0
      do { *e-- = 0; } while (e >= p && (*e == ' ' || *e == '\t'));
2705
2706
      /* locate value after colon */
2707
0
      do { v++; } while (*v == ' ' || *v == '\t');
2708
2709
      /* ARM */
2710
0
      if (strcmp(p, "CPU implementer") == 0)
2711
0
        cpu_implem = strtoul(v, NULL, 0);
2712
0
      else if (strcmp(p, "CPU architecture") == 0)
2713
0
        cpu_arch = strtoul(v, NULL, 0);
2714
0
      else if (strcmp(p, "CPU variant") == 0)
2715
0
        cpu_variant = strtoul(v, NULL, 0);
2716
0
      else if (strcmp(p, "CPU part") == 0)
2717
0
        cpu_part = strtoul(v, NULL, 0);
2718
0
      else if (strcmp(p, "CPU revision") == 0)
2719
0
        cpu_rev = strtoul(v, NULL, 0);
2720
2721
      /* x86 */
2722
0
      else if (strcmp(p, "cpu family") == 0)
2723
0
        cpu_family = strtoul(v, NULL, 0);
2724
0
      else if (strcmp(p, "model") == 0)
2725
0
        model = strtoul(v, NULL, 0);
2726
0
      else if (strcmp(p, "stepping") == 0)
2727
0
        stepping = strtoul(v, NULL, 0);
2728
0
      else if (strcmp(p, "vendor_id") == 0)
2729
0
        strlcpy2(vendor_id, v, sizeof(vendor_id));
2730
0
      else if (strcmp(p, "model name") == 0)
2731
0
        strlcpy2(model_name, v, sizeof(model_name));
2732
0
      else if (strcmp(p, "flags") == 0) {
2733
0
        if (strstr(v, "hypervisor")) {
2734
0
          if (strncmp(post_mortem.platform.hw_vendor, "QEMU", 4) == 0)
2735
0
            virt = "qemu";
2736
0
          else if (strncmp(post_mortem.platform.hw_vendor, "VMware", 6) == 0)
2737
0
            virt = "vmware";
2738
0
          else
2739
0
            virt = "yes";
2740
0
        }
2741
0
      }
2742
2743
      /* MIPS */
2744
0
      else if (strcmp(p, "system type") == 0)
2745
0
        strlcpy2(system_type, v, sizeof(system_type));
2746
0
      else if (strcmp(p, "machine") == 0)
2747
0
        strlcpy2(machine, v, sizeof(machine));
2748
0
      else if (strcmp(p, "cpu model") == 0)
2749
0
        strlcpy2(cpu_model, v, sizeof(cpu_model));
2750
0
    }
2751
0
    fclose(file);
2752
2753
    /* Machine may replace hw_product on MIPS */
2754
0
    if (!*post_mortem.platform.hw_model)
2755
0
      strlcpy2(post_mortem.platform.hw_model, machine, sizeof(post_mortem.platform.hw_model));
2756
2757
    /* SoC vendor */
2758
0
    strlcpy2(post_mortem.platform.soc_vendor, vendor_id, sizeof(post_mortem.platform.soc_vendor));
2759
2760
    /* SoC model */
2761
0
    if (*system_type) {
2762
      /* MIPS */
2763
0
      strlcpy2(post_mortem.platform.soc_model, system_type, sizeof(post_mortem.platform.soc_model));
2764
0
      *system_type = 0;
2765
0
    } else if (*model_name) {
2766
      /* x86 */
2767
0
      strlcpy2(post_mortem.platform.soc_model, model_name, sizeof(post_mortem.platform.soc_model));
2768
0
      *model_name = 0;
2769
0
    }
2770
2771
    /* Create a CPU model name based on available IDs */
2772
0
    if (cpu_implem) // arm
2773
0
      snprintf(cpu_model + strlen(cpu_model),
2774
0
         sizeof(cpu_model) - strlen(cpu_model),
2775
0
         "%sImpl %#02x", *cpu_model ? " " : "", cpu_implem);
2776
2777
0
    if (cpu_family) // x86
2778
0
      snprintf(cpu_model + strlen(cpu_model),
2779
0
         sizeof(cpu_model) - strlen(cpu_model),
2780
0
         "%sFam %u", *cpu_model ? " " : "", cpu_family);
2781
2782
0
    if (model) // x86
2783
0
      snprintf(cpu_model + strlen(cpu_model),
2784
0
         sizeof(cpu_model) - strlen(cpu_model),
2785
0
         "%sModel %u", *cpu_model ? " " : "", model);
2786
2787
0
    if (stepping) // x86
2788
0
      snprintf(cpu_model + strlen(cpu_model),
2789
0
         sizeof(cpu_model) - strlen(cpu_model),
2790
0
         "%sStep %u", *cpu_model ? " " : "", stepping);
2791
2792
0
    if (cpu_arch) // arm
2793
0
      snprintf(cpu_model + strlen(cpu_model),
2794
0
         sizeof(cpu_model) - strlen(cpu_model),
2795
0
         "%sArch %u", *cpu_model ? " " : "", cpu_arch);
2796
2797
0
    if (cpu_part) // arm
2798
0
      snprintf(cpu_model + strlen(cpu_model),
2799
0
         sizeof(cpu_model) - strlen(cpu_model),
2800
0
         "%sPart %#03x", *cpu_model ? " " : "", cpu_part);
2801
2802
0
    if (cpu_variant || cpu_rev) // arm
2803
0
      snprintf(cpu_model + strlen(cpu_model),
2804
0
         sizeof(cpu_model) - strlen(cpu_model),
2805
0
         "%sr%up%u", *cpu_model ? " " : "", cpu_variant, cpu_rev);
2806
2807
0
    strlcpy2(post_mortem.platform.cpu_model, cpu_model, sizeof(post_mortem.platform.cpu_model));
2808
2809
0
    if (*virt)
2810
0
      strlcpy2(post_mortem.platform.virt_techno, virt, sizeof(post_mortem.platform.virt_techno));
2811
0
  }
2812
0
#endif // __linux__
2813
0
}
2814
2815
static int feed_post_mortem()
2816
0
{
2817
0
  FILE *file;
2818
0
  struct stat statbuf;
2819
0
  char line[64];
2820
0
  char file_path[32];
2821
0
  int line_cnt = 0;
2822
2823
  /* write an easily identifiable magic at the beginning of the struct */
2824
0
  strncpy(post_mortem.post_mortem_magic,
2825
0
    "POST-MORTEM STARTS HERE+7654321\0",
2826
0
    sizeof(post_mortem.post_mortem_magic));
2827
  /* kernel type, version and arch */
2828
0
  uname(&post_mortem.platform.utsname);
2829
2830
  /* try to find os-release file, this may give the current minor version of
2831
   * distro if it was recently updated.
2832
   */
2833
0
  snprintf(file_path, sizeof(file_path), "%s", "/etc/os-release");
2834
0
  if (stat(file_path, &statbuf) != 0) {
2835
    /* fallback to "/usr/lib/os-release" */
2836
0
    snprintf(file_path, sizeof(file_path), "%s", "/usr/lib/os-release");
2837
0
    if (stat(file_path, &statbuf) != 0 ) {
2838
0
      goto process_info;
2839
0
    }
2840
0
  }
2841
2842
  /* try open and find the line with distro PRETTY_NAME (name + full version) */
2843
0
  if ((file = fopen(file_path, "r")) == NULL) {
2844
0
    goto process_info;
2845
0
  }
2846
2847
0
  while ((fgets(line, sizeof(post_mortem.platform.distro), file)) && (line_cnt < MAX_LINES_TO_READ)) {
2848
0
    line_cnt++;
2849
0
    if (strncmp(line, "PRETTY_NAME=", 12) == 0) {
2850
      /* cut \n and trim possible quotes */
2851
0
      char *start = line + 12;
2852
0
      char *newline = strchr(start, '\n');
2853
2854
0
      if (newline) {
2855
0
        *newline = '\0';
2856
0
      } else {
2857
0
        newline = start + strlen(start);
2858
0
      }
2859
2860
      /* trim possible quotes */
2861
0
      if (*start == '"')
2862
0
        start++;
2863
0
      if (newline > start && *(newline - 1) == '"')
2864
0
        *(--newline) = '\0';
2865
2866
0
      strlcpy2(post_mortem.platform.distro, start, sizeof(post_mortem.platform.distro));
2867
0
      break;
2868
0
    }
2869
0
  }
2870
0
  fclose(file);
2871
2872
0
process_info:
2873
  /* some boot-time info related to the process */
2874
0
  post_mortem.process.pid = getpid();
2875
0
  post_mortem.process.boot_uid = geteuid();
2876
0
  post_mortem.process.boot_gid = getegid();
2877
0
  post_mortem.process.argc = global.argc;
2878
0
  post_mortem.process.argv = global.argv;
2879
2880
#if defined(USE_LINUX_CAP)
2881
  if (capget(&cap_hdr_haproxy, post_mortem.process.caps.boot) == -1)
2882
    post_mortem.process.caps.err_boot = errno;
2883
#endif
2884
0
  post_mortem.process.boot_lim_fd.rlim_cur = rlim_fd_cur_at_boot;
2885
0
  post_mortem.process.boot_lim_fd.rlim_max = rlim_fd_max_at_boot;
2886
0
  getrlimit(RLIMIT_DATA, &post_mortem.process.boot_lim_ram);
2887
2888
0
  if (strcmp(post_mortem.platform.utsname.sysname, "Linux") == 0)
2889
0
    feed_post_mortem_linux();
2890
2891
#if defined(HA_HAVE_DUMP_LIBS)
2892
  chunk_reset(&trash);
2893
  if (dump_libs(&trash, 1))
2894
    post_mortem.libs = strdup(trash.area);
2895
#endif
2896
2897
0
  post_mortem.tgroup_info = ha_tgroup_info;
2898
0
  post_mortem.thread_info = ha_thread_info;
2899
0
  post_mortem.tgroup_ctx  = ha_tgroup_ctx;
2900
0
  post_mortem.thread_ctx  = ha_thread_ctx;
2901
0
  post_mortem.pools = &pools;
2902
0
  post_mortem.proxies = &proxies_list;
2903
0
  post_mortem.global = &global;
2904
0
  post_mortem.fdtab = &fdtab;
2905
0
  post_mortem.activity = activity;
2906
2907
0
  return ERR_NONE;
2908
0
}
2909
2910
REGISTER_POST_CHECK(feed_post_mortem);
2911
2912
static void deinit_post_mortem(void)
2913
0
{
2914
0
  int comp;
2915
2916
#if defined(HA_HAVE_DUMP_LIBS)
2917
  ha_free(&post_mortem.libs);
2918
#endif
2919
0
  for (comp = 0; comp < post_mortem.nb_components; comp++) {
2920
0
    free(post_mortem.components[comp].toolchain);
2921
0
    free(post_mortem.components[comp].toolchain_opts);
2922
0
    free(post_mortem.components[comp].build_settings);
2923
0
    free(post_mortem.components[comp].path);
2924
0
  }
2925
0
  ha_free(&post_mortem.components);
2926
0
}
2927
2928
REGISTER_POST_DEINIT(deinit_post_mortem);
2929
2930
/* Appends a component to the list of post_portem info. May silently fail
2931
 * on allocation errors but we don't care since the goal is to provide info
2932
 * we have in case it helps.
2933
 */
2934
void post_mortem_add_component(const char *name, const char *version,
2935
             const char *toolchain, const char *toolchain_opts,
2936
             const char *build_settings, const char *path)
2937
0
{
2938
0
  struct post_mortem_component *comp;
2939
0
  int nbcomp = post_mortem.nb_components;
2940
2941
0
  comp = realloc(post_mortem.components, (nbcomp + 1) * sizeof(*comp));
2942
0
  if (!comp)
2943
0
    return;
2944
2945
0
  memset(&comp[nbcomp], 0, sizeof(*comp));
2946
0
  strlcpy2(comp[nbcomp].name, name, sizeof(comp[nbcomp].name));
2947
0
  strlcpy2(comp[nbcomp].version, version, sizeof(comp[nbcomp].version));
2948
0
  comp[nbcomp].toolchain      = strdup(toolchain);
2949
0
  comp[nbcomp].toolchain_opts = strdup(toolchain_opts);
2950
0
  comp[nbcomp].build_settings = strdup(build_settings);
2951
0
  comp[nbcomp].path = strdup(path);
2952
2953
0
  post_mortem.nb_components++;
2954
0
  post_mortem.components = comp;
2955
0
}
2956
2957
#ifdef USE_THREAD
2958
/* init code is called one at a time so let's collect all per-thread info on
2959
 * the last starting thread. These info are not critical anyway and there's no
2960
 * problem if we get them slightly late.
2961
 */
2962
static int feed_post_mortem_late()
2963
{
2964
  static int per_thread_info_collected;
2965
2966
  if (HA_ATOMIC_ADD_FETCH(&per_thread_info_collected, 1) != global.nbthread)
2967
    return 1;
2968
2969
  /* also set runtime process settings. At this stage we are sure, that all
2970
   * config options and limits adjustments are successfully applied.
2971
   */
2972
  post_mortem.process.run_uid = geteuid();
2973
  post_mortem.process.run_gid = getegid();
2974
#if defined(USE_LINUX_CAP)
2975
  if (capget(&cap_hdr_haproxy, post_mortem.process.caps.run) == -1) {
2976
    post_mortem.process.caps.err_run = errno;
2977
  }
2978
#endif
2979
  getrlimit(RLIMIT_NOFILE, &post_mortem.process.run_lim_fd);
2980
  getrlimit(RLIMIT_DATA, &post_mortem.process.run_lim_ram);
2981
2982
  return 1;
2983
}
2984
2985
REGISTER_PER_THREAD_INIT(feed_post_mortem_late);
2986
#endif
2987
2988
#ifdef DEBUG_UNIT
2989
2990
extern struct list unittest_list;
2991
2992
void list_unittests()
2993
{
2994
  struct unittest_fct *unit;
2995
  int found = 0;
2996
2997
  fprintf(stdout, "Unit tests list :");
2998
2999
  list_for_each_entry(unit, &unittest_list, list) {
3000
    fprintf(stdout, " %s", unit->name);
3001
    found = 1;
3002
  }
3003
3004
  if (!found)
3005
    fprintf(stdout, " none");
3006
3007
  fprintf(stdout, "\n");
3008
}
3009
3010
#endif
3011
3012
#if DEBUG_STRICT > 1
3013
/* config parser for global "debug.counters", accepts "on" or "off" */
3014
static int cfg_parse_debug_counters(char **args, int section_type, struct proxy *curpx,
3015
                                    const struct proxy *defpx, const char *file, int line,
3016
                                    char **err)
3017
{
3018
  if (too_many_args(1, args, err, NULL))
3019
    return -1;
3020
3021
  if (strcmp(args[1], "on") == 0) {
3022
    HA_ATOMIC_STORE(&debug_enable_counters, 1);
3023
  }
3024
  else if (strcmp(args[1], "off") == 0)
3025
    HA_ATOMIC_STORE(&debug_enable_counters, 0);
3026
  else {
3027
    memprintf(err, "'%s' expects either 'on' or 'off' but got '%s'.", args[0], args[1]);
3028
    return -1;
3029
  }
3030
  return 0;
3031
}
3032
#endif
3033
3034
/* config keyword parsers */
3035
static struct cfg_kw_list cfg_kws = {ILH, {
3036
#if DEBUG_STRICT > 1
3037
  { CFG_GLOBAL, "debug.counters",         cfg_parse_debug_counters      },
3038
#endif
3039
  { 0, NULL, NULL }
3040
}};
3041
INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
3042
3043
/* register cli keywords */
3044
static struct cli_kw_list cli_kws = {{ },{
3045
#if !defined(USE_OBSOLETE_LINKER)
3046
  {{ "debug", "counters", NULL },        "debug counters [?|all|bug|cnt|chk|glt]* : dump/reset rare event counters",          debug_parse_cli_counters, debug_iohandler_counters, NULL, NULL, 0 },
3047
#endif
3048
  {{ "debug", "dev", "bug", NULL },      "debug dev bug                           : call BUG_ON() and crash",                 debug_parse_cli_bug,   NULL, NULL, NULL, ACCESS_EXPERT },
3049
  {{ "debug", "dev", "check", NULL },    "debug dev check                         : call CHECK_IF() and possibly crash",      debug_parse_cli_check, NULL, NULL, NULL, ACCESS_EXPERT },
3050
  {{ "debug", "dev", "close", NULL },    "debug dev close  <fd> [hard]            : close this file descriptor",              debug_parse_cli_close, NULL, NULL, NULL, ACCESS_EXPERT },
3051
  {{ "debug", "dev", "deadlock", NULL }, "debug dev deadlock [nbtask]             : deadlock between this number of tasks",   debug_parse_cli_deadlock, NULL, NULL, NULL, ACCESS_EXPERT },
3052
  {{ "debug", "dev", "delay", NULL },    "debug dev delay  [ms]                   : sleep this long",                         debug_parse_cli_delay, NULL, NULL, NULL, ACCESS_EXPERT },
3053
#if defined(DEBUG_DEV)
3054
  {{ "debug", "dev", "delay-inj", NULL },"debug dev delay-inj <inter> <count>     : inject random delays into threads",       debug_parse_delay_inj, NULL, NULL, NULL, ACCESS_EXPERT },
3055
  {{ "debug", "dev", "exec",  NULL },    "debug dev exec   [cmd] ...              : show this command's output",              debug_parse_cli_exec,  NULL, NULL, NULL, ACCESS_EXPERT },
3056
#endif
3057
  {{ "debug", "dev", "fd", NULL },       "debug dev fd                            : scan for rogue/unhandled FDs",            debug_parse_cli_fd,    debug_iohandler_fd, NULL, NULL, ACCESS_EXPERT },
3058
  {{ "debug", "dev", "exit",  NULL },    "debug dev exit   [code]                 : immediately exit the process",            debug_parse_cli_exit,  NULL, NULL, NULL, ACCESS_EXPERT },
3059
  {{ "debug", "dev", "hash", NULL },     "debug dev hash   [msg]                  : return msg hashed if anon is set",        debug_parse_cli_hash,  NULL, NULL, NULL, 0 },
3060
  {{ "debug", "dev", "hex",   NULL },    "debug dev hex    <addr> [len]           : dump a memory area",                      debug_parse_cli_hex,   NULL, NULL, NULL, ACCESS_EXPERT },
3061
  {{ "debug", "dev", "log",   NULL },    "debug dev log    [msg] ...              : send this msg to global logs",            debug_parse_cli_log,   NULL, NULL, NULL, ACCESS_EXPERT },
3062
  {{ "debug", "dev", "loop",  NULL },    "debug dev loop   <ms> [isolated|warn]   : loop this long, possibly isolated",       debug_parse_cli_loop,  NULL, NULL, NULL, ACCESS_EXPERT },
3063
#if defined(DEBUG_MEM_STATS)
3064
  {{ "debug", "dev", "memstats", NULL }, "debug dev memstats [reset|all|match ...]: dump/reset memory statistics",            debug_parse_cli_memstats, debug_iohandler_memstats, debug_release_memstats, NULL, 0 },
3065
#endif
3066
  {{ "debug", "dev", "panic", NULL },    "debug dev panic                         : immediately trigger a panic",             debug_parse_cli_panic, NULL, NULL, NULL, ACCESS_EXPERT },
3067
  {{ "debug", "dev", "sched", NULL },    "debug dev sched  {task|tasklet} [k=v]*  : stress the scheduler",                    debug_parse_cli_sched, NULL, NULL, NULL, ACCESS_EXPERT },
3068
  {{ "debug", "dev", "stream",NULL },    "debug dev stream [k=v]*                 : show/manipulate stream flags",            debug_parse_cli_stream,NULL, NULL, NULL, ACCESS_EXPERT },
3069
  {{ "debug", "dev", "sym",   NULL },    "debug dev sym    <addr>                 : resolve symbol address",                  debug_parse_cli_sym,   NULL, NULL, NULL, ACCESS_EXPERT },
3070
  {{ "debug", "dev", "task",  NULL },    "debug dev task <ptr> [wake|expire|kill] : show/wake/expire/kill task/tasklet",      debug_parse_cli_task,  NULL, NULL, NULL, ACCESS_EXPERT },
3071
  {{ "debug", "dev", "tkill", NULL },    "debug dev tkill  [thr] [sig]            : send signal to thread",                   debug_parse_cli_tkill, NULL, NULL, NULL, ACCESS_EXPERT },
3072
#if defined(DEBUG_DEV)
3073
  {{ "debug", "dev", "trace", NULL },    "debug dev trace [nbthr]                 : flood traces from that many threads",     debug_parse_cli_trace,  NULL, NULL, NULL, ACCESS_EXPERT },
3074
#endif
3075
  {{ "debug", "dev", "warn",  NULL },    "debug dev warn                          : call WARN_ON() and possibly crash",       debug_parse_cli_warn,  NULL, NULL, NULL, ACCESS_EXPERT },
3076
  {{ "debug", "dev", "write", NULL },    "debug dev write  [size]                 : write that many bytes in return",         debug_parse_cli_write, NULL, NULL, NULL, ACCESS_EXPERT },
3077
3078
  {{ "show", "dev", NULL, NULL },        "show dev                                : show debug info for developers",          debug_parse_cli_show_dev, NULL, NULL },
3079
#if defined(HA_HAVE_DUMP_LIBS)
3080
  {{ "show", "libs", NULL, NULL },       "show libs                               : show loaded object files and libraries", debug_parse_cli_show_libs, NULL, NULL },
3081
#endif
3082
  {{ "show", "threads", NULL, NULL },    "show threads                            : show some threads debugging information", NULL, cli_io_handler_show_threads, NULL },
3083
  {{},}
3084
}};
3085
3086
INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);