2019-05-16 11:44:30 -04:00
/*
* Process debugging functions .
*
* Copyright 2000 - 2019 Willy Tarreau < willy @ haproxy . org > .
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; either version
* 2 of the License , or ( at your option ) any later version .
*
*/
2020-03-03 09:40:23 -05:00
DEBUG: cli: add a new "debug dev fd" expert command
This command will scan the whole file descriptors space to look for
existing FDs that are unknown to haproxy's fdtab, and will try to dump
a maximum number of information about them (including type, mode, device,
size, uid/gid, cloexec, O_* flags, socket types and addresses when
relevant). The goal is to help detecting inherited FDs from parent
processes as well as potential leaks.
Some of those listed are actually known but handled so deep into some
systems that they're not in the fdtab (such as epoll FDs or inter-
thread pipes). This might be refined in the future so that these ones
become known and do not appear.
Example of output:
$ socat - /tmp/sock1 <<< "expert-mode on;debug dev fd"
0 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
1 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
2 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
3 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x18112348 getfd=+0
4 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
33 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8251 getfd=+0 getfl=O_RDONLY
34 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
36 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8d1b getfd=+0 getfl=O_RDONLY
37 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
39 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24afa04f getfd=+0 getfl=O_RDONLY
41 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8252 getfd=+0 getfl=O_RDONLY
42 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
2022-01-24 14:26:09 -05:00
# include <errno.h>
2019-12-06 11:18:28 -05:00
# include <fcntl.h>
2019-05-16 11:44:30 -04:00
# include <signal.h>
# include <time.h>
# include <stdio.h>
2019-05-20 08:25:05 -04:00
# include <stdlib.h>
2020-06-04 16:01:04 -04:00
# include <syslog.h>
2023-11-22 09:37:57 -05:00
# include <sys/resource.h>
DEBUG: cli: add a new "debug dev fd" expert command
This command will scan the whole file descriptors space to look for
existing FDs that are unknown to haproxy's fdtab, and will try to dump
a maximum number of information about them (including type, mode, device,
size, uid/gid, cloexec, O_* flags, socket types and addresses when
relevant). The goal is to help detecting inherited FDs from parent
processes as well as potential leaks.
Some of those listed are actually known but handled so deep into some
systems that they're not in the fdtab (such as epoll FDs or inter-
thread pipes). This might be refined in the future so that these ones
become known and do not appear.
Example of output:
$ socat - /tmp/sock1 <<< "expert-mode on;debug dev fd"
0 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
1 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
2 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
3 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x18112348 getfd=+0
4 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
33 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8251 getfd=+0 getfl=O_RDONLY
34 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
36 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8d1b getfd=+0 getfl=O_RDONLY
37 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
39 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24afa04f getfd=+0 getfl=O_RDONLY
41 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8252 getfd=+0 getfl=O_RDONLY
42 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
2022-01-24 14:26:09 -05:00
# include <sys/stat.h>
2019-12-06 11:18:28 -05:00
# include <sys/types.h>
2023-11-22 05:28:06 -05:00
# include <sys/utsname.h>
2019-12-06 11:18:28 -05:00
# include <sys/wait.h>
DEBUG: cli: add a new "debug dev fd" expert command
This command will scan the whole file descriptors space to look for
existing FDs that are unknown to haproxy's fdtab, and will try to dump
a maximum number of information about them (including type, mode, device,
size, uid/gid, cloexec, O_* flags, socket types and addresses when
relevant). The goal is to help detecting inherited FDs from parent
processes as well as potential leaks.
Some of those listed are actually known but handled so deep into some
systems that they're not in the fdtab (such as epoll FDs or inter-
thread pipes). This might be refined in the future so that these ones
become known and do not appear.
Example of output:
$ socat - /tmp/sock1 <<< "expert-mode on;debug dev fd"
0 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
1 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
2 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
3 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x18112348 getfd=+0
4 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
33 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8251 getfd=+0 getfl=O_RDONLY
34 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
36 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8d1b getfd=+0 getfl=O_RDONLY
37 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
39 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24afa04f getfd=+0 getfl=O_RDONLY
41 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8252 getfd=+0 getfl=O_RDONLY
42 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
2022-01-24 14:26:09 -05:00
# include <unistd.h>
# ifdef USE_EPOLL
# include <sys/epoll.h>
# endif
2019-05-16 11:44:30 -04:00
2020-05-27 06:58:42 -04:00
# include <haproxy/api.h>
2022-04-04 05:29:28 -04:00
# include <haproxy/applet.h>
2020-05-27 11:22:10 -04:00
# include <haproxy/buf.h>
2020-06-04 14:19:54 -04:00
# include <haproxy/cli.h>
2021-10-08 03:33:24 -04:00
# include <haproxy/clock.h>
2020-05-27 10:58:08 -04:00
# include <haproxy/debug.h>
2020-06-09 03:07:15 -04:00
# include <haproxy/fd.h>
# include <haproxy/global.h>
2020-06-04 03:20:54 -04:00
# include <haproxy/hlua.h>
2021-10-06 12:56:42 -04:00
# include <haproxy/http_ana.h>
2024-07-12 11:55:15 -04:00
# include <haproxy/limits.h>
2024-06-21 12:11:46 -04:00
# if defined(USE_LINUX_CAP)
# include <haproxy/linuxcap.h>
# endif
2020-06-04 16:01:04 -04:00
# include <haproxy/log.h>
2020-06-09 03:07:15 -04:00
# include <haproxy/net_helper.h>
2022-05-27 03:25:10 -04:00
# include <haproxy/sc_strm.h>
MINOR: debug: store important pointers in post_mortem
Dealing with a core and a stripped executable is a pain when it comes
to finding pools, proxies or thread contexts. Let's put a pointer to
these heads and arrays in the post_mortem struct for easier location.
Other critical lists like this could possibly benefit from being added
later.
Here we now have:
- tgroup_info
- thread_info
- tgroup_ctx
- thread_ctx
- pools
- proxies
Example:
$ objdump -h haproxy|grep post
34 _post_mortem 000014b0 0000000000cfd400 0000000000cfd400 008fc400 2**8
(gdb) set $pm=(struct post_mortem*)0x0000000000cfd400
(gdb) p $pm->tgroup_ctx[0]
$8 = {
threads_harmless = 254,
threads_idle = 254,
stopping_threads = 0,
timers = {
b = {0x0, 0x0}
},
niced_tasks = 0,
__pad = 0xf5662c <ha_tgroup_ctx+44> "",
__end = 0xf56640 <ha_tgroup_ctx+64> ""
}
(gdb) info thr
Id Target Id Frame
* 1 Thread 0x7f9e7706a440 (LWP 21169) 0x00007f9e76a9c868 in raise () from /lib64/libc.so.6
2 Thread 0x7f9e76a60640 (LWP 21175) 0x00007f9e76b343c7 in wait4 () from /lib64/libc.so.6
3 Thread 0x7f9e7613d640 (LWP 21176) 0x00007f9e76b343c7 in wait4 () from /lib64/libc.so.6
4 Thread 0x7f9e7493a640 (LWP 21179) 0x00007f9e76b343c7 in wait4 () from /lib64/libc.so.6
5 Thread 0x7f9e7593c640 (LWP 21177) 0x00007f9e76b343c7 in wait4 () from /lib64/libc.so.6
6 Thread 0x7f9e7513b640 (LWP 21178) 0x00007f9e76b343c7 in wait4 () from /lib64/libc.so.6
7 Thread 0x7f9e6ffff640 (LWP 21180) 0x00007f9e76b343c7 in wait4 () from /lib64/libc.so.6
8 Thread 0x7f9e6f7fe640 (LWP 21181) 0x00007f9e76b343c7 in wait4 () from /lib64/libc.so.6
(gdb) p/x $pm->thread_info[0].pth_id
$12 = 0x7f9e7706a440
(gdb) p/x $pm->thread_info[1].pth_id
$13 = 0x7f9e76a60640
(gdb) set $px = *$pm->proxies
while ($px != 0)
printf "%#lx %s served=%u\n", $px, $px->id, $px->served
set $px = ($px)->next
end
0x125eda0 GLOBAL served=0
0x12645b0 stats served=0
0x1266940 comp served=0
0x1268e10 comp_bck served=0
0x1260cf0 <OCSP-UPDATE> served=0
0x12714c0 <HTTPCLIENT> served=0
2024-10-24 08:37:12 -04:00
# include <haproxy/proxy.h>
2022-05-27 03:47:12 -04:00
# include <haproxy/stconn.h>
2020-06-04 11:25:40 -04:00
# include <haproxy/task.h>
2020-05-28 09:29:19 -04:00
# include <haproxy/thread.h>
2021-10-08 03:33:24 -04:00
# include <haproxy/time.h>
2020-06-03 12:09:46 -04:00
# include <haproxy/tools.h>
2024-03-15 02:16:08 -04:00
# include <haproxy/trace.h>
2024-05-29 05:27:21 -04:00
# include <haproxy/version.h>
2020-06-09 03:07:15 -04:00
# include <import/ist.h>
2019-05-16 11:44:30 -04:00
2022-07-15 07:14:03 -04:00
/* The dump state is made of:
* - num_thread on the lowest 15 bits
* - a SYNC flag on bit 15 ( waiting for sync start )
* - number of participating threads on bits 16 - 30
* Initiating a dump consists in setting it to SYNC and incrementing the
* num_thread part when entering the function . The first thread periodically
* recounts active threads and compares it to the ready ones , and clears SYNC
* and sets the number of participants to the value found , which serves as a
* start signal . A thread finished dumping looks up the TID of the next active
* thread after it and writes it in the lowest part . If there ' s none , it sets
* the thread counter to the number of participants and resets that part ,
* which serves as an end - of - dump signal . All threads decrement the num_thread
* part . Then all threads wait for the value to reach zero . Only used when
* USE_THREAD_DUMP is set .
2019-07-31 13:20:39 -04:00
*/
2022-07-15 07:14:03 -04:00
# define THREAD_DUMP_TMASK 0x00007FFFU
# define THREAD_DUMP_FSYNC 0x00008000U
# define THREAD_DUMP_PMASK 0x7FFF0000U
2023-11-23 05:25:34 -05:00
/* Description of a component with name, version, path, build options etc. E.g.
* one of them is haproxy . Others might be some clearly identified shared libs .
* They ' re intentionally self - contained and to be placed into an array to make
* it easier to find them in a core . The important fields ( name and version )
* are locally allocated , other ones are dynamic .
*/
struct post_mortem_component {
char name [ 32 ] ; // symbolic short name
char version [ 32 ] ; // exact version
char * toolchain ; // compiler and version (e.g. gcc-11.4.0)
char * toolchain_opts ; // optims, arch-specific options (e.g. CFLAGS)
char * build_settings ; // build options (e.g. USE_*, TARGET, etc)
char * path ; // path if known.
} ;
2023-11-22 05:28:06 -05:00
/* This is a collection of information that are centralized to help with core
* dump analysis . It must be used with a public variable and gather elements
* as much as possible without dereferences so that even when identified in a
* core dump it ' s possible to get the most out of it even if the core file is
* not much exploitable . It ' s aligned to 256 so that it ' s easy to spot , given
* that being that large it will not change its size much .
*/
struct post_mortem {
/* platform-specific information */
MINOR: debug: place a magic pattern at the beginning of post_mortem
In order to ease finding of the post_mortem struct in core dumps, let's
make it start with a recognizable pattern of exactly 32 chars (to
preserve alignment):
"POST-MORTEM STARTS HERE+7654321\0"
It can then be found like this from gdb:
(gdb) find 0x000000012345678, 0x0000000100000000, 'P','O','S','T','-','M','O','R','T','E','M'
0xcfd300 <post_mortem>
1 pattern found.
Or easier with any other more practical tool (who as ever used "find" in
gdb, given that it cannot iterate over maps and is 100% useless?).
2024-10-24 05:56:07 -04:00
char post_mortem_magic [ 32 ] ; // "POST-MORTEM STARTS HERE+7654321\0"
2023-11-22 05:28:06 -05:00
struct {
struct utsname utsname ; // OS name+ver+arch+hostname
2023-11-22 05:32:51 -05:00
char hw_vendor [ 64 ] ; // hardware/hypervisor vendor when known
char hw_family [ 64 ] ; // hardware/hypervisor product family when known
char hw_model [ 64 ] ; // hardware/hypervisor product/model when known
char brd_vendor [ 64 ] ; // mainboard vendor when known
char brd_model [ 64 ] ; // mainboard model when known
MINOR: debug: detect CPU model and store it in post_mortem
The CPU model and type has significant impact on certain bugs, such
as contention issues caused by CPUs having split L3 caches, or stricter
memory models that exhibit some barrier issues. It's complicated though
because the info about the model depends on the arch. For example, x86
reports an SKU name while ARM rather reports the CPU core types, families
and versions for each CPU core. There, the SoC will sometimes be reported
in the device tree or DMI info instead. But we don't really care, it's
essentially useful to know if the code is running on an armv8.0 such as
A53, a 8.2 such as A55/A76/Neoverse etc. For MIPS the model appears to
generally be there, and in addition the SoC is often present in the
"system type" field before the first CPU, and the type of machine in the
"machine" field, to replace the missing DMI and DT, so they are also
collected. Note that only the first CPU is checked and reported, that's
expected to be vastly sufficient, since we're just trying to spot known
incompatibilities or issues.
2023-11-22 05:55:22 -05:00
char soc_vendor [ 64 ] ; // SoC/CPU vendor from cpuinfo
char soc_model [ 64 ] ; // SoC model when known and relevant
char cpu_model [ 64 ] ; // CPU model when different from SoC
2023-11-22 06:04:02 -05:00
char virt_techno [ 16 ] ; // when provided by cpuid
2023-11-22 05:37:37 -05:00
char cont_techno [ 16 ] ; // empty, "no", "yes", "docker" or others
2023-11-22 05:28:06 -05:00
} platform ;
2023-11-22 09:37:57 -05:00
/* process-specific information */
struct {
pid_t pid ;
uid_t boot_uid ;
gid_t boot_gid ;
2024-07-12 11:50:18 -04:00
uid_t run_uid ;
gid_t run_gid ;
2024-06-21 12:11:46 -04:00
# if defined(USE_LINUX_CAP)
struct {
// initial process capabilities
struct __user_cap_data_struct boot [ _LINUX_CAPABILITY_U32S_3 ] ;
2024-07-14 09:20:02 -04:00
int err_boot ; // errno, if capget() syscall fails at boot
// runtime process capabilities
struct __user_cap_data_struct run [ _LINUX_CAPABILITY_U32S_3 ] ;
int err_run ; // errno, if capget() syscall fails at runtime
2024-06-21 12:11:46 -04:00
} caps ;
# endif
2024-07-13 07:23:46 -04:00
struct rlimit boot_lim_fd ; // RLIMIT_NOFILE at startup
struct rlimit boot_lim_ram ; // RLIMIT_DATA at startup
2024-07-14 10:58:02 -04:00
struct rlimit run_lim_fd ; // RLIMIT_NOFILE just before enter in polling loop
struct rlimit run_lim_ram ; // RLIMIT_DATA just before enter in polling loop
2024-05-29 05:27:21 -04:00
char * * argv ;
2023-11-22 12:30:19 -05:00
# if defined(USE_THREAD)
struct {
ullong pth_id ; // pthread_t cast to a ullong
void * stack_top ; // top of the stack
} thread_info [ MAX_THREADS ] ;
# endif
2024-05-29 05:27:21 -04:00
unsigned char argc ;
2023-11-22 09:37:57 -05:00
} process ;
2023-11-23 02:26:52 -05:00
# if defined(HA_HAVE_DUMP_LIBS)
/* information about dynamic shared libraries involved */
char * libs ; // dump of one addr / path per line, or NULL
# endif
MINOR: debug: store important pointers in post_mortem
Dealing with a core and a stripped executable is a pain when it comes
to finding pools, proxies or thread contexts. Let's put a pointer to
these heads and arrays in the post_mortem struct for easier location.
Other critical lists like this could possibly benefit from being added
later.
Here we now have:
- tgroup_info
- thread_info
- tgroup_ctx
- thread_ctx
- pools
- proxies
Example:
$ objdump -h haproxy|grep post
34 _post_mortem 000014b0 0000000000cfd400 0000000000cfd400 008fc400 2**8
(gdb) set $pm=(struct post_mortem*)0x0000000000cfd400
(gdb) p $pm->tgroup_ctx[0]
$8 = {
threads_harmless = 254,
threads_idle = 254,
stopping_threads = 0,
timers = {
b = {0x0, 0x0}
},
niced_tasks = 0,
__pad = 0xf5662c <ha_tgroup_ctx+44> "",
__end = 0xf56640 <ha_tgroup_ctx+64> ""
}
(gdb) info thr
Id Target Id Frame
* 1 Thread 0x7f9e7706a440 (LWP 21169) 0x00007f9e76a9c868 in raise () from /lib64/libc.so.6
2 Thread 0x7f9e76a60640 (LWP 21175) 0x00007f9e76b343c7 in wait4 () from /lib64/libc.so.6
3 Thread 0x7f9e7613d640 (LWP 21176) 0x00007f9e76b343c7 in wait4 () from /lib64/libc.so.6
4 Thread 0x7f9e7493a640 (LWP 21179) 0x00007f9e76b343c7 in wait4 () from /lib64/libc.so.6
5 Thread 0x7f9e7593c640 (LWP 21177) 0x00007f9e76b343c7 in wait4 () from /lib64/libc.so.6
6 Thread 0x7f9e7513b640 (LWP 21178) 0x00007f9e76b343c7 in wait4 () from /lib64/libc.so.6
7 Thread 0x7f9e6ffff640 (LWP 21180) 0x00007f9e76b343c7 in wait4 () from /lib64/libc.so.6
8 Thread 0x7f9e6f7fe640 (LWP 21181) 0x00007f9e76b343c7 in wait4 () from /lib64/libc.so.6
(gdb) p/x $pm->thread_info[0].pth_id
$12 = 0x7f9e7706a440
(gdb) p/x $pm->thread_info[1].pth_id
$13 = 0x7f9e76a60640
(gdb) set $px = *$pm->proxies
while ($px != 0)
printf "%#lx %s served=%u\n", $px, $px->id, $px->served
set $px = ($px)->next
end
0x125eda0 GLOBAL served=0
0x12645b0 stats served=0
0x1266940 comp served=0
0x1268e10 comp_bck served=0
0x1260cf0 <OCSP-UPDATE> served=0
0x12714c0 <HTTPCLIENT> served=0
2024-10-24 08:37:12 -04:00
struct tgroup_info * tgroup_info ; // pointer to ha_tgroup_info
struct thread_info * thread_info ; // pointer to ha_thread_info
struct tgroup_ctx * tgroup_ctx ; // pointer to ha_tgroup_ctx
struct thread_ctx * thread_ctx ; // pointer to ha_thread_ctx
struct list * pools ; // pointer to the head of the pools list
struct proxy * * proxies ; // pointer to the head of the proxies list
2023-11-23 05:25:34 -05:00
/* info about identified distinct components (executable, shared libs, etc).
* These can be all listed at once in gdb using :
* p * post_mortem . components @ post_mortem . nb_components
*/
uint nb_components ; // # of components below
struct post_mortem_component * components ; // NULL or array
2024-10-24 05:59:32 -04:00
} post_mortem ALIGNED ( 256 ) HA_SECTION ( " _post_mortem " ) = { } ;
2023-11-22 05:28:06 -05:00
2019-10-24 12:18:02 -04:00
unsigned int debug_commands_issued = 0 ;
2019-07-31 13:20:39 -04:00
2021-01-22 07:52:41 -05:00
/* dumps a backtrace of the current thread that is appended to buffer <buf>.
* Lines are prefixed with the string < prefix > which may be empty ( used for
* indenting ) . It is recommended to use this at a function ' s tail so that
2021-01-22 08:48:34 -05:00
* the function does not appear in the call stack . The < dump > argument
* indicates what dump state to start from , and should usually be zero . It
* may be among the following values :
* - 0 : search usual callers before step 1 , or directly jump to 2
* - 1 : skip usual callers before step 2
* - 2 : dump until polling loop , scheduler , or main ( ) ( excluded )
* - 3 : end
* - 4 - 7 : like 0 but stops * after * main .
2021-01-22 07:52:41 -05:00
*/
2021-01-22 08:48:34 -05:00
void ha_dump_backtrace ( struct buffer * buf , const char * prefix , int dump )
2021-01-22 07:52:41 -05:00
{
struct buffer bak ;
char pfx2 [ 100 ] ;
void * callers [ 100 ] ;
int j , nptrs ;
const void * addr ;
nptrs = my_backtrace ( callers , sizeof ( callers ) / sizeof ( * callers ) ) ;
if ( ! nptrs )
return ;
if ( snprintf ( pfx2 , sizeof ( pfx2 ) , " %s| " , prefix ) > sizeof ( pfx2 ) )
pfx2 [ 0 ] = 0 ;
/* The call backtrace_symbols_fd(callers, nptrs, STDOUT_FILENO would
* produce similar output to the following :
*/
chunk_appendf ( buf , " %scall trace(%d): \n " , prefix , nptrs ) ;
2021-01-22 08:48:34 -05:00
for ( j = 0 ; ( j < nptrs | | ( dump & 3 ) < 2 ) ; j + + ) {
if ( j = = nptrs & & ! ( dump & 3 ) ) {
2021-01-22 07:52:41 -05:00
/* we failed to spot the starting point of the
* dump , let ' s start over dumping everything we
* have .
*/
2021-01-22 08:48:34 -05:00
dump + = 2 ;
2021-01-22 07:52:41 -05:00
j = 0 ;
}
bak = * buf ;
dump_addr_and_bytes ( buf , pfx2 , callers [ j ] , 8 ) ;
addr = resolve_sym_name ( buf , " : " , callers [ j ] ) ;
2021-01-22 08:48:34 -05:00
if ( ( dump & 3 ) = = 0 ) {
MEDIUM: debug: simplify the thread dump mechanism
The thread dump mechanism that is used by "show threads" and by the
panic dump is overly complicated due to an initial misdesign. It
firsts wakes all threads, then serializes their dumps, then releases
them, while taking extreme care not to face colliding dumps. In fact
this is not what we need and it reached a limit where big machines
cannot dump all their threads anymore due to buffer size limitations.
What is needed instead is to be able to dump *one* thread, and to let
the requester iterate on all threads.
That's what this patch does. It adds the thread_dump_buffer to the
struct thread_ctx so that the requester offers the buffer to the
thread that is about to be dumped. This buffer also serves as a lock.
A thread at rest has a NULL, a valid pointer indicates the thread is
using it, and 0x1 (NULL+1) is used by the dumped thread to tell the
requester it's done. This makes sure that a given thread is dumped
once at a time. In addition to this, the calling thread decides
whether it accesses the thread by itself or via the debug signal
handler, in order to get a backtrace. This is much saner because the
calling thread is free to do whatever it wants with the buffer after
each thread is dumped, and there is no dependency between threads,
once they've dumped, they're free to continue (and possibly to dump
for another requester if needed). Finally, when the THREAD_DUMP
feature is disabled and the debug signal is not used, the requester
accesses the thread by itself like before.
For now we still have the buffer size limitation but it will be
addressed in future patches.
2023-05-04 10:23:51 -04:00
/* dump not started, will start *after* ha_thread_dump_one(),
2023-05-04 13:19:04 -04:00
* ha_panic and ha_backtrace_to_stderr
2021-01-22 07:52:41 -05:00
*/
2023-05-04 13:19:04 -04:00
if ( addr = = ha_panic | |
MEDIUM: debug: simplify the thread dump mechanism
The thread dump mechanism that is used by "show threads" and by the
panic dump is overly complicated due to an initial misdesign. It
firsts wakes all threads, then serializes their dumps, then releases
them, while taking extreme care not to face colliding dumps. In fact
this is not what we need and it reached a limit where big machines
cannot dump all their threads anymore due to buffer size limitations.
What is needed instead is to be able to dump *one* thread, and to let
the requester iterate on all threads.
That's what this patch does. It adds the thread_dump_buffer to the
struct thread_ctx so that the requester offers the buffer to the
thread that is about to be dumped. This buffer also serves as a lock.
A thread at rest has a NULL, a valid pointer indicates the thread is
using it, and 0x1 (NULL+1) is used by the dumped thread to tell the
requester it's done. This makes sure that a given thread is dumped
once at a time. In addition to this, the calling thread decides
whether it accesses the thread by itself or via the debug signal
handler, in order to get a backtrace. This is much saner because the
calling thread is free to do whatever it wants with the buffer after
each thread is dumped, and there is no dependency between threads,
once they've dumped, they're free to continue (and possibly to dump
for another requester if needed). Finally, when the THREAD_DUMP
feature is disabled and the debug signal is not used, the requester
accesses the thread by itself like before.
For now we still have the buffer size limitation but it will be
addressed in future patches.
2023-05-04 10:23:51 -04:00
addr = = ha_backtrace_to_stderr | | addr = = ha_thread_dump_one )
2021-01-22 08:48:34 -05:00
dump + + ;
2021-01-22 07:52:41 -05:00
* buf = bak ;
continue ;
}
2021-01-22 08:48:34 -05:00
if ( ( dump & 3 ) = = 1 ) {
2021-01-22 07:52:41 -05:00
/* starting */
2023-05-04 13:19:04 -04:00
if ( addr = = ha_panic | |
MEDIUM: debug: simplify the thread dump mechanism
The thread dump mechanism that is used by "show threads" and by the
panic dump is overly complicated due to an initial misdesign. It
firsts wakes all threads, then serializes their dumps, then releases
them, while taking extreme care not to face colliding dumps. In fact
this is not what we need and it reached a limit where big machines
cannot dump all their threads anymore due to buffer size limitations.
What is needed instead is to be able to dump *one* thread, and to let
the requester iterate on all threads.
That's what this patch does. It adds the thread_dump_buffer to the
struct thread_ctx so that the requester offers the buffer to the
thread that is about to be dumped. This buffer also serves as a lock.
A thread at rest has a NULL, a valid pointer indicates the thread is
using it, and 0x1 (NULL+1) is used by the dumped thread to tell the
requester it's done. This makes sure that a given thread is dumped
once at a time. In addition to this, the calling thread decides
whether it accesses the thread by itself or via the debug signal
handler, in order to get a backtrace. This is much saner because the
calling thread is free to do whatever it wants with the buffer after
each thread is dumped, and there is no dependency between threads,
once they've dumped, they're free to continue (and possibly to dump
for another requester if needed). Finally, when the THREAD_DUMP
feature is disabled and the debug signal is not used, the requester
accesses the thread by itself like before.
For now we still have the buffer size limitation but it will be
addressed in future patches.
2023-05-04 10:23:51 -04:00
addr = = ha_backtrace_to_stderr | | addr = = ha_thread_dump_one ) {
2021-01-22 07:52:41 -05:00
* buf = bak ;
continue ;
}
2021-01-22 08:48:34 -05:00
dump + + ;
2021-01-22 07:52:41 -05:00
}
2021-01-22 08:48:34 -05:00
if ( ( dump & 3 ) = = 2 ) {
/* still dumping */
if ( dump = = 6 ) {
/* we only stop *after* main and we must send the LF */
if ( addr = = main ) {
j = nptrs ;
dump + + ;
}
}
else if ( addr = = run_poll_loop | | addr = = main | | addr = = run_tasks_from_lists ) {
dump + + ;
2021-01-22 07:52:41 -05:00
* buf = bak ;
break ;
}
}
/* OK, line dumped */
chunk_appendf ( buf , " \n " ) ;
}
}
2021-01-22 08:12:27 -05:00
/* dump a backtrace of current thread's stack to stderr. */
2022-05-06 09:16:19 -04:00
void ha_backtrace_to_stderr ( void )
2021-01-22 08:12:27 -05:00
{
char area [ 2048 ] ;
struct buffer b = b_make ( area , sizeof ( area ) , 0 , 0 ) ;
2021-01-22 08:48:34 -05:00
ha_dump_backtrace ( & b , " " , 4 ) ;
2021-01-22 08:12:27 -05:00
if ( b . data )
2021-01-22 09:58:26 -05:00
DISGUISE ( write ( 2 , b . area , b . data ) ) ;
2021-01-22 08:12:27 -05:00
}
MEDIUM: debug: simplify the thread dump mechanism
The thread dump mechanism that is used by "show threads" and by the
panic dump is overly complicated due to an initial misdesign. It
firsts wakes all threads, then serializes their dumps, then releases
them, while taking extreme care not to face colliding dumps. In fact
this is not what we need and it reached a limit where big machines
cannot dump all their threads anymore due to buffer size limitations.
What is needed instead is to be able to dump *one* thread, and to let
the requester iterate on all threads.
That's what this patch does. It adds the thread_dump_buffer to the
struct thread_ctx so that the requester offers the buffer to the
thread that is about to be dumped. This buffer also serves as a lock.
A thread at rest has a NULL, a valid pointer indicates the thread is
using it, and 0x1 (NULL+1) is used by the dumped thread to tell the
requester it's done. This makes sure that a given thread is dumped
once at a time. In addition to this, the calling thread decides
whether it accesses the thread by itself or via the debug signal
handler, in order to get a backtrace. This is much saner because the
calling thread is free to do whatever it wants with the buffer after
each thread is dumped, and there is no dependency between threads,
once they've dumped, they're free to continue (and possibly to dump
for another requester if needed). Finally, when the THREAD_DUMP
feature is disabled and the debug signal is not used, the requester
accesses the thread by itself like before.
For now we still have the buffer size limitation but it will be
addressed in future patches.
2023-05-04 10:23:51 -04:00
/* Dumps to the thread's buffer some known information for the desired thread,
* and optionally extra info when it ' s safe to do so ( current thread or
* isolated ) . The dump will be appended to the buffer , so the caller is
* responsible for preliminary initializing it . The < from_signal > argument will
* indicate if the function is called from the debug signal handler , indicating
* the thread was dumped upon request from another one , otherwise if the thread
* it the current one , a star ( ' * ' ) will be displayed in front of the thread to
* indicate the requesting one . Any stuck thread is also prefixed with a ' > ' .
* The caller is responsible for atomically setting up the thread ' s dump buffer
* to point to a valid buffer with enough room . Output will be truncated if it
2024-10-19 07:53:39 -04:00
* does not fit . When the dump is complete , the dump buffer will have bit 0 set
* to 1 to tell the caller it ' s done , and the caller will then change that value
* to indicate it ' s done once the contents are collected .
2019-05-16 11:44:30 -04:00
*/
MEDIUM: debug: simplify the thread dump mechanism
The thread dump mechanism that is used by "show threads" and by the
panic dump is overly complicated due to an initial misdesign. It
firsts wakes all threads, then serializes their dumps, then releases
them, while taking extreme care not to face colliding dumps. In fact
this is not what we need and it reached a limit where big machines
cannot dump all their threads anymore due to buffer size limitations.
What is needed instead is to be able to dump *one* thread, and to let
the requester iterate on all threads.
That's what this patch does. It adds the thread_dump_buffer to the
struct thread_ctx so that the requester offers the buffer to the
thread that is about to be dumped. This buffer also serves as a lock.
A thread at rest has a NULL, a valid pointer indicates the thread is
using it, and 0x1 (NULL+1) is used by the dumped thread to tell the
requester it's done. This makes sure that a given thread is dumped
once at a time. In addition to this, the calling thread decides
whether it accesses the thread by itself or via the debug signal
handler, in order to get a backtrace. This is much saner because the
calling thread is free to do whatever it wants with the buffer after
each thread is dumped, and there is no dependency between threads,
once they've dumped, they're free to continue (and possibly to dump
for another requester if needed). Finally, when the THREAD_DUMP
feature is disabled and the debug signal is not used, the requester
accesses the thread by itself like before.
For now we still have the buffer size limitation but it will be
addressed in future patches.
2023-05-04 10:23:51 -04:00
void ha_thread_dump_one ( int thr , int from_signal )
2019-05-16 11:44:30 -04:00
{
MEDIUM: debug: simplify the thread dump mechanism
The thread dump mechanism that is used by "show threads" and by the
panic dump is overly complicated due to an initial misdesign. It
firsts wakes all threads, then serializes their dumps, then releases
them, while taking extreme care not to face colliding dumps. In fact
this is not what we need and it reached a limit where big machines
cannot dump all their threads anymore due to buffer size limitations.
What is needed instead is to be able to dump *one* thread, and to let
the requester iterate on all threads.
That's what this patch does. It adds the thread_dump_buffer to the
struct thread_ctx so that the requester offers the buffer to the
thread that is about to be dumped. This buffer also serves as a lock.
A thread at rest has a NULL, a valid pointer indicates the thread is
using it, and 0x1 (NULL+1) is used by the dumped thread to tell the
requester it's done. This makes sure that a given thread is dumped
once at a time. In addition to this, the calling thread decides
whether it accesses the thread by itself or via the debug signal
handler, in order to get a backtrace. This is much saner because the
calling thread is free to do whatever it wants with the buffer after
each thread is dumped, and there is no dependency between threads,
once they've dumped, they're free to continue (and possibly to dump
for another requester if needed). Finally, when the THREAD_DUMP
feature is disabled and the debug signal is not used, the requester
accesses the thread by itself like before.
For now we still have the buffer size limitation but it will be
addressed in future patches.
2023-05-04 10:23:51 -04:00
struct buffer * buf = HA_ATOMIC_LOAD ( & ha_thread_ctx [ thr ] . thread_dump_buffer ) ;
2023-05-07 09:02:30 -04:00
unsigned long __maybe_unused thr_bit = ha_thread_info [ thr ] . ltid_bit ;
int __maybe_unused tgrp = ha_thread_info [ thr ] . tgid ;
2021-09-30 12:28:49 -04:00
unsigned long long p = ha_thread_ctx [ thr ] . prev_cpu_time ;
2021-10-08 09:09:17 -04:00
unsigned long long n = now_cpu_time_thread ( thr ) ;
2021-09-30 12:48:37 -04:00
int stuck = ! ! ( ha_thread_ctx [ thr ] . flags & TH_FL_STUCK ) ;
2019-05-16 11:44:30 -04:00
2019-05-17 04:36:08 -04:00
chunk_appendf ( buf ,
2020-05-01 06:26:03 -04:00
" %c%cThread %-2u: id=0x%llx act=%d glob=%d wq=%d rq=%d tl=%d tlsz=%d rqsz=%d \n "
2021-09-13 13:24:47 -04:00
" %2u/%-2u stuck=%d prof=%d " ,
MEDIUM: debug: simplify the thread dump mechanism
The thread dump mechanism that is used by "show threads" and by the
panic dump is overly complicated due to an initial misdesign. It
firsts wakes all threads, then serializes their dumps, then releases
them, while taking extreme care not to face colliding dumps. In fact
this is not what we need and it reached a limit where big machines
cannot dump all their threads anymore due to buffer size limitations.
What is needed instead is to be able to dump *one* thread, and to let
the requester iterate on all threads.
That's what this patch does. It adds the thread_dump_buffer to the
struct thread_ctx so that the requester offers the buffer to the
thread that is about to be dumped. This buffer also serves as a lock.
A thread at rest has a NULL, a valid pointer indicates the thread is
using it, and 0x1 (NULL+1) is used by the dumped thread to tell the
requester it's done. This makes sure that a given thread is dumped
once at a time. In addition to this, the calling thread decides
whether it accesses the thread by itself or via the debug signal
handler, in order to get a backtrace. This is much saner because the
calling thread is free to do whatever it wants with the buffer after
each thread is dumped, and there is no dependency between threads,
once they've dumped, they're free to continue (and possibly to dump
for another requester if needed). Finally, when the THREAD_DUMP
feature is disabled and the debug signal is not used, the requester
accesses the thread by itself like before.
For now we still have the buffer size limitation but it will be
addressed in future patches.
2023-05-04 10:23:51 -04:00
( thr = = tid & & ! from_signal ) ? ' * ' : ' ' , stuck ? ' > ' : ' ' , thr + 1 ,
MINOR: threads: export the POSIX thread ID in panic dumps
It is very difficult to map a panic dump against a gdb thread dump
because the thread numbers do not match. However gdb provides the
pthread ID but this one is supposed to be opaque and not to be cast
to a scalar.
This patch provides a fnuction, ha_get_pthread_id() which retrieves
the pthread ID of the indicated thread and casts it to an unsigned
long long so as to lose the least possible amount of information from
it. This is done cleanly using a union to maintain alignment so as
long as these IDs are stored on 1..8 bytes they will be properly
reported. This ID is now presented in the panic dumps so it now
becomes possible to map these threads. When threads are disabled,
zero is returned. For example, this is a panic dump:
Thread 1 is about to kill the process.
*>Thread 1 : id=0x7fe92b825180 act=0 glob=0 wq=1 rq=0 tl=0 tlsz=0 rqsz=0
stuck=1 prof=0 harmless=0 wantrdv=0
cpu_ns: poll=5119122 now=2009446995 diff=2004327873
curr_task=0xc99bf0 (task) calls=4 last=0
fct=0x592440(task_run_applet) ctx=0xca9c50(<CLI>)
strm=0xc996a0 src=unix fe=GLOBAL be=GLOBAL dst=<CLI>
rqf=848202 rqa=0 rpf=80048202 rpa=0 sif=EST,200008 sib=EST,204018
af=(nil),0 csf=0xc9ba40,8200
ab=0xca9c50,4 csb=(nil),0
cof=0xbf0e50,1300:PASS(0xc9cee0)/RAW((nil))/unix_stream(20)
cob=(nil),0:NONE((nil))/NONE((nil))/NONE(0)
call trace(20):
| 0x59e4cf [48 83 c4 10 5b 5d 41 5c]: wdt_handler+0xff/0x10c
| 0x7fe92c170690 [48 c7 c0 0f 00 00 00 0f]: libpthread:+0x13690
| 0x7ffce29519d9 [48 c1 e2 20 48 09 d0 48]: linux-vdso:+0x9d9
| 0x7ffce2951d54 [eb d9 f3 90 e9 1c ff ff]: linux-vdso:__vdso_gettimeofday+0x104/0x133
| 0x57b484 [48 89 e6 48 8d 7c 24 10]: main+0x157114
| 0x50ee6a [85 c0 75 76 48 8b 55 38]: main+0xeaafa
| 0x50f69c [48 63 54 24 20 85 c0 0f]: main+0xeb32c
| 0x59252c [48 c7 c6 d8 ff ff ff 44]: task_run_applet+0xec/0x88c
Thread 2 : id=0x7fe92b6e6700 act=0 glob=0 wq=0 rq=0 tl=0 tlsz=0 rqsz=0
stuck=0 prof=0 harmless=1 wantrdv=0
cpu_ns: poll=786738 now=1086955 diff=300217
curr_task=0
Thread 3 : id=0x7fe92aee5700 act=0 glob=0 wq=0 rq=0 tl=0 tlsz=0 rqsz=0
stuck=0 prof=0 harmless=1 wantrdv=0
cpu_ns: poll=828056 now=1129738 diff=301682
curr_task=0
Thread 4 : id=0x7fe92a6e4700 act=0 glob=0 wq=0 rq=0 tl=0 tlsz=0 rqsz=0
stuck=0 prof=0 harmless=1 wantrdv=0
cpu_ns: poll=818900 now=1153551 diff=334651
curr_task=0
And this is the gdb output:
(gdb) info thr
Id Target Id Frame
* 1 Thread 0x7fe92b825180 (LWP 15234) 0x00007fe92ba81d6b in raise () from /lib64/libc.so.6
2 Thread 0x7fe92b6e6700 (LWP 15235) 0x00007fe92bb56a56 in epoll_wait () from /lib64/libc.so.6
3 Thread 0x7fe92a6e4700 (LWP 15237) 0x00007fe92bb56a56 in epoll_wait () from /lib64/libc.so.6
4 Thread 0x7fe92aee5700 (LWP 15236) 0x00007fe92bb56a56 in epoll_wait () from /lib64/libc.so.6
We can clearly see that while threads 1 and 2 are the same, gdb's
threads 3 and 4 respectively are haproxy's threads 4 and 3.
This may be backported to 2.0 as it removes some confusion in github issues.
2020-05-01 05:28:49 -04:00
ha_get_pthread_id ( thr ) ,
2019-05-29 13:22:43 -04:00
thread_has_tasks ( ) ,
2022-06-16 09:59:36 -04:00
! eb_is_empty ( & ha_thread_ctx [ thr ] . rqueue_shared ) ,
2021-10-01 05:30:33 -04:00
! eb_is_empty ( & ha_thread_ctx [ thr ] . timers ) ,
! eb_is_empty ( & ha_thread_ctx [ thr ] . rqueue ) ,
! ( LIST_ISEMPTY ( & ha_thread_ctx [ thr ] . tasklets [ TL_URGENT ] ) & &
LIST_ISEMPTY ( & ha_thread_ctx [ thr ] . tasklets [ TL_NORMAL ] ) & &
LIST_ISEMPTY ( & ha_thread_ctx [ thr ] . tasklets [ TL_BULK ] ) & &
MT_LIST_ISEMPTY ( & ha_thread_ctx [ thr ] . shared_tasklet_list ) ) ,
ha_thread_ctx [ thr ] . tasks_in_list ,
ha_thread_ctx [ thr ] . rq_total ,
2022-06-28 04:49:57 -04:00
ha_thread_info [ thr ] . tgid , ha_thread_info [ thr ] . ltid + 1 ,
2019-05-22 01:06:44 -04:00
stuck ,
2023-05-04 05:30:55 -04:00
! ! ( ha_thread_ctx [ thr ] . flags & TH_FL_TASK_PROFILING ) ) ;
2019-05-16 11:44:30 -04:00
2023-05-07 09:02:30 -04:00
# if defined(USE_THREAD)
2019-05-17 04:36:08 -04:00
chunk_appendf ( buf ,
2023-05-04 05:30:55 -04:00
" harmless=%d isolated=%d " ,
2022-06-27 10:02:24 -04:00
! ! ( _HA_ATOMIC_LOAD ( & ha_tgroup_ctx [ tgrp - 1 ] . threads_harmless ) & thr_bit ) ,
2023-05-04 05:30:55 -04:00
isolated_thread = = thr ) ;
2023-05-07 09:02:30 -04:00
# endif
2019-05-16 11:44:30 -04:00
2019-05-17 04:36:08 -04:00
chunk_appendf ( buf , " \n " ) ;
2019-05-20 14:52:20 -04:00
chunk_appendf ( buf , " cpu_ns: poll=%llu now=%llu diff=%llu \n " , p , n , n - p ) ;
2019-05-16 11:44:30 -04:00
2021-09-13 13:24:47 -04:00
/* this is the end of what we can dump from outside the current thread */
2019-05-16 11:44:30 -04:00
MEDIUM: debug: simplify the thread dump mechanism
The thread dump mechanism that is used by "show threads" and by the
panic dump is overly complicated due to an initial misdesign. It
firsts wakes all threads, then serializes their dumps, then releases
them, while taking extreme care not to face colliding dumps. In fact
this is not what we need and it reached a limit where big machines
cannot dump all their threads anymore due to buffer size limitations.
What is needed instead is to be able to dump *one* thread, and to let
the requester iterate on all threads.
That's what this patch does. It adds the thread_dump_buffer to the
struct thread_ctx so that the requester offers the buffer to the
thread that is about to be dumped. This buffer also serves as a lock.
A thread at rest has a NULL, a valid pointer indicates the thread is
using it, and 0x1 (NULL+1) is used by the dumped thread to tell the
requester it's done. This makes sure that a given thread is dumped
once at a time. In addition to this, the calling thread decides
whether it accesses the thread by itself or via the debug signal
handler, in order to get a backtrace. This is much saner because the
calling thread is free to do whatever it wants with the buffer after
each thread is dumped, and there is no dependency between threads,
once they've dumped, they're free to continue (and possibly to dump
for another requester if needed). Finally, when the THREAD_DUMP
feature is disabled and the debug signal is not used, the requester
accesses the thread by itself like before.
For now we still have the buffer size limitation but it will be
addressed in future patches.
2023-05-04 10:23:51 -04:00
if ( thr ! = tid & & ! thread_isolated ( ) )
goto leave ;
2019-05-16 11:44:30 -04:00
2019-05-17 04:36:08 -04:00
chunk_appendf ( buf , " curr_task= " ) ;
2021-10-01 05:30:33 -04:00
ha_task_dump ( buf , th_ctx - > current , " " ) ;
2020-03-03 09:40:23 -05:00
2024-10-24 09:14:55 -04:00
if ( thr = = tid & & ! ( HA_ATOMIC_LOAD ( & tg_ctx - > threads_idle ) & ti - > ltid_bit ) ) {
/* only dump the stack of active threads */
2023-10-25 09:02:59 -04:00
# ifdef USE_LUA
if ( th_ctx - > current & &
th_ctx - > current - > process = = process_stream & & th_ctx - > current - > context ) {
const struct stream * s = ( const struct stream * ) th_ctx - > current - > context ;
BUG/MEDIUM: hlua: streams don't support mixing lua-load with lua-load-per-thread (2nd try)
While trying to reproduce another crash case involving lua filters
reported by @bgrooot on GH #2467, we found out that mixing filters loaded
from different contexts ('lua-load' vs 'lua-load-per-thread') for the same
stream isn't supported and may even cause the process to crash.
Historically, mixing lua-load and lua-load-per-threads for a stream wasn't
supported, but this changed thanks to 0913386 ("BUG/MEDIUM: hlua: streams
don't support mixing lua-load with lua-load-per-thread").
However, the above fix didn't consider lua filters's use-case properly:
unlike lua fetches, actions or even services, lua filters don't simply
use the stream hlua context as a "temporary" hlua running context to
process some hlua code. For fetches, actions.. hlua executions are
processed sequentially, so we simply reuse the hlua context from the
previous action/fetch to run the next one (this allows to bypass memory
allocations and initialization, thus it increases performance), unless
we need to run on a different hlua state-id, in which case we perform a
reset of the hlua context.
But this cannot work with filters: indeed, once registered, a filter will
last for the whole stream duration. It means that the filter will rely
on the stream hlua context from ->attach() to ->detach(). And here is the
catch, if for the same stream we register 2 lua filters from different
contexts ('lua-load' + 'lua-load-per-thread'), then we have an issue,
because the hlua stream will be re-created each time we switch between
runtime contexts, which means each time we switch between the filters (may
happen for each stream processing step), and since lua filters rely on the
stream hlua to carry context between filtering steps, this context will be
lost upon a switch. Given that lua filters code was not designed with that
in mind, it would confuse the code and cause unexpected behaviors ranging
from lua errors to crashing process.
So here we take another approach: instead of re-creating the stream hlua
context each time we switch between "global" and "per-thread" runtime
context, let's have both of them inside the stream directly as initially
suggested by Christopher back then when talked about the original issue.
For this we leverage hlua_stream_ctx_prepare() and hlua_stream_ctx_get()
helper functions which return the proper hlua context for a given stream
and state_id combination.
As for debugging infos reported after ha_panic(), we check for both hlua
runtime contexts to check if one of them was active when the panic occured
(only 1 runtime ctx per stream may be active at a given time).
This should be backported to all stable versions with 0913386
("BUG/MEDIUM: hlua: streams don't support mixing lua-load with lua-load-per-thread")
This commit depends on:
- "DEBUG: lua: precisely identify if stream is stuck inside lua or not"
[for versions < 2.9 the ha_thread_dump_one() part should be skipped]
- "MINOR: hlua: use accessors for stream hlua ctx"
For 2.4, the filters API didn't exist. However it may be a good idea to
backport it anyway because ->set_priv()/->get_priv() from tcp/http lua
applets may also be affected by this bug, plus it will ease code
maintenance. Of course, filters-related parts should be skipped in this
case.
2024-03-12 12:05:54 -04:00
struct hlua * hlua = NULL ;
2023-10-25 09:02:59 -04:00
BUG/MEDIUM: hlua: streams don't support mixing lua-load with lua-load-per-thread (2nd try)
While trying to reproduce another crash case involving lua filters
reported by @bgrooot on GH #2467, we found out that mixing filters loaded
from different contexts ('lua-load' vs 'lua-load-per-thread') for the same
stream isn't supported and may even cause the process to crash.
Historically, mixing lua-load and lua-load-per-threads for a stream wasn't
supported, but this changed thanks to 0913386 ("BUG/MEDIUM: hlua: streams
don't support mixing lua-load with lua-load-per-thread").
However, the above fix didn't consider lua filters's use-case properly:
unlike lua fetches, actions or even services, lua filters don't simply
use the stream hlua context as a "temporary" hlua running context to
process some hlua code. For fetches, actions.. hlua executions are
processed sequentially, so we simply reuse the hlua context from the
previous action/fetch to run the next one (this allows to bypass memory
allocations and initialization, thus it increases performance), unless
we need to run on a different hlua state-id, in which case we perform a
reset of the hlua context.
But this cannot work with filters: indeed, once registered, a filter will
last for the whole stream duration. It means that the filter will rely
on the stream hlua context from ->attach() to ->detach(). And here is the
catch, if for the same stream we register 2 lua filters from different
contexts ('lua-load' + 'lua-load-per-thread'), then we have an issue,
because the hlua stream will be re-created each time we switch between
runtime contexts, which means each time we switch between the filters (may
happen for each stream processing step), and since lua filters rely on the
stream hlua to carry context between filtering steps, this context will be
lost upon a switch. Given that lua filters code was not designed with that
in mind, it would confuse the code and cause unexpected behaviors ranging
from lua errors to crashing process.
So here we take another approach: instead of re-creating the stream hlua
context each time we switch between "global" and "per-thread" runtime
context, let's have both of them inside the stream directly as initially
suggested by Christopher back then when talked about the original issue.
For this we leverage hlua_stream_ctx_prepare() and hlua_stream_ctx_get()
helper functions which return the proper hlua context for a given stream
and state_id combination.
As for debugging infos reported after ha_panic(), we check for both hlua
runtime contexts to check if one of them was active when the panic occured
(only 1 runtime ctx per stream may be active at a given time).
This should be backported to all stable versions with 0913386
("BUG/MEDIUM: hlua: streams don't support mixing lua-load with lua-load-per-thread")
This commit depends on:
- "DEBUG: lua: precisely identify if stream is stuck inside lua or not"
[for versions < 2.9 the ha_thread_dump_one() part should be skipped]
- "MINOR: hlua: use accessors for stream hlua ctx"
For 2.4, the filters API didn't exist. However it may be a good idea to
backport it anyway because ->set_priv()/->get_priv() from tcp/http lua
applets may also be affected by this bug, plus it will ease code
maintenance. Of course, filters-related parts should be skipped in this
case.
2024-03-12 12:05:54 -04:00
if ( s ) {
if ( s - > hlua [ 0 ] & & HLUA_IS_BUSY ( s - > hlua [ 0 ] ) )
hlua = s - > hlua [ 0 ] ;
else if ( s - > hlua [ 1 ] & & HLUA_IS_BUSY ( s - > hlua [ 1 ] ) )
hlua = s - > hlua [ 1 ] ;
}
if ( hlua ) {
2023-10-25 09:02:59 -04:00
mark_tainted ( TAINTED_LUA_STUCK ) ;
if ( hlua - > state_id = = 0 )
mark_tainted ( TAINTED_LUA_STUCK_SHARED ) ;
}
}
# endif
2023-10-25 09:42:27 -04:00
if ( HA_ATOMIC_LOAD ( & pool_trim_in_progress ) )
mark_tainted ( TAINTED_MEM_TRIMMING_STUCK ) ;
2021-01-22 08:48:34 -05:00
ha_dump_backtrace ( buf , " " , 0 ) ;
2020-03-03 09:40:23 -05:00
}
MEDIUM: debug: simplify the thread dump mechanism
The thread dump mechanism that is used by "show threads" and by the
panic dump is overly complicated due to an initial misdesign. It
firsts wakes all threads, then serializes their dumps, then releases
them, while taking extreme care not to face colliding dumps. In fact
this is not what we need and it reached a limit where big machines
cannot dump all their threads anymore due to buffer size limitations.
What is needed instead is to be able to dump *one* thread, and to let
the requester iterate on all threads.
That's what this patch does. It adds the thread_dump_buffer to the
struct thread_ctx so that the requester offers the buffer to the
thread that is about to be dumped. This buffer also serves as a lock.
A thread at rest has a NULL, a valid pointer indicates the thread is
using it, and 0x1 (NULL+1) is used by the dumped thread to tell the
requester it's done. This makes sure that a given thread is dumped
once at a time. In addition to this, the calling thread decides
whether it accesses the thread by itself or via the debug signal
handler, in order to get a backtrace. This is much saner because the
calling thread is free to do whatever it wants with the buffer after
each thread is dumped, and there is no dependency between threads,
once they've dumped, they're free to continue (and possibly to dump
for another requester if needed). Finally, when the THREAD_DUMP
feature is disabled and the debug signal is not used, the requester
accesses the thread by itself like before.
For now we still have the buffer size limitation but it will be
addressed in future patches.
2023-05-04 10:23:51 -04:00
leave :
/* end of dump, setting the buffer to 0x1 will tell the caller we're done */
2024-10-24 09:04:25 -04:00
HA_ATOMIC_OR ( ( ulong * ) DISGUISE ( & ha_thread_ctx [ thr ] . thread_dump_buffer ) , 0x1UL ) ;
2019-05-16 11:44:30 -04:00
}
MEDIUM: debug: simplify the thread dump mechanism
The thread dump mechanism that is used by "show threads" and by the
panic dump is overly complicated due to an initial misdesign. It
firsts wakes all threads, then serializes their dumps, then releases
them, while taking extreme care not to face colliding dumps. In fact
this is not what we need and it reached a limit where big machines
cannot dump all their threads anymore due to buffer size limitations.
What is needed instead is to be able to dump *one* thread, and to let
the requester iterate on all threads.
That's what this patch does. It adds the thread_dump_buffer to the
struct thread_ctx so that the requester offers the buffer to the
thread that is about to be dumped. This buffer also serves as a lock.
A thread at rest has a NULL, a valid pointer indicates the thread is
using it, and 0x1 (NULL+1) is used by the dumped thread to tell the
requester it's done. This makes sure that a given thread is dumped
once at a time. In addition to this, the calling thread decides
whether it accesses the thread by itself or via the debug signal
handler, in order to get a backtrace. This is much saner because the
calling thread is free to do whatever it wants with the buffer after
each thread is dumped, and there is no dependency between threads,
once they've dumped, they're free to continue (and possibly to dump
for another requester if needed). Finally, when the THREAD_DUMP
feature is disabled and the debug signal is not used, the requester
accesses the thread by itself like before.
For now we still have the buffer size limitation but it will be
addressed in future patches.
2023-05-04 10:23:51 -04:00
/* Triggers a thread dump from thread <thr>, either directly if it's the
* current thread or if thread dump signals are not implemented , or by sending
* a signal if it ' s a remote one and the feature is supported . The buffer < buf >
* will get the dump appended , and the caller is responsible for making sure
2024-10-19 07:45:57 -04:00
* there is enough room otherwise some contents will be truncated . The function
2024-10-19 07:53:39 -04:00
* waits for the called thread to fill the buffer before returning ( or cancelling
* by reporting NULL ) . It does not release the called thread yet . It returns a
* pointer to the buffer used if the dump was done , otherwise NULL .
MEDIUM: debug: simplify the thread dump mechanism
The thread dump mechanism that is used by "show threads" and by the
panic dump is overly complicated due to an initial misdesign. It
firsts wakes all threads, then serializes their dumps, then releases
them, while taking extreme care not to face colliding dumps. In fact
this is not what we need and it reached a limit where big machines
cannot dump all their threads anymore due to buffer size limitations.
What is needed instead is to be able to dump *one* thread, and to let
the requester iterate on all threads.
That's what this patch does. It adds the thread_dump_buffer to the
struct thread_ctx so that the requester offers the buffer to the
thread that is about to be dumped. This buffer also serves as a lock.
A thread at rest has a NULL, a valid pointer indicates the thread is
using it, and 0x1 (NULL+1) is used by the dumped thread to tell the
requester it's done. This makes sure that a given thread is dumped
once at a time. In addition to this, the calling thread decides
whether it accesses the thread by itself or via the debug signal
handler, in order to get a backtrace. This is much saner because the
calling thread is free to do whatever it wants with the buffer after
each thread is dumped, and there is no dependency between threads,
once they've dumped, they're free to continue (and possibly to dump
for another requester if needed). Finally, when the THREAD_DUMP
feature is disabled and the debug signal is not used, the requester
accesses the thread by itself like before.
For now we still have the buffer size limitation but it will be
addressed in future patches.
2023-05-04 10:23:51 -04:00
*/
2024-10-19 07:53:39 -04:00
struct buffer * ha_thread_dump_fill ( struct buffer * buf , int thr )
MEDIUM: debug: simplify the thread dump mechanism
The thread dump mechanism that is used by "show threads" and by the
panic dump is overly complicated due to an initial misdesign. It
firsts wakes all threads, then serializes their dumps, then releases
them, while taking extreme care not to face colliding dumps. In fact
this is not what we need and it reached a limit where big machines
cannot dump all their threads anymore due to buffer size limitations.
What is needed instead is to be able to dump *one* thread, and to let
the requester iterate on all threads.
That's what this patch does. It adds the thread_dump_buffer to the
struct thread_ctx so that the requester offers the buffer to the
thread that is about to be dumped. This buffer also serves as a lock.
A thread at rest has a NULL, a valid pointer indicates the thread is
using it, and 0x1 (NULL+1) is used by the dumped thread to tell the
requester it's done. This makes sure that a given thread is dumped
once at a time. In addition to this, the calling thread decides
whether it accesses the thread by itself or via the debug signal
handler, in order to get a backtrace. This is much saner because the
calling thread is free to do whatever it wants with the buffer after
each thread is dumped, and there is no dependency between threads,
once they've dumped, they're free to continue (and possibly to dump
for another requester if needed). Finally, when the THREAD_DUMP
feature is disabled and the debug signal is not used, the requester
accesses the thread by itself like before.
For now we still have the buffer size limitation but it will be
addressed in future patches.
2023-05-04 10:23:51 -04:00
{
struct buffer * old = NULL ;
/* try to impose our dump buffer and to reserve the target thread's
* next dump for us .
*/
do {
if ( old )
ha_thread_relax ( ) ;
old = NULL ;
} while ( ! HA_ATOMIC_CAS ( & ha_thread_ctx [ thr ] . thread_dump_buffer , & old , buf ) ) ;
# ifdef USE_THREAD_DUMP
/* asking the remote thread to dump itself allows to get more details
* including a backtrace .
*/
if ( thr ! = tid )
ha_tkill ( thr , DEBUGSIG ) ;
else
# endif
ha_thread_dump_one ( thr , thr ! = tid ) ;
2024-10-19 07:53:39 -04:00
/* now wait for the dump to be done (or cancelled) */
while ( 1 ) {
old = HA_ATOMIC_LOAD ( & ha_thread_ctx [ thr ] . thread_dump_buffer ) ;
if ( ( ulong ) old & 0x1 )
break ;
if ( ! old )
return old ;
2024-10-19 07:45:57 -04:00
ha_thread_relax ( ) ;
2024-10-19 07:53:39 -04:00
}
return ( struct buffer * ) ( ( ulong ) old & ~ 0x1UL ) ;
2024-10-19 07:45:57 -04:00
}
2024-10-19 08:52:35 -04:00
/* Indicates to the called thread that the dumped data are collected by writing
* < buf > into the designated thread ' s dump buffer ( usually buf is NULL ) . It
* waits for the dump to be completed if it was not the case , and can also
* leave if the pointer is NULL ( e . g . if a thread has aborted ) .
2024-10-19 07:45:57 -04:00
*/
void ha_thread_dump_done ( struct buffer * buf , int thr )
{
struct buffer * old ;
2024-10-19 07:53:39 -04:00
/* now wait for the dump to be done or cancelled, and release it */
MEDIUM: debug: simplify the thread dump mechanism
The thread dump mechanism that is used by "show threads" and by the
panic dump is overly complicated due to an initial misdesign. It
firsts wakes all threads, then serializes their dumps, then releases
them, while taking extreme care not to face colliding dumps. In fact
this is not what we need and it reached a limit where big machines
cannot dump all their threads anymore due to buffer size limitations.
What is needed instead is to be able to dump *one* thread, and to let
the requester iterate on all threads.
That's what this patch does. It adds the thread_dump_buffer to the
struct thread_ctx so that the requester offers the buffer to the
thread that is about to be dumped. This buffer also serves as a lock.
A thread at rest has a NULL, a valid pointer indicates the thread is
using it, and 0x1 (NULL+1) is used by the dumped thread to tell the
requester it's done. This makes sure that a given thread is dumped
once at a time. In addition to this, the calling thread decides
whether it accesses the thread by itself or via the debug signal
handler, in order to get a backtrace. This is much saner because the
calling thread is free to do whatever it wants with the buffer after
each thread is dumped, and there is no dependency between threads,
once they've dumped, they're free to continue (and possibly to dump
for another requester if needed). Finally, when the THREAD_DUMP
feature is disabled and the debug signal is not used, the requester
accesses the thread by itself like before.
For now we still have the buffer size limitation but it will be
addressed in future patches.
2023-05-04 10:23:51 -04:00
do {
2024-10-19 07:45:57 -04:00
old = HA_ATOMIC_LOAD ( & ha_thread_ctx [ thr ] . thread_dump_buffer ) ;
2024-10-19 07:53:39 -04:00
if ( ! ( ( ulong ) old & 0x1 ) ) {
if ( ! old )
return ;
MEDIUM: debug: simplify the thread dump mechanism
The thread dump mechanism that is used by "show threads" and by the
panic dump is overly complicated due to an initial misdesign. It
firsts wakes all threads, then serializes their dumps, then releases
them, while taking extreme care not to face colliding dumps. In fact
this is not what we need and it reached a limit where big machines
cannot dump all their threads anymore due to buffer size limitations.
What is needed instead is to be able to dump *one* thread, and to let
the requester iterate on all threads.
That's what this patch does. It adds the thread_dump_buffer to the
struct thread_ctx so that the requester offers the buffer to the
thread that is about to be dumped. This buffer also serves as a lock.
A thread at rest has a NULL, a valid pointer indicates the thread is
using it, and 0x1 (NULL+1) is used by the dumped thread to tell the
requester it's done. This makes sure that a given thread is dumped
once at a time. In addition to this, the calling thread decides
whether it accesses the thread by itself or via the debug signal
handler, in order to get a backtrace. This is much saner because the
calling thread is free to do whatever it wants with the buffer after
each thread is dumped, and there is no dependency between threads,
once they've dumped, they're free to continue (and possibly to dump
for another requester if needed). Finally, when the THREAD_DUMP
feature is disabled and the debug signal is not used, the requester
accesses the thread by itself like before.
For now we still have the buffer size limitation but it will be
addressed in future patches.
2023-05-04 10:23:51 -04:00
ha_thread_relax ( ) ;
2024-10-19 07:45:57 -04:00
continue ;
}
2024-10-19 08:52:35 -04:00
} while ( ! HA_ATOMIC_CAS ( & ha_thread_ctx [ thr ] . thread_dump_buffer , & old , buf ) ) ;
MEDIUM: debug: simplify the thread dump mechanism
The thread dump mechanism that is used by "show threads" and by the
panic dump is overly complicated due to an initial misdesign. It
firsts wakes all threads, then serializes their dumps, then releases
them, while taking extreme care not to face colliding dumps. In fact
this is not what we need and it reached a limit where big machines
cannot dump all their threads anymore due to buffer size limitations.
What is needed instead is to be able to dump *one* thread, and to let
the requester iterate on all threads.
That's what this patch does. It adds the thread_dump_buffer to the
struct thread_ctx so that the requester offers the buffer to the
thread that is about to be dumped. This buffer also serves as a lock.
A thread at rest has a NULL, a valid pointer indicates the thread is
using it, and 0x1 (NULL+1) is used by the dumped thread to tell the
requester it's done. This makes sure that a given thread is dumped
once at a time. In addition to this, the calling thread decides
whether it accesses the thread by itself or via the debug signal
handler, in order to get a backtrace. This is much saner because the
calling thread is free to do whatever it wants with the buffer after
each thread is dumped, and there is no dependency between threads,
once they've dumped, they're free to continue (and possibly to dump
for another requester if needed). Finally, when the THREAD_DUMP
feature is disabled and the debug signal is not used, the requester
accesses the thread by itself like before.
For now we still have the buffer size limitation but it will be
addressed in future patches.
2023-05-04 10:23:51 -04:00
}
2019-05-16 11:44:30 -04:00
2019-05-17 04:36:08 -04:00
/* dumps into the buffer some information related to task <task> (which may
2019-05-16 11:44:30 -04:00
* either be a task or a tasklet , and prepend each line except the first one
2019-05-17 04:36:08 -04:00
* with < pfx > . The buffer is only appended and the first output starts by the
* pointer itself . The caller is responsible for making sure the task is not
* going to vanish during the dump .
2019-05-16 11:44:30 -04:00
*/
2019-05-17 04:36:08 -04:00
void ha_task_dump ( struct buffer * buf , const struct task * task , const char * pfx )
2019-05-16 11:44:30 -04:00
{
2019-05-22 03:43:09 -04:00
const struct stream * s = NULL ;
2019-08-21 08:12:19 -04:00
const struct appctx __maybe_unused * appctx = NULL ;
2019-08-21 08:16:02 -04:00
struct hlua __maybe_unused * hlua = NULL ;
2022-07-15 06:08:40 -04:00
const struct stconn * sc ;
2019-05-22 03:43:09 -04:00
2019-05-17 04:34:25 -04:00
if ( ! task ) {
2019-05-17 04:36:08 -04:00
chunk_appendf ( buf , " 0 \n " ) ;
2019-05-17 04:39:47 -04:00
return ;
}
2019-05-17 08:14:35 -04:00
if ( TASK_IS_TASKLET ( task ) )
chunk_appendf ( buf ,
" %p (tasklet) calls=%u \n " ,
task ,
task - > calls ) ;
else
chunk_appendf ( buf ,
" %p (task) calls=%u last=%llu%s \n " ,
task ,
task - > calls ,
2022-09-07 08:49:50 -04:00
task - > wake_date ? ( unsigned long long ) ( now_mono_time ( ) - task - > wake_date ) : 0 ,
task - > wake_date ? " ns ago " : " " ) ;
2019-05-16 11:44:30 -04:00
2020-03-03 11:13:02 -05:00
chunk_appendf ( buf , " %s fct=%p( " , pfx , task - > process ) ;
resolve_sym_name ( buf , NULL , task - > process ) ;
chunk_appendf ( buf , " ) ctx=%p " , task - > context ) ;
2019-05-22 03:43:09 -04:00
2019-08-21 08:12:19 -04:00
if ( task - > process = = task_run_applet & & ( appctx = task - > context ) )
chunk_appendf ( buf , " (%s) \n " , appctx - > applet - > name ) ;
else
chunk_appendf ( buf , " \n " ) ;
2019-05-22 03:43:09 -04:00
if ( task - > process = = process_stream & & task - > context )
s = ( struct stream * ) task - > context ;
2022-07-15 06:08:40 -04:00
else if ( task - > process = = task_run_applet & & task - > context & & ( sc = appctx_sc ( ( struct appctx * ) task - > context ) ) )
s = sc_strm ( sc ) ;
2022-05-18 12:06:53 -04:00
else if ( task - > process = = sc_conn_io_cb & & task - > context )
2022-05-18 10:10:52 -04:00
s = sc_strm ( ( ( struct stconn * ) task - > context ) ) ;
2019-05-22 03:43:09 -04:00
2023-09-29 10:43:07 -04:00
if ( s ) {
chunk_appendf ( buf , " %sstream= " , pfx ) ;
2023-09-29 02:39:21 -04:00
strm_dump_to_buffer ( buf , s , pfx , HA_ATOMIC_LOAD ( & global . anon_key ) ) ;
2023-09-29 10:43:07 -04:00
}
2019-08-21 08:16:02 -04:00
# ifdef USE_LUA
hlua = NULL ;
BUG/MEDIUM: hlua: streams don't support mixing lua-load with lua-load-per-thread (2nd try)
While trying to reproduce another crash case involving lua filters
reported by @bgrooot on GH #2467, we found out that mixing filters loaded
from different contexts ('lua-load' vs 'lua-load-per-thread') for the same
stream isn't supported and may even cause the process to crash.
Historically, mixing lua-load and lua-load-per-threads for a stream wasn't
supported, but this changed thanks to 0913386 ("BUG/MEDIUM: hlua: streams
don't support mixing lua-load with lua-load-per-thread").
However, the above fix didn't consider lua filters's use-case properly:
unlike lua fetches, actions or even services, lua filters don't simply
use the stream hlua context as a "temporary" hlua running context to
process some hlua code. For fetches, actions.. hlua executions are
processed sequentially, so we simply reuse the hlua context from the
previous action/fetch to run the next one (this allows to bypass memory
allocations and initialization, thus it increases performance), unless
we need to run on a different hlua state-id, in which case we perform a
reset of the hlua context.
But this cannot work with filters: indeed, once registered, a filter will
last for the whole stream duration. It means that the filter will rely
on the stream hlua context from ->attach() to ->detach(). And here is the
catch, if for the same stream we register 2 lua filters from different
contexts ('lua-load' + 'lua-load-per-thread'), then we have an issue,
because the hlua stream will be re-created each time we switch between
runtime contexts, which means each time we switch between the filters (may
happen for each stream processing step), and since lua filters rely on the
stream hlua to carry context between filtering steps, this context will be
lost upon a switch. Given that lua filters code was not designed with that
in mind, it would confuse the code and cause unexpected behaviors ranging
from lua errors to crashing process.
So here we take another approach: instead of re-creating the stream hlua
context each time we switch between "global" and "per-thread" runtime
context, let's have both of them inside the stream directly as initially
suggested by Christopher back then when talked about the original issue.
For this we leverage hlua_stream_ctx_prepare() and hlua_stream_ctx_get()
helper functions which return the proper hlua context for a given stream
and state_id combination.
As for debugging infos reported after ha_panic(), we check for both hlua
runtime contexts to check if one of them was active when the panic occured
(only 1 runtime ctx per stream may be active at a given time).
This should be backported to all stable versions with 0913386
("BUG/MEDIUM: hlua: streams don't support mixing lua-load with lua-load-per-thread")
This commit depends on:
- "DEBUG: lua: precisely identify if stream is stuck inside lua or not"
[for versions < 2.9 the ha_thread_dump_one() part should be skipped]
- "MINOR: hlua: use accessors for stream hlua ctx"
For 2.4, the filters API didn't exist. However it may be a good idea to
backport it anyway because ->set_priv()/->get_priv() from tcp/http lua
applets may also be affected by this bug, plus it will ease code
maintenance. Of course, filters-related parts should be skipped in this
case.
2024-03-12 12:05:54 -04:00
if ( s & & ( ( s - > hlua [ 0 ] & & HLUA_IS_BUSY ( s - > hlua [ 0 ] ) ) | |
( s - > hlua [ 1 ] & & HLUA_IS_BUSY ( s - > hlua [ 1 ] ) ) ) ) {
hlua = ( s - > hlua [ 0 ] & & HLUA_IS_BUSY ( s - > hlua [ 0 ] ) ) ? s - > hlua [ 0 ] : s - > hlua [ 1 ] ;
2019-08-21 08:16:02 -04:00
chunk_appendf ( buf , " %sCurrent executing Lua from a stream analyser -- " , pfx ) ;
}
else if ( task - > process = = hlua_process_task & & ( hlua = task - > context ) ) {
chunk_appendf ( buf , " %sCurrent executing a Lua task -- " , pfx ) ;
}
else if ( task - > process = = task_run_applet & & ( appctx = task - > context ) & &
2022-05-06 08:07:13 -04:00
( appctx - > applet - > fct = = hlua_applet_tcp_fct ) ) {
2019-08-21 08:16:02 -04:00
chunk_appendf ( buf , " %sCurrent executing a Lua TCP service -- " , pfx ) ;
}
else if ( task - > process = = task_run_applet & & ( appctx = task - > context ) & &
2022-05-06 08:26:10 -04:00
( appctx - > applet - > fct = = hlua_applet_http_fct ) ) {
2019-08-21 08:16:02 -04:00
chunk_appendf ( buf , " %sCurrent executing a Lua HTTP service -- " , pfx ) ;
}
2020-07-24 13:08:05 -04:00
if ( hlua & & hlua - > T ) {
2021-03-24 09:52:24 -04:00
chunk_appendf ( buf , " stack traceback: \n " ) ;
append_prefixed_str ( buf , hlua_traceback ( hlua - > T , " \n " ) , pfx , ' \n ' , 0 ) ;
2019-08-21 08:16:02 -04:00
}
2023-05-04 10:28:30 -04:00
/* we may need to terminate the current line */
if ( * b_peek ( buf , b_data ( buf ) - 1 ) ! = ' \n ' )
2020-07-24 13:08:05 -04:00
b_putchr ( buf , ' \n ' ) ;
2019-08-21 08:16:02 -04:00
# endif
2019-05-16 11:44:30 -04:00
}
/* This function dumps all profiling settings. It returns 0 if the output
* buffer is full and it needs to be called again , otherwise non - zero .
*/
static int cli_io_handler_show_threads ( struct appctx * appctx )
{
BUG/MEDIUM: debug/cli: fix "show threads" crashing with low thread counts
The "show threads" command introduced early in the 2.0 dev cycle uses
appctx->st1 to store its context (the number of the next thread to dump).
It goes back to an era where contexts were shared between the various
applets and the CLI's command handlers.
In fact it was already not good by then because st1 could possibly have
APPCTX_CLI_ST1_PAYLOAD (2) in it, that would make the dmup start at
thread 2, though it was extremely unlikely.
When contexts were finally cleaned up and moved to their own storage,
this one was overlooked, maybe due to using st1 instead of st2 like
most others. So it continues to rely on st1, and more recently some
new flags were appended, one of which is APPCTX_CLI_ST1_LASTCMD (16)
and is always there. This results in "show threads" to believe it must
start do dump from thread 16, and if this thread is not present, it can
simply crash the process. A tiny reproducer is:
global
nbthread 1
stats socket /tmp/sock1 level admin mode 666
$ socat /tmp/sock1 - <<< "show threads"
The fix for modern versions simply consists in assigning a context to
this command from the applet storage. We're using a single int, no need
for a struct, an int* will do it. That's valid till 2.6.
Prior to 2.6, better switch to appctx->ctx.cli.i0 or i1 which are all
properly initialized before the command is executed.
This must be backported to all stable versions.
Thanks to Andjelko Horvat for the report and the reproducer.
2024-07-16 05:14:49 -04:00
int * thr = appctx - > svcctx ;
2019-05-16 11:44:30 -04:00
BUG/MEDIUM: debug/cli: fix "show threads" crashing with low thread counts
The "show threads" command introduced early in the 2.0 dev cycle uses
appctx->st1 to store its context (the number of the next thread to dump).
It goes back to an era where contexts were shared between the various
applets and the CLI's command handlers.
In fact it was already not good by then because st1 could possibly have
APPCTX_CLI_ST1_PAYLOAD (2) in it, that would make the dmup start at
thread 2, though it was extremely unlikely.
When contexts were finally cleaned up and moved to their own storage,
this one was overlooked, maybe due to using st1 instead of st2 like
most others. So it continues to rely on st1, and more recently some
new flags were appended, one of which is APPCTX_CLI_ST1_LASTCMD (16)
and is always there. This results in "show threads" to believe it must
start do dump from thread 16, and if this thread is not present, it can
simply crash the process. A tiny reproducer is:
global
nbthread 1
stats socket /tmp/sock1 level admin mode 666
$ socat /tmp/sock1 - <<< "show threads"
The fix for modern versions simply consists in assigning a context to
this command from the applet storage. We're using a single int, no need
for a struct, an int* will do it. That's valid till 2.6.
Prior to 2.6, better switch to appctx->ctx.cli.i0 or i1 which are all
properly initialized before the command is executed.
This must be backported to all stable versions.
Thanks to Andjelko Horvat for the report and the reproducer.
2024-07-16 05:14:49 -04:00
if ( ! thr )
thr = applet_reserve_svcctx ( appctx , sizeof ( * thr ) ) ;
2019-05-16 11:44:30 -04:00
2023-05-04 13:07:56 -04:00
do {
chunk_reset ( & trash ) ;
2024-10-19 08:15:20 -04:00
if ( ha_thread_dump_fill ( & trash , * thr ) ) {
ha_thread_dump_done ( NULL , * thr ) ;
if ( applet_putchk ( appctx , & trash ) = = - 1 ) {
/* failed, try again */
return 0 ;
}
2023-05-04 13:07:56 -04:00
}
BUG/MEDIUM: debug/cli: fix "show threads" crashing with low thread counts
The "show threads" command introduced early in the 2.0 dev cycle uses
appctx->st1 to store its context (the number of the next thread to dump).
It goes back to an era where contexts were shared between the various
applets and the CLI's command handlers.
In fact it was already not good by then because st1 could possibly have
APPCTX_CLI_ST1_PAYLOAD (2) in it, that would make the dmup start at
thread 2, though it was extremely unlikely.
When contexts were finally cleaned up and moved to their own storage,
this one was overlooked, maybe due to using st1 instead of st2 like
most others. So it continues to rely on st1, and more recently some
new flags were appended, one of which is APPCTX_CLI_ST1_LASTCMD (16)
and is always there. This results in "show threads" to believe it must
start do dump from thread 16, and if this thread is not present, it can
simply crash the process. A tiny reproducer is:
global
nbthread 1
stats socket /tmp/sock1 level admin mode 666
$ socat /tmp/sock1 - <<< "show threads"
The fix for modern versions simply consists in assigning a context to
this command from the applet storage. We're using a single int, no need
for a struct, an int* will do it. That's valid till 2.6.
Prior to 2.6, better switch to appctx->ctx.cli.i0 or i1 which are all
properly initialized before the command is executed.
This must be backported to all stable versions.
Thanks to Andjelko Horvat for the report and the reproducer.
2024-07-16 05:14:49 -04:00
( * thr ) + + ;
} while ( * thr < global . nbthread ) ;
2019-05-17 04:36:08 -04:00
2019-05-16 11:44:30 -04:00
return 1 ;
}
2021-12-28 03:57:10 -05:00
# if defined(HA_HAVE_DUMP_LIBS)
/* parse a "show libs" command. It returns 1 if it emits anything otherwise zero. */
static int debug_parse_cli_show_libs ( char * * args , char * payload , struct appctx * appctx , void * private )
{
if ( ! cli_has_level ( appctx , ACCESS_LVL_OPER ) )
return 1 ;
chunk_reset ( & trash ) ;
if ( dump_libs ( & trash , 1 ) )
return cli_msg ( appctx , LOG_INFO , trash . area ) ;
else
return 0 ;
}
# endif
2023-11-22 05:28:06 -05:00
/* parse a "show dev" command. It returns 1 if it emits anything otherwise zero. */
static int debug_parse_cli_show_dev ( char * * args , char * payload , struct appctx * appctx , void * private )
{
2024-01-02 05:08:04 -05:00
const char * * build_opt ;
2024-05-29 05:27:21 -04:00
int i ;
2024-01-02 05:08:04 -05:00
2023-11-22 05:28:06 -05:00
if ( * args [ 2 ] )
return cli_err ( appctx , " This command takes no argument. \n " ) ;
chunk_reset ( & trash ) ;
2024-05-29 05:27:21 -04:00
chunk_appendf ( & trash , " HAProxy version %s \n " , haproxy_version ) ;
2024-01-02 05:08:04 -05:00
chunk_appendf ( & trash , " Features \n %s \n " , build_features ) ;
chunk_appendf ( & trash , " Build options \n " ) ;
for ( build_opt = NULL ; ( build_opt = hap_get_next_build_opt ( build_opt ) ) ; )
if ( append_prefixed_str ( & trash , * build_opt , " " , ' \n ' , 0 ) = = 0 )
chunk_strcat ( & trash , " \n " ) ;
2023-11-22 05:28:06 -05:00
chunk_appendf ( & trash , " Platform info \n " ) ;
2023-11-22 05:32:51 -05:00
if ( * post_mortem . platform . hw_vendor )
chunk_appendf ( & trash , " machine vendor: %s \n " , post_mortem . platform . hw_vendor ) ;
if ( * post_mortem . platform . hw_family )
chunk_appendf ( & trash , " machine family: %s \n " , post_mortem . platform . hw_family ) ;
if ( * post_mortem . platform . hw_model )
chunk_appendf ( & trash , " machine model: %s \n " , post_mortem . platform . hw_model ) ;
if ( * post_mortem . platform . brd_vendor )
chunk_appendf ( & trash , " board vendor: %s \n " , post_mortem . platform . brd_vendor ) ;
if ( * post_mortem . platform . brd_model )
chunk_appendf ( & trash , " board model: %s \n " , post_mortem . platform . brd_model ) ;
MINOR: debug: detect CPU model and store it in post_mortem
The CPU model and type has significant impact on certain bugs, such
as contention issues caused by CPUs having split L3 caches, or stricter
memory models that exhibit some barrier issues. It's complicated though
because the info about the model depends on the arch. For example, x86
reports an SKU name while ARM rather reports the CPU core types, families
and versions for each CPU core. There, the SoC will sometimes be reported
in the device tree or DMI info instead. But we don't really care, it's
essentially useful to know if the code is running on an armv8.0 such as
A53, a 8.2 such as A55/A76/Neoverse etc. For MIPS the model appears to
generally be there, and in addition the SoC is often present in the
"system type" field before the first CPU, and the type of machine in the
"machine" field, to replace the missing DMI and DT, so they are also
collected. Note that only the first CPU is checked and reported, that's
expected to be vastly sufficient, since we're just trying to spot known
incompatibilities or issues.
2023-11-22 05:55:22 -05:00
if ( * post_mortem . platform . soc_vendor )
chunk_appendf ( & trash , " soc vendor: %s \n " , post_mortem . platform . soc_vendor ) ;
if ( * post_mortem . platform . soc_model )
chunk_appendf ( & trash , " soc model: %s \n " , post_mortem . platform . soc_model ) ;
if ( * post_mortem . platform . cpu_model )
chunk_appendf ( & trash , " cpu model: %s \n " , post_mortem . platform . cpu_model ) ;
2023-11-22 06:04:02 -05:00
if ( * post_mortem . platform . virt_techno )
chunk_appendf ( & trash , " virtual machine: %s \n " , post_mortem . platform . virt_techno ) ;
2023-11-22 05:37:37 -05:00
if ( * post_mortem . platform . cont_techno )
chunk_appendf ( & trash , " container: %s \n " , post_mortem . platform . cont_techno ) ;
2023-11-22 05:28:06 -05:00
if ( * post_mortem . platform . utsname . sysname )
chunk_appendf ( & trash , " OS name: %s \n " , post_mortem . platform . utsname . sysname ) ;
if ( * post_mortem . platform . utsname . release )
chunk_appendf ( & trash , " OS release: %s \n " , post_mortem . platform . utsname . release ) ;
if ( * post_mortem . platform . utsname . version )
chunk_appendf ( & trash , " OS version: %s \n " , post_mortem . platform . utsname . version ) ;
if ( * post_mortem . platform . utsname . machine )
chunk_appendf ( & trash , " OS architecture: %s \n " , post_mortem . platform . utsname . machine ) ;
if ( * post_mortem . platform . utsname . nodename )
chunk_appendf ( & trash , " node name: %s \n " , HA_ANON_CLI ( post_mortem . platform . utsname . nodename ) ) ;
2023-11-22 09:37:57 -05:00
chunk_appendf ( & trash , " Process info \n " ) ;
chunk_appendf ( & trash , " pid: %d \n " , post_mortem . process . pid ) ;
2024-05-29 05:27:21 -04:00
chunk_appendf ( & trash , " cmdline: " ) ;
for ( i = 0 ; i < post_mortem . process . argc ; i + + )
chunk_appendf ( & trash , " %s " , post_mortem . process . argv [ i ] ) ;
chunk_appendf ( & trash , " \n " ) ;
2024-07-12 11:50:18 -04:00
2023-11-22 09:37:57 -05:00
chunk_appendf ( & trash , " boot uid: %d \n " , post_mortem . process . boot_uid ) ;
2024-07-12 11:50:18 -04:00
chunk_appendf ( & trash , " runtime uid: %d \n " , post_mortem . process . run_uid ) ;
2023-11-22 09:37:57 -05:00
chunk_appendf ( & trash , " boot gid: %d \n " , post_mortem . process . boot_gid ) ;
2024-07-12 11:50:18 -04:00
chunk_appendf ( & trash , " runtime gid: %d \n " , post_mortem . process . run_gid ) ;
2023-11-22 09:37:57 -05:00
2024-06-21 12:11:46 -04:00
# if defined(USE_LINUX_CAP)
/* let's dump saved in feed_post_mortem() initial capabilities sets */
2024-07-14 09:20:02 -04:00
if ( ! post_mortem . process . caps . err_boot ) {
2024-06-21 12:11:46 -04:00
chunk_appendf ( & trash , " boot capabilities: \n " ) ;
chunk_appendf ( & trash , " \t CapEff: 0x%016llx \n " ,
2024-07-14 09:54:43 -04:00
CAPS_TO_ULLONG ( post_mortem . process . caps . boot [ 0 ] . effective ,
post_mortem . process . caps . boot [ 1 ] . effective ) ) ;
2024-06-21 12:11:46 -04:00
chunk_appendf ( & trash , " \t CapPrm: 0x%016llx \n " ,
2024-07-14 09:54:43 -04:00
CAPS_TO_ULLONG ( post_mortem . process . caps . boot [ 0 ] . permitted ,
post_mortem . process . caps . boot [ 1 ] . permitted ) ) ;
2024-06-21 12:11:46 -04:00
chunk_appendf ( & trash , " \t CapInh: 0x%016llx \n " ,
2024-07-14 09:54:43 -04:00
CAPS_TO_ULLONG ( post_mortem . process . caps . boot [ 0 ] . inheritable ,
post_mortem . process . caps . boot [ 1 ] . inheritable ) ) ;
2024-06-21 12:11:46 -04:00
} else
2024-07-14 09:54:43 -04:00
chunk_appendf ( & trash , " capget() failed at boot with: %s. \n " ,
2024-07-14 09:20:02 -04:00
strerror ( post_mortem . process . caps . err_boot ) ) ;
2024-06-21 12:11:46 -04:00
/* let's print actual capabilities sets, could be useful in order to compare */
2024-07-14 09:20:02 -04:00
if ( ! post_mortem . process . caps . err_run ) {
2024-06-21 12:11:46 -04:00
chunk_appendf ( & trash , " runtime capabilities: \n " ) ;
chunk_appendf ( & trash , " \t CapEff: 0x%016llx \n " ,
2024-07-14 09:20:02 -04:00
CAPS_TO_ULLONG ( post_mortem . process . caps . run [ 0 ] . effective ,
post_mortem . process . caps . run [ 1 ] . effective ) ) ;
2024-06-21 12:11:46 -04:00
chunk_appendf ( & trash , " \t CapPrm: 0x%016llx \n " ,
2024-07-14 09:20:02 -04:00
CAPS_TO_ULLONG ( post_mortem . process . caps . run [ 0 ] . permitted ,
post_mortem . process . caps . run [ 1 ] . permitted ) ) ;
2024-06-21 12:11:46 -04:00
chunk_appendf ( & trash , " \t CapInh: 0x%016llx \n " ,
2024-07-14 09:20:02 -04:00
CAPS_TO_ULLONG ( post_mortem . process . caps . run [ 0 ] . inheritable ,
post_mortem . process . caps . run [ 1 ] . inheritable ) ) ;
2024-06-21 12:11:46 -04:00
} else
2024-07-14 09:54:43 -04:00
chunk_appendf ( & trash , " capget() failed at runtime with: %s. \n " ,
2024-07-14 09:20:02 -04:00
strerror ( post_mortem . process . caps . err_run ) ) ;
2024-06-21 12:11:46 -04:00
# endif
2024-07-13 07:23:46 -04:00
chunk_appendf ( & trash , " boot limits: \n " ) ;
chunk_appendf ( & trash , " \t fd limit (soft): %s \n " ,
LIM2A ( normalize_rlim ( ( ulong ) post_mortem . process . boot_lim_fd . rlim_cur ) , " unlimited " ) ) ;
chunk_appendf ( & trash , " \t fd limit (hard): %s \n " ,
LIM2A ( normalize_rlim ( ( ulong ) post_mortem . process . boot_lim_fd . rlim_max ) , " unlimited " ) ) ;
chunk_appendf ( & trash , " \t ram limit (soft): %s \n " ,
LIM2A ( normalize_rlim ( ( ulong ) post_mortem . process . boot_lim_ram . rlim_cur ) , " unlimited " ) ) ;
chunk_appendf ( & trash , " \t ram limit (hard): %s \n " ,
LIM2A ( normalize_rlim ( ( ulong ) post_mortem . process . boot_lim_ram . rlim_max ) , " unlimited " ) ) ;
2023-11-22 09:37:57 -05:00
2024-07-14 10:58:02 -04:00
chunk_appendf ( & trash , " runtime limits: \n " ) ;
chunk_appendf ( & trash , " \t fd limit (soft): %s \n " ,
LIM2A ( normalize_rlim ( ( ulong ) post_mortem . process . run_lim_fd . rlim_cur ) , " unlimited " ) ) ;
chunk_appendf ( & trash , " \t fd limit (hard): %s \n " ,
LIM2A ( normalize_rlim ( ( ulong ) post_mortem . process . run_lim_fd . rlim_max ) , " unlimited " ) ) ;
chunk_appendf ( & trash , " \t ram limit (soft): %s \n " ,
LIM2A ( normalize_rlim ( ( ulong ) post_mortem . process . run_lim_ram . rlim_cur ) , " unlimited " ) ) ;
chunk_appendf ( & trash , " \t ram limit (hard): %s \n " ,
LIM2A ( normalize_rlim ( ( ulong ) post_mortem . process . run_lim_ram . rlim_max ) , " unlimited " ) ) ;
2023-11-22 05:28:06 -05:00
return cli_msg ( appctx , LOG_INFO , trash . area ) ;
}
2024-10-19 09:18:44 -04:00
/* Dumps a state of all threads into the trash and on fd #2, then aborts. */
2019-05-20 07:48:29 -04:00
void ha_panic ( )
{
2024-10-19 09:18:44 -04:00
struct buffer * buf ;
2023-05-04 12:52:51 -04:00
unsigned int thr ;
2024-10-19 09:18:44 -04:00
if ( mark_tainted ( TAINTED_PANIC ) & TAINTED_PANIC ) {
2022-07-15 06:48:58 -04:00
/* a panic dump is already in progress, let's not disturb it,
* we ' ll be called via signal DEBUGSIG . By returning we may be
* able to leave a current signal handler ( e . g . WDT ) so that
* this will ensure more reliable signal delivery .
*/
return ;
}
2023-05-04 12:52:51 -04:00
2024-10-19 08:53:11 -04:00
chunk_printf ( & trash , " Thread %u is about to kill the process. \n " , tid + 1 ) ;
DISGUISE ( write ( 2 , trash . area , trash . data ) ) ;
2023-05-04 12:52:51 -04:00
for ( thr = 0 ; thr < global . nbthread ; thr + + ) {
2024-10-19 08:53:11 -04:00
if ( thr = = tid )
buf = get_trash_chunk ( ) ;
else
buf = ( void * ) 0x2UL ; // let the target thread allocate it
buf = ha_thread_dump_fill ( buf , thr ) ;
if ( ! buf )
2024-10-19 08:15:20 -04:00
continue ;
2024-10-19 08:53:11 -04:00
DISGUISE ( write ( 2 , buf - > area , buf - > data ) ) ;
/* restore the thread's dump pointer for easier post-mortem analysis */
ha_thread_dump_done ( buf , thr ) ;
2023-05-04 12:52:51 -04:00
}
2023-10-25 09:02:59 -04:00
# ifdef USE_LUA
if ( get_tainted ( ) & TAINTED_LUA_STUCK_SHARED & & global . nbthread > 1 ) {
chunk_printf ( & trash ,
" ### Note: at least one thread was stuck in a Lua context loaded using the \n "
" 'lua-load' directive, which is known for causing heavy contention \n "
" when used with threads. Please consider using 'lua-load-per-thread' \n "
" instead if your code is safe to run in parallel on multiple threads. \n " ) ;
DISGUISE ( write ( 2 , trash . area , trash . data ) ) ;
}
else if ( get_tainted ( ) & TAINTED_LUA_STUCK ) {
chunk_printf ( & trash ,
" ### Note: at least one thread was stuck in a Lua context in a way that suggests \n "
" heavy processing inside a dependency or a long loop that can't yield. \n "
" Please make sure any external code you may rely on is safe for use in \n "
" an event-driven engine. \n " ) ;
DISGUISE ( write ( 2 , trash . area , trash . data ) ) ;
}
# endif
2023-10-25 09:42:27 -04:00
if ( get_tainted ( ) & TAINTED_MEM_TRIMMING_STUCK ) {
chunk_printf ( & trash ,
" ### Note: one thread was found stuck under malloc_trim(), which can run for a \n "
" very long time on large memory systems. You way want to disable this \n "
" memory reclaiming feature by setting 'no-memory-trimming' in the \n "
" 'global' section of your configuration to avoid this in the future. \n " ) ;
DISGUISE ( write ( 2 , trash . area , trash . data ) ) ;
}
2023-10-25 09:02:59 -04:00
2024-06-21 08:04:46 -04:00
chunk_printf ( & trash ,
" \n "
" Hint: when reporting this bug to developers, please check if a core file was \n "
" produced, open it with 'gdb', issue 't a a bt full', check that the \n "
" output does not contain sensitive data, then join it with the bug report. \n "
" For more info, please see https://github.com/haproxy/haproxy/issues/2374 \n " ) ;
DISGUISE ( write ( 2 , trash . area , trash . data ) ) ;
2019-05-20 07:48:29 -04:00
for ( ; ; )
abort ( ) ;
}
DEBUG: reduce the footprint of BUG_ON() calls
Many inline functions involve some BUG_ON() calls and because of the
partial complexity of the functions, they're not inlined anymore (e.g.
co_data()). The reason is that the expression instantiates the message,
its size, sometimes a counter, then the atomic OR to taint the process,
and the back trace. That can be a lot for an inline function and most
of it is always the same.
This commit modifies this by delegating the common parts to a dedicated
function "complain()" that takes care of updating the counter if needed,
writing the message and measuring its length, and tainting the process.
This way the caller only has to check a condition, pass a pointer to the
preset message, and the info about the type (bug or warn) for the tainting,
then decide whether to dump or crash. Note that this part could also be
moved to the function but resulted in complain() always being at the top
of the stack, which didn't seem like an improvement.
Thanks to these changes, the BUG_ON() calls do not result in uninlining
functions anymore and the overall code size was reduced by 60 to 120 kB
depending on the build options.
2022-03-02 09:52:03 -05:00
/* Complain with message <msg> on stderr. If <counter> is not NULL, it is
* atomically incremented , and the message is only printed when the counter
* was zero , so that the message is only printed once . < taint > is only checked
* on bit 1 , and will taint the process either for a bug ( 2 ) or warn ( 0 ) .
*/
void complain ( int * counter , const char * msg , int taint )
{
if ( counter & & _HA_ATOMIC_FETCH_ADD ( counter , 1 ) )
return ;
DISGUISE ( write ( 2 , msg , strlen ( msg ) ) ) ;
if ( taint & 2 )
mark_tainted ( TAINTED_BUG ) ;
else
mark_tainted ( TAINTED_WARN ) ;
}
2019-05-20 08:25:05 -04:00
/* parse a "debug dev exit" command. It always returns 1, though it should never return. */
static int debug_parse_cli_exit ( char * * args , char * payload , struct appctx * appctx , void * private )
{
int code = atoi ( args [ 3 ] ) ;
if ( ! cli_has_level ( appctx , ACCESS_LVL_ADMIN ) )
return 1 ;
2021-04-06 07:53:36 -04:00
_HA_ATOMIC_INC ( & debug_commands_issued ) ;
2019-05-20 08:25:05 -04:00
exit ( code ) ;
return 1 ;
}
2021-01-22 08:15:46 -05:00
/* parse a "debug dev bug" command. It always returns 1, though it should never return.
* Note : we make sure not to make the function static so that it appears in the trace .
*/
int debug_parse_cli_bug ( char * * args , char * payload , struct appctx * appctx , void * private )
{
if ( ! cli_has_level ( appctx , ACCESS_LVL_ADMIN ) )
return 1 ;
2021-04-06 07:53:36 -04:00
_HA_ATOMIC_INC ( & debug_commands_issued ) ;
2024-02-05 10:23:32 -05:00
BUG_ON ( one > zero , " This was triggered on purpose from the CLI 'debug dev bug' command. " ) ;
2021-01-22 08:15:46 -05:00
return 1 ;
}
2022-02-25 02:52:39 -05:00
/* parse a "debug dev warn" command. It always returns 1.
* Note : we make sure not to make the function static so that it appears in the trace .
*/
int debug_parse_cli_warn ( char * * args , char * payload , struct appctx * appctx , void * private )
{
if ( ! cli_has_level ( appctx , ACCESS_LVL_ADMIN ) )
return 1 ;
_HA_ATOMIC_INC ( & debug_commands_issued ) ;
2024-02-05 10:23:32 -05:00
WARN_ON ( one > zero , " This was triggered on purpose from the CLI 'debug dev warn' command. " ) ;
2022-02-25 02:52:39 -05:00
return 1 ;
}
2022-02-28 05:51:23 -05:00
/* parse a "debug dev check" command. It always returns 1.
2022-02-25 02:55:11 -05:00
* Note : we make sure not to make the function static so that it appears in the trace .
*/
2022-02-28 05:51:23 -05:00
int debug_parse_cli_check ( char * * args , char * payload , struct appctx * appctx , void * private )
2022-02-25 02:55:11 -05:00
{
if ( ! cli_has_level ( appctx , ACCESS_LVL_ADMIN ) )
return 1 ;
_HA_ATOMIC_INC ( & debug_commands_issued ) ;
2024-02-05 10:23:32 -05:00
CHECK_IF ( one > zero , " This was triggered on purpose from the CLI 'debug dev check' command. " ) ;
2022-02-25 02:55:11 -05:00
return 1 ;
}
2019-05-20 08:25:05 -04:00
/* parse a "debug dev close" command. It always returns 1. */
static int debug_parse_cli_close ( char * * args , char * payload , struct appctx * appctx , void * private )
{
int fd ;
if ( ! cli_has_level ( appctx , ACCESS_LVL_ADMIN ) )
return 1 ;
2019-08-09 05:21:01 -04:00
if ( ! * args [ 3 ] )
return cli_err ( appctx , " Missing file descriptor number. \n " ) ;
2019-05-20 08:25:05 -04:00
fd = atoi ( args [ 3 ] ) ;
2019-08-09 05:21:01 -04:00
if ( fd < 0 | | fd > = global . maxsock )
return cli_err ( appctx , " File descriptor out of range. \n " ) ;
2019-05-20 08:25:05 -04:00
2019-08-09 05:21:01 -04:00
if ( ! fdtab [ fd ] . owner )
return cli_msg ( appctx , LOG_INFO , " File descriptor was already closed. \n " ) ;
2019-05-20 08:25:05 -04:00
2021-04-06 07:53:36 -04:00
_HA_ATOMIC_INC ( & debug_commands_issued ) ;
2019-05-20 08:25:05 -04:00
fd_delete ( fd ) ;
return 1 ;
}
2022-07-15 02:25:03 -04:00
/* this is meant to cause a deadlock when more than one task is running it or when run twice */
static struct task * debug_run_cli_deadlock ( struct task * task , void * ctx , unsigned int state )
{
static HA_SPINLOCK_T lock __maybe_unused ;
HA_SPIN_LOCK ( OTHER_LOCK , & lock ) ;
return NULL ;
}
/* parse a "debug dev deadlock" command. It always returns 1. */
static int debug_parse_cli_deadlock ( char * * args , char * payload , struct appctx * appctx , void * private )
{
int tasks ;
if ( ! cli_has_level ( appctx , ACCESS_LVL_ADMIN ) )
return 1 ;
_HA_ATOMIC_INC ( & debug_commands_issued ) ;
for ( tasks = atoi ( args [ 3 ] ) ; tasks > 0 ; tasks - - ) {
struct task * t = task_new_on ( tasks % global . nbthread ) ;
if ( ! t )
continue ;
t - > process = debug_run_cli_deadlock ;
t - > context = NULL ;
task_wakeup ( t , TASK_WOKEN_INIT ) ;
}
return 1 ;
}
2019-05-20 08:25:05 -04:00
/* parse a "debug dev delay" command. It always returns 1. */
static int debug_parse_cli_delay ( char * * args , char * payload , struct appctx * appctx , void * private )
{
int delay = atoi ( args [ 3 ] ) ;
if ( ! cli_has_level ( appctx , ACCESS_LVL_ADMIN ) )
return 1 ;
2021-04-06 07:53:36 -04:00
_HA_ATOMIC_INC ( & debug_commands_issued ) ;
2019-05-20 08:25:05 -04:00
usleep ( ( long ) delay * 1000 ) ;
return 1 ;
}
/* parse a "debug dev log" command. It always returns 1. */
static int debug_parse_cli_log ( char * * args , char * payload , struct appctx * appctx , void * private )
{
int arg ;
if ( ! cli_has_level ( appctx , ACCESS_LVL_ADMIN ) )
return 1 ;
2021-04-06 07:53:36 -04:00
_HA_ATOMIC_INC ( & debug_commands_issued ) ;
2019-05-20 08:25:05 -04:00
chunk_reset ( & trash ) ;
for ( arg = 3 ; * args [ arg ] ; arg + + ) {
if ( arg > 3 )
chunk_strcat ( & trash , " " ) ;
chunk_strcat ( & trash , args [ arg ] ) ;
}
send_log ( NULL , LOG_INFO , " %s \n " , trash . area ) ;
return 1 ;
}
/* parse a "debug dev loop" command. It always returns 1. */
static int debug_parse_cli_loop ( char * * args , char * payload , struct appctx * appctx , void * private )
{
struct timeval deadline , curr ;
int loop = atoi ( args [ 3 ] ) ;
2023-05-04 05:50:26 -04:00
int isolate ;
2019-05-20 08:25:05 -04:00
if ( ! cli_has_level ( appctx , ACCESS_LVL_ADMIN ) )
return 1 ;
2023-05-04 05:50:26 -04:00
isolate = strcmp ( args [ 4 ] , " isolated " ) = = 0 ;
2021-04-06 07:53:36 -04:00
_HA_ATOMIC_INC ( & debug_commands_issued ) ;
2019-05-20 08:25:05 -04:00
gettimeofday ( & curr , NULL ) ;
tv_ms_add ( & deadline , & curr , loop ) ;
2023-05-04 05:50:26 -04:00
if ( isolate )
thread_isolate ( ) ;
2019-05-20 08:25:05 -04:00
while ( tv_ms_cmp ( & curr , & deadline ) < 0 )
gettimeofday ( & curr , NULL ) ;
2023-05-04 05:50:26 -04:00
if ( isolate )
thread_release ( ) ;
2019-05-20 08:25:05 -04:00
return 1 ;
}
/* parse a "debug dev panic" command. It always returns 1, though it should never return. */
static int debug_parse_cli_panic ( char * * args , char * payload , struct appctx * appctx , void * private )
{
if ( ! cli_has_level ( appctx , ACCESS_LVL_ADMIN ) )
return 1 ;
2021-04-06 07:53:36 -04:00
_HA_ATOMIC_INC ( & debug_commands_issued ) ;
2019-05-20 08:25:05 -04:00
ha_panic ( ) ;
return 1 ;
}
/* parse a "debug dev exec" command. It always returns 1. */
2019-10-24 12:03:39 -04:00
# if defined(DEBUG_DEV)
2019-05-20 08:25:05 -04:00
static int debug_parse_cli_exec ( char * * args , char * payload , struct appctx * appctx , void * private )
{
2019-12-06 11:18:28 -05:00
int pipefd [ 2 ] ;
2019-05-20 08:25:05 -04:00
int arg ;
2019-12-06 11:18:28 -05:00
int pid ;
2019-05-20 08:25:05 -04:00
if ( ! cli_has_level ( appctx , ACCESS_LVL_ADMIN ) )
return 1 ;
2021-04-06 07:53:36 -04:00
_HA_ATOMIC_INC ( & debug_commands_issued ) ;
2019-05-20 08:25:05 -04:00
chunk_reset ( & trash ) ;
for ( arg = 3 ; * args [ arg ] ; arg + + ) {
if ( arg > 3 )
chunk_strcat ( & trash , " " ) ;
chunk_strcat ( & trash , args [ arg ] ) ;
}
2019-12-06 11:18:28 -05:00
thread_isolate ( ) ;
if ( pipe ( pipefd ) < 0 )
goto fail_pipe ;
2019-05-20 08:25:05 -04:00
2022-04-26 04:24:14 -04:00
if ( fd_set_cloexec ( pipefd [ 0 ] ) = = - 1 )
2019-12-06 11:18:28 -05:00
goto fail_fcntl ;
2022-04-26 04:24:14 -04:00
if ( fd_set_cloexec ( pipefd [ 1 ] ) = = - 1 )
2019-12-06 11:18:28 -05:00
goto fail_fcntl ;
pid = fork ( ) ;
if ( pid < 0 )
goto fail_fork ;
else if ( pid = = 0 ) {
/* child */
char * cmd [ 4 ] = { " /bin/sh " , " -c " , 0 , 0 } ;
close ( 0 ) ;
dup2 ( pipefd [ 1 ] , 1 ) ;
dup2 ( pipefd [ 1 ] , 2 ) ;
cmd [ 2 ] = trash . area ;
execvp ( cmd [ 0 ] , cmd ) ;
printf ( " execvp() failed \n " ) ;
exit ( 1 ) ;
}
/* parent */
thread_release ( ) ;
close ( pipefd [ 1 ] ) ;
2019-05-20 08:25:05 -04:00
chunk_reset ( & trash ) ;
while ( 1 ) {
2019-12-06 11:18:28 -05:00
size_t ret = read ( pipefd [ 0 ] , trash . area + trash . data , trash . size - 20 - trash . data ) ;
if ( ret < = 0 )
2019-05-20 08:25:05 -04:00
break ;
trash . data + = ret ;
if ( trash . data + 20 = = trash . size ) {
chunk_strcat ( & trash , " \n [[[TRUNCATED]]] \n " ) ;
break ;
}
}
2019-12-06 11:18:28 -05:00
close ( pipefd [ 0 ] ) ;
waitpid ( pid , NULL , WNOHANG ) ;
2019-05-20 08:25:05 -04:00
trash . area [ trash . data ] = 0 ;
2019-08-09 05:21:01 -04:00
return cli_msg ( appctx , LOG_INFO , trash . area ) ;
2019-12-06 11:18:28 -05:00
fail_fork :
fail_fcntl :
close ( pipefd [ 0 ] ) ;
close ( pipefd [ 1 ] ) ;
fail_pipe :
thread_release ( ) ;
return cli_err ( appctx , " Failed to execute command. \n " ) ;
2019-05-20 08:25:05 -04:00
}
2023-03-09 02:25:01 -05:00
/* handles SIGRTMAX to inject random delays on the receiving thread in order
* to try to increase the likelihood to reproduce inter - thread races . The
* signal is periodically sent by a task initiated by " debug dev delay-inj " .
*/
void debug_delay_inj_sighandler ( int sig , siginfo_t * si , void * arg )
{
volatile int i = statistical_prng_range ( 10000 ) ;
while ( i - - )
__ha_cpu_relax ( ) ;
}
2019-10-24 12:03:39 -04:00
# endif
2019-05-20 08:25:05 -04:00
/* parse a "debug dev hex" command. It always returns 1. */
static int debug_parse_cli_hex ( char * * args , char * payload , struct appctx * appctx , void * private )
{
unsigned long start , len ;
if ( ! cli_has_level ( appctx , ACCESS_LVL_ADMIN ) )
return 1 ;
2019-08-09 05:21:01 -04:00
if ( ! * args [ 3 ] )
return cli_err ( appctx , " Missing memory address to dump from. \n " ) ;
2019-05-20 08:25:05 -04:00
start = strtoul ( args [ 3 ] , NULL , 0 ) ;
2019-08-09 05:21:01 -04:00
if ( ! start )
return cli_err ( appctx , " Will not dump from NULL address. \n " ) ;
2019-05-20 08:25:05 -04:00
2021-04-06 07:53:36 -04:00
_HA_ATOMIC_INC ( & debug_commands_issued ) ;
2019-10-24 12:18:02 -04:00
2019-05-20 08:25:05 -04:00
/* by default, dump ~128 till next block of 16 */
len = strtoul ( args [ 4 ] , NULL , 0 ) ;
if ( ! len )
len = ( ( start + 128 ) & - 16 ) - start ;
chunk_reset ( & trash ) ;
2019-05-20 10:48:20 -04:00
dump_hex ( & trash , " " , ( const void * ) start , len , 1 ) ;
2019-05-20 08:25:05 -04:00
trash . area [ trash . data ] = 0 ;
2019-08-09 05:21:01 -04:00
return cli_msg ( appctx , LOG_INFO , trash . area ) ;
2019-05-20 08:25:05 -04:00
}
2021-05-04 12:40:50 -04:00
/* parse a "debug dev sym <addr>" command. It always returns 1. */
static int debug_parse_cli_sym ( char * * args , char * payload , struct appctx * appctx , void * private )
{
unsigned long addr ;
if ( ! cli_has_level ( appctx , ACCESS_LVL_ADMIN ) )
return 1 ;
if ( ! * args [ 3 ] )
return cli_err ( appctx , " Missing memory address to be resolved. \n " ) ;
_HA_ATOMIC_INC ( & debug_commands_issued ) ;
addr = strtoul ( args [ 3 ] , NULL , 0 ) ;
chunk_printf ( & trash , " %#lx resolves to " , addr ) ;
resolve_sym_name ( & trash , NULL , ( const void * ) addr ) ;
chunk_appendf ( & trash , " \n " ) ;
return cli_msg ( appctx , LOG_INFO , trash . area ) ;
}
2019-05-20 08:25:05 -04:00
/* parse a "debug dev tkill" command. It always returns 1. */
static int debug_parse_cli_tkill ( char * * args , char * payload , struct appctx * appctx , void * private )
{
int thr = 0 ;
int sig = SIGABRT ;
if ( ! cli_has_level ( appctx , ACCESS_LVL_ADMIN ) )
return 1 ;
if ( * args [ 3 ] )
thr = atoi ( args [ 3 ] ) ;
2019-08-09 05:21:01 -04:00
if ( thr < 0 | | thr > global . nbthread )
return cli_err ( appctx , " Thread number out of range (use 0 for current) . \ n " ) ;
2019-05-20 08:25:05 -04:00
if ( * args [ 4 ] )
sig = atoi ( args [ 4 ] ) ;
2021-04-06 07:53:36 -04:00
_HA_ATOMIC_INC ( & debug_commands_issued ) ;
2019-05-20 08:25:05 -04:00
if ( thr )
2019-05-22 02:46:59 -04:00
ha_tkill ( thr - 1 , sig ) ;
2019-05-20 08:25:05 -04:00
else
raise ( sig ) ;
return 1 ;
}
2022-09-14 11:24:22 -04:00
/* hashes 'word' in "debug dev hash 'word' ". */
static int debug_parse_cli_hash ( char * * args , char * payload , struct appctx * appctx , void * private )
{
char * msg = NULL ;
cli_dynmsg ( appctx , LOG_INFO , memprintf ( & msg , " %s \n " , HA_ANON_CLI ( args [ 3 ] ) ) ) ;
return 1 ;
}
2020-03-05 11:16:24 -05:00
/* parse a "debug dev write" command. It always returns 1. */
static int debug_parse_cli_write ( char * * args , char * payload , struct appctx * appctx , void * private )
{
unsigned long len ;
if ( ! * args [ 3 ] )
return cli_err ( appctx , " Missing output size. \n " ) ;
len = strtoul ( args [ 3 ] , NULL , 0 ) ;
if ( len > = trash . size )
return cli_err ( appctx , " Output too large, must be <tune.bufsize. \n " ) ;
2021-04-06 07:53:36 -04:00
_HA_ATOMIC_INC ( & debug_commands_issued ) ;
2020-03-05 11:16:24 -05:00
chunk_reset ( & trash ) ;
trash . data = len ;
memset ( trash . area , ' . ' , trash . data ) ;
trash . area [ trash . data ] = 0 ;
for ( len = 64 ; len < trash . data ; len + = 64 )
trash . area [ len ] = ' \n ' ;
return cli_msg ( appctx , LOG_INFO , trash . area ) ;
}
2019-10-23 11:23:25 -04:00
/* parse a "debug dev stream" command */
/*
* debug dev stream [ strm = < ptr > ] [ strm . f [ { + - = } < flags > ] ] [ txn . f [ { + - = } < flags > ] ] \
* [ req . f [ { + - = } < flags > ] ] [ res . f [ { + - = } < flags > ] ] \
* [ sif . f [ { + - = < flags > ] ] [ sib . f [ { + - = < flags > ] ] \
* [ sif . s [ = < state > ] ] [ sib . s [ = < state > ] ]
*/
static int debug_parse_cli_stream ( char * * args , char * payload , struct appctx * appctx , void * private )
{
2022-05-11 08:09:57 -04:00
struct stream * s = appctx_strm ( appctx ) ;
2019-10-23 11:23:25 -04:00
int arg ;
void * ptr ;
int size ;
const char * word , * end ;
struct ist name ;
char * msg = NULL ;
char * endarg ;
unsigned long long old , new ;
if ( ! cli_has_level ( appctx , ACCESS_LVL_ADMIN ) )
return 1 ;
ptr = NULL ; size = 0 ;
if ( ! * args [ 3 ] ) {
return cli_err ( appctx ,
2023-05-02 10:37:13 -04:00
" Usage: debug dev stream [ strm=<ptr> ] { <obj> <op> <value> | wake }* \n "
" <obj> = { strm.f | strm.x | scf.s | scb.s | txn.f | req.f | res.f } \n "
2019-10-23 11:23:25 -04:00
" <op> = {'' (show) | '=' (assign) | '^' (xor) | '+' (or) | '-' (andnot)} \n "
" <value> = 'now' | 64-bit dec/hex integer (0x prefix supported) \n "
2023-11-21 13:54:16 -05:00
" 'wake' wakes the stream assigned to 'strm' (default: current) \n "
2019-10-23 11:23:25 -04:00
) ;
}
2021-04-06 07:53:36 -04:00
_HA_ATOMIC_INC ( & debug_commands_issued ) ;
2019-10-23 11:23:25 -04:00
for ( arg = 3 ; * args [ arg ] ; arg + + ) {
old = 0 ;
end = word = args [ arg ] ;
while ( * end & & * end ! = ' = ' & & * end ! = ' ^ ' & & * end ! = ' + ' & & * end ! = ' - ' )
end + + ;
name = ist2 ( word , end - word ) ;
if ( isteq ( name , ist ( " strm " ) ) ) {
2019-10-25 04:06:55 -04:00
ptr = ( ! s | | ! may_access ( s ) ) ? NULL : & s ; size = sizeof ( s ) ;
2019-10-23 11:23:25 -04:00
} else if ( isteq ( name , ist ( " strm.f " ) ) ) {
2019-10-25 04:06:55 -04:00
ptr = ( ! s | | ! may_access ( s ) ) ? NULL : & s - > flags ; size = sizeof ( s - > flags ) ;
2022-03-29 13:02:31 -04:00
} else if ( isteq ( name , ist ( " strm.x " ) ) ) {
ptr = ( ! s | | ! may_access ( s ) ) ? NULL : & s - > conn_exp ; size = sizeof ( s - > conn_exp ) ;
2019-10-23 11:23:25 -04:00
} else if ( isteq ( name , ist ( " txn.f " ) ) ) {
2019-10-25 04:06:55 -04:00
ptr = ( ! s | | ! may_access ( s ) ) ? NULL : & s - > txn - > flags ; size = sizeof ( s - > txn - > flags ) ;
2019-10-23 11:23:25 -04:00
} else if ( isteq ( name , ist ( " req.f " ) ) ) {
2019-10-25 04:06:55 -04:00
ptr = ( ! s | | ! may_access ( s ) ) ? NULL : & s - > req . flags ; size = sizeof ( s - > req . flags ) ;
2019-10-23 11:23:25 -04:00
} else if ( isteq ( name , ist ( " res.f " ) ) ) {
2019-10-25 04:06:55 -04:00
ptr = ( ! s | | ! may_access ( s ) ) ? NULL : & s - > res . flags ; size = sizeof ( s - > res . flags ) ;
2022-05-17 13:40:40 -04:00
} else if ( isteq ( name , ist ( " scf.s " ) ) ) {
ptr = ( ! s | | ! may_access ( s ) ) ? NULL : & s - > scf - > state ; size = sizeof ( s - > scf - > state ) ;
} else if ( isteq ( name , ist ( " scb.s " ) ) ) {
ptr = ( ! s | | ! may_access ( s ) ) ? NULL : & s - > scf - > state ; size = sizeof ( s - > scb - > state ) ;
2019-10-23 11:23:25 -04:00
} else if ( isteq ( name , ist ( " wake " ) ) ) {
2019-10-24 12:28:23 -04:00
if ( s & & may_access ( s ) & & may_access ( ( void * ) s + sizeof ( * s ) - 1 ) )
2019-10-23 11:23:25 -04:00
task_wakeup ( s - > task , TASK_WOKEN_TIMER | TASK_WOKEN_IO | TASK_WOKEN_MSG ) ;
continue ;
} else
return cli_dynerr ( appctx , memprintf ( & msg , " Unsupported field name: '%s'. \n " , word ) ) ;
/* read previous value */
2019-10-24 12:28:23 -04:00
if ( ( s | | ptr = = & s ) & & ptr & & may_access ( ptr ) & & may_access ( ptr + size - 1 ) ) {
2019-10-23 11:23:25 -04:00
if ( size = = 8 )
old = read_u64 ( ptr ) ;
else if ( size = = 4 )
old = read_u32 ( ptr ) ;
else if ( size = = 2 )
old = read_u16 ( ptr ) ;
else
old = * ( const uint8_t * ) ptr ;
2019-10-24 12:28:23 -04:00
} else {
memprintf ( & msg ,
" %sSkipping inaccessible pointer %p for field '%.*s'. \n " ,
msg ? msg : " " , ptr , ( int ) ( end - word ) , word ) ;
continue ;
2019-10-23 11:23:25 -04:00
}
/* parse the new value . */
new = strtoll ( end + 1 , & endarg , 0 ) ;
if ( end [ 1 ] & & * endarg ) {
if ( strcmp ( end + 1 , " now " ) = = 0 )
new = now_ms ;
else {
memprintf ( & msg ,
" %sIgnoring unparsable value '%s' for field '%.*s'. \n " ,
msg ? msg : " " , end + 1 , ( int ) ( end - word ) , word ) ;
continue ;
}
}
switch ( * end ) {
case ' \0 ' : /* show */
memprintf ( & msg , " %s%.*s=%#llx " , msg ? msg : " " , ( int ) ( end - word ) , word , old ) ;
new = old ; // do not change the value
break ;
case ' = ' : /* set */
break ;
case ' ^ ' : /* XOR */
new = old ^ new ;
break ;
case ' + ' : /* OR */
new = old | new ;
break ;
case ' - ' : /* AND NOT */
new = old & ~ new ;
break ;
default :
break ;
}
/* write the new value */
2019-10-24 12:28:23 -04:00
if ( new ! = old ) {
2019-10-23 11:23:25 -04:00
if ( size = = 8 )
write_u64 ( ptr , new ) ;
else if ( size = = 4 )
write_u32 ( ptr , new ) ;
else if ( size = = 2 )
write_u16 ( ptr , new ) ;
else
* ( uint8_t * ) ptr = new ;
}
}
if ( msg & & * msg )
return cli_dynmsg ( appctx , LOG_INFO , msg ) ;
return 1 ;
}
2023-05-03 05:22:45 -04:00
/* parse a "debug dev stream" command */
/*
* debug dev task < ptr > [ " wake " | " expire " | " kill " ]
* Show / change status of a task / tasklet
*/
static int debug_parse_cli_task ( char * * args , char * payload , struct appctx * appctx , void * private )
{
const struct ha_caller * caller ;
struct task * t ;
char * endarg ;
char * msg ;
void * ptr ;
int ret = 1 ;
int task_ok ;
int arg ;
if ( ! cli_has_level ( appctx , ACCESS_LVL_ADMIN ) )
return 1 ;
/* parse the pointer value */
2023-05-11 22:40:06 -04:00
ptr = ( void * ) strtoul ( args [ 3 ] , & endarg , 0 ) ;
2023-05-03 05:22:45 -04:00
if ( ! * args [ 3 ] | | * endarg )
goto usage ;
_HA_ATOMIC_INC ( & debug_commands_issued ) ;
/* everything below must run under thread isolation till reaching label "leave" */
thread_isolate ( ) ;
/* struct tasklet is smaller than struct task and is sufficient to check
* the TASK_COMMON part .
*/
if ( ! may_access ( ptr ) | | ! may_access ( ptr + sizeof ( struct tasklet ) - 1 ) | |
( ( const struct tasklet * ) ptr ) - > tid < - 1 | |
( ( const struct tasklet * ) ptr ) - > tid > = ( int ) MAX_THREADS ) {
ret = cli_err ( appctx , " The designated memory area doesn't look like a valid task/tasklet \n " ) ;
goto leave ;
}
t = ptr ;
caller = t - > caller ;
msg = NULL ;
2023-05-15 05:59:08 -04:00
task_ok = may_access ( ptr + sizeof ( * t ) - 1 ) ;
2023-05-03 05:22:45 -04:00
chunk_reset ( & trash ) ;
resolve_sym_name ( & trash , NULL , ( const void * ) t - > process ) ;
/* we need to be careful here because we may dump a freed task that's
* still in the pool cache , containing garbage in pointers .
*/
if ( ! * args [ 4 ] ) {
memprintf ( & msg , " %s%p: %s state=%#x tid=%d process=%s ctx=%p calls=%d last=%s:%d intl=%d " ,
msg ? msg : " " , t , ( t - > state & TASK_F_TASKLET ) ? " tasklet " : " task " ,
t - > state , t - > tid , trash . area , t - > context , t - > calls ,
2023-05-03 10:28:54 -04:00
caller & & may_access ( caller ) & & may_access ( caller - > func ) & & isalnum ( ( uchar ) * caller - > func ) ? caller - > func : " 0 " ,
2023-05-03 05:22:45 -04:00
caller ? t - > caller - > line : 0 ,
( t - > state & TASK_F_TASKLET ) ? LIST_INLIST ( & ( ( const struct tasklet * ) t ) - > list ) : 0 ) ;
if ( task_ok & & ! ( t - > state & TASK_F_TASKLET ) )
memprintf ( & msg , " %s inrq=%d inwq=%d exp=%d nice=%d " ,
msg ? msg : " " , task_in_rq ( t ) , task_in_wq ( t ) , t - > expire , t - > nice ) ;
memprintf ( & msg , " %s \n " , msg ? msg : " " ) ;
}
for ( arg = 4 ; * args [ arg ] ; arg + + ) {
if ( strcmp ( args [ arg ] , " expire " ) = = 0 ) {
if ( t - > state & TASK_F_TASKLET ) {
/* do nothing for tasklets */
}
else if ( task_ok ) {
/* unlink task and wake with timer flag */
__task_unlink_wq ( t ) ;
t - > expire = now_ms ;
task_wakeup ( t , TASK_WOKEN_TIMER ) ;
}
} else if ( strcmp ( args [ arg ] , " wake " ) = = 0 ) {
/* wake with all flags but init / timer */
if ( t - > state & TASK_F_TASKLET )
tasklet_wakeup ( ( struct tasklet * ) t ) ;
else if ( task_ok )
task_wakeup ( t , TASK_WOKEN_ANY & ~ ( TASK_WOKEN_INIT | TASK_WOKEN_TIMER ) ) ;
} else if ( strcmp ( args [ arg ] , " kill " ) = = 0 ) {
/* Kill the task. This is not idempotent! */
if ( ! ( t - > state & TASK_KILLED ) ) {
if ( t - > state & TASK_F_TASKLET )
tasklet_kill ( ( struct tasklet * ) t ) ;
else if ( task_ok )
task_kill ( t ) ;
}
} else {
thread_release ( ) ;
goto usage ;
}
}
if ( msg & & * msg )
ret = cli_dynmsg ( appctx , LOG_INFO , msg ) ;
leave :
thread_release ( ) ;
return ret ;
usage :
return cli_err ( appctx ,
" Usage: debug dev task <ptr> [ wake | expire | kill ] \n "
" By default, dumps some info on task/tasklet <ptr>. 'wake' will wake it up \n "
" with all conditions flags but init/exp. 'expire' will expire the entry, and \n "
" 'kill' will kill it (warning: may crash since later not idempotent!). All \n "
" changes may crash the process if performed on a wrong object! \n "
) ;
}
2023-03-09 02:25:01 -05:00
# if defined(DEBUG_DEV)
static struct task * debug_delay_inj_task ( struct task * t , void * ctx , unsigned int state )
{
unsigned long * tctx = ctx ; // [0] = interval, [1] = nbwakeups
unsigned long inter = tctx [ 0 ] ;
unsigned long count = tctx [ 1 ] ;
unsigned long rnd ;
if ( inter )
t - > expire = tick_add ( now_ms , inter ) ;
else
task_wakeup ( t , TASK_WOKEN_MSG ) ;
/* wake a random thread */
while ( count - - ) {
rnd = statistical_prng_range ( global . nbthread ) ;
ha_tkill ( rnd , SIGRTMAX ) ;
}
return t ;
}
/* parse a "debug dev delay-inj" command
* debug dev delay - inj < inter > < count >
*/
static int debug_parse_delay_inj ( char * * args , char * payload , struct appctx * appctx , void * private )
{
unsigned long * tctx ; // [0] = inter, [2] = count
struct task * task ;
if ( ! cli_has_level ( appctx , ACCESS_LVL_ADMIN ) )
return 1 ;
if ( ! * args [ 4 ] )
return cli_err ( appctx , " Usage: debug dev delay-inj <inter_ms> <count>* \n " ) ;
_HA_ATOMIC_INC ( & debug_commands_issued ) ;
2024-02-10 05:35:07 -05:00
tctx = calloc ( 2 , sizeof ( * tctx ) ) ;
2023-03-09 02:25:01 -05:00
if ( ! tctx )
goto fail ;
tctx [ 0 ] = atoi ( args [ 3 ] ) ;
tctx [ 1 ] = atoi ( args [ 4 ] ) ;
task = task_new_here /*anywhere*/ ( ) ;
if ( ! task )
goto fail ;
task - > process = debug_delay_inj_task ;
task - > context = tctx ;
task_wakeup ( task , TASK_WOKEN_INIT ) ;
return 1 ;
fail :
free ( tctx ) ;
return cli_err ( appctx , " Not enough memory " ) ;
}
# endif // DEBUG_DEV
2021-03-02 10:09:26 -05:00
static struct task * debug_task_handler ( struct task * t , void * ctx , unsigned int state )
2020-11-29 11:12:15 -05:00
{
unsigned long * tctx = ctx ; // [0] = #tasks, [1] = inter, [2+] = { tl | (tsk+1) }
unsigned long inter = tctx [ 1 ] ;
unsigned long rnd ;
t - > expire = tick_add ( now_ms , inter ) ;
/* half of the calls will wake up another entry */
2021-03-02 08:01:35 -05:00
rnd = statistical_prng ( ) ;
2020-11-29 11:12:15 -05:00
if ( rnd & 1 ) {
rnd > > = 1 ;
rnd % = tctx [ 0 ] ;
rnd = tctx [ rnd + 2 ] ;
if ( rnd & 1 )
task_wakeup ( ( struct task * ) ( rnd - 1 ) , TASK_WOKEN_MSG ) ;
else
tasklet_wakeup ( ( struct tasklet * ) rnd ) ;
}
return t ;
}
2021-03-02 10:09:26 -05:00
static struct task * debug_tasklet_handler ( struct task * t , void * ctx , unsigned int state )
2020-11-29 11:12:15 -05:00
{
unsigned long * tctx = ctx ; // [0] = #tasks, [1] = inter, [2+] = { tl | (tsk+1) }
unsigned long rnd ;
int i ;
/* wake up two random entries */
for ( i = 0 ; i < 2 ; i + + ) {
2021-03-02 08:01:35 -05:00
rnd = statistical_prng ( ) % tctx [ 0 ] ;
2020-11-29 11:12:15 -05:00
rnd = tctx [ rnd + 2 ] ;
if ( rnd & 1 )
task_wakeup ( ( struct task * ) ( rnd - 1 ) , TASK_WOKEN_MSG ) ;
else
tasklet_wakeup ( ( struct tasklet * ) rnd ) ;
}
return t ;
}
/* parse a "debug dev sched" command
* debug dev sched { task | tasklet } [ count = < count > ] [ mask = < mask > ] [ single = < single > ] [ inter = < inter > ]
*/
static int debug_parse_cli_sched ( char * * args , char * payload , struct appctx * appctx , void * private )
{
int arg ;
void * ptr ;
int size ;
const char * word , * end ;
struct ist name ;
char * msg = NULL ;
char * endarg ;
unsigned long long new ;
unsigned long count = 0 ;
2022-06-15 10:32:41 -04:00
unsigned long thrid = tid ;
2020-11-29 11:12:15 -05:00
unsigned int inter = 0 ;
unsigned long i ;
int mode = 0 ; // 0 = tasklet; 1 = task
unsigned long * tctx ; // [0] = #tasks, [1] = inter, [2+] = { tl | (tsk+1) }
if ( ! cli_has_level ( appctx , ACCESS_LVL_ADMIN ) )
return 1 ;
ptr = NULL ; size = 0 ;
if ( strcmp ( args [ 3 ] , " task " ) ! = 0 & & strcmp ( args [ 3 ] , " tasklet " ) ! = 0 ) {
return cli_err ( appctx ,
" Usage: debug dev sched {task|tasklet} { <obj> = <value> }* \n "
2022-06-15 10:32:41 -04:00
" <obj> = {count | tid | inter } \n "
2020-11-29 11:12:15 -05:00
" <value> = 64-bit dec/hex integer (0x prefix supported) \n "
) ;
}
mode = strcmp ( args [ 3 ] , " task " ) = = 0 ;
2021-04-06 07:53:36 -04:00
_HA_ATOMIC_INC ( & debug_commands_issued ) ;
2020-11-29 11:12:15 -05:00
for ( arg = 4 ; * args [ arg ] ; arg + + ) {
end = word = args [ arg ] ;
while ( * end & & * end ! = ' = ' & & * end ! = ' ^ ' & & * end ! = ' + ' & & * end ! = ' - ' )
end + + ;
name = ist2 ( word , end - word ) ;
if ( isteq ( name , ist ( " count " ) ) ) {
ptr = & count ; size = sizeof ( count ) ;
} else if ( isteq ( name , ist ( " tid " ) ) ) {
ptr = & thrid ; size = sizeof ( thrid ) ;
} else if ( isteq ( name , ist ( " inter " ) ) ) {
ptr = & inter ; size = sizeof ( inter ) ;
} else
return cli_dynerr ( appctx , memprintf ( & msg , " Unsupported setting: '%s'. \n " , word ) ) ;
/* parse the new value . */
new = strtoll ( end + 1 , & endarg , 0 ) ;
if ( end [ 1 ] & & * endarg ) {
memprintf ( & msg ,
" %sIgnoring unparsable value '%s' for field '%.*s'. \n " ,
msg ? msg : " " , end + 1 , ( int ) ( end - word ) , word ) ;
continue ;
}
/* write the new value */
if ( size = = 8 )
write_u64 ( ptr , new ) ;
else if ( size = = 4 )
write_u32 ( ptr , new ) ;
else if ( size = = 2 )
write_u16 ( ptr , new ) ;
else
* ( uint8_t * ) ptr = new ;
}
2024-02-10 05:35:07 -05:00
tctx = calloc ( count + 2 , sizeof ( * tctx ) ) ;
2020-11-29 11:12:15 -05:00
if ( ! tctx )
goto fail ;
tctx [ 0 ] = ( unsigned long ) count ;
tctx [ 1 ] = ( unsigned long ) inter ;
2022-06-15 10:32:41 -04:00
if ( thrid > = global . nbthread )
thrid = tid ;
2020-11-29 11:12:15 -05:00
for ( i = 0 ; i < count ; i + + ) {
/* now, if poly or mask was set, tmask corresponds to the
* valid thread mask to use , otherwise it remains zero .
*/
//printf("%lu: mode=%d mask=%#lx\n", i, mode, tmask);
if ( mode = = 0 ) {
struct tasklet * tl = tasklet_new ( ) ;
if ( ! tl )
goto fail ;
2022-06-15 10:32:41 -04:00
tl - > tid = thrid ;
2020-11-29 11:12:15 -05:00
tl - > process = debug_tasklet_handler ;
tl - > context = tctx ;
tctx [ i + 2 ] = ( unsigned long ) tl ;
} else {
2022-06-15 10:32:41 -04:00
struct task * task = task_new_on ( thrid ) ;
2020-11-29 11:12:15 -05:00
if ( ! task )
goto fail ;
task - > process = debug_task_handler ;
task - > context = tctx ;
tctx [ i + 2 ] = ( unsigned long ) task + 1 ;
}
}
/* start the tasks and tasklets */
for ( i = 0 ; i < count ; i + + ) {
unsigned long ctx = tctx [ i + 2 ] ;
if ( ctx & 1 )
task_wakeup ( ( struct task * ) ( ctx - 1 ) , TASK_WOKEN_INIT ) ;
else
tasklet_wakeup ( ( struct tasklet * ) ctx ) ;
}
if ( msg & & * msg )
return cli_dynmsg ( appctx , LOG_INFO , msg ) ;
return 1 ;
fail :
/* free partially allocated entries */
for ( i = 0 ; tctx & & i < count ; i + + ) {
unsigned long ctx = tctx [ i + 2 ] ;
if ( ! ctx )
break ;
if ( ctx & 1 )
task_destroy ( ( struct task * ) ( ctx - 1 ) ) ;
else
tasklet_free ( ( struct tasklet * ) ctx ) ;
}
free ( tctx ) ;
return cli_err ( appctx , " Not enough memory " ) ;
}
2024-03-15 02:16:08 -04:00
# if defined(DEBUG_DEV)
/* All of this is for "trace dbg" */
static struct trace_source trace_dbg __read_mostly = {
. name = IST ( " dbg " ) ,
. desc = " trace debugger " ,
. report_events = ~ 0 , // report everything by default
} ;
# define TRACE_SOURCE &trace_dbg
INITCALL1 ( STG_REGISTER , trace_register_source , TRACE_SOURCE ) ;
/* This is the task handler used to send traces in loops. Note that the task's
* context contains the number of remaining calls to be done . The task sends 20
* messages per wakeup .
*/
static struct task * debug_trace_task ( struct task * t , void * ctx , unsigned int state )
{
ulong count ;
/* send 2 traces enter/leave +18 devel = 20 traces total */
TRACE_ENTER ( 1 ) ;
TRACE_DEVEL ( " msg01 has 20 bytes . " , 1 ) ;
TRACE_DEVEL ( " msg02 has 20 bytes . " , 1 ) ;
TRACE_DEVEL ( " msg03 has 20 bytes . " , 1 ) ;
TRACE_DEVEL ( " msg04 has 70 bytes payload: 0123456789 0123456789 0123456789 012345678 " , 1 ) ;
TRACE_DEVEL ( " msg05 has 70 bytes payload: 0123456789 0123456789 0123456789 012345678 " , 1 ) ;
TRACE_DEVEL ( " msg06 has 70 bytes payload: 0123456789 0123456789 0123456789 012345678 " , 1 ) ;
TRACE_DEVEL ( " msg07 has 120 bytes payload: 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 012 " , 1 ) ;
TRACE_DEVEL ( " msg08 has 120 bytes payload: 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 012 " , 1 ) ;
TRACE_DEVEL ( " msg09 has 120 bytes payload: 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 012 " , 1 ) ;
TRACE_DEVEL ( " msg10 has 170 bytes payload: 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 012345678 " , 1 ) ;
TRACE_DEVEL ( " msg11 has 170 bytes payload: 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 012345678 " , 1 ) ;
TRACE_DEVEL ( " msg12 has 170 bytes payload: 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 012345678 " , 1 ) ;
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 ) ;
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 ) ;
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 ) ;
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 ) ;
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 ) ;
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 ) ;
TRACE_LEAVE ( 1 ) ;
count = ( ulong ) t - > context ;
t - > context = ( void * ) count - 1 ;
if ( count )
task_wakeup ( t , TASK_WOKEN_MSG ) ;
else {
task_destroy ( t ) ;
t = NULL ;
}
return t ;
}
/* parse a "debug dev trace" command
* debug dev trace < nbthr > .
* It will create as many tasks ( one per thread ) , starting from lowest threads .
* The traces will stop after 1 M wakeups or 20 M messages ~ = 4 GB of data .
*/
static int debug_parse_cli_trace ( char * * args , char * payload , struct appctx * appctx , void * private )
{
unsigned long count = 1 ;
unsigned long i ;
char * msg = NULL ;
char * endarg ;
if ( ! cli_has_level ( appctx , ACCESS_LVL_ADMIN ) )
return 1 ;
_HA_ATOMIC_INC ( & debug_commands_issued ) ;
if ( ! args [ 3 ] [ 0 ] ) {
memprintf ( & msg , " Need a thread count. Note that 20M msg will be sent per thread. \n " ) ;
goto fail ;
}
/* parse the new value . */
count = strtoll ( args [ 3 ] , & endarg , 0 ) ;
if ( args [ 3 ] [ 1 ] & & * endarg ) {
memprintf ( & msg , " Ignoring unparsable thread number '%s'. \n " , args [ 3 ] ) ;
goto fail ;
}
if ( count > = global . nbthread )
count = global . nbthread ;
for ( i = 0 ; i < count ; i + + ) {
struct task * task = task_new_on ( i ) ;
if ( ! task )
goto fail ;
task - > process = debug_trace_task ;
task - > context = ( void * ) ( ulong ) 1000000 ; // 1M wakeups = 20M messages
task_wakeup ( task , TASK_WOKEN_INIT ) ;
}
if ( msg & & * msg )
return cli_dynmsg ( appctx , LOG_INFO , msg ) ;
return 1 ;
fail :
return cli_dynmsg ( appctx , LOG_ERR , msg ) ;
}
# endif /* DEBUG_DEV */
2022-05-05 08:26:28 -04:00
/* CLI state for "debug dev fd" */
struct dev_fd_ctx {
int start_fd ;
} ;
DEBUG: cli: add a new "debug dev fd" expert command
This command will scan the whole file descriptors space to look for
existing FDs that are unknown to haproxy's fdtab, and will try to dump
a maximum number of information about them (including type, mode, device,
size, uid/gid, cloexec, O_* flags, socket types and addresses when
relevant). The goal is to help detecting inherited FDs from parent
processes as well as potential leaks.
Some of those listed are actually known but handled so deep into some
systems that they're not in the fdtab (such as epoll FDs or inter-
thread pipes). This might be refined in the future so that these ones
become known and do not appear.
Example of output:
$ socat - /tmp/sock1 <<< "expert-mode on;debug dev fd"
0 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
1 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
2 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
3 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x18112348 getfd=+0
4 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
33 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8251 getfd=+0 getfl=O_RDONLY
34 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
36 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8d1b getfd=+0 getfl=O_RDONLY
37 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
39 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24afa04f getfd=+0 getfl=O_RDONLY
41 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8252 getfd=+0 getfl=O_RDONLY
42 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
2022-01-24 14:26:09 -05:00
/* CLI parser for the "debug dev fd" command. The current FD to restart from is
2022-05-05 08:26:28 -04:00
* stored in a struct dev_fd_ctx pointed to by svcctx .
DEBUG: cli: add a new "debug dev fd" expert command
This command will scan the whole file descriptors space to look for
existing FDs that are unknown to haproxy's fdtab, and will try to dump
a maximum number of information about them (including type, mode, device,
size, uid/gid, cloexec, O_* flags, socket types and addresses when
relevant). The goal is to help detecting inherited FDs from parent
processes as well as potential leaks.
Some of those listed are actually known but handled so deep into some
systems that they're not in the fdtab (such as epoll FDs or inter-
thread pipes). This might be refined in the future so that these ones
become known and do not appear.
Example of output:
$ socat - /tmp/sock1 <<< "expert-mode on;debug dev fd"
0 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
1 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
2 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
3 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x18112348 getfd=+0
4 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
33 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8251 getfd=+0 getfl=O_RDONLY
34 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
36 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8d1b getfd=+0 getfl=O_RDONLY
37 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
39 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24afa04f getfd=+0 getfl=O_RDONLY
41 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8252 getfd=+0 getfl=O_RDONLY
42 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
2022-01-24 14:26:09 -05:00
*/
static int debug_parse_cli_fd ( char * * args , char * payload , struct appctx * appctx , void * private )
{
2022-05-05 08:26:28 -04:00
struct dev_fd_ctx * ctx = applet_reserve_svcctx ( appctx , sizeof ( * ctx ) ) ;
DEBUG: cli: add a new "debug dev fd" expert command
This command will scan the whole file descriptors space to look for
existing FDs that are unknown to haproxy's fdtab, and will try to dump
a maximum number of information about them (including type, mode, device,
size, uid/gid, cloexec, O_* flags, socket types and addresses when
relevant). The goal is to help detecting inherited FDs from parent
processes as well as potential leaks.
Some of those listed are actually known but handled so deep into some
systems that they're not in the fdtab (such as epoll FDs or inter-
thread pipes). This might be refined in the future so that these ones
become known and do not appear.
Example of output:
$ socat - /tmp/sock1 <<< "expert-mode on;debug dev fd"
0 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
1 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
2 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
3 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x18112348 getfd=+0
4 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
33 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8251 getfd=+0 getfl=O_RDONLY
34 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
36 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8d1b getfd=+0 getfl=O_RDONLY
37 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
39 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24afa04f getfd=+0 getfl=O_RDONLY
41 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8252 getfd=+0 getfl=O_RDONLY
42 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
2022-01-24 14:26:09 -05:00
if ( ! cli_has_level ( appctx , ACCESS_LVL_OPER ) )
return 1 ;
/* start at fd #0 */
2022-05-05 08:26:28 -04:00
ctx - > start_fd = 0 ;
DEBUG: cli: add a new "debug dev fd" expert command
This command will scan the whole file descriptors space to look for
existing FDs that are unknown to haproxy's fdtab, and will try to dump
a maximum number of information about them (including type, mode, device,
size, uid/gid, cloexec, O_* flags, socket types and addresses when
relevant). The goal is to help detecting inherited FDs from parent
processes as well as potential leaks.
Some of those listed are actually known but handled so deep into some
systems that they're not in the fdtab (such as epoll FDs or inter-
thread pipes). This might be refined in the future so that these ones
become known and do not appear.
Example of output:
$ socat - /tmp/sock1 <<< "expert-mode on;debug dev fd"
0 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
1 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
2 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
3 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x18112348 getfd=+0
4 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
33 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8251 getfd=+0 getfl=O_RDONLY
34 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
36 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8d1b getfd=+0 getfl=O_RDONLY
37 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
39 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24afa04f getfd=+0 getfl=O_RDONLY
41 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8252 getfd=+0 getfl=O_RDONLY
42 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
2022-01-24 14:26:09 -05:00
return 0 ;
}
/* CLI I/O handler for the "debug dev fd" command. Dumps all FDs that are
* accessible from the process but not known from fdtab . The FD number to
2022-05-05 08:26:28 -04:00
* restart from is stored in a struct dev_fd_ctx pointed to by svcctx .
DEBUG: cli: add a new "debug dev fd" expert command
This command will scan the whole file descriptors space to look for
existing FDs that are unknown to haproxy's fdtab, and will try to dump
a maximum number of information about them (including type, mode, device,
size, uid/gid, cloexec, O_* flags, socket types and addresses when
relevant). The goal is to help detecting inherited FDs from parent
processes as well as potential leaks.
Some of those listed are actually known but handled so deep into some
systems that they're not in the fdtab (such as epoll FDs or inter-
thread pipes). This might be refined in the future so that these ones
become known and do not appear.
Example of output:
$ socat - /tmp/sock1 <<< "expert-mode on;debug dev fd"
0 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
1 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
2 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
3 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x18112348 getfd=+0
4 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
33 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8251 getfd=+0 getfl=O_RDONLY
34 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
36 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8d1b getfd=+0 getfl=O_RDONLY
37 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
39 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24afa04f getfd=+0 getfl=O_RDONLY
41 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8252 getfd=+0 getfl=O_RDONLY
42 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
2022-01-24 14:26:09 -05:00
*/
static int debug_iohandler_fd ( struct appctx * appctx )
{
2022-05-05 08:26:28 -04:00
struct dev_fd_ctx * ctx = appctx - > svcctx ;
DEBUG: cli: add a new "debug dev fd" expert command
This command will scan the whole file descriptors space to look for
existing FDs that are unknown to haproxy's fdtab, and will try to dump
a maximum number of information about them (including type, mode, device,
size, uid/gid, cloexec, O_* flags, socket types and addresses when
relevant). The goal is to help detecting inherited FDs from parent
processes as well as potential leaks.
Some of those listed are actually known but handled so deep into some
systems that they're not in the fdtab (such as epoll FDs or inter-
thread pipes). This might be refined in the future so that these ones
become known and do not appear.
Example of output:
$ socat - /tmp/sock1 <<< "expert-mode on;debug dev fd"
0 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
1 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
2 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
3 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x18112348 getfd=+0
4 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
33 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8251 getfd=+0 getfl=O_RDONLY
34 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
36 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8d1b getfd=+0 getfl=O_RDONLY
37 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
39 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24afa04f getfd=+0 getfl=O_RDONLY
41 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8252 getfd=+0 getfl=O_RDONLY
42 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
2022-01-24 14:26:09 -05:00
struct sockaddr_storage sa ;
struct stat statbuf ;
socklen_t salen , vlen ;
int ret1 , ret2 , port ;
char * addrstr ;
int ret = 1 ;
int i , fd ;
chunk_reset ( & trash ) ;
thread_isolate ( ) ;
/* we have two inner loops here, one for the proxy, the other one for
* the buffer .
*/
2022-05-05 08:26:28 -04:00
for ( fd = ctx - > start_fd ; fd < global . maxsock ; fd + + ) {
DEBUG: cli: add a new "debug dev fd" expert command
This command will scan the whole file descriptors space to look for
existing FDs that are unknown to haproxy's fdtab, and will try to dump
a maximum number of information about them (including type, mode, device,
size, uid/gid, cloexec, O_* flags, socket types and addresses when
relevant). The goal is to help detecting inherited FDs from parent
processes as well as potential leaks.
Some of those listed are actually known but handled so deep into some
systems that they're not in the fdtab (such as epoll FDs or inter-
thread pipes). This might be refined in the future so that these ones
become known and do not appear.
Example of output:
$ socat - /tmp/sock1 <<< "expert-mode on;debug dev fd"
0 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
1 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
2 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
3 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x18112348 getfd=+0
4 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
33 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8251 getfd=+0 getfl=O_RDONLY
34 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
36 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8d1b getfd=+0 getfl=O_RDONLY
37 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
39 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24afa04f getfd=+0 getfl=O_RDONLY
41 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8252 getfd=+0 getfl=O_RDONLY
42 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
2022-01-24 14:26:09 -05:00
/* check for FD's existence */
ret1 = fcntl ( fd , F_GETFD , 0 ) ;
if ( ret1 = = - 1 )
continue ; // not known to the process
if ( fdtab [ fd ] . owner )
continue ; // well-known
/* OK we're seeing an orphan let's try to retrieve as much
* information as possible about it .
*/
chunk_printf ( & trash , " %5d " , fd ) ;
if ( fstat ( fd , & statbuf ) ! = - 1 ) {
chunk_appendf ( & trash , " type=%s mod=%04o dev=%#llx siz=%#llx uid=%lld gid=%lld fs=%#llx ino=%#llx " ,
isatty ( fd ) ? " tty. " :
S_ISREG ( statbuf . st_mode ) ? " file " :
S_ISDIR ( statbuf . st_mode ) ? " dir. " :
S_ISCHR ( statbuf . st_mode ) ? " chr. " :
S_ISBLK ( statbuf . st_mode ) ? " blk. " :
S_ISFIFO ( statbuf . st_mode ) ? " pipe " :
S_ISLNK ( statbuf . st_mode ) ? " link " :
S_ISSOCK ( statbuf . st_mode ) ? " sock " :
# ifdef USE_EPOLL
2023-07-02 04:49:49 -04:00
/* trick: epoll_ctl() will return -ENOENT when trying
* to remove from a valid epoll FD an FD that was not
* registered against it . But we don ' t want to risk
* disabling a random FD . Instead we ' ll create a new
* one by duplicating 0 ( it should be valid since
* pointing to a terminal or / dev / null ) , and try to
* remove it .
*/
( {
int fd2 = dup ( 0 ) ;
int ret = fd2 ;
if ( ret > = 0 ) {
ret = epoll_ctl ( fd , EPOLL_CTL_DEL , fd2 , NULL ) ;
if ( ret = = - 1 & & errno = = ENOENT )
ret = 0 ; // that's a real epoll
else
ret = - 1 ; // it's something else
close ( fd2 ) ;
}
ret ;
} ) = = 0 ? " epol " :
DEBUG: cli: add a new "debug dev fd" expert command
This command will scan the whole file descriptors space to look for
existing FDs that are unknown to haproxy's fdtab, and will try to dump
a maximum number of information about them (including type, mode, device,
size, uid/gid, cloexec, O_* flags, socket types and addresses when
relevant). The goal is to help detecting inherited FDs from parent
processes as well as potential leaks.
Some of those listed are actually known but handled so deep into some
systems that they're not in the fdtab (such as epoll FDs or inter-
thread pipes). This might be refined in the future so that these ones
become known and do not appear.
Example of output:
$ socat - /tmp/sock1 <<< "expert-mode on;debug dev fd"
0 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
1 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
2 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
3 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x18112348 getfd=+0
4 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
33 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8251 getfd=+0 getfl=O_RDONLY
34 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
36 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8d1b getfd=+0 getfl=O_RDONLY
37 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
39 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24afa04f getfd=+0 getfl=O_RDONLY
41 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8252 getfd=+0 getfl=O_RDONLY
42 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
2022-01-24 14:26:09 -05:00
# endif
" ???? " ,
( uint ) statbuf . st_mode & 07777 ,
( ullong ) statbuf . st_rdev ,
( ullong ) statbuf . st_size ,
( ullong ) statbuf . st_uid ,
( ullong ) statbuf . st_gid ,
( ullong ) statbuf . st_dev ,
( ullong ) statbuf . st_ino ) ;
}
chunk_appendf ( & trash , " getfd=%s+%#x " ,
( ret1 & FD_CLOEXEC ) ? " cloex " : " " ,
ret1 & ~ FD_CLOEXEC ) ;
/* FD options */
ret2 = fcntl ( fd , F_GETFL , 0 ) ;
if ( ret2 ) {
chunk_appendf ( & trash , " getfl=%s " ,
( ret1 & 3 ) > = 2 ? " O_RDWR " :
( ret1 & 1 ) ? " O_WRONLY " : " O_RDONLY " ) ;
for ( i = 2 ; i < 32 ; i + + ) {
if ( ! ( ret2 & ( 1UL < < i ) ) )
continue ;
switch ( 1UL < < i ) {
case O_CREAT : chunk_appendf ( & trash , " ,O_CREAT " ) ; break ;
case O_EXCL : chunk_appendf ( & trash , " ,O_EXCL " ) ; break ;
case O_NOCTTY : chunk_appendf ( & trash , " ,O_NOCTTY " ) ; break ;
case O_TRUNC : chunk_appendf ( & trash , " ,O_TRUNC " ) ; break ;
case O_APPEND : chunk_appendf ( & trash , " ,O_APPEND " ) ; break ;
2022-01-25 08:51:53 -05:00
# ifdef O_ASYNC
DEBUG: cli: add a new "debug dev fd" expert command
This command will scan the whole file descriptors space to look for
existing FDs that are unknown to haproxy's fdtab, and will try to dump
a maximum number of information about them (including type, mode, device,
size, uid/gid, cloexec, O_* flags, socket types and addresses when
relevant). The goal is to help detecting inherited FDs from parent
processes as well as potential leaks.
Some of those listed are actually known but handled so deep into some
systems that they're not in the fdtab (such as epoll FDs or inter-
thread pipes). This might be refined in the future so that these ones
become known and do not appear.
Example of output:
$ socat - /tmp/sock1 <<< "expert-mode on;debug dev fd"
0 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
1 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
2 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
3 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x18112348 getfd=+0
4 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
33 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8251 getfd=+0 getfl=O_RDONLY
34 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
36 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8d1b getfd=+0 getfl=O_RDONLY
37 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
39 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24afa04f getfd=+0 getfl=O_RDONLY
41 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8252 getfd=+0 getfl=O_RDONLY
42 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
2022-01-24 14:26:09 -05:00
case O_ASYNC : chunk_appendf ( & trash , " ,O_ASYNC " ) ; break ;
2022-01-25 08:51:53 -05:00
# endif
DEBUG: cli: add a new "debug dev fd" expert command
This command will scan the whole file descriptors space to look for
existing FDs that are unknown to haproxy's fdtab, and will try to dump
a maximum number of information about them (including type, mode, device,
size, uid/gid, cloexec, O_* flags, socket types and addresses when
relevant). The goal is to help detecting inherited FDs from parent
processes as well as potential leaks.
Some of those listed are actually known but handled so deep into some
systems that they're not in the fdtab (such as epoll FDs or inter-
thread pipes). This might be refined in the future so that these ones
become known and do not appear.
Example of output:
$ socat - /tmp/sock1 <<< "expert-mode on;debug dev fd"
0 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
1 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
2 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
3 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x18112348 getfd=+0
4 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
33 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8251 getfd=+0 getfl=O_RDONLY
34 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
36 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8d1b getfd=+0 getfl=O_RDONLY
37 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
39 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24afa04f getfd=+0 getfl=O_RDONLY
41 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8252 getfd=+0 getfl=O_RDONLY
42 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
2022-01-24 14:26:09 -05:00
# ifdef O_DIRECT
case O_DIRECT : chunk_appendf ( & trash , " ,O_DIRECT " ) ; break ;
# endif
# ifdef O_NOATIME
case O_NOATIME : chunk_appendf ( & trash , " ,O_NOATIME " ) ; break ;
# endif
}
}
}
vlen = sizeof ( ret2 ) ;
ret1 = getsockopt ( fd , SOL_SOCKET , SO_TYPE , & ret2 , & vlen ) ;
if ( ret1 ! = - 1 )
chunk_appendf ( & trash , " so_type=%d " , ret2 ) ;
vlen = sizeof ( ret2 ) ;
ret1 = getsockopt ( fd , SOL_SOCKET , SO_ACCEPTCONN , & ret2 , & vlen ) ;
if ( ret1 ! = - 1 )
chunk_appendf ( & trash , " so_accept=%d " , ret2 ) ;
vlen = sizeof ( ret2 ) ;
ret1 = getsockopt ( fd , SOL_SOCKET , SO_ERROR , & ret2 , & vlen ) ;
if ( ret1 ! = - 1 )
chunk_appendf ( & trash , " so_error=%d " , ret2 ) ;
salen = sizeof ( sa ) ;
if ( getsockname ( fd , ( struct sockaddr * ) & sa , & salen ) ! = - 1 ) {
2024-10-24 10:31:56 -04:00
int i ;
DEBUG: cli: add a new "debug dev fd" expert command
This command will scan the whole file descriptors space to look for
existing FDs that are unknown to haproxy's fdtab, and will try to dump
a maximum number of information about them (including type, mode, device,
size, uid/gid, cloexec, O_* flags, socket types and addresses when
relevant). The goal is to help detecting inherited FDs from parent
processes as well as potential leaks.
Some of those listed are actually known but handled so deep into some
systems that they're not in the fdtab (such as epoll FDs or inter-
thread pipes). This might be refined in the future so that these ones
become known and do not appear.
Example of output:
$ socat - /tmp/sock1 <<< "expert-mode on;debug dev fd"
0 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
1 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
2 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
3 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x18112348 getfd=+0
4 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
33 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8251 getfd=+0 getfl=O_RDONLY
34 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
36 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8d1b getfd=+0 getfl=O_RDONLY
37 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
39 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24afa04f getfd=+0 getfl=O_RDONLY
41 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8252 getfd=+0 getfl=O_RDONLY
42 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
2022-01-24 14:26:09 -05:00
if ( sa . ss_family = = AF_INET )
port = ntohs ( ( ( const struct sockaddr_in * ) & sa ) - > sin_port ) ;
else if ( sa . ss_family = = AF_INET6 )
port = ntohs ( ( ( const struct sockaddr_in6 * ) & sa ) - > sin6_port ) ;
else
port = 0 ;
addrstr = sa2str ( & sa , port , 0 ) ;
2024-10-24 10:31:56 -04:00
/* cleanup the output */
for ( i = 0 ; i < strlen ( addrstr ) ; i + + ) {
if ( iscntrl ( ( unsigned char ) addrstr [ i ] ) | | ! isprint ( ( unsigned char ) addrstr [ i ] ) )
addrstr [ i ] = ' . ' ;
}
DEBUG: cli: add a new "debug dev fd" expert command
This command will scan the whole file descriptors space to look for
existing FDs that are unknown to haproxy's fdtab, and will try to dump
a maximum number of information about them (including type, mode, device,
size, uid/gid, cloexec, O_* flags, socket types and addresses when
relevant). The goal is to help detecting inherited FDs from parent
processes as well as potential leaks.
Some of those listed are actually known but handled so deep into some
systems that they're not in the fdtab (such as epoll FDs or inter-
thread pipes). This might be refined in the future so that these ones
become known and do not appear.
Example of output:
$ socat - /tmp/sock1 <<< "expert-mode on;debug dev fd"
0 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
1 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
2 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
3 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x18112348 getfd=+0
4 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
33 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8251 getfd=+0 getfl=O_RDONLY
34 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
36 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8d1b getfd=+0 getfl=O_RDONLY
37 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
39 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24afa04f getfd=+0 getfl=O_RDONLY
41 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8252 getfd=+0 getfl=O_RDONLY
42 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
2022-01-24 14:26:09 -05:00
chunk_appendf ( & trash , " laddr=%s " , addrstr ) ;
free ( addrstr ) ;
}
salen = sizeof ( sa ) ;
if ( getpeername ( fd , ( struct sockaddr * ) & sa , & salen ) ! = - 1 ) {
if ( sa . ss_family = = AF_INET )
port = ntohs ( ( ( const struct sockaddr_in * ) & sa ) - > sin_port ) ;
else if ( sa . ss_family = = AF_INET6 )
port = ntohs ( ( ( const struct sockaddr_in6 * ) & sa ) - > sin6_port ) ;
else
port = 0 ;
addrstr = sa2str ( & sa , port , 0 ) ;
2024-10-24 10:31:56 -04:00
/* cleanup the output */
for ( i = 0 ; i < strlen ( addrstr ) ; i + + ) {
if ( ( iscntrl ( ( unsigned char ) addrstr [ i ] ) ) | | ! isprint ( ( unsigned char ) addrstr [ i ] ) )
addrstr [ i ] = ' . ' ;
}
DEBUG: cli: add a new "debug dev fd" expert command
This command will scan the whole file descriptors space to look for
existing FDs that are unknown to haproxy's fdtab, and will try to dump
a maximum number of information about them (including type, mode, device,
size, uid/gid, cloexec, O_* flags, socket types and addresses when
relevant). The goal is to help detecting inherited FDs from parent
processes as well as potential leaks.
Some of those listed are actually known but handled so deep into some
systems that they're not in the fdtab (such as epoll FDs or inter-
thread pipes). This might be refined in the future so that these ones
become known and do not appear.
Example of output:
$ socat - /tmp/sock1 <<< "expert-mode on;debug dev fd"
0 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
1 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
2 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
3 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x18112348 getfd=+0
4 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
33 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8251 getfd=+0 getfl=O_RDONLY
34 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
36 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8d1b getfd=+0 getfl=O_RDONLY
37 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
39 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24afa04f getfd=+0 getfl=O_RDONLY
41 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8252 getfd=+0 getfl=O_RDONLY
42 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
2022-01-24 14:26:09 -05:00
chunk_appendf ( & trash , " raddr=%s " , addrstr ) ;
free ( addrstr ) ;
}
chunk_appendf ( & trash , " \n " ) ;
2022-05-18 09:07:19 -04:00
if ( applet_putchk ( appctx , & trash ) = = - 1 ) {
2022-05-05 08:26:28 -04:00
ctx - > start_fd = fd ;
DEBUG: cli: add a new "debug dev fd" expert command
This command will scan the whole file descriptors space to look for
existing FDs that are unknown to haproxy's fdtab, and will try to dump
a maximum number of information about them (including type, mode, device,
size, uid/gid, cloexec, O_* flags, socket types and addresses when
relevant). The goal is to help detecting inherited FDs from parent
processes as well as potential leaks.
Some of those listed are actually known but handled so deep into some
systems that they're not in the fdtab (such as epoll FDs or inter-
thread pipes). This might be refined in the future so that these ones
become known and do not appear.
Example of output:
$ socat - /tmp/sock1 <<< "expert-mode on;debug dev fd"
0 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
1 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
2 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
3 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x18112348 getfd=+0
4 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
33 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8251 getfd=+0 getfl=O_RDONLY
34 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
36 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8d1b getfd=+0 getfl=O_RDONLY
37 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
39 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24afa04f getfd=+0 getfl=O_RDONLY
41 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8252 getfd=+0 getfl=O_RDONLY
42 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
2022-01-24 14:26:09 -05:00
ret = 0 ;
break ;
}
}
thread_release ( ) ;
return ret ;
}
2020-07-02 03:14:48 -04:00
# if defined(DEBUG_MEM_STATS)
2022-05-05 08:39:51 -04:00
/* CLI state for "debug dev memstats" */
struct dev_mem_ctx {
struct mem_stats * start , * stop ; /* begin/end of dump */
2022-11-30 10:42:54 -05:00
char * match ; /* non-null if a name prefix is specified */
2022-05-05 08:39:51 -04:00
int show_all ; /* show all entries if non-null */
2022-11-30 11:16:51 -05:00
int width ; /* 1st column width */
long tot_size ; /* sum of alloc-free */
ulong tot_calls ; /* sum of calls */
2022-05-05 08:39:51 -04:00
} ;
/* CLI parser for the "debug dev memstats" command. Sets a dev_mem_ctx shown above. */
2020-07-02 03:14:48 -04:00
static int debug_parse_cli_memstats ( char * * args , char * payload , struct appctx * appctx , void * private )
{
2022-05-05 08:39:51 -04:00
struct dev_mem_ctx * ctx = applet_reserve_svcctx ( appctx , sizeof ( * ctx ) ) ;
2022-11-30 10:50:48 -05:00
int arg ;
2022-05-05 08:39:51 -04:00
2020-07-02 03:14:48 -04:00
extern __attribute__ ( ( __weak__ ) ) struct mem_stats __start_mem_stats ;
extern __attribute__ ( ( __weak__ ) ) struct mem_stats __stop_mem_stats ;
if ( ! cli_has_level ( appctx , ACCESS_LVL_OPER ) )
return 1 ;
2022-11-30 10:50:48 -05:00
for ( arg = 3 ; * args [ arg ] ; arg + + ) {
if ( strcmp ( args [ arg ] , " reset " ) = = 0 ) {
struct mem_stats * ptr ;
2020-07-02 03:14:48 -04:00
2022-11-30 10:50:48 -05:00
if ( ! cli_has_level ( appctx , ACCESS_LVL_ADMIN ) )
return 1 ;
2020-07-02 03:14:48 -04:00
2022-11-30 10:50:48 -05:00
for ( ptr = & __start_mem_stats ; ptr < & __stop_mem_stats ; ptr + + ) {
_HA_ATOMIC_STORE ( & ptr - > calls , 0 ) ;
_HA_ATOMIC_STORE ( & ptr - > size , 0 ) ;
}
return 1 ;
2020-07-02 03:14:48 -04:00
}
2022-11-30 10:50:48 -05:00
else if ( strcmp ( args [ arg ] , " all " ) = = 0 ) {
ctx - > show_all = 1 ;
continue ;
}
2022-11-30 10:42:54 -05:00
else if ( strcmp ( args [ arg ] , " match " ) = = 0 & & * args [ arg + 1 ] ) {
ha_free ( & ctx - > match ) ;
ctx - > match = strdup ( args [ arg + 1 ] ) ;
arg + + ;
continue ;
}
2022-11-30 10:50:48 -05:00
else
2022-11-30 10:42:54 -05:00
return cli_err ( appctx , " Expects either 'reset', 'all', or 'match <pfx>'. \n " ) ;
2020-07-02 03:14:48 -04:00
}
/* otherwise proceed with the dump from p0 to p1 */
2022-05-05 08:39:51 -04:00
ctx - > start = & __start_mem_stats ;
ctx - > stop = & __stop_mem_stats ;
2022-08-09 02:51:08 -04:00
ctx - > width = 0 ;
2020-07-02 03:14:48 -04:00
return 0 ;
}
2022-05-05 08:39:51 -04:00
/* CLI I/O handler for the "debug dev memstats" command using a dev_mem_ctx
* found in appctx - > svcctx . Dumps all mem_stats structs referenced by pointers
* located between - > start and - > stop . Dumps all entries if - > show_all ! = 0 ,
* otherwise only non - zero calls .
2020-07-02 03:14:48 -04:00
*/
static int debug_iohandler_memstats ( struct appctx * appctx )
{
2022-05-05 08:39:51 -04:00
struct dev_mem_ctx * ctx = appctx - > svcctx ;
2022-08-09 02:51:08 -04:00
struct mem_stats * ptr ;
2022-11-30 10:42:54 -05:00
const char * pfx = ctx - > match ;
2020-07-02 03:14:48 -04:00
int ret = 1 ;
2022-08-09 02:51:08 -04:00
if ( ! ctx - > width ) {
/* we don't know the first column's width, let's compute it
* now based on a first pass on printable entries and their
* expected width ( approximated ) .
*/
for ( ptr = ctx - > start ; ptr ! = ctx - > stop ; ptr + + ) {
const char * p , * name ;
int w = 0 ;
char tmp ;
if ( ! ptr - > size & & ! ptr - > calls & & ! ctx - > show_all )
continue ;
2022-09-06 02:05:59 -04:00
for ( p = name = ptr - > caller . file ; * p ; p + + ) {
2022-08-09 02:51:08 -04:00
if ( * p = = ' / ' )
name = p + 1 ;
}
if ( ctx - > show_all )
2022-09-06 02:05:59 -04:00
w = snprintf ( & tmp , 0 , " %s(%s:%d) " , ptr - > caller . func , name , ptr - > caller . line ) ;
2022-08-09 02:51:08 -04:00
else
2022-09-06 02:05:59 -04:00
w = snprintf ( & tmp , 0 , " %s:%d " , name , ptr - > caller . line ) ;
2022-08-09 02:51:08 -04:00
if ( w > ctx - > width )
ctx - > width = w ;
}
}
2020-07-02 03:14:48 -04:00
/* we have two inner loops here, one for the proxy, the other one for
* the buffer .
*/
2022-08-09 02:51:08 -04:00
for ( ptr = ctx - > start ; ptr ! = ctx - > stop ; ptr + + ) {
2020-07-02 03:14:48 -04:00
const char * type ;
const char * name ;
const char * p ;
2022-08-09 02:15:27 -04:00
const char * info = NULL ;
2022-08-09 02:40:08 -04:00
const char * func = NULL ;
2022-11-30 11:16:51 -05:00
int direction = 0 ; // neither alloc nor free (e.g. realloc)
2020-07-02 03:14:48 -04:00
2022-05-05 08:39:51 -04:00
if ( ! ptr - > size & & ! ptr - > calls & & ! ctx - > show_all )
2020-07-02 03:14:48 -04:00
continue ;
/* basename only */
2022-09-06 02:05:59 -04:00
for ( p = name = ptr - > caller . file ; * p ; p + + ) {
2020-07-02 03:14:48 -04:00
if ( * p = = ' / ' )
name = p + 1 ;
}
2022-09-06 02:05:59 -04:00
func = ptr - > caller . func ;
2022-08-09 02:40:08 -04:00
2022-09-06 02:05:59 -04:00
switch ( ptr - > caller . what ) {
2022-11-30 11:16:51 -05:00
case MEM_STATS_TYPE_CALLOC : type = " CALLOC " ; direction = 1 ; break ;
case MEM_STATS_TYPE_FREE : type = " FREE " ; direction = - 1 ; break ;
case MEM_STATS_TYPE_MALLOC : type = " MALLOC " ; direction = 1 ; break ;
2020-07-02 03:14:48 -04:00
case MEM_STATS_TYPE_REALLOC : type = " REALLOC " ; break ;
2022-11-30 11:16:51 -05:00
case MEM_STATS_TYPE_STRDUP : type = " STRDUP " ; direction = 1 ; break ;
case MEM_STATS_TYPE_P_ALLOC : type = " P_ALLOC " ; direction = 1 ; if ( ptr - > extra ) info = ( ( const struct pool_head * ) ptr - > extra ) - > name ; break ;
case MEM_STATS_TYPE_P_FREE : type = " P_FREE " ; direction = - 1 ; if ( ptr - > extra ) info = ( ( const struct pool_head * ) ptr - > extra ) - > name ; break ;
2020-07-02 03:14:48 -04:00
default : type = " UNSET " ; break ;
}
//chunk_printf(&trash,
// "%20s:%-5d %7s size: %12lu calls: %9lu size/call: %6lu\n",
// name, ptr->line, type,
// (unsigned long)ptr->size, (unsigned long)ptr->calls,
// (unsigned long)(ptr->calls ? (ptr->size / ptr->calls) : 0));
2022-11-30 10:42:54 -05:00
/* only match requested prefixes */
if ( pfx & & ( ! info | | strncmp ( info , pfx , strlen ( pfx ) ) ! = 0 ) )
continue ;
2022-08-09 02:40:08 -04:00
chunk_reset ( & trash ) ;
if ( ctx - > show_all )
chunk_appendf ( & trash , " %s( " , func ) ;
2022-09-06 02:05:59 -04:00
chunk_appendf ( & trash , " %s:%d " , name , ptr - > caller . line ) ;
2022-08-09 02:40:08 -04:00
if ( ctx - > show_all )
chunk_appendf ( & trash , " ) " ) ;
2022-08-09 02:51:08 -04:00
while ( trash . data < ctx - > width )
2020-07-02 03:14:48 -04:00
trash . area [ trash . data + + ] = ' ' ;
2022-08-09 02:40:08 -04:00
2022-08-09 02:15:27 -04:00
chunk_appendf ( & trash , " %7s size: %12lu calls: %9lu size/call: %6lu %s \n " ,
2020-07-02 03:14:48 -04:00
type ,
( unsigned long ) ptr - > size , ( unsigned long ) ptr - > calls ,
2022-08-09 02:15:27 -04:00
( unsigned long ) ( ptr - > calls ? ( ptr - > size / ptr - > calls ) : 0 ) ,
info ? info : " " ) ;
2020-07-02 03:14:48 -04:00
2022-05-18 09:07:19 -04:00
if ( applet_putchk ( appctx , & trash ) = = - 1 ) {
2022-05-05 08:39:51 -04:00
ctx - > start = ptr ;
2020-07-02 03:14:48 -04:00
ret = 0 ;
2022-11-30 11:16:51 -05:00
goto end ;
}
if ( direction > 0 ) {
ctx - > tot_size + = ( ulong ) ptr - > size ;
ctx - > tot_calls + = ( ulong ) ptr - > calls ;
}
else if ( direction < 0 ) {
ctx - > tot_size - = ( ulong ) ptr - > size ;
ctx - > tot_calls + = ( ulong ) ptr - > calls ;
2020-07-02 03:14:48 -04:00
}
}
2022-11-30 11:16:51 -05:00
/* now dump a summary */
chunk_reset ( & trash ) ;
chunk_appendf ( & trash , " Total " ) ;
while ( trash . data < ctx - > width )
trash . area [ trash . data + + ] = ' ' ;
chunk_appendf ( & trash , " %7s size: %12ld calls: %9lu size/call: %6ld %s \n " ,
" BALANCE " ,
ctx - > tot_size , ctx - > tot_calls ,
( long ) ( ctx - > tot_calls ? ( ctx - > tot_size / ctx - > tot_calls ) : 0 ) ,
" (excl. realloc) " ) ;
if ( applet_putchk ( appctx , & trash ) = = - 1 ) {
ctx - > start = ptr ;
ret = 0 ;
goto end ;
}
2020-07-02 03:14:48 -04:00
end :
return ret ;
}
2022-11-30 10:42:54 -05:00
/* release the "show pools" context */
static void debug_release_memstats ( struct appctx * appctx )
{
struct dev_mem_ctx * ctx = appctx - > svcctx ;
ha_free ( & ctx - > match ) ;
}
2020-07-02 03:14:48 -04:00
# endif
2024-10-21 12:51:55 -04:00
# if !defined(USE_OBSOLETE_LINKER)
/* CLI state for "debug dev counters" */
struct dev_cnt_ctx {
struct debug_count * start , * stop ; /* begin/end of dump */
int types ; /* OR mask of 1<<type */
int show_all ; /* show all entries if non-null */
} ;
/* CLI parser for the "debug dev counters" command. Sets a dev_cnt_ctx shown above. */
static int debug_parse_cli_counters ( char * * args , char * payload , struct appctx * appctx , void * private )
{
struct dev_cnt_ctx * ctx = applet_reserve_svcctx ( appctx , sizeof ( * ctx ) ) ;
int action ;
int arg ;
if ( ! cli_has_level ( appctx , ACCESS_LVL_OPER ) )
return 1 ;
action = 0 ; // 0=show, 1=reset
for ( arg = 3 ; * args [ arg ] ; arg + + ) {
if ( strcmp ( args [ arg ] , " reset " ) = = 0 ) {
action = 1 ;
continue ;
}
else if ( strcmp ( args [ arg ] , " all " ) = = 0 ) {
ctx - > show_all = 1 ;
continue ;
}
else if ( strcmp ( args [ arg ] , " show " ) = = 0 ) {
action = 0 ;
continue ;
}
else if ( strcmp ( args [ arg ] , " bug " ) = = 0 ) {
ctx - > types | = 1 < < DBG_BUG ;
continue ;
}
else if ( strcmp ( args [ arg ] , " chk " ) = = 0 ) {
ctx - > types | = 1 < < DBG_BUG_ONCE ;
continue ;
}
else if ( strcmp ( args [ arg ] , " cnt " ) = = 0 ) {
ctx - > types | = 1 < < DBG_COUNT_IF ;
continue ;
}
else
return cli_err ( appctx , " Expects an optional action ('reset','show') , optional types ( ' bug ' , ' chk ' , ' cnt ' ) and optionally ' all ' to even dump null counters . \ n " ) ;
}
if ( action = = 1 ) { // reset
struct debug_count * ptr ;
if ( ! cli_has_level ( appctx , ACCESS_LVL_ADMIN ) )
return 1 ;
for ( ptr = & __start_dbg_cnt ; ptr < & __stop_dbg_cnt ; ptr + + ) {
if ( ctx - > types & & ! ( ctx - > types & ( 1 < < ptr - > type ) ) )
continue ;
_HA_ATOMIC_STORE ( & ptr - > count , 0 ) ;
}
return 1 ;
}
/* OK it's a show, let's dump relevant counters */
ctx - > start = & __start_dbg_cnt ;
ctx - > stop = & __stop_dbg_cnt ;
return 0 ;
}
/* CLI I/O handler for the "debug dev counters" command using a dev_cnt_ctx
* found in appctx - > svcctx . Dumps all mem_stats structs referenced by pointers
* located between - > start and - > stop . Dumps all entries if - > show_all ! = 0 ,
* otherwise only non - zero calls .
*/
static int debug_iohandler_counters ( struct appctx * appctx )
{
const char * bug_type [ DBG_COUNTER_TYPES ] = {
[ DBG_BUG ] = " BUG " ,
[ DBG_BUG_ONCE ] = " CHK " ,
[ DBG_COUNT_IF ] = " CNT " ,
} ;
struct dev_cnt_ctx * ctx = appctx - > svcctx ;
struct debug_count * ptr ;
int ret = 1 ;
/* we have two inner loops here, one for the proxy, the other one for
* the buffer .
*/
chunk_printf ( & trash , " Count Type Location function(): \" condition \" [comment] \n " ) ;
for ( ptr = ctx - > start ; ptr ! = ctx - > stop ; ptr + + ) {
const char * p , * name ;
if ( ctx - > types & & ! ( ctx - > types & ( 1 < < ptr - > type ) ) )
continue ;
if ( ! ptr - > count & & ! ctx - > show_all )
continue ;
for ( p = name = ptr - > file ; * p ; p + + ) {
if ( * p = = ' / ' )
name = p + 1 ;
}
if ( ptr - > type < DBG_COUNTER_TYPES )
chunk_appendf ( & trash , " %-10u %3s %s:%d %s(): %s \n " ,
ptr - > count , bug_type [ ptr - > type ] ,
name , ptr - > line , ptr - > func , ptr - > desc ) ;
if ( applet_putchk ( appctx , & trash ) = = - 1 ) {
ctx - > start = ptr ;
ret = 0 ;
goto end ;
}
}
/* we could even dump a summary here if needed, returning ret=0 */
end :
return ret ;
}
# endif /* USE_OBSOLETE_LINKER */
MEDIUM: debug: simplify the thread dump mechanism
The thread dump mechanism that is used by "show threads" and by the
panic dump is overly complicated due to an initial misdesign. It
firsts wakes all threads, then serializes their dumps, then releases
them, while taking extreme care not to face colliding dumps. In fact
this is not what we need and it reached a limit where big machines
cannot dump all their threads anymore due to buffer size limitations.
What is needed instead is to be able to dump *one* thread, and to let
the requester iterate on all threads.
That's what this patch does. It adds the thread_dump_buffer to the
struct thread_ctx so that the requester offers the buffer to the
thread that is about to be dumped. This buffer also serves as a lock.
A thread at rest has a NULL, a valid pointer indicates the thread is
using it, and 0x1 (NULL+1) is used by the dumped thread to tell the
requester it's done. This makes sure that a given thread is dumped
once at a time. In addition to this, the calling thread decides
whether it accesses the thread by itself or via the debug signal
handler, in order to get a backtrace. This is much saner because the
calling thread is free to do whatever it wants with the buffer after
each thread is dumped, and there is no dependency between threads,
once they've dumped, they're free to continue (and possibly to dump
for another requester if needed). Finally, when the THREAD_DUMP
feature is disabled and the debug signal is not used, the requester
accesses the thread by itself like before.
For now we still have the buffer size limitation but it will be
addressed in future patches.
2023-05-04 10:23:51 -04:00
# ifdef USE_THREAD_DUMP
2019-05-17 04:08:49 -04:00
2022-07-15 07:14:03 -04:00
/* handles DEBUGSIG to dump the state of the thread it's working on. This is
* appended at the end of thread_dump_buffer which must be protected against
2024-10-19 08:53:11 -04:00
* reentrance from different threads ( a thread - local buffer works fine ) . If
* the buffer pointer is equal to 0x2 , then it ' s a panic . The thread allocates
* the buffer from its own trash chunks so that the contents remain visible in
* the core , and it never returns .
2022-07-15 07:14:03 -04:00
*/
2019-05-17 04:08:49 -04:00
void debug_handler ( int sig , siginfo_t * si , void * arg )
{
MEDIUM: debug: simplify the thread dump mechanism
The thread dump mechanism that is used by "show threads" and by the
panic dump is overly complicated due to an initial misdesign. It
firsts wakes all threads, then serializes their dumps, then releases
them, while taking extreme care not to face colliding dumps. In fact
this is not what we need and it reached a limit where big machines
cannot dump all their threads anymore due to buffer size limitations.
What is needed instead is to be able to dump *one* thread, and to let
the requester iterate on all threads.
That's what this patch does. It adds the thread_dump_buffer to the
struct thread_ctx so that the requester offers the buffer to the
thread that is about to be dumped. This buffer also serves as a lock.
A thread at rest has a NULL, a valid pointer indicates the thread is
using it, and 0x1 (NULL+1) is used by the dumped thread to tell the
requester it's done. This makes sure that a given thread is dumped
once at a time. In addition to this, the calling thread decides
whether it accesses the thread by itself or via the debug signal
handler, in order to get a backtrace. This is much saner because the
calling thread is free to do whatever it wants with the buffer after
each thread is dumped, and there is no dependency between threads,
once they've dumped, they're free to continue (and possibly to dump
for another requester if needed). Finally, when the THREAD_DUMP
feature is disabled and the debug signal is not used, the requester
accesses the thread by itself like before.
For now we still have the buffer size limitation but it will be
addressed in future patches.
2023-05-04 10:23:51 -04:00
struct buffer * buf = HA_ATOMIC_LOAD ( & th_ctx - > thread_dump_buffer ) ;
2022-07-01 11:11:03 -04:00
int harmless = is_thread_harmless ( ) ;
2024-10-19 08:53:11 -04:00
int no_return = 0 ;
2022-07-01 11:11:03 -04:00
2020-03-03 02:31:34 -05:00
/* first, let's check it's really for us and that we didn't just get
* a spurious DEBUGSIG .
*/
2024-10-19 07:53:39 -04:00
if ( ! buf | | ( ulong ) buf & 0x1UL )
2020-03-03 02:31:34 -05:00
return ;
2024-10-19 08:53:11 -04:00
/* Special value 0x2 is used during panics and requires that the thread
* allocates its own dump buffer among its own trash buffers . The goal
* is that all threads keep a copy of their own dump .
*/
if ( ( ulong ) buf = = 0x2UL ) {
no_return = 1 ;
buf = get_trash_chunk ( ) ;
HA_ATOMIC_STORE ( & th_ctx - > thread_dump_buffer , buf ) ;
}
MEDIUM: debug: simplify the thread dump mechanism
The thread dump mechanism that is used by "show threads" and by the
panic dump is overly complicated due to an initial misdesign. It
firsts wakes all threads, then serializes their dumps, then releases
them, while taking extreme care not to face colliding dumps. In fact
this is not what we need and it reached a limit where big machines
cannot dump all their threads anymore due to buffer size limitations.
What is needed instead is to be able to dump *one* thread, and to let
the requester iterate on all threads.
That's what this patch does. It adds the thread_dump_buffer to the
struct thread_ctx so that the requester offers the buffer to the
thread that is about to be dumped. This buffer also serves as a lock.
A thread at rest has a NULL, a valid pointer indicates the thread is
using it, and 0x1 (NULL+1) is used by the dumped thread to tell the
requester it's done. This makes sure that a given thread is dumped
once at a time. In addition to this, the calling thread decides
whether it accesses the thread by itself or via the debug signal
handler, in order to get a backtrace. This is much saner because the
calling thread is free to do whatever it wants with the buffer after
each thread is dumped, and there is no dependency between threads,
once they've dumped, they're free to continue (and possibly to dump
for another requester if needed). Finally, when the THREAD_DUMP
feature is disabled and the debug signal is not used, the requester
accesses the thread by itself like before.
For now we still have the buffer size limitation but it will be
addressed in future patches.
2023-05-04 10:23:51 -04:00
/* now dump the current state into the designated buffer, and indicate
* we come from a sig handler .
2022-07-15 07:14:03 -04:00
*/
MEDIUM: debug: simplify the thread dump mechanism
The thread dump mechanism that is used by "show threads" and by the
panic dump is overly complicated due to an initial misdesign. It
firsts wakes all threads, then serializes their dumps, then releases
them, while taking extreme care not to face colliding dumps. In fact
this is not what we need and it reached a limit where big machines
cannot dump all their threads anymore due to buffer size limitations.
What is needed instead is to be able to dump *one* thread, and to let
the requester iterate on all threads.
That's what this patch does. It adds the thread_dump_buffer to the
struct thread_ctx so that the requester offers the buffer to the
thread that is about to be dumped. This buffer also serves as a lock.
A thread at rest has a NULL, a valid pointer indicates the thread is
using it, and 0x1 (NULL+1) is used by the dumped thread to tell the
requester it's done. This makes sure that a given thread is dumped
once at a time. In addition to this, the calling thread decides
whether it accesses the thread by itself or via the debug signal
handler, in order to get a backtrace. This is much saner because the
calling thread is free to do whatever it wants with the buffer after
each thread is dumped, and there is no dependency between threads,
once they've dumped, they're free to continue (and possibly to dump
for another requester if needed). Finally, when the THREAD_DUMP
feature is disabled and the debug signal is not used, the requester
accesses the thread by itself like before.
For now we still have the buffer size limitation but it will be
addressed in future patches.
2023-05-04 10:23:51 -04:00
ha_thread_dump_one ( tid , 1 ) ;
2019-05-22 01:06:44 -04:00
/* mark the current thread as stuck to detect it upon next invocation
* if it didn ' t move .
*/
2022-07-01 11:11:03 -04:00
if ( ! harmless & &
2022-06-20 03:23:24 -04:00
! ( _HA_ATOMIC_LOAD ( & th_ctx - > flags ) & TH_FL_SLEEPING ) )
2022-06-22 03:19:46 -04:00
_HA_ATOMIC_OR ( & th_ctx - > flags , TH_FL_STUCK ) ;
2024-10-19 08:53:11 -04:00
/* in case of panic, no return is planned so that we don't destroy
* the buffer ' s contents and we make sure not to trigger in loops .
*/
while ( no_return )
wait ( NULL ) ;
2019-05-17 04:08:49 -04:00
}
static int init_debug_per_thread ( )
{
sigset_t set ;
/* unblock the DEBUGSIG signal we intend to use */
sigemptyset ( & set ) ;
sigaddset ( & set , DEBUGSIG ) ;
2023-03-09 02:25:01 -05:00
# if defined(DEBUG_DEV)
sigaddset ( & set , SIGRTMAX ) ;
# endif
2019-05-17 04:08:49 -04:00
ha_sigmask ( SIG_UNBLOCK , & set , NULL ) ;
return 1 ;
}
static int init_debug ( )
{
struct sigaction sa ;
2021-01-22 06:12:29 -05:00
void * callers [ 1 ] ;
2019-05-17 04:08:49 -04:00
2020-03-04 00:01:40 -05:00
/* calling backtrace() will access libgcc at runtime. We don't want to
* do it after the chroot , so let ' s perform a first call to have it
* ready in memory for later use .
*/
2020-03-04 01:44:06 -05:00
my_backtrace ( callers , sizeof ( callers ) / sizeof ( * callers ) ) ;
2019-05-17 04:08:49 -04:00
sa . sa_handler = NULL ;
sa . sa_sigaction = debug_handler ;
sigemptyset ( & sa . sa_mask ) ;
sa . sa_flags = SA_SIGINFO ;
sigaction ( DEBUGSIG , & sa , NULL ) ;
2023-03-09 02:25:01 -05:00
# if defined(DEBUG_DEV)
sa . sa_handler = NULL ;
sa . sa_sigaction = debug_delay_inj_sighandler ;
sigemptyset ( & sa . sa_mask ) ;
sa . sa_flags = SA_SIGINFO ;
sigaction ( SIGRTMAX , & sa , NULL ) ;
# endif
2020-11-06 09:24:23 -05:00
return ERR_NONE ;
2019-05-17 04:08:49 -04:00
}
REGISTER_POST_CHECK ( init_debug ) ;
REGISTER_PER_THREAD_INIT ( init_debug_per_thread ) ;
# endif /* USE_THREAD_DUMP */
2023-11-22 05:28:06 -05:00
2023-11-22 05:32:51 -05:00
static void feed_post_mortem_linux ( )
{
# if defined(__linux__)
2023-11-22 05:48:23 -05:00
struct stat statbuf ;
MINOR: debug: detect CPU model and store it in post_mortem
The CPU model and type has significant impact on certain bugs, such
as contention issues caused by CPUs having split L3 caches, or stricter
memory models that exhibit some barrier issues. It's complicated though
because the info about the model depends on the arch. For example, x86
reports an SKU name while ARM rather reports the CPU core types, families
and versions for each CPU core. There, the SoC will sometimes be reported
in the device tree or DMI info instead. But we don't really care, it's
essentially useful to know if the code is running on an armv8.0 such as
A53, a 8.2 such as A55/A76/Neoverse etc. For MIPS the model appears to
generally be there, and in addition the SoC is often present in the
"system type" field before the first CPU, and the type of machine in the
"machine" field, to replace the missing DMI and DT, so they are also
collected. Note that only the first CPU is checked and reported, that's
expected to be vastly sufficient, since we're just trying to spot known
incompatibilities or issues.
2023-11-22 05:55:22 -05:00
FILE * file ;
2023-11-22 05:48:23 -05:00
2023-11-22 05:32:51 -05:00
/* DMI reports either HW or hypervisor, this allows to detect most VMs.
* On ARM the device - tree is often more precise for the model . Since many
* boards present " to be filled by OEM " or so in many fields , we dedup
* them as much as possible .
*/
if ( read_line_to_trash ( " /sys/class/dmi/id/sys_vendor " ) > 0 )
strlcpy2 ( post_mortem . platform . hw_vendor , trash . area , sizeof ( post_mortem . platform . hw_vendor ) ) ;
if ( read_line_to_trash ( " /sys/class/dmi/id/product_family " ) > 0 & &
strcmp ( trash . area , post_mortem . platform . hw_vendor ) ! = 0 )
strlcpy2 ( post_mortem . platform . hw_family , trash . area , sizeof ( post_mortem . platform . hw_family ) ) ;
if ( ( read_line_to_trash ( " /sys/class/dmi/id/product_name " ) > 0 & &
strcmp ( trash . area , post_mortem . platform . hw_vendor ) ! = 0 & &
strcmp ( trash . area , post_mortem . platform . hw_family ) ! = 0 ) )
strlcpy2 ( post_mortem . platform . hw_model , trash . area , sizeof ( post_mortem . platform . hw_model ) ) ;
if ( ( read_line_to_trash ( " /sys/class/dmi/id/board_vendor " ) > 0 & &
strcmp ( trash . area , post_mortem . platform . hw_vendor ) ! = 0 ) )
strlcpy2 ( post_mortem . platform . brd_vendor , trash . area , sizeof ( post_mortem . platform . brd_vendor ) ) ;
if ( ( read_line_to_trash ( " /sys/firmware/devicetree/base/model " ) > 0 & &
strcmp ( trash . area , post_mortem . platform . brd_vendor ) ! = 0 & &
strcmp ( trash . area , post_mortem . platform . hw_vendor ) ! = 0 & &
strcmp ( trash . area , post_mortem . platform . hw_family ) ! = 0 & &
strcmp ( trash . area , post_mortem . platform . hw_model ) ! = 0 ) | |
( read_line_to_trash ( " /sys/class/dmi/id/board_name " ) > 0 & &
strcmp ( trash . area , post_mortem . platform . brd_vendor ) ! = 0 & &
strcmp ( trash . area , post_mortem . platform . hw_vendor ) ! = 0 & &
strcmp ( trash . area , post_mortem . platform . hw_family ) ! = 0 & &
strcmp ( trash . area , post_mortem . platform . hw_model ) ! = 0 ) )
strlcpy2 ( post_mortem . platform . brd_model , trash . area , sizeof ( post_mortem . platform . brd_model ) ) ;
2023-11-22 05:37:37 -05:00
/* Check for containers. In a container on linux we don't see keventd (2.4) kthreadd (2.6+) on pid 2 */
if ( read_line_to_trash ( " /proc/2/status " ) < = 0 | |
( strcmp ( trash . area , " Name: \t kthreadd " ) ! = 0 & &
strcmp ( trash . area , " Name: \t keventd " ) ! = 0 ) ) {
2023-11-22 05:48:23 -05:00
/* OK we're in a container. Docker often has /.dockerenv */
const char * tech = " yes " ;
if ( stat ( " /.dockerenv " , & statbuf ) = = 0 )
tech = " docker " ;
strlcpy2 ( post_mortem . platform . cont_techno , tech , sizeof ( post_mortem . platform . cont_techno ) ) ;
2023-11-22 05:37:37 -05:00
}
else {
strlcpy2 ( post_mortem . platform . cont_techno , " no " , sizeof ( post_mortem . platform . cont_techno ) ) ;
}
MINOR: debug: detect CPU model and store it in post_mortem
The CPU model and type has significant impact on certain bugs, such
as contention issues caused by CPUs having split L3 caches, or stricter
memory models that exhibit some barrier issues. It's complicated though
because the info about the model depends on the arch. For example, x86
reports an SKU name while ARM rather reports the CPU core types, families
and versions for each CPU core. There, the SoC will sometimes be reported
in the device tree or DMI info instead. But we don't really care, it's
essentially useful to know if the code is running on an armv8.0 such as
A53, a 8.2 such as A55/A76/Neoverse etc. For MIPS the model appears to
generally be there, and in addition the SoC is often present in the
"system type" field before the first CPU, and the type of machine in the
"machine" field, to replace the missing DMI and DT, so they are also
collected. Note that only the first CPU is checked and reported, that's
expected to be vastly sufficient, since we're just trying to spot known
incompatibilities or issues.
2023-11-22 05:55:22 -05:00
file = fopen ( " /proc/cpuinfo " , " r " ) ;
if ( file ) {
uint cpu_implem = 0 , cpu_arch = 0 , cpu_variant = 0 , cpu_part = 0 , cpu_rev = 0 ; // arm
uint cpu_family = 0 , model = 0 , stepping = 0 ; // x86
char vendor_id [ 64 ] = " " , model_name [ 64 ] = " " ; // x86
char machine [ 64 ] = " " , system_type [ 64 ] = " " , cpu_model [ 64 ] = " " ; // mips
2023-11-22 06:04:02 -05:00
const char * virt = " no " ;
MINOR: debug: detect CPU model and store it in post_mortem
The CPU model and type has significant impact on certain bugs, such
as contention issues caused by CPUs having split L3 caches, or stricter
memory models that exhibit some barrier issues. It's complicated though
because the info about the model depends on the arch. For example, x86
reports an SKU name while ARM rather reports the CPU core types, families
and versions for each CPU core. There, the SoC will sometimes be reported
in the device tree or DMI info instead. But we don't really care, it's
essentially useful to know if the code is running on an armv8.0 such as
A53, a 8.2 such as A55/A76/Neoverse etc. For MIPS the model appears to
generally be there, and in addition the SoC is often present in the
"system type" field before the first CPU, and the type of machine in the
"machine" field, to replace the missing DMI and DT, so they are also
collected. Note that only the first CPU is checked and reported, that's
expected to be vastly sufficient, since we're just trying to spot known
incompatibilities or issues.
2023-11-22 05:55:22 -05:00
char * p , * e , * v , * lf ;
/* let's figure what CPU we're working with */
while ( ( p = fgets ( trash . area , trash . size , file ) ) ! = NULL ) {
lf = strchr ( p , ' \n ' ) ;
if ( lf )
* lf = 0 ;
/* stop at first line break */
if ( ! * p )
break ;
/* skip colon and spaces and trim spaces after name */
v = e = strchr ( p , ' : ' ) ;
if ( ! e )
continue ;
do { * e - - = 0 ; } while ( e > = p & & ( * e = = ' ' | | * e = = ' \t ' ) ) ;
/* locate value after colon */
do { v + + ; } while ( * v = = ' ' | | * v = = ' \t ' ) ;
/* ARM */
if ( strcmp ( p , " CPU implementer " ) = = 0 )
cpu_implem = strtoul ( v , NULL , 0 ) ;
else if ( strcmp ( p , " CPU architecture " ) = = 0 )
cpu_arch = strtoul ( v , NULL , 0 ) ;
else if ( strcmp ( p , " CPU variant " ) = = 0 )
cpu_variant = strtoul ( v , NULL , 0 ) ;
else if ( strcmp ( p , " CPU part " ) = = 0 )
cpu_part = strtoul ( v , NULL , 0 ) ;
else if ( strcmp ( p , " CPU revision " ) = = 0 )
cpu_rev = strtoul ( v , NULL , 0 ) ;
/* x86 */
else if ( strcmp ( p , " cpu family " ) = = 0 )
cpu_family = strtoul ( v , NULL , 0 ) ;
else if ( strcmp ( p , " model " ) = = 0 )
model = strtoul ( v , NULL , 0 ) ;
else if ( strcmp ( p , " stepping " ) = = 0 )
stepping = strtoul ( v , NULL , 0 ) ;
else if ( strcmp ( p , " vendor_id " ) = = 0 )
strlcpy2 ( vendor_id , v , sizeof ( vendor_id ) ) ;
else if ( strcmp ( p , " model name " ) = = 0 )
strlcpy2 ( model_name , v , sizeof ( model_name ) ) ;
2023-11-22 06:04:02 -05:00
else if ( strcmp ( p , " flags " ) = = 0 ) {
if ( strstr ( v , " hypervisor " ) ) {
if ( strncmp ( post_mortem . platform . hw_vendor , " QEMU " , 4 ) = = 0 )
virt = " qemu " ;
else if ( strncmp ( post_mortem . platform . hw_vendor , " VMware " , 6 ) = = 0 )
virt = " vmware " ;
else
virt = " yes " ;
}
}
MINOR: debug: detect CPU model and store it in post_mortem
The CPU model and type has significant impact on certain bugs, such
as contention issues caused by CPUs having split L3 caches, or stricter
memory models that exhibit some barrier issues. It's complicated though
because the info about the model depends on the arch. For example, x86
reports an SKU name while ARM rather reports the CPU core types, families
and versions for each CPU core. There, the SoC will sometimes be reported
in the device tree or DMI info instead. But we don't really care, it's
essentially useful to know if the code is running on an armv8.0 such as
A53, a 8.2 such as A55/A76/Neoverse etc. For MIPS the model appears to
generally be there, and in addition the SoC is often present in the
"system type" field before the first CPU, and the type of machine in the
"machine" field, to replace the missing DMI and DT, so they are also
collected. Note that only the first CPU is checked and reported, that's
expected to be vastly sufficient, since we're just trying to spot known
incompatibilities or issues.
2023-11-22 05:55:22 -05:00
/* MIPS */
else if ( strcmp ( p , " system type " ) = = 0 )
strlcpy2 ( system_type , v , sizeof ( system_type ) ) ;
else if ( strcmp ( p , " machine " ) = = 0 )
strlcpy2 ( machine , v , sizeof ( machine ) ) ;
else if ( strcmp ( p , " cpu model " ) = = 0 )
strlcpy2 ( cpu_model , v , sizeof ( cpu_model ) ) ;
}
fclose ( file ) ;
/* Machine may replace hw_product on MIPS */
if ( ! * post_mortem . platform . hw_model )
strlcpy2 ( post_mortem . platform . hw_model , machine , sizeof ( post_mortem . platform . hw_model ) ) ;
/* SoC vendor */
strlcpy2 ( post_mortem . platform . soc_vendor , vendor_id , sizeof ( post_mortem . platform . soc_vendor ) ) ;
/* SoC model */
if ( * system_type ) {
/* MIPS */
strlcpy2 ( post_mortem . platform . soc_model , system_type , sizeof ( post_mortem . platform . soc_model ) ) ;
* system_type = 0 ;
} else if ( * model_name ) {
/* x86 */
strlcpy2 ( post_mortem . platform . soc_model , model_name , sizeof ( post_mortem . platform . soc_model ) ) ;
* model_name = 0 ;
}
/* Create a CPU model name based on available IDs */
if ( cpu_implem ) // arm
snprintf ( cpu_model + strlen ( cpu_model ) ,
sizeof ( cpu_model ) - strlen ( cpu_model ) ,
" %sImpl %#02x " , * cpu_model ? " " : " " , cpu_implem ) ;
if ( cpu_family ) // x86
snprintf ( cpu_model + strlen ( cpu_model ) ,
sizeof ( cpu_model ) - strlen ( cpu_model ) ,
" %sFam %u " , * cpu_model ? " " : " " , cpu_family ) ;
if ( model ) // x86
snprintf ( cpu_model + strlen ( cpu_model ) ,
sizeof ( cpu_model ) - strlen ( cpu_model ) ,
" %sModel %u " , * cpu_model ? " " : " " , model ) ;
if ( stepping ) // x86
snprintf ( cpu_model + strlen ( cpu_model ) ,
sizeof ( cpu_model ) - strlen ( cpu_model ) ,
" %sStep %u " , * cpu_model ? " " : " " , stepping ) ;
if ( cpu_arch ) // arm
snprintf ( cpu_model + strlen ( cpu_model ) ,
sizeof ( cpu_model ) - strlen ( cpu_model ) ,
" %sArch %u " , * cpu_model ? " " : " " , cpu_arch ) ;
if ( cpu_part ) // arm
snprintf ( cpu_model + strlen ( cpu_model ) ,
sizeof ( cpu_model ) - strlen ( cpu_model ) ,
" %sPart %#03x " , * cpu_model ? " " : " " , cpu_part ) ;
if ( cpu_variant | | cpu_rev ) // arm
snprintf ( cpu_model + strlen ( cpu_model ) ,
sizeof ( cpu_model ) - strlen ( cpu_model ) ,
" %sr%up%u " , * cpu_model ? " " : " " , cpu_variant , cpu_rev ) ;
strlcpy2 ( post_mortem . platform . cpu_model , cpu_model , sizeof ( post_mortem . platform . cpu_model ) ) ;
2023-11-22 06:04:02 -05:00
if ( * virt )
strlcpy2 ( post_mortem . platform . virt_techno , virt , sizeof ( post_mortem . platform . virt_techno ) ) ;
MINOR: debug: detect CPU model and store it in post_mortem
The CPU model and type has significant impact on certain bugs, such
as contention issues caused by CPUs having split L3 caches, or stricter
memory models that exhibit some barrier issues. It's complicated though
because the info about the model depends on the arch. For example, x86
reports an SKU name while ARM rather reports the CPU core types, families
and versions for each CPU core. There, the SoC will sometimes be reported
in the device tree or DMI info instead. But we don't really care, it's
essentially useful to know if the code is running on an armv8.0 such as
A53, a 8.2 such as A55/A76/Neoverse etc. For MIPS the model appears to
generally be there, and in addition the SoC is often present in the
"system type" field before the first CPU, and the type of machine in the
"machine" field, to replace the missing DMI and DT, so they are also
collected. Note that only the first CPU is checked and reported, that's
expected to be vastly sufficient, since we're just trying to spot known
incompatibilities or issues.
2023-11-22 05:55:22 -05:00
}
2023-11-22 05:32:51 -05:00
# endif // __linux__
}
2023-11-22 05:28:06 -05:00
static int feed_post_mortem ( )
{
MINOR: debug: place a magic pattern at the beginning of post_mortem
In order to ease finding of the post_mortem struct in core dumps, let's
make it start with a recognizable pattern of exactly 32 chars (to
preserve alignment):
"POST-MORTEM STARTS HERE+7654321\0"
It can then be found like this from gdb:
(gdb) find 0x000000012345678, 0x0000000100000000, 'P','O','S','T','-','M','O','R','T','E','M'
0xcfd300 <post_mortem>
1 pattern found.
Or easier with any other more practical tool (who as ever used "find" in
gdb, given that it cannot iterate over maps and is 100% useless?).
2024-10-24 05:56:07 -04:00
/* write an easily identifiable magic at the beginning of the struct */
strncpy ( post_mortem . post_mortem_magic ,
" POST-MORTEM STARTS HERE+7654321 \0 " ,
sizeof ( post_mortem . post_mortem_magic ) ) ;
2023-11-22 05:28:06 -05:00
/* kernel type, version and arch */
uname ( & post_mortem . platform . utsname ) ;
2023-11-22 05:32:51 -05:00
2023-11-22 09:37:57 -05:00
/* some boot-time info related to the process */
post_mortem . process . pid = getpid ( ) ;
post_mortem . process . boot_uid = geteuid ( ) ;
post_mortem . process . boot_gid = getegid ( ) ;
2024-05-29 05:27:21 -04:00
post_mortem . process . argc = global . argc ;
post_mortem . process . argv = global . argv ;
2023-11-22 09:37:57 -05:00
2024-06-21 12:11:46 -04:00
# if defined(USE_LINUX_CAP)
if ( capget ( & cap_hdr_haproxy , post_mortem . process . caps . boot ) = = - 1 )
2024-07-14 09:20:02 -04:00
post_mortem . process . caps . err_boot = errno ;
2024-06-21 12:11:46 -04:00
# endif
2024-07-13 07:23:46 -04:00
post_mortem . process . boot_lim_fd . rlim_cur = rlim_fd_cur_at_boot ;
post_mortem . process . boot_lim_fd . rlim_max = rlim_fd_max_at_boot ;
getrlimit ( RLIMIT_DATA , & post_mortem . process . boot_lim_ram ) ;
2023-11-22 09:37:57 -05:00
2023-11-22 05:32:51 -05:00
if ( strcmp ( post_mortem . platform . utsname . sysname , " Linux " ) = = 0 )
feed_post_mortem_linux ( ) ;
2023-11-23 02:26:52 -05:00
# if defined(HA_HAVE_DUMP_LIBS)
chunk_reset ( & trash ) ;
if ( dump_libs ( & trash , 1 ) )
post_mortem . libs = strdup ( trash . area ) ;
# endif
MINOR: debug: store important pointers in post_mortem
Dealing with a core and a stripped executable is a pain when it comes
to finding pools, proxies or thread contexts. Let's put a pointer to
these heads and arrays in the post_mortem struct for easier location.
Other critical lists like this could possibly benefit from being added
later.
Here we now have:
- tgroup_info
- thread_info
- tgroup_ctx
- thread_ctx
- pools
- proxies
Example:
$ objdump -h haproxy|grep post
34 _post_mortem 000014b0 0000000000cfd400 0000000000cfd400 008fc400 2**8
(gdb) set $pm=(struct post_mortem*)0x0000000000cfd400
(gdb) p $pm->tgroup_ctx[0]
$8 = {
threads_harmless = 254,
threads_idle = 254,
stopping_threads = 0,
timers = {
b = {0x0, 0x0}
},
niced_tasks = 0,
__pad = 0xf5662c <ha_tgroup_ctx+44> "",
__end = 0xf56640 <ha_tgroup_ctx+64> ""
}
(gdb) info thr
Id Target Id Frame
* 1 Thread 0x7f9e7706a440 (LWP 21169) 0x00007f9e76a9c868 in raise () from /lib64/libc.so.6
2 Thread 0x7f9e76a60640 (LWP 21175) 0x00007f9e76b343c7 in wait4 () from /lib64/libc.so.6
3 Thread 0x7f9e7613d640 (LWP 21176) 0x00007f9e76b343c7 in wait4 () from /lib64/libc.so.6
4 Thread 0x7f9e7493a640 (LWP 21179) 0x00007f9e76b343c7 in wait4 () from /lib64/libc.so.6
5 Thread 0x7f9e7593c640 (LWP 21177) 0x00007f9e76b343c7 in wait4 () from /lib64/libc.so.6
6 Thread 0x7f9e7513b640 (LWP 21178) 0x00007f9e76b343c7 in wait4 () from /lib64/libc.so.6
7 Thread 0x7f9e6ffff640 (LWP 21180) 0x00007f9e76b343c7 in wait4 () from /lib64/libc.so.6
8 Thread 0x7f9e6f7fe640 (LWP 21181) 0x00007f9e76b343c7 in wait4 () from /lib64/libc.so.6
(gdb) p/x $pm->thread_info[0].pth_id
$12 = 0x7f9e7706a440
(gdb) p/x $pm->thread_info[1].pth_id
$13 = 0x7f9e76a60640
(gdb) set $px = *$pm->proxies
while ($px != 0)
printf "%#lx %s served=%u\n", $px, $px->id, $px->served
set $px = ($px)->next
end
0x125eda0 GLOBAL served=0
0x12645b0 stats served=0
0x1266940 comp served=0
0x1268e10 comp_bck served=0
0x1260cf0 <OCSP-UPDATE> served=0
0x12714c0 <HTTPCLIENT> served=0
2024-10-24 08:37:12 -04:00
post_mortem . tgroup_info = ha_tgroup_info ;
post_mortem . thread_info = ha_thread_info ;
post_mortem . tgroup_ctx = ha_tgroup_ctx ;
post_mortem . thread_ctx = ha_thread_ctx ;
post_mortem . pools = & pools ;
post_mortem . proxies = & proxies_list ;
2023-11-22 05:28:06 -05:00
return ERR_NONE ;
}
REGISTER_POST_CHECK ( feed_post_mortem ) ;
2023-11-23 02:26:52 -05:00
static void deinit_post_mortem ( void )
{
2023-11-23 05:25:34 -05:00
int comp ;
2023-11-23 02:26:52 -05:00
# if defined(HA_HAVE_DUMP_LIBS)
ha_free ( & post_mortem . libs ) ;
# endif
2023-11-23 05:25:34 -05:00
for ( comp = 0 ; comp < post_mortem . nb_components ; comp + + ) {
free ( post_mortem . components [ comp ] . toolchain ) ;
free ( post_mortem . components [ comp ] . toolchain_opts ) ;
free ( post_mortem . components [ comp ] . build_settings ) ;
free ( post_mortem . components [ comp ] . path ) ;
}
ha_free ( & post_mortem . components ) ;
2023-11-23 02:26:52 -05:00
}
REGISTER_POST_DEINIT ( deinit_post_mortem ) ;
2023-11-23 05:25:34 -05:00
/* Appends a component to the list of post_portem info. May silently fail
* on allocation errors but we don ' t care since the goal is to provide info
* we have in case it helps .
*/
void post_mortem_add_component ( const char * name , const char * version ,
const char * toolchain , const char * toolchain_opts ,
const char * build_settings , const char * path )
{
struct post_mortem_component * comp ;
int nbcomp = post_mortem . nb_components ;
comp = realloc ( post_mortem . components , ( nbcomp + 1 ) * sizeof ( * comp ) ) ;
if ( ! comp )
return ;
memset ( & comp [ nbcomp ] , 0 , sizeof ( * comp ) ) ;
strlcpy2 ( comp [ nbcomp ] . name , name , sizeof ( comp [ nbcomp ] . name ) ) ;
strlcpy2 ( comp [ nbcomp ] . version , version , sizeof ( comp [ nbcomp ] . version ) ) ;
comp [ nbcomp ] . toolchain = strdup ( toolchain ) ;
comp [ nbcomp ] . toolchain_opts = strdup ( toolchain_opts ) ;
comp [ nbcomp ] . build_settings = strdup ( build_settings ) ;
comp [ nbcomp ] . path = strdup ( path ) ;
post_mortem . nb_components + + ;
post_mortem . components = comp ;
}
2023-11-22 12:30:19 -05:00
# ifdef USE_THREAD
/* init code is called one at a time so let's collect all per-thread info on
* the last starting thread . These info are not critical anyway and there ' s no
* problem if we get them slightly late .
*/
static int feed_post_mortem_late ( )
{
static int per_thread_info_collected ;
2024-07-15 08:56:24 -04:00
int i ;
2023-11-22 12:30:19 -05:00
2024-07-15 08:56:24 -04:00
if ( HA_ATOMIC_ADD_FETCH ( & per_thread_info_collected , 1 ) ! = global . nbthread )
return 1 ;
/* Collect thread info, only when we are in the last thread context.
* feed_post_mortem_late ( ) is registered in per_thread_init_list . Each
* thread takes a mutex before looping over this list , so
* feed_post_mortem_late ( ) will be called by each thread in exclusive
2024-08-26 17:40:15 -04:00
* manner , one by one in synchronous order . Thread unlocks mutex only
2024-07-15 08:56:24 -04:00
* after executing all init functions from this list .
*/
for ( i = 0 ; i < global . nbthread ; i + + ) {
post_mortem . process . thread_info [ i ] . pth_id = ha_thread_info [ i ] . pth_id ;
post_mortem . process . thread_info [ i ] . stack_top = ha_thread_info [ i ] . stack_top ;
2023-11-22 12:30:19 -05:00
}
2024-07-15 08:56:24 -04:00
2024-07-12 11:50:18 -04:00
/* also set runtime process settings. At this stage we are sure, that all
2024-08-26 17:40:15 -04:00
* config options and limits adjustments are successfully applied .
2024-07-12 11:50:18 -04:00
*/
post_mortem . process . run_uid = geteuid ( ) ;
post_mortem . process . run_gid = getegid ( ) ;
2024-07-14 09:20:02 -04:00
# if defined(USE_LINUX_CAP)
if ( capget ( & cap_hdr_haproxy , post_mortem . process . caps . run ) = = - 1 ) {
post_mortem . process . caps . err_run = errno ;
}
# endif
2024-07-14 10:58:02 -04:00
getrlimit ( RLIMIT_NOFILE , & post_mortem . process . run_lim_fd ) ;
getrlimit ( RLIMIT_DATA , & post_mortem . process . run_lim_ram ) ;
2024-07-12 11:50:18 -04:00
2023-11-22 12:30:19 -05:00
return 1 ;
}
REGISTER_PER_THREAD_INIT ( feed_post_mortem_late ) ;
# endif
2019-05-16 11:44:30 -04:00
/* register cli keywords */
static struct cli_kw_list cli_kws = { { } , {
2021-05-07 05:38:37 -04:00
{ { " debug " , " dev " , " bug " , NULL } , " debug dev bug : call BUG_ON() and crash " , debug_parse_cli_bug , NULL , NULL , NULL , ACCESS_EXPERT } ,
2022-02-28 05:51:23 -05:00
{ { " debug " , " dev " , " check " , NULL } , " debug dev check : call CHECK_IF() and possibly crash " , debug_parse_cli_check , NULL , NULL , NULL , ACCESS_EXPERT } ,
2021-05-07 05:38:37 -04:00
{ { " debug " , " dev " , " close " , NULL } , " debug dev close <fd> : close this file descriptor " , debug_parse_cli_close , NULL , NULL , NULL , ACCESS_EXPERT } ,
2024-10-21 12:51:55 -04:00
# if !defined(USE_OBSOLETE_LINKER)
{ { " debug " , " dev " , " counters " , NULL } , " debug dev counters [all|bug|cnt|chk|?]* : dump/reset rare event counters " , debug_parse_cli_counters , debug_iohandler_counters , NULL , NULL , 0 } ,
# endif
2022-07-15 02:25:03 -04:00
{ { " debug " , " dev " , " deadlock " , NULL } , " debug dev deadlock [nbtask] : deadlock between this number of tasks " , debug_parse_cli_deadlock , NULL , NULL , NULL , ACCESS_EXPERT } ,
2021-05-07 05:38:37 -04:00
{ { " debug " , " dev " , " delay " , NULL } , " debug dev delay [ms] : sleep this long " , debug_parse_cli_delay , NULL , NULL , NULL , ACCESS_EXPERT } ,
2019-05-20 08:25:05 -04:00
# if defined(DEBUG_DEV)
2023-03-09 02:25:01 -05:00
{ { " 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 } ,
2021-05-07 05:38:37 -04:00
{ { " debug " , " dev " , " exec " , NULL } , " debug dev exec [cmd] ... : show this command's output " , debug_parse_cli_exec , NULL , NULL , NULL , ACCESS_EXPERT } ,
2019-05-20 08:25:05 -04:00
# endif
DEBUG: cli: add a new "debug dev fd" expert command
This command will scan the whole file descriptors space to look for
existing FDs that are unknown to haproxy's fdtab, and will try to dump
a maximum number of information about them (including type, mode, device,
size, uid/gid, cloexec, O_* flags, socket types and addresses when
relevant). The goal is to help detecting inherited FDs from parent
processes as well as potential leaks.
Some of those listed are actually known but handled so deep into some
systems that they're not in the fdtab (such as epoll FDs or inter-
thread pipes). This might be refined in the future so that these ones
become known and do not appear.
Example of output:
$ socat - /tmp/sock1 <<< "expert-mode on;debug dev fd"
0 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
1 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
2 type=tty. mod=0620 dev=0x8803 siz=0 uid=1000 gid=5 fs=0x16 ino=0x6 getfd=+0 getfl=O_RDONLY,O_APPEND
3 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x18112348 getfd=+0
4 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
33 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8251 getfd=+0 getfl=O_RDONLY
34 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
36 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8d1b getfd=+0 getfl=O_RDONLY
37 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
39 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24afa04f getfd=+0 getfl=O_RDONLY
41 type=pipe mod=0600 dev=0 siz=0 uid=1000 gid=100 fs=0xc ino=0x24af8252 getfd=+0 getfl=O_RDONLY
42 type=epol mod=0600 dev=0 siz=0 uid=0 gid=0 fs=0xd ino=0x3674 getfd=+0 getfl=O_RDONLY
2022-01-24 14:26:09 -05:00
{ { " debug " , " dev " , " fd " , NULL } , " debug dev fd : scan for rogue/unhandled FDs " , debug_parse_cli_fd , debug_iohandler_fd , NULL , NULL , ACCESS_EXPERT } ,
2021-05-07 05:38:37 -04:00
{ { " debug " , " dev " , " exit " , NULL } , " debug dev exit [code] : immediately exit the process " , debug_parse_cli_exit , NULL , NULL , NULL , ACCESS_EXPERT } ,
2022-11-30 11:51:47 -05:00
{ { " debug " , " dev " , " hash " , NULL } , " debug dev hash [msg] : return msg hashed if anon is set " , debug_parse_cli_hash , NULL , NULL , NULL , 0 } ,
2021-05-07 05:38:37 -04:00
{ { " debug " , " dev " , " hex " , NULL } , " debug dev hex <addr> [len] : dump a memory area " , debug_parse_cli_hex , NULL , NULL , NULL , ACCESS_EXPERT } ,
{ { " debug " , " dev " , " log " , NULL } , " debug dev log [msg] ... : send this msg to global logs " , debug_parse_cli_log , NULL , NULL , NULL , ACCESS_EXPERT } ,
2023-05-04 05:50:26 -04:00
{ { " debug " , " dev " , " loop " , NULL } , " debug dev loop <ms> [isolated] : loop this long, possibly isolated " , debug_parse_cli_loop , NULL , NULL , NULL , ACCESS_EXPERT } ,
2020-07-02 03:14:48 -04:00
# if defined(DEBUG_MEM_STATS)
2022-11-30 10:42:54 -05:00
{ { " 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 } ,
2020-07-02 03:14:48 -04:00
# endif
2021-05-07 05:38:37 -04:00
{ { " debug " , " dev " , " panic " , NULL } , " debug dev panic : immediately trigger a panic " , debug_parse_cli_panic , NULL , NULL , NULL , ACCESS_EXPERT } ,
{ { " debug " , " dev " , " sched " , NULL } , " debug dev sched {task|tasklet} [k=v]* : stress the scheduler " , debug_parse_cli_sched , NULL , NULL , NULL , ACCESS_EXPERT } ,
{ { " debug " , " dev " , " stream " , NULL } , " debug dev stream [k=v]* : show/manipulate stream flags " , debug_parse_cli_stream , NULL , NULL , NULL , ACCESS_EXPERT } ,
{ { " debug " , " dev " , " sym " , NULL } , " debug dev sym <addr> : resolve symbol address " , debug_parse_cli_sym , NULL , NULL , NULL , ACCESS_EXPERT } ,
2023-05-03 05:22:45 -04:00
{ { " 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 } ,
2021-05-07 05:38:37 -04:00
{ { " debug " , " dev " , " tkill " , NULL } , " debug dev tkill [thr] [sig] : send signal to thread " , debug_parse_cli_tkill , NULL , NULL , NULL , ACCESS_EXPERT } ,
2024-03-15 02:16:08 -04:00
# if defined(DEBUG_DEV)
{ { " debug " , " dev " , " trace " , NULL } , " debug dev trace [nbthr] : flood traces from that many threads " , debug_parse_cli_trace , NULL , NULL , NULL , ACCESS_EXPERT } ,
# endif
2022-02-25 02:52:39 -05:00
{ { " debug " , " dev " , " warn " , NULL } , " debug dev warn : call WARN_ON() and possibly crash " , debug_parse_cli_warn , NULL , NULL , NULL , ACCESS_EXPERT } ,
2021-05-07 05:38:37 -04:00
{ { " debug " , " dev " , " write " , NULL } , " debug dev write [size] : write that many bytes in return " , debug_parse_cli_write , NULL , NULL , NULL , ACCESS_EXPERT } ,
2022-09-14 11:24:22 -04:00
2023-11-22 05:28:06 -05:00
{ { " show " , " dev " , NULL , NULL } , " show dev : show debug info for developers " , debug_parse_cli_show_dev , NULL , NULL } ,
2021-12-28 03:57:10 -05:00
# if defined(HA_HAVE_DUMP_LIBS)
{ { " show " , " libs " , NULL , NULL } , " show libs : show loaded object files and libraries " , debug_parse_cli_show_libs , NULL , NULL } ,
# endif
2021-05-07 05:38:37 -04:00
{ { " show " , " threads " , NULL , NULL } , " show threads : show some threads debugging information " , NULL , cli_io_handler_show_threads , NULL } ,
2019-05-16 11:44:30 -04:00
{ { } , }
} } ;
INITCALL1 ( STG_REGISTER , cli_register_kw , & cli_kws ) ;