From a921202a85e7be3d66851adba43a07399f750e63 Mon Sep 17 00:00:00 2001 From: Christian Hopps Date: Mon, 23 Oct 2023 05:10:50 -0400 Subject: [PATCH] tests: add --gdb-use-emacs option When specified `--gdb-use-emacs` will launch the daemon with gdb inside a running emacs server using `emacsclient --eval` commands. Signed-off-by: Christian Hopps --- doc/developer/topotests.rst | 6 ++ tests/topotests/conftest.py | 6 ++ tests/topotests/lib/topotest.py | 100 +++++++++++++++++++++++++++++--- 3 files changed, 105 insertions(+), 7 deletions(-) diff --git a/doc/developer/topotests.rst b/doc/developer/topotests.rst index 0e4f05ed00..fb860fb45f 100644 --- a/doc/developer/topotests.rst +++ b/doc/developer/topotests.rst @@ -583,6 +583,12 @@ Here's an example of launching ``zebra`` and ``bgpd`` inside ``gdb`` on router --gdb-breakpoints=nb_config_diff \ all-protocol-startup +Finally, for Emacs users, you can specify ``--gdb-use-emacs``. When specified +the first router and daemon to be launched in gdb will be launched and run with +Emacs gdb functionality by using `emacsclient --eval` commands. This provides an +IDE debugging experience for Emacs users. This functionality works best when +using password-less sudo. + Reporting Memleaks with FRR Memory Statistics """"""""""""""""""""""""""""""""""""""""""""" diff --git a/tests/topotests/conftest.py b/tests/topotests/conftest.py index 0afebde1cf..b6b2880db8 100755 --- a/tests/topotests/conftest.py +++ b/tests/topotests/conftest.py @@ -102,6 +102,12 @@ def pytest_addoption(parser): help="Comma-separated list of routers to spawn gdb on, or 'all'", ) + parser.addoption( + "--gdb-use-emacs", + action="store_true", + help="Use emacsclient to run gdb instead of a shell", + ) + parser.addoption( "--logd", action="append", diff --git a/tests/topotests/lib/topotest.py b/tests/topotests/lib/topotest.py index c220bcfed1..093491617d 100644 --- a/tests/topotests/lib/topotest.py +++ b/tests/topotests/lib/topotest.py @@ -31,7 +31,8 @@ from copy import deepcopy import lib.topolog as topolog from lib.micronet_compat import Node from lib.topolog import logger -from munet.base import Timeout +from munet.base import commander, get_exec_path_host, Timeout +from munet.testing.util import retry from lib import micronet @@ -1261,8 +1262,8 @@ def rlimit_atleast(rname, min_value, raises=False): def fix_netns_limits(ns): # Maximum read and write socket buffer sizes - sysctl_atleast(ns, "net.ipv4.tcp_rmem", [10 * 1024, 87380, 16 * 2 ** 20]) - sysctl_atleast(ns, "net.ipv4.tcp_wmem", [10 * 1024, 87380, 16 * 2 ** 20]) + sysctl_atleast(ns, "net.ipv4.tcp_rmem", [10 * 1024, 87380, 16 * 2**20]) + sysctl_atleast(ns, "net.ipv4.tcp_wmem", [10 * 1024, 87380, 16 * 2**20]) sysctl_assure(ns, "net.ipv4.conf.all.rp_filter", 0) sysctl_assure(ns, "net.ipv4.conf.default.rp_filter", 0) @@ -1321,8 +1322,8 @@ def fix_host_limits(): sysctl_atleast(None, "net.core.netdev_max_backlog", 4 * 1024) # Maximum read and write socket buffer sizes - sysctl_atleast(None, "net.core.rmem_max", 16 * 2 ** 20) - sysctl_atleast(None, "net.core.wmem_max", 16 * 2 ** 20) + sysctl_atleast(None, "net.core.rmem_max", 16 * 2**20) + sysctl_atleast(None, "net.core.wmem_max", 16 * 2**20) # Garbage Collection Settings for ARP and Neighbors sysctl_atleast(None, "net.ipv4.neigh.default.gc_thresh2", 4 * 1024) @@ -1363,6 +1364,8 @@ def setup_node_tmpdir(logdir, name): class Router(Node): "A Node with IPv4/IPv6 forwarding enabled" + gdb_emacs_router = None + def __init__(self, name, *posargs, **params): # Backward compatibility: # Load configuration defaults like topogen. @@ -1799,6 +1802,7 @@ class Router(Node): gdb_breakpoints = g_pytest_config.get_option_list("--gdb-breakpoints") gdb_daemons = g_pytest_config.get_option_list("--gdb-daemons") gdb_routers = g_pytest_config.get_option_list("--gdb-routers") + gdb_use_emacs = bool(g_pytest_config.option.gdb_use_emacs) valgrind_extra = bool(g_pytest_config.option.valgrind_extra) valgrind_memleaks = bool(g_pytest_config.option.valgrind_memleaks) strace_daemons = g_pytest_config.get_option_list("--strace-daemons") @@ -1926,12 +1930,19 @@ class Router(Node): cmdopt += " " + extra_opts if ( - (gdb_routers or gdb_daemons) + (not gdb_use_emacs or Router.gdb_emacs_router) + and (gdb_routers or gdb_daemons) and ( not gdb_routers or self.name in gdb_routers or "all" in gdb_routers ) and (not gdb_daemons or daemon in gdb_daemons or "all" in gdb_daemons) ): + if Router.gdb_emacs_router is not None: + logger.warning( + "--gdb-use-emacs can only run a single router and daemon, using" + " new window" + ) + if daemon == "snmpd": cmdopt += " -f " @@ -1942,9 +1953,84 @@ class Router(Node): for bp in gdb_breakpoints: gdbcmd += " -ex 'b {}'".format(bp) gdbcmd += " -ex 'run {}'".format(cmdopt) - self.run_in_window(gdbcmd, daemon) + logger.info( + "%s: %s %s launched in gdb window", self, self.routertype, daemon + ) + elif ( + gdb_use_emacs + and (daemon in gdb_daemons) + and (not gdb_routers or self.name in gdb_routers) + ): + assert Router.gdb_emacs_router is None + Router.gdb_emacs_router = self + + if daemon == "snmpd": + cmdopt += " -f " + cmdopt += rediropt + + sudo_path = get_exec_path_host("sudo") + ecbin = [ + sudo_path, + "-Eu", + os.environ["SUDO_USER"], + get_exec_path_host("emacsclient"), + ] + pre_cmd = self._get_pre_cmd(True, False, ns_only=True, root_level=True) + # why fail:? gdb -i=mi -iex='set debuginfod enabled off' {binary} " + gdbcmd = f"{sudo_path} {pre_cmd} gdb -i=mi {binary} " + + commander.cmd_raises( + ecbin + + [ + "--eval", + f'(gdb "{gdbcmd}"))', + ] + ) + + elcheck = ( + '(ignore-errors (with-current-buffer "*gud-nsenter*"' + " (and (string-match-p" + ' "(gdb) "' + " (buffer-substring-no-properties " + ' (- (point-max) 10) (point-max))) "ready")))' + ) + + @retry(10) + def emacs_gdb_ready(): + check = commander.cmd_nostatus(ecbin + ["--eval", elcheck]) + return None if "ready" in check else False + + emacs_gdb_ready() + + # target gdb commands + cmd = "set breakpoint pending on" + self.cmd_raises( + ecbin + + [ + "--eval", + f'(gud-gdb-run-command-fetch-lines "{cmd}" "*gud-gdb*")', + ] + ) + # gdb breakpoints + for bp in gdb_breakpoints: + self.cmd_raises( + ecbin + + [ + "--eval", + f'(gud-gdb-run-command-fetch-lines "br {bp}" "*gud-gdb*")', + ] + ) + # gdb run cmd + self.cmd_raises( + ecbin + + [ + "--eval", + f'(gud-gdb-run-command-fetch-lines "run {cmdopt}" "*gud-gdb*")', + ] + ) + logger.info( "%s: %s %s launched in gdb window", self, self.routertype, daemon )