diff --git a/README.md b/README.md index 3746a94..fdae74c 100644 --- a/README.md +++ b/README.md @@ -118,10 +118,3 @@ This will most likely break the usage of other plugins until the backup is resto * Other Arm cores than Cortex-M33: This plugin has only been tested initially on nrf9160dk, and no work has been done to verify differences between Arm architectures on Zephyr. * Other than Arm cores: Zero effort has been used to other architectures, and the plugin does not even check the core parameter, so it might probably crash or show garbage values. * Distributing the built .DLL file in Windows requires user to install [Visual C++ Redistributable](https://support.microsoft.com/en-us/topic/the-latest-supported-visual-c-downloads-2647da03-1eea-4433-9aff-95f26a218cc0) on a target machine. - - -## Known problems in JLinkGDBServer - -* When `RTOS_GetNumThreads()` returns zero, GDB server still calls `RTOS_GetThreadRegList()` with thread id zero and generates a new dummy thread for GDB. This breaks debugging of the board startup. -* There is no easy way to tell GDBServer that CPU is not yet running in Thread mode, and therefore - plugin should be ignored. diff --git a/unittests/plugin_test.c b/unittests/plugin_test.c index b011c2e..b0e565b 100644 --- a/unittests/plugin_test.c +++ b/unittests/plugin_test.c @@ -6,6 +6,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include #include #include #include @@ -14,6 +15,7 @@ #include "acutest.h" +#define GDB_NO_THREAD 0xDEAD /************************* * Dummy data @@ -76,17 +78,21 @@ struct stack { struct dummy_data { uint32_t padding[1]; // Cannot start from offset zero. struct my_kernel kernel; + bool post_kernel; uint32_t debug_offsets[LEN_DEBUG_OFFSETS]; struct my_thread thread1; struct my_thread thread2; struct stack stack; uint8_t extra_mem[2048]; -} dummy_data = { +}; + +struct dummy_data dummy_data = { .kernel = { .version = 1, .current = offsetof(struct dummy_data, thread1), .threads = offsetof(struct dummy_data, thread1) }, + .post_kernel = true, .thread1 = { .name = "First Thread", .saved = {.psp = offsetof(struct dummy_data, stack) }, @@ -115,7 +121,33 @@ struct dummy_data { } }; -uint8_t *mem = (uint8_t *) &dummy_data; +struct dummy_data boot_dummy_data = { + .kernel = { + .version = 1, + .current = 0, + .threads = 0 + }, + .post_kernel = false, + .stack = { + .r0 = 0xdeadbeef, + .pc = 0xcafebabe, + }, + .debug_offsets = { + offsetof(struct my_kernel, version), + offsetof(struct my_kernel, current), + offsetof(struct my_kernel, threads), + offsetof(struct my_thread, next), + offsetof(struct my_thread, next), + offsetof(struct my_thread, state), + offsetof(struct my_thread, state), + offsetof(struct my_thread, priority), + offsetof(struct my_thread, saved.psp), + offsetof(struct my_thread, name), + offsetof(struct my_thread, arch), + } +}; + +uint8_t *mem; @@ -265,10 +297,12 @@ void test_init(void) TEST_ASSERT(symbols != NULL); symbols[0].address = offsetof(struct dummy_data, kernel); symbols[1].address = offsetof(struct dummy_data, debug_offsets); + symbols[4].address = offsetof(struct dummy_data, post_kernel); } void test_parsing(void) { + mem = (uint8_t *) &dummy_data; test_init(); int ret = RTOS_UpdateThreads(); TEST_ASSERT(ret == 0); @@ -295,8 +329,41 @@ void test_parsing(void) RTOS_Init(&api, 0); } +void test_boot(void) +{ + mem = (uint8_t *) &boot_dummy_data; + test_init(); + + // Order of calls matches what is seen from JlinkGDBServer + uint32_t n = RTOS_GetNumThreads(); + TEST_CHECK(n == 1); + + int ret = RTOS_UpdateThreads(); + TEST_ASSERT(ret == -1); + + TEST_CHECK(RTOS_GetThreadId(0) == GDB_NO_THREAD); + + char display[32]; + ret = RTOS_GetThreadDisplay(display, GDB_NO_THREAD); + TEST_ASSERT(ret > 0); + + n = RTOS_GetNumThreads(); + TEST_CHECK(n == 1); + + uint32_t id = RTOS_GetCurrentThreadId(); + TEST_CHECK(id == GDB_NO_THREAD); + + char val[10]; + ret = RTOS_GetThreadReg(val, RTOS_PLUGIN_CPU_REG_CORTEX_M_PC, id); + TEST_CHECK(ret == -1); + + // Force clear + RTOS_Init(&api, 0); +} + TEST_LIST = { { "test_init", test_init }, { "test_parsing", test_parsing}, + { "test_boot", test_boot}, { NULL, NULL } /* zeroed record marking the end of the list */ }; diff --git a/zephyr_plugin.c b/zephyr_plugin.c index 0a65b1c..06825cc 100644 --- a/zephyr_plugin.c +++ b/zephyr_plugin.c @@ -38,6 +38,11 @@ #define JLINK_CORE_CORTEX_M7 0x0E0100FF #define JLINK_CORE_CORTEX_M_V8MAINL 0x0E0200FF +/** + * Magic number used by GDB when no thread is running. + */ +#define GDB_NO_THREAD 0xDEAD + /** * List of debug information that Zephyr exposes. * @@ -168,7 +173,7 @@ bool threads_updated = false; /** Head of thread list */ struct thread_t *threads_head = NULL; /** Currently running thread */ -uint32_t current_base = 0; +uint32_t current_base = GDB_NO_THREAD; /* size_t size */ uint8_t size_t_size = 0; /** Num thread offset */ @@ -238,7 +243,7 @@ static void clear(void) p = q; } threads_head = NULL; - current_base = 0; + current_base = GDB_NO_THREAD; } struct thread_t *base_to_thread(uint32_t base) { @@ -291,7 +296,8 @@ static void update_handler_thread(void) #endif t = new_thread(); sprintf(t->name, "EXCEPTION/INTERRUPT"); - current_base = 0; + t->base = GDB_NO_THREAD; + current_base = GDB_NO_THREAD; } } @@ -555,7 +561,7 @@ EXPORT uint32_t RTOS_GetThreadId(uint32_t n) { if (t) return t->base; else - return 0; + return current_base; } EXPORT int RTOS_GetThreadDisplay(char *pDisplay, uint32_t threadid) { @@ -753,7 +759,7 @@ EXPORT int RTOS_UpdateThreads(void) { EXPORT uint32_t RTOS_GetNumThreads(void) { #if !defined(_NO_DEBUG_LOG) && VERBOSE_LOGGING - api->pfLogOutf("%s() --> %d\n", __func__, n_threads()); + api->pfLogOutf("%s()\n", __func__); #endif /* RTOS_GetNumThreads can be called before RTOS_UpdateThreads when first * attaching a debugger. As RTOS_UpdateThreads populates the thread @@ -763,9 +769,17 @@ EXPORT uint32_t RTOS_GetNumThreads(void) { * immediately. */ if (!threads_updated) { - if (RTOS_UpdateThreads() < 0) { - return 0; - } + RTOS_UpdateThreads(); + } + uint32_t threads = n_threads(); + /* GDB gets confused if there is no thread running. Consider the current + * execution context a thread even if the kernel has not started yet. + */ + if( threads == 0 ) { + threads = 1; } - return n_threads(); +#if !defined(_NO_DEBUG_LOG) && VERBOSE_LOGGING + api->pfLogOutf("%s() returned %u\n", __func__, threads); +#endif + return threads; }