]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: debug/cli: add some debugging commands for developers
authorWilly Tarreau <w@1wt.eu>
Mon, 20 May 2019 12:25:05 +0000 (14:25 +0200)
committerWilly Tarreau <w@1wt.eu>
Mon, 20 May 2019 14:59:30 +0000 (16:59 +0200)
When haproxy is built with DEBUG_DEV, the following commands are added
to the CLI :

  debug dev close <fd>        : close this file descriptor
  debug dev delay [ms]        : sleep this long
  debug dev exec  [cmd] ...   : show this command's output
  debug dev exit  [code]      : immediately exit the process
  debug dev hex   <addr> [len]: dump a memory area
  debug dev log   [msg] ...   : send this msg to global logs
  debug dev loop  [ms]        : loop this long
  debug dev panic             : immediately trigger a panic
  debug dev tkill [thr] [sig] : send signal to thread

These are essentially aimed at helping developers trigger certain
conditions and are expected to be complemented over time.

Makefile
doc/management.txt
src/debug.c

index ddf29f0cd611bf2082bf410e71dbe0ec40414095..3248819ec9172e0889ee0dd6b519581a7b193fb9 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -209,8 +209,8 @@ SMALL_OPTS =
 #### Debug settings
 # You can enable debugging on specific code parts by setting DEBUG=-DDEBUG_xxx.
 # Currently defined DEBUG macros include DEBUG_FULL, DEBUG_MEMORY, DEBUG_FSM,
-# DEBUG_HASH, DEBUG_AUTH, DEBUG_SPOE, DEBUG_UAF and DEBUG_THREAD. Please check
-# sources for exact meaning or do not use at all.
+# DEBUG_HASH, DEBUG_AUTH, DEBUG_SPOE, DEBUG_UAF and DEBUG_THREAD, DEBUG_STRICT,
+# DEBUG_DEV. Please check sources for exact meaning or do not use at all.
 DEBUG =
 
 #### Trace options
index 06c42877b51548e8a6dbe953046c08196be49272..9baea96b3a603ab54e144c71863d506c96572732 100644 (file)
@@ -1454,6 +1454,12 @@ clear table <table> [ data.<type> <operator> <value> ] | [ key <key> ]
         $ echo "show table http_proxy" | socat stdio /tmp/sock1
     >>> # table: http_proxy, type: ip, size:204800, used:1
 
+debug dev <command> [args]*
+  Call a developer-specific command. Only supported when haproxy is built with
+  DEBUG_DEV defined. Supported commands are then listed in the help message.
+  All of these commands require admin privileges, and must never appear on a
+  production system as most of them are unsafe and dangerous.
+
 del acl <acl> [<key>|#<ref>]
   Delete all the acl entries from the acl <acl> corresponding to the key <key>.
   <acl> is the #<id> or the <file> returned by "show acl". If the <ref> is used,
index 3e38bfa6c225aad79000b1220b79b994e18a2671..91bf3a30dfa7216df6e155a80f3354b3240b1fd2 100644 (file)
@@ -13,6 +13,7 @@
 #include <signal.h>
 #include <time.h>
 #include <stdio.h>
+#include <stdlib.h>
 
 #include <common/buf.h>
 #include <common/config.h>
@@ -147,6 +148,228 @@ void ha_panic()
                abort();
 }
 
+#if defined(DEBUG_DEV)
+/* 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;
+
+       exit(code);
+       return 1;
+}
+
+/* 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;
+
+       if (!*args[3]) {
+               appctx->ctx.cli.msg = "Missing file descriptor number.\n";
+               goto reterr;
+       }
+
+       fd = atoi(args[3]);
+       if (fd < 0 || fd >= global.maxsock) {
+               appctx->ctx.cli.msg = "File descriptor out of range.\n";
+               goto reterr;
+       }
+
+       if (!fdtab[fd].owner) {
+               appctx->ctx.cli.msg = "File descriptor was already closed.\n";
+               goto retinfo;
+       }
+
+       fd_delete(fd);
+       return 1;
+ retinfo:
+       appctx->ctx.cli.severity = LOG_INFO;
+       appctx->st0 = CLI_ST_PRINT;
+       return 1;
+ reterr:
+       appctx->ctx.cli.severity = LOG_ERR;
+       appctx->st0 = CLI_ST_PRINT;
+       return 1;
+}
+
+/* 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;
+
+       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;
+
+       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]);
+
+       if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+               return 1;
+
+       gettimeofday(&curr, NULL);
+       tv_ms_add(&deadline, &curr, loop);
+
+       while (tv_ms_cmp(&curr, &deadline) < 0)
+               gettimeofday(&curr, NULL);
+
+       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;
+
+       ha_panic();
+       return 1;
+}
+
+/* parse a "debug dev exec" command. It always returns 1. */
+static int debug_parse_cli_exec(char **args, char *payload, struct appctx *appctx, void *private)
+{
+       FILE *f;
+       int arg;
+
+       if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+               return 1;
+
+       chunk_reset(&trash);
+       for (arg = 3; *args[arg]; arg++) {
+               if (arg > 3)
+                       chunk_strcat(&trash, " ");
+               chunk_strcat(&trash, args[arg]);
+       }
+
+       f = popen(trash.area, "re");
+       if (!f) {
+               appctx->ctx.cli.severity = LOG_ERR;
+               appctx->ctx.cli.msg = "Failed to execute command.\n";
+               appctx->st0 = CLI_ST_PRINT;
+               return 1;
+       }
+
+       chunk_reset(&trash);
+       while (1) {
+               size_t ret = fread(trash.area + trash.data, 1, trash.size - 20 - trash.data, f);
+               if (!ret)
+                       break;
+               trash.data += ret;
+               if (trash.data + 20 == trash.size) {
+                       chunk_strcat(&trash, "\n[[[TRUNCATED]]]\n");
+                       break;
+               }
+       }
+
+       fclose(f);
+       trash.area[trash.data] = 0;
+       appctx->ctx.cli.severity = LOG_INFO;
+       appctx->ctx.cli.msg = trash.area;
+       appctx->st0 = CLI_ST_PRINT;
+       return 1;
+}
+
+/* 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;
+
+       if (!*args[3]) {
+               appctx->ctx.cli.msg = "Missing memory address to dump from.\n";
+               goto reterr;
+       }
+
+       start = strtoul(args[3], NULL, 0);
+       if (!start) {
+               appctx->ctx.cli.msg = "Will not dump from NULL address.\n";
+               goto reterr;
+       }
+
+       /* 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);
+       dump_hex(&trash, "  ", (const void *)start, len);
+       trash.area[trash.data] = 0;
+       appctx->ctx.cli.severity = LOG_INFO;
+       appctx->ctx.cli.msg = trash.area;
+       appctx->st0 = CLI_ST_PRINT;
+       return 1;
+ reterr:
+       appctx->ctx.cli.severity = LOG_ERR;
+       appctx->st0 = CLI_ST_PRINT;
+       return 1;
+}
+
+/* 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]);
+
+       if (thr < 0 || thr > global.nbthread) {
+               appctx->ctx.cli.severity = LOG_ERR;
+               appctx->ctx.cli.msg = "Thread number out of range (use 0 for current).\n";
+               appctx->st0 = CLI_ST_PRINT;
+               return 1;
+       }
+
+       if (*args[4])
+               sig = atoi(args[4]);
+
+#if defined(USE_THREAD)
+       if (thr)
+               pthread_kill(thread_info[thr-1].pthread, sig);
+       else
+#endif
+               raise(sig);
+       return 1;
+}
+
+#endif
+
 #ifndef USE_THREAD_DUMP
 
 /* This function dumps all threads' state to the trash. This version is the
@@ -271,6 +494,17 @@ REGISTER_PER_THREAD_INIT(init_debug_per_thread);
 
 /* register cli keywords */
 static struct cli_kw_list cli_kws = {{ },{
+#if defined(DEBUG_DEV)
+       {{ "debug", "dev", "close", NULL }, "debug dev close <fd>        : close this file descriptor",      debug_parse_cli_close, NULL },
+       {{ "debug", "dev", "delay", NULL }, "debug dev delay [ms]        : sleep this long",                 debug_parse_cli_delay, NULL },
+       {{ "debug", "dev", "exec",  NULL }, "debug dev exec  [cmd] ...   : show this command's output",      debug_parse_cli_exec,  NULL },
+       {{ "debug", "dev", "exit",  NULL }, "debug dev exit  [code]      : immediately exit the process",    debug_parse_cli_exit,  NULL },
+       {{ "debug", "dev", "hex",   NULL }, "debug dev hex   <addr> [len]: dump a memory area",              debug_parse_cli_hex,   NULL },
+       {{ "debug", "dev", "log",   NULL }, "debug dev log   [msg] ...   : send this msg to global logs",    debug_parse_cli_log,   NULL },
+       {{ "debug", "dev", "loop",  NULL }, "debug dev loop  [ms]        : loop this long",                  debug_parse_cli_loop,  NULL },
+       {{ "debug", "dev", "panic", NULL }, "debug dev panic             : immediately trigger a panic",     debug_parse_cli_panic, NULL },
+       {{ "debug", "dev", "tkill", NULL }, "debug dev tkill [thr] [sig] : send signal to thread",           debug_parse_cli_tkill, NULL },
+#endif
        { { "show", "threads", NULL },    "show threads   : show some threads debugging information",   NULL, cli_io_handler_show_threads, NULL },
        {{},}
 }};