--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "sd-bus.h"
+
+#include "alloc-util.h"
+#include "analyze.h"
+#include "analyze-unit-gdb.h"
+#include "bus-error.h"
+#include "bus-util.h"
+#include "log.h"
+#include "pidref.h"
+#include "process-util.h"
+#include "runtime-scope.h"
+#include "signal-util.h"
+#include "strv.h"
+#include "unit-def.h"
+#include "unit-name.h"
+
+int verb_unit_gdb(int argc, char *argv[], void *userdata) {
+ static const struct sigaction sa = {
+ .sa_sigaction = sigterm_process_group_handler,
+ .sa_flags = SA_SIGINFO,
+ };
+
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_free_ char *unit = NULL;
+ int r;
+
+ if (arg_transport != BUS_TRANSPORT_LOCAL)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot spawn a debugger for a remote service");
+
+ r = unit_name_mangle_with_suffix(argv[1], "as unit", UNIT_NAME_MANGLE_WARN, ".service", &unit);
+ if (r < 0)
+ return log_error_errno(r, "Failed to mangle name '%s': %m", argv[1]);
+
+ r = acquire_bus(&bus, /* use_full_bus= */ NULL);
+ if (r < 0)
+ return bus_log_connect_error(r, arg_transport, arg_runtime_scope);
+
+ _cleanup_free_ char *object = unit_dbus_path_from_name(unit);
+ if (!object)
+ return log_oom();
+
+ r = sd_bus_get_property(
+ bus,
+ "org.freedesktop.systemd1",
+ object,
+ "org.freedesktop.systemd1.Service",
+ "MainPID",
+ &error,
+ &reply,
+ "u");
+ if (r < 0)
+ return log_error_errno(r, "Failed to get the main PID of %s: %s", unit, bus_error_message(&error, r));
+
+ pid_t pid;
+ r = sd_bus_message_read(reply, "u", &pid);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read the main PID of %s from reply: %m", unit);
+
+ if (!arg_debugger)
+ arg_debugger = secure_getenv("SYSTEMD_DEBUGGER") ?: "gdb";
+
+ _cleanup_strv_free_ char **debugger_call = NULL;
+ r = strv_extend(&debugger_call, arg_debugger);
+ if (r < 0)
+ return log_oom();
+
+ if (!STR_IN_SET(arg_debugger, "gdb", "lldb"))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The debugger must be either 'gdb' or 'lldb'");
+
+ if (streq(arg_debugger, "gdb")) {
+ r = strv_extendf(&debugger_call, "--pid=" PID_FMT, pid);
+ if (r < 0)
+ return log_oom();
+ }
+
+ if (streq(arg_debugger, "lldb")) {
+ r = strv_extendf(&debugger_call, "--attach-pid=" PID_FMT, pid);
+ if (r < 0)
+ return log_oom();
+ }
+
+ if (arg_root) {
+ if (streq(arg_debugger, "gdb")) {
+ _cleanup_free_ char *sysroot_cmd = strjoin("set sysroot ", arg_root);
+ r = strv_extend_many(&debugger_call, "-iex", sysroot_cmd);
+ if (r < 0)
+ return log_oom();
+ } else if (streq(arg_debugger, "lldb")) {
+ _cleanup_free_ char *sysroot_cmd = strjoin("platform select --sysroot ", arg_root, " host");
+ r = strv_extend_many(&debugger_call, "-O", sysroot_cmd);
+ if (r < 0)
+ return log_oom();
+ }
+ }
+
+ /* Don't interfere with debugger and its handling of SIGINT. */
+ (void) ignore_signals(SIGINT);
+ (void) sigaction(SIGTERM, &sa, NULL);
+
+ _cleanup_free_ char *fork_name = strjoin("(", debugger_call[0], ")");
+ _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
+ r = pidref_safe_fork(fork_name, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, &pidref);
+ if (r < 0)
+ return log_error_errno(r, "Fork failed: %m");
+
+ if (r == 0) {
+ (void) execvp(debugger_call[0], debugger_call);
+ log_error_errno(errno, "Failed to invoke '%s': %m", debugger_call[0]);
+ _exit(EXIT_FAILURE);
+ }
+
+ return pidref_wait_for_terminate_and_check(
+ debugger_call[0],
+ &pidref,
+ WAIT_LOG_ABNORMAL|WAIT_LOG_NON_ZERO_EXIT_STATUS);
+}
#include "analyze-timespan.h"
#include "analyze-timestamp.h"
#include "analyze-unit-files.h"
+#include "analyze-unit-gdb.h"
#include "analyze-unit-paths.h"
#include "analyze-unit-shell.h"
#include "analyze-verify.h"
#include "bus-util.h"
#include "calendarspec.h"
#include "dissect-image.h"
+#include "extract-word.h"
#include "image-policy.h"
#include "log.h"
#include "loop-util.h"
PagerFlags arg_pager_flags = 0;
CatFlags arg_cat_flags = 0;
BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
+const char *arg_debugger = NULL;
+static char **arg_debugger_args = NULL;
const char *arg_host = NULL;
RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
RecursiveErrors arg_recursive_errors = _RECURSIVE_ERRORS_INVALID;
" security [UNIT...] Analyze security of unit\n"
" fdstore SERVICE... Show file descriptor store contents of service\n"
" malloc [D-BUS SERVICE...] Dump malloc stats of a D-Bus service\n"
+ " unit-gdb SERVICE Attach a debugger to the given running service\n"
" unit-shell SERVICE [Command]\n"
" Run command on the namespace of the service\n"
"\n%3$sExecutable Analysis:%4$s\n"
" --image-policy=POLICY Specify disk image dissection policy\n"
" -m --mask Parse parameter as numeric capability mask\n"
" --drm-device=PATH Use this DRM device sysfs path to get EDID\n"
+ " --debugger=DEBUGGER Use the given debugger\n"
+ " -A --debugger-arguments=ARGS\n"
+ " Pass the given arguments to the debugger\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
ARG_SCALE_FACTOR_SVG,
ARG_DETAILED_SVG,
ARG_DRM_DEVICE_PATH,
+ ARG_DEBUGGER,
};
static const struct option options[] = {
- { "help", no_argument, NULL, 'h' },
- { "version", no_argument, NULL, ARG_VERSION },
- { "quiet", no_argument, NULL, 'q' },
- { "order", no_argument, NULL, ARG_ORDER },
- { "require", no_argument, NULL, ARG_REQUIRE },
- { "root", required_argument, NULL, ARG_ROOT },
- { "image", required_argument, NULL, ARG_IMAGE },
- { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
- { "recursive-errors", required_argument, NULL, ARG_RECURSIVE_ERRORS },
- { "offline", required_argument, NULL, ARG_OFFLINE },
- { "threshold", required_argument, NULL, ARG_THRESHOLD },
- { "security-policy", required_argument, NULL, ARG_SECURITY_POLICY },
- { "system", no_argument, NULL, ARG_SYSTEM },
- { "user", no_argument, NULL, ARG_USER },
- { "global", no_argument, NULL, ARG_GLOBAL },
- { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN },
- { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN },
- { "fuzz", required_argument, NULL, ARG_FUZZ },
- { "no-pager", no_argument, NULL, ARG_NO_PAGER },
- { "man", optional_argument, NULL, ARG_MAN },
- { "generators", optional_argument, NULL, ARG_GENERATORS },
- { "instance", required_argument, NULL, ARG_INSTANCE },
- { "host", required_argument, NULL, 'H' },
- { "machine", required_argument, NULL, 'M' },
- { "iterations", required_argument, NULL, ARG_ITERATIONS },
- { "base-time", required_argument, NULL, ARG_BASE_TIME },
- { "unit", required_argument, NULL, 'U' },
- { "json", required_argument, NULL, ARG_JSON },
- { "profile", required_argument, NULL, ARG_PROFILE },
- { "table", optional_argument, NULL, ARG_TABLE },
- { "no-legend", optional_argument, NULL, ARG_NO_LEGEND },
- { "tldr", no_argument, NULL, ARG_TLDR },
- { "mask", no_argument, NULL, 'm' },
- { "scale-svg", required_argument, NULL, ARG_SCALE_FACTOR_SVG },
- { "detailed", no_argument, NULL, ARG_DETAILED_SVG },
- { "drm-device", required_argument, NULL, ARG_DRM_DEVICE_PATH },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "quiet", no_argument, NULL, 'q' },
+ { "order", no_argument, NULL, ARG_ORDER },
+ { "require", no_argument, NULL, ARG_REQUIRE },
+ { "root", required_argument, NULL, ARG_ROOT },
+ { "image", required_argument, NULL, ARG_IMAGE },
+ { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
+ { "recursive-errors" , required_argument, NULL, ARG_RECURSIVE_ERRORS },
+ { "offline", required_argument, NULL, ARG_OFFLINE },
+ { "threshold", required_argument, NULL, ARG_THRESHOLD },
+ { "security-policy", required_argument, NULL, ARG_SECURITY_POLICY },
+ { "system", no_argument, NULL, ARG_SYSTEM },
+ { "user", no_argument, NULL, ARG_USER },
+ { "global", no_argument, NULL, ARG_GLOBAL },
+ { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN },
+ { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN },
+ { "fuzz", required_argument, NULL, ARG_FUZZ },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
+ { "man", optional_argument, NULL, ARG_MAN },
+ { "generators", optional_argument, NULL, ARG_GENERATORS },
+ { "instance", required_argument, NULL, ARG_INSTANCE },
+ { "host", required_argument, NULL, 'H' },
+ { "machine", required_argument, NULL, 'M' },
+ { "iterations", required_argument, NULL, ARG_ITERATIONS },
+ { "base-time", required_argument, NULL, ARG_BASE_TIME },
+ { "unit", required_argument, NULL, 'U' },
+ { "json", required_argument, NULL, ARG_JSON },
+ { "profile", required_argument, NULL, ARG_PROFILE },
+ { "table", optional_argument, NULL, ARG_TABLE },
+ { "no-legend", optional_argument, NULL, ARG_NO_LEGEND },
+ { "tldr", no_argument, NULL, ARG_TLDR },
+ { "mask", no_argument, NULL, 'm' },
+ { "scale-svg", required_argument, NULL, ARG_SCALE_FACTOR_SVG },
+ { "detailed", no_argument, NULL, ARG_DETAILED_SVG },
+ { "drm-device", required_argument, NULL, ARG_DRM_DEVICE_PATH },
+ { "debugger", required_argument, NULL, ARG_DEBUGGER },
+ { "debugger-arguments", required_argument, NULL, 'A' },
{}
};
optind = 0;
for (;;) {
- static const char option_string[] = "-hqH:M:U:m";
+ static const char option_string[] = "-hqH:M:U:mA:";
c = getopt_long(argc, argv, option_string + reorder, options, NULL);
if (c < 0)
return r;
break;
+ case ARG_DEBUGGER:
+ arg_debugger = strdup(optarg);
+ break;
+
+ case 'A': {
+ _cleanup_strv_free_ char **l = NULL;
+ r = strv_split_full(&l, optarg, WHITESPACE, EXTRACT_UNQUOTE);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse debugger arguments '%s': %m", optarg);
+ strv_free_and_replace(arg_debugger_args, l);
+ break;
+ }
+
case '?':
return -EINVAL;
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Option --security-policy= is only supported for security.");
- if ((arg_root || arg_image) && (!STRPTR_IN_SET(argv[optind], "cat-config", "verify", "condition", "inspect-elf")) &&
+ if ((arg_root || arg_image) && (!STRPTR_IN_SET(argv[optind], "cat-config", "verify", "condition", "inspect-elf", "unit-gdb")) &&
(!(streq_ptr(argv[optind], "security") && arg_offline)))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Options --root= and --image= are only supported for cat-config, verify, condition and security when used with --offline= right now.");
+ "Options --root= and --image= are only supported for cat-config, verify, condition, unit-gdb, and security when used with --offline= right now.");
/* Having both an image and a root is not supported by the code */
if (arg_root && arg_image)
{ "dump", VERB_ANY, VERB_ANY, 0, verb_dump },
{ "cat-config", 2, VERB_ANY, 0, verb_cat_config },
{ "unit-files", VERB_ANY, VERB_ANY, 0, verb_unit_files },
+ { "unit-gdb", 2, VERB_ANY, 0, verb_unit_gdb },
{ "unit-paths", 1, 1, 0, verb_unit_paths },
{ "unit-shell", 2, VERB_ANY, 0, verb_unit_shell },
{ "exit-status", VERB_ANY, VERB_ANY, 0, verb_exit_status },