From: Willy Tarreau Date: Mon, 20 May 2019 12:25:05 +0000 (+0200) Subject: MINOR: debug/cli: add some debugging commands for developers X-Git-Tag: v2.0-dev4~52 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=6bdf3e9b111bd197ad1d933c87b6229dbd782ad1;p=thirdparty%2Fhaproxy.git MINOR: debug/cli: add some debugging commands for developers When haproxy is built with DEBUG_DEV, the following commands are added to the CLI : debug dev close : 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 [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. --- diff --git a/Makefile b/Makefile index ddf29f0cd6..3248819ec9 100644 --- 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 diff --git a/doc/management.txt b/doc/management.txt index 06c42877b5..9baea96b3a 100644 --- a/doc/management.txt +++ b/doc/management.txt @@ -1454,6 +1454,12 @@ clear table [ data. ] | [ key ] $ echo "show table http_proxy" | socat stdio /tmp/sock1 >>> # table: http_proxy, type: ip, size:204800, used:1 +debug dev [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 [|#] Delete all the acl entries from the acl corresponding to the key . is the # or the returned by "show acl". If the is used, diff --git a/src/debug.c b/src/debug.c index 3e38bfa6c2..91bf3a30df 100644 --- a/src/debug.c +++ b/src/debug.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -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 : 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 [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 }, {{},} }};