diff --git a/boards/Kconfig b/boards/Kconfig index fbab1e97d984e6..f56c2cab04ef9a 100644 --- a/boards/Kconfig +++ b/boards/Kconfig @@ -111,6 +111,15 @@ config QEMU_GDBSERVER_LISTEN_DEV as the `QEMU_EXTRA_FLAGS` environment variable. Refer to application development doc and/or QEMU invocation doc for more info. +config QEMU_EXTRA_FLAGS + string "QEMU extra flags" + depends on QEMU_TARGET + default "" + help + This option is to pass onto QEMU an extra list of parameters + to setup devices, for example to allocate interface for Zephyr + GDBstub over serial with `-serial tcp:127.0.0.1:5678,server` + # There might not be any board options, hence the optional source osource "$(BOARD_DIR)/Kconfig" endmenu diff --git a/cmake/emu/qemu.cmake b/cmake/emu/qemu.cmake index a3a8a94515b537..6103fc1cefc15f 100644 --- a/cmake/emu/qemu.cmake +++ b/cmake/emu/qemu.cmake @@ -399,6 +399,13 @@ set(env_qemu $ENV{QEMU_EXTRA_FLAGS}) separate_arguments(env_qemu) list(APPEND QEMU_EXTRA_FLAGS ${env_qemu}) +# Also append QEMU flags from config +if(NOT CONFIG_QEMU_EXTRA_FLAGS STREQUAL "") + set(config_qemu_flags ${CONFIG_QEMU_EXTRA_FLAGS}) + separate_arguments(config_qemu_flags) + list(APPEND QEMU_EXTRA_FLAGS "${config_qemu_flags}") +endif() + list(APPEND MORE_FLAGS_FOR_debugserver_qemu -S) if(NOT CONFIG_QEMU_GDBSERVER_LISTEN_DEV STREQUAL "") diff --git a/doc/services/debugging/gdbstub.rst b/doc/services/debugging/gdbstub.rst index 5b702d9569802f..50b830fb5b86a6 100644 --- a/doc/services/debugging/gdbstub.rst +++ b/doc/services/debugging/gdbstub.rst @@ -87,8 +87,9 @@ Using Serial Backend Example ******* -This is an example using ``samples/subsys/debug/gdbstub`` to demonstrate -how GDB stub works. +This is an example to demonstrate how GDB stub works. +You can also refer to ``tests/subsys/debug/gdbstub`` +for its implementation as a Twister test. #. Open two terminal windows. diff --git a/samples/subsys/debug/gdbstub/README.rst b/samples/subsys/debug/gdbstub/README.rst deleted file mode 100644 index dc8430cd339891..00000000000000 --- a/samples/subsys/debug/gdbstub/README.rst +++ /dev/null @@ -1,31 +0,0 @@ -.. zephyr:code-sample:: gdb-debug - :name: GDB debug - - Use GDB Remote Serial Protocol to debug a Zephyr application running on QEMU. - -Overview -******** - -A simple sample that can be used with QEMU to show debug using GDB -Remote Serial Protocol (RSP) capabilities. - -Building and Running -******************** - -This application can be built and executed on QEMU as follows: - -.. zephyr-app-commands:: - :zephyr-app: samples/subsys/debug/gdbstub - :host-os: unix - :board: qemu_x86 - :goals: run - :compact: - -Open a new terminal and use gdb to connect to the running qemu as follows: - -.. code-block:: bash - - gdb build/zephyr/zephyr.elf - (gdb) target remote :5678 - -Exit QEMU by pressing :kbd:`CTRL+A` :kbd:`x`. diff --git a/samples/subsys/debug/gdbstub/boards/qemu_x86_64.overlay b/samples/subsys/debug/gdbstub/boards/qemu_x86_64.overlay deleted file mode 100644 index e6c560fae72751..00000000000000 --- a/samples/subsys/debug/gdbstub/boards/qemu_x86_64.overlay +++ /dev/null @@ -1,7 +0,0 @@ -/* SPDX-License-Identifier: Apache-2.0 */ - -/ { - chosen { - zephyr,gdbstub-uart = &uart1; - }; -}; diff --git a/samples/subsys/debug/gdbstub/pytest/test_gdbstub.py b/samples/subsys/debug/gdbstub/pytest/test_gdbstub.py deleted file mode 100755 index 41dca1cfcae9a8..00000000000000 --- a/samples/subsys/debug/gdbstub/pytest/test_gdbstub.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright (c) 2023 Intel Corporation. -# -# SPDX-License-Identifier: Apache-2.0 - -import os -import subprocess -from twister_harness import DeviceAdapter -import sys -import logging -import shlex - -ZEPHYR_BASE = os.getenv("ZEPHYR_BASE") -sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts", "pylib", "twister")) -from twisterlib.cmakecache import CMakeCache - -logger = logging.getLogger(__name__) - - -def test_gdbstub(dut: DeviceAdapter): - """ - Test gdbstub feature using a gdb script. We connect to the DUT and run some - basic gdb commands and evaluate return code to determine pass or failure. - """ - build_dir = dut.device_config.build_dir - cmake_cache = CMakeCache.from_file(build_dir / 'CMakeCache.txt') - gdb = cmake_cache.get('CMAKE_GDB', None) - assert gdb - source_dir = cmake_cache.get('APPLICATION_SOURCE_DIR', None) - assert source_dir - cmd = [gdb, '-x', f'{source_dir}/run.gdbinit', f'{build_dir}/zephyr/zephyr.elf'] - logger.info(f'Test command: {shlex.join(cmd)}') - result = subprocess.run(cmd, capture_output=True, text=True, timeout=20) - logger.debug('Output:\n%s' % result.stdout) - assert result.returncode == 0 diff --git a/samples/subsys/debug/gdbstub/run.gdbinit b/samples/subsys/debug/gdbstub/run.gdbinit deleted file mode 100644 index abce598f54af7a..00000000000000 --- a/samples/subsys/debug/gdbstub/run.gdbinit +++ /dev/null @@ -1,17 +0,0 @@ -set pagination off -#symbol-file build/zephyr/zephyr.elf -target remote :5678 -b test -b main.c:33 -c - -s -set var a = 2 -c -if ret == 6 - printf "PASSED\n" - quit 0 -else - printf "FAILED\n" - quit 1 -end diff --git a/samples/subsys/debug/gdbstub/sample.yaml b/samples/subsys/debug/gdbstub/sample.yaml deleted file mode 100644 index 638c5ec16cc37b..00000000000000 --- a/samples/subsys/debug/gdbstub/sample.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# -# Copyright (c) 2020 intel Corporation. -# -# SPDX-License-Identifier: Apache-2.0 -# - -sample: - name: gdbstub sample -tests: - sample.debug.gdbstub: - platform_allow: qemu_x86 - harness: pytest - tags: - - debug - - gdbstub diff --git a/samples/subsys/debug/gdbstub/CMakeLists.txt b/tests/subsys/debug/gdbstub/CMakeLists.txt similarity index 59% rename from samples/subsys/debug/gdbstub/CMakeLists.txt rename to tests/subsys/debug/gdbstub/CMakeLists.txt index 9759ca6c7f0c36..69377e717f56d7 100644 --- a/samples/subsys/debug/gdbstub/CMakeLists.txt +++ b/tests/subsys/debug/gdbstub/CMakeLists.txt @@ -2,11 +2,7 @@ cmake_minimum_required(VERSION 3.20.0) -if(BOARD MATCHES "qemu_x86") - list(APPEND QEMU_EXTRA_FLAGS -serial tcp:127.0.0.1:5678,server) -endif() - find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) -project(debug) +project(debug_gdbstub) target_sources(app PRIVATE src/main.c) diff --git a/samples/subsys/debug/gdbstub/boards/qemu_x86.overlay b/tests/subsys/debug/gdbstub/boards/qemu_x86.overlay similarity index 100% rename from samples/subsys/debug/gdbstub/boards/qemu_x86.overlay rename to tests/subsys/debug/gdbstub/boards/qemu_x86.overlay diff --git a/samples/subsys/debug/gdbstub/prj.conf b/tests/subsys/debug/gdbstub/prj.conf similarity index 74% rename from samples/subsys/debug/gdbstub/prj.conf rename to tests/subsys/debug/gdbstub/prj.conf index da68f3823c4d5c..8b490380226248 100644 --- a/samples/subsys/debug/gdbstub/prj.conf +++ b/tests/subsys/debug/gdbstub/prj.conf @@ -1,4 +1,5 @@ CONFIG_GDBSTUB=y +CONFIG_GDBSTUB_SERIAL_BACKEND=y CONFIG_NO_OPTIMIZATIONS=y CONFIG_USERSPACE=y CONFIG_KOBJECT_TEXT_AREA=4096 diff --git a/tests/subsys/debug/gdbstub/pytest/conftest.py b/tests/subsys/debug/gdbstub/pytest/conftest.py new file mode 100644 index 00000000000000..12d5ced7590a95 --- /dev/null +++ b/tests/subsys/debug/gdbstub/pytest/conftest.py @@ -0,0 +1,25 @@ +# +# Copyright (c) 2023 intel Corporation. +# +# SPDX-License-Identifier: Apache-2.0 +# + +import pytest + +def pytest_addoption(parser): + parser.addoption('--gdb_target_remote') + parser.addoption('--gdb_timeout') + parser.addoption('--gdb_script') + +@pytest.fixture() +def gdb_script(request): + return request.config.getoption('--gdb_script') + +@pytest.fixture() +def gdb_timeout(request): + return int(request.config.getoption('--gdb_timeout', default=60)) + +@pytest.fixture() +def gdb_target_remote(request): + return request.config.getoption('--gdb_target_remote', default=":5678") +# diff --git a/tests/subsys/debug/gdbstub/pytest/test_gdbstub.py b/tests/subsys/debug/gdbstub/pytest/test_gdbstub.py new file mode 100755 index 00000000000000..fe063905df75d3 --- /dev/null +++ b/tests/subsys/debug/gdbstub/pytest/test_gdbstub.py @@ -0,0 +1,83 @@ +# Copyright (c) 2023 Intel Corporation. +# +# SPDX-License-Identifier: Apache-2.0 + +import os +import subprocess +import sys +import logging +import shlex +import re +import pytest +from twister_harness import DeviceAdapter + +ZEPHYR_BASE = os.getenv("ZEPHYR_BASE") +sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts", "pylib", "twister")) +from twisterlib.cmakecache import CMakeCache + +logger = logging.getLogger(__name__) + +@pytest.fixture() +def gdb_process(dut: DeviceAdapter, gdb_script, gdb_timeout, gdb_target_remote) -> subprocess.CompletedProcess: + build_dir = dut.device_config.build_dir + cmake_cache = CMakeCache.from_file(os.path.join(build_dir, 'CMakeCache.txt')) + gdb_exec = cmake_cache.get('CMAKE_GDB', None) + assert gdb_exec + source_dir = cmake_cache.get('APPLICATION_SOURCE_DIR', None) + assert source_dir + build_image = cmake_cache.get('BYPRODUCT_KERNEL_ELF_NAME', None) + assert build_image + gdb_log_file = os.path.join(build_dir, 'gdb.log') + cmd = [gdb_exec, '-batch', + '-ex', f'set pagination off', + '-ex', f'set trace-commands on', + '-ex', f'set logging file {gdb_log_file}', + '-ex', f'set logging enabled on', + '-ex', f'target remote {gdb_target_remote}', + '-x', f'{source_dir}/{gdb_script}', build_image] + logger.info(f'Run GDB: {shlex.join(cmd)}') + result = subprocess.run(cmd, capture_output=True, text=True, timeout=gdb_timeout) + logger.info(f'GDB ends rc={result.returncode}') + return result +# + +@pytest.fixture(scope="module") +def expected_app(): + return [ + re.compile(r"Booting from ROM"), + re.compile(r"Booting Zephyr OS build"), + re.compile(r"main\(\):enter"), + ] + +@pytest.fixture(scope="module") +def expected_gdb(): + return [ + re.compile(r'Breakpoint 1 at 0x'), + re.compile(r'Breakpoint 2 at 0x'), + re.compile(r'Breakpoint 1, test '), + re.compile(r'Breakpoint 2, main '), + re.compile(r'GDB:PASSED'), + ] + +@pytest.fixture(scope="module") +def expected_gdb_detach(): + return [ + re.compile(r'Inferior.*will be killed'), # Zephyr gdbstub + re.compile(r'Inferior.*detached') # QEMU gdbstub + ] + + +def test_gdbstub(dut: DeviceAdapter, gdb_process, expected_app, expected_gdb, expected_gdb_detach): + """ + Test gdbstub feature using a GDB script. We connect to the DUT, run the + GDB script then evaluate return code and expected patterns at the GDB + and Test Applicaiton outputs. + """ + logger.debug(f"GDB output:\n{gdb_process.stdout}\n") + assert gdb_process.returncode == 0 + assert all([ex_re.search(gdb_process.stdout, re.MULTILINE) for ex_re in expected_gdb]), 'No expected GDB output' + assert any([ex_re.search(gdb_process.stdout, re.MULTILINE) for ex_re in expected_gdb_detach]), 'No expected GDB quit' + app_output = '\n'.join(dut.readlines(print_output = False)) + logger.debug(f"App output:\n{app_output}\n") + assert all([ex_re.search(app_output, re.MULTILINE) for ex_re in expected_app]), 'No expected Application output' +# diff --git a/samples/subsys/debug/gdbstub/src/main.c b/tests/subsys/debug/gdbstub/src/main.c similarity index 60% rename from samples/subsys/debug/gdbstub/src/main.c rename to tests/subsys/debug/gdbstub/src/main.c index 51995db4ab7370..4d54f6b2ff9c4f 100644 --- a/samples/subsys/debug/gdbstub/src/main.c +++ b/tests/subsys/debug/gdbstub/src/main.c @@ -20,19 +20,12 @@ static int test(void) return a + b; } -static void thread_entry(void *p1, void *p2, void *p3) -{ - printk("Hello from user thread!\n"); -} - int main(void) { int ret; + printk("%s():enter\n", __func__); ret = test(); - printk("%d\n", ret); + printk("ret=%d\n", ret); return 0; } - -K_THREAD_DEFINE(thread, STACKSIZE, thread_entry, NULL, NULL, NULL, - 7, K_USER, 0); diff --git a/tests/subsys/debug/gdbstub/test_breakpoints.gdbinit b/tests/subsys/debug/gdbstub/test_breakpoints.gdbinit new file mode 100644 index 00000000000000..d79e1aa5f284e7 --- /dev/null +++ b/tests/subsys/debug/gdbstub/test_breakpoints.gdbinit @@ -0,0 +1,17 @@ +b test +b main.c:29 +c + +# break at test() +s +set var a = 2 +c + +# break at main() +if ret == 6 + printf "GDB:PASSED\n" + quit 0 +else + printf "GDB:FAILED\n" + quit 1 +end diff --git a/tests/subsys/debug/gdbstub/testcase.yaml b/tests/subsys/debug/gdbstub/testcase.yaml new file mode 100644 index 00000000000000..adb259b4122143 --- /dev/null +++ b/tests/subsys/debug/gdbstub/testcase.yaml @@ -0,0 +1,59 @@ +# +# Copyright (c) 2020, 2023 intel Corporation. +# +# SPDX-License-Identifier: Apache-2.0 +# + +tests: + # Connect to Zephyr gdbstub and run a simple GDB script + debug.gdbstub.breakpoints: + platform_allow: qemu_x86 + harness: pytest + harness_config: + pytest_root: + - "pytest/test_gdbstub.py" + pytest_args: + - "--gdb_timeout" + - "20" + - "--gdb_script" + - "test_breakpoints.gdbinit" + - "--gdb_target_remote" + - "tcp::5678" + tags: + - debug + - gdbstub + extra_configs: + # Make sure the gdbstub port chosen is unique for this test to avoid conflicts + # when Twister runs tests in parallel on the same host. + - CONFIG_QEMU_EXTRA_FLAGS="-serial tcp::5678,server" + + # Connect to QEMU gdbstub backend and run the same GDB test script + # to check it against a reference RDP backend implementation expecting + # similar behavior as for Zephyr's gdbstub. + # Use non-default QEMU gdbstub port 1235 for this test to avoid conflicts + # with some other test on QEMU running in parallel. + debug.gdbstub_qemu.breakpoints: + platform_allow: qemu_x86 + harness: pytest + harness_config: + pytest_root: + - "pytest/test_gdbstub.py" + pytest_args: + - "--gdb_timeout" + - "20" + - "--gdb_script" + - "test_breakpoints.gdbinit" + - "--gdb_target_remote" + - "tcp::1235" + tags: + - debug + - gdbstub + extra_configs: + # Turn off Zephyr's gdbstub for this test case. + - CONFIG_GDBSTUB=n + - CONFIG_GDBSTUB_SERIAL_BACKEND=n + # Make sure the gdbstub port chosen is unique for this test to avoid conflicts + # when Twister runs tests in parallel on the same host. + - CONFIG_QEMU_EXTRA_FLAGS="-S -gdb tcp::1235" + # Clear QEMU default 'tcp::1234' + - CONFIG_QEMU_GDBSERVER_LISTEN_DEV=""