diff --git a/tests/framework/microvm.py b/tests/framework/microvm.py index 74ae180950c..681c397d444 100644 --- a/tests/framework/microvm.py +++ b/tests/framework/microvm.py @@ -287,6 +287,8 @@ def __init__( self.help = MicrovmHelpers(self) + self.gdb_socket = None + def __repr__(self): return f"" @@ -1198,6 +1200,11 @@ def wait_for_ssh_up(self): # run commands. The actual connection retry loop happens in SSHConnection._init_connection _ = self.ssh_iface(0) + def enable_gdb(self): + """Enables GDB debugging""" + self.gdb_socket = "gdb.socket" + self.api.machine_config.patch(gdb_socket_path=self.gdb_socket) + class MicroVMFactory: """MicroVM factory""" diff --git a/tests/framework/microvm_helpers.py b/tests/framework/microvm_helpers.py index f42b63222fb..7c7aa4fd0b1 100644 --- a/tests/framework/microvm_helpers.py +++ b/tests/framework/microvm_helpers.py @@ -7,6 +7,7 @@ import os import platform import subprocess +import tempfile from pathlib import Path @@ -245,3 +246,30 @@ def trace_cmd_guest(self, fns, cmd, port=4321): f"trace-cmd record -N {host_ip}:{port} -p function {' '.join(fns)} {cmd}" ) return list(Path(".").glob("trace.*.dat")) + + def tmux_gdb(self): + """Run GDB on a new tmux window""" + chroot_gdb_socket = Path(self.vm.jailer.chroot_path(), self.vm.gdb_socket) + + with tempfile.NamedTemporaryFile( + mode="w", suffix=".gdb", delete=False, prefix="fc_gdb_" + ) as f: + f.write( + f""" + target remote {chroot_gdb_socket} + directory resources/linux + hbreak start_kernel + continue + """ + ) + gdb_script = f.name + + self.tmux_neww( + f""" + until [ -S {chroot_gdb_socket} ]; do + echo 'waiting for {chroot_gdb_socket}'; + sleep 1; + done; + gdb {self.vm.kernel_file} -x {gdb_script} + """ + ) diff --git a/tests/integration_tests/build/test_gdb.py b/tests/integration_tests/build/test_gdb.py index 3c9ebbb6d87..872f392f620 100644 --- a/tests/integration_tests/build/test_gdb.py +++ b/tests/integration_tests/build/test_gdb.py @@ -2,15 +2,91 @@ # SPDX-License-Identifier: Apache-2.0 """A test that ensures that firecracker builds with GDB feature enabled at integration time.""" +import os import platform +import signal +import subprocess +import tempfile +from pathlib import Path import host_tools.cargo_build as host +import pytest +from framework.defs import LOCAL_BUILD_PATH +from framework.microvm import MicroVMFactory MACHINE = platform.machine() TARGET = "{}-unknown-linux-musl".format(MACHINE) +BUILD_PATH = LOCAL_BUILD_PATH / "gdb" + + +def build_gdb(): + """Builds Firecracker with GDB feature enabled""" + + host.cargo( + "build", + f"--features gdb --target {TARGET} --all", + env={"CARGO_TARGET_DIR": BUILD_PATH}, + ) def test_gdb_compiles(): """Checks that Firecracker compiles with GDB enabled""" - host.cargo("build", f"--features gdb --target {TARGET}") + build_gdb() + + +@pytest.mark.skipif( + platform.machine() != "x86_64", + reason="GDB requires a vmlinux but we ship a uImage for ARM in our CI", +) +def test_gdb_connects(guest_kernel_linux_6_1, rootfs): + """Checks that GDB works in a FC VM""" + + build_gdb() + + vmfcty = MicroVMFactory(BUILD_PATH / TARGET / "debug") + kernel_dbg = guest_kernel_linux_6_1.parent / "debug" / guest_kernel_linux_6_1.name + uvm = vmfcty.build(kernel_dbg, rootfs) + uvm.spawn(validate_api=False) + uvm.add_net_iface() + uvm.basic_config() + uvm.enable_gdb() + + chroot_gdb_socket = Path(uvm.jailer.chroot_path(), uvm.gdb_socket) + + gdb_commands = f""" + target remote {chroot_gdb_socket} + hbreak start_kernel + # continue to start_kernel + continue + # continue boot until interrupted + continue + """ + + with tempfile.NamedTemporaryFile( + mode="w", suffix=".gdb", delete=False, prefix="fc_gdb_" + ) as f: + f.write(gdb_commands) + gdb_script = f.name + + gdb_proc = subprocess.Popen( + f""" + until [ -S {chroot_gdb_socket} ]; do + echo 'waiting for {chroot_gdb_socket}'; + sleep 1; + done; + gdb {kernel_dbg} -batch -x {gdb_script} + """, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + uvm.start() + os.kill(uvm.firecracker_pid, signal.SIGKILL) + gdb_proc.terminate() + uvm.mark_killed() + stdout, stderr = gdb_proc.communicate(timeout=10) + assert ( + "hit Breakpoint 1, start_kernel" in stdout + ), f"Breakpoint wasn't hit:\nstdout:\n{stdout}\n\nstderr:\n{stderr}" diff --git a/tools/devtool b/tools/devtool index 5bac70d0310..af0afe352af 100755 --- a/tools/devtool +++ b/tools/devtool @@ -944,7 +944,7 @@ cmd_test_debug() { cmd_fmt() { cmd_sh "cargo fmt --all -- --config $(tr '\n' ','