1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
import argparse
import json
import os
import sys
from pathlib import Path
from typing import List, Optional, Tuple, Type
from knot_resolver.client.command import Command, CommandArgs, CompWords, comp_get_words, register_command
from knot_resolver.utils import which
from knot_resolver.utils.requests import request
PROCS_TYPE = List
@register_command
class DebugCommand(Command):
def __init__(self, namespace: argparse.Namespace) -> None:
self.proc_type: Optional[str] = namespace.proc_type
self.sudo: bool = namespace.sudo
self.gdb: str = namespace.gdb
self.print_only: bool = namespace.print_only
self.gdb_args: List[str] = namespace.extra if namespace.extra is not None else []
super().__init__(namespace)
@staticmethod
def register_args_subparser(
subparser: "argparse._SubParsersAction[argparse.ArgumentParser]",
) -> Tuple[argparse.ArgumentParser, "Type[Command]"]:
debug = subparser.add_parser(
"debug",
help="Run GDB on the manager's subprocesses",
)
debug.add_argument(
"--sudo",
dest="sudo",
help="Run GDB with sudo",
action="store_true",
default=False,
)
debug.add_argument(
"--gdb",
help="Custom GDB executable (may be a command on PATH, or an absolute path)",
type=str,
default=None,
)
debug.add_argument(
"--print-only",
help="Prints the GDB command line into stderr as a Python array, does not execute GDB",
action="store_true",
default=False,
)
debug.add_argument(
"proc_type",
help="Optional, the type of process to debug. May be 'kresd', 'gc', or 'all'.",
choices=["kresd", "gc", "all"],
type=str,
nargs="?",
default="kresd",
)
return debug, DebugCommand
@staticmethod
def completion(args: List[str], parser: argparse.ArgumentParser) -> CompWords:
return comp_get_words(args, parser._actions) # noqa: SLF001
def run(self, args: CommandArgs) -> None: # noqa: PLR0912, PLR0915
if self.gdb is None:
try:
gdb_cmd = str(which.which("gdb"))
except RuntimeError:
print("Could not find 'gdb' in $PATH. Is GDB installed?", file=sys.stderr)
sys.exit(1)
elif "/" not in self.gdb:
try:
gdb_cmd = str(which.which(self.gdb))
except RuntimeError:
print(f"Could not find '{self.gdb}' in $PATH.", file=sys.stderr)
sys.exit(1)
else:
gdb_cmd_path = Path(self.gdb).absolute()
if not gdb_cmd_path.exists():
print(f"Could not find '{self.gdb}'.", file=sys.stderr)
sys.exit(1)
gdb_cmd = str(gdb_cmd_path)
response = request(args.socket, "GET", f"processes/{self.proc_type}")
if response.status != 200:
print(response, file=sys.stderr)
sys.exit(1)
procs = json.loads(response.body)
if not isinstance(procs, PROCS_TYPE):
print(
f"Unexpected response type '{type(procs).__name__}' from manager. Expected '{PROCS_TYPE.__name__}'",
file=sys.stderr,
)
sys.exit(1)
if len(procs) == 0:
print(
f"There are no processes of type '{self.proc_type}' available to debug",
file=sys.stderr,
)
exec_args = []
# Put `sudo --` at the beginning of the command.
if self.sudo:
try:
sudo_cmd = str(which.which("sudo"))
except RuntimeError:
print("Could not find 'sudo' in $PATH. Is sudo installed?", file=sys.stderr)
sys.exit(1)
exec_args.extend([sudo_cmd, "--"])
# Attach GDB to processes - the processes are attached using the `add-inferior` and `attach` GDB
# commands. This way, we can debug multiple processes.
exec_args.extend([gdb_cmd, "--"])
exec_args.extend(["-init-eval-command", "set detach-on-fork off"])
exec_args.extend(["-init-eval-command", "set schedule-multiple on"])
exec_args.extend(["-init-eval-command", f'attach {procs[0]["pid"]}'])
inferior = 2
for proc in procs[1:]:
exec_args.extend(["-init-eval-command", "add-inferior"])
exec_args.extend(["-init-eval-command", f"inferior {inferior}"])
exec_args.extend(["-init-eval-command", f'attach {proc["pid"]}'])
inferior += 1
num_inferiors = inferior - 1
if num_inferiors > 1:
# Now we switch back to the first process and add additional provided GDB arguments.
exec_args.extend(["-init-eval-command", "inferior 1"])
exec_args.extend(
[
"-init-eval-command",
"echo \\n\\nYou are now debugging multiple Knot Resolver processes. To switch between "
"them, use the 'inferior <n>' command, where <n> is an integer from 1 to "
f"{num_inferiors}.\\n\\n",
]
)
exec_args.extend(self.gdb_args)
if self.print_only:
print(f"{exec_args}")
else:
os.execl(*exec_args)
|