From 85ba2dc4c6ab4c91f4461c2853660cc2cb9e2623 Mon Sep 17 00:00:00 2001 From: Matthias Welwarsky Date: Fri, 17 Feb 2017 16:16:51 +0100 Subject: [PATCH] rtos/hwthread: add hardware-thread pseudo rtos This patch adds "hwthread", a pseudo rtos that represents cpu cores in an SMP system as threads to gdb. This allows to debug SMP system kernels in a more sensible manner and removes the current atrocities of switching gdb manually between CPU cores to update the context. Change-Id: Ib781c6c34097689d21d9e02011e4d74a4a742379 Signed-off-by: Matthias Welwarsky Reviewed-on: http://openocd.zylin.com/3999 Tested-by: jenkins Reviewed-by: Tim Newsome Reviewed-by: Matthias Welwarsky Reviewed-by: Graham Sanderson --- doc/openocd.texi | 118 ++++++++------- src/rtos/Makefile.am | 1 + src/rtos/hwthread.c | 347 +++++++++++++++++++++++++++++++++++++++++++ src/rtos/rtos.c | 2 + 4 files changed, 417 insertions(+), 51 deletions(-) create mode 100644 src/rtos/hwthread.c diff --git a/doc/openocd.texi b/doc/openocd.texi index 2e04182b3c..bbb9075589 100644 --- a/doc/openocd.texi +++ b/doc/openocd.texi @@ -10199,55 +10199,6 @@ and GDB would require stopping the target to get the prompt back. Do not use this mode under an IDE like Eclipse as it caches values of previously shown varibles. -@anchor{usingopenocdsmpwithgdb} -@section Using OpenOCD SMP with GDB -@cindex SMP -For SMP support following GDB serial protocol packet have been defined : -@itemize @bullet -@item j - smp status request -@item J - smp set request -@end itemize - -OpenOCD implements : -@itemize @bullet -@item @option{jc} packet for reading core id displayed by -GDB connection. Reply is @option{XXXXXXXX} (8 hex digits giving core id) or - @option{E01} for target not smp. -@item @option{JcXXXXXXXX} (8 hex digits) packet for setting core id displayed at next GDB continue -(core id -1 is reserved for returning to normal resume mode). Reply @option{E01} -for target not smp or @option{OK} on success. -@end itemize - -Handling of this packet within GDB can be done : -@itemize @bullet -@item by the creation of an internal variable (i.e @option{_core}) by mean -of function allocate_computed_value allowing following GDB command. -@example -set $_core 1 -#Jc01 packet is sent -print $_core -#jc packet is sent and result is affected in $ -@end example - -@item by the usage of GDB maintenance command as described in following example (2 cpus in SMP with -core id 0 and 1 @pxref{definecputargetsworkinginsmp,,Define CPU targets working in SMP}). - -@example -# toggle0 : force display of coreid 0 -define toggle0 -maint packet Jc0 -continue -main packet Jc-1 -end -# toggle1 : force display of coreid 1 -define toggle1 -maint packet Jc1 -continue -main packet Jc-1 -end -@end example -@end itemize - @section RTOS Support @cindex RTOS Support @anchor{gdbrtossupport} @@ -10278,12 +10229,11 @@ Currently supported rtos's include: @item @option{mqx} @item @option{uCOS-III} @item @option{nuttx} +@item @option{hwthread} (This is not an actual RTOS. @xref{usingopenocdsmpwithgdb,,Using OpenOCD SMP with GDB}.) @end itemize -@quotation Note Before an RTOS can be detected, it must export certain symbols; otherwise, it cannot be used by OpenOCD. Below is a list of the required symbols for each supported RTOS. -@end quotation @table @code @item eCos symbols @@ -10330,6 +10280,72 @@ contrib/rtos-helpers/FreeRTOS-openocd.c contrib/rtos-helpers/uCOS-III-openocd.c @end table +@anchor{usingopenocdsmpwithgdb} +@section Using OpenOCD SMP with GDB +@cindex SMP +@cindex RTOS +@cindex hwthread +OpenOCD includes a pseudo RTOS called @emph{hwthread} that presents CPU cores +("hardware threads") in an SMP system as threads to GDB. With this extension, +GDB can be used to inspect the state of an SMP system in a natural way. +After halting the system, using the GDB command @command{info threads} will +list the context of each active CPU core in the system. GDB's @command{thread} +command can be used to switch the view to a different CPU core. +The @command{step} and @command{stepi} commands can be used to step a specific core +while other cores are free-running or remain halted, depending on the +scheduler-locking mode configured in GDB. + +@section Legacy SMP core switching support +@quotation Note +This method is deprecated in favor of the @emph{hwthread} pseudo RTOS. +@end quotation + +For SMP support following GDB serial protocol packet have been defined : +@itemize @bullet +@item j - smp status request +@item J - smp set request +@end itemize + +OpenOCD implements : +@itemize @bullet +@item @option{jc} packet for reading core id displayed by +GDB connection. Reply is @option{XXXXXXXX} (8 hex digits giving core id) or + @option{E01} for target not smp. +@item @option{JcXXXXXXXX} (8 hex digits) packet for setting core id displayed at next GDB continue +(core id -1 is reserved for returning to normal resume mode). Reply @option{E01} +for target not smp or @option{OK} on success. +@end itemize + +Handling of this packet within GDB can be done : +@itemize @bullet +@item by the creation of an internal variable (i.e @option{_core}) by mean +of function allocate_computed_value allowing following GDB command. +@example +set $_core 1 +#Jc01 packet is sent +print $_core +#jc packet is sent and result is affected in $ +@end example + +@item by the usage of GDB maintenance command as described in following example (2 cpus in SMP with +core id 0 and 1 @pxref{definecputargetsworkinginsmp,,Define CPU targets working in SMP}). + +@example +# toggle0 : force display of coreid 0 +define toggle0 +maint packet Jc0 +continue +main packet Jc-1 +end +# toggle1 : force display of coreid 1 +define toggle1 +maint packet Jc1 +continue +main packet Jc-1 +end +@end example +@end itemize + @node Tcl Scripting API @chapter Tcl Scripting API @cindex Tcl Scripting API diff --git a/src/rtos/Makefile.am b/src/rtos/Makefile.am index bbf66a6349..38adb6e923 100644 --- a/src/rtos/Makefile.am +++ b/src/rtos/Makefile.am @@ -17,6 +17,7 @@ noinst_LTLIBRARIES += %D%/librtos.la %D%/mqx.c \ %D%/uCOS-III.c \ %D%/nuttx.c \ + %D%/hwthread.c \ %D%/rtos.h \ %D%/rtos_standard_stackings.h \ %D%/rtos_ecos_stackings.h \ diff --git a/src/rtos/hwthread.c b/src/rtos/hwthread.c new file mode 100644 index 0000000000..2d9e42fa60 --- /dev/null +++ b/src/rtos/hwthread.c @@ -0,0 +1,347 @@ +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include "target/target.h" +#include "target/target_type.h" +#include "target/register.h" +#include "rtos.h" +#include "helper/log.h" +#include "helper/types.h" +#include "server/gdb_server.h" + +static bool hwthread_detect_rtos(struct target *target); +static int hwthread_create(struct target *target); +static int hwthread_update_threads(struct rtos *rtos); +static int hwthread_get_thread_reg_list(struct rtos *rtos, int64_t thread_id, + struct rtos_reg **reg_list, int *num_regs); +static int hwthread_get_symbol_list_to_lookup(symbol_table_elem_t *symbol_list[]); +static int hwthread_smp_init(struct target *target); + +#define HW_THREAD_NAME_STR_SIZE (32) + +extern int rtos_thread_packet(struct connection *connection, const char *packet, int packet_size); + +static inline threadid_t threadid_from_target(const struct target *target) +{ + return target->coreid + 1; +} + +const struct rtos_type hwthread_rtos = { + .name = "hwthread", + .detect_rtos = hwthread_detect_rtos, + .create = hwthread_create, + .update_threads = hwthread_update_threads, + .get_thread_reg_list = hwthread_get_thread_reg_list, + .get_symbol_list_to_lookup = hwthread_get_symbol_list_to_lookup, + .smp_init = hwthread_smp_init, +}; + +struct hwthread_params { + int dummy_param; +}; + +static int hwthread_fill_thread(struct rtos *rtos, struct target *curr, int thread_num) +{ + char tmp_str[HW_THREAD_NAME_STR_SIZE]; + threadid_t tid = threadid_from_target(curr); + + memset(tmp_str, 0, HW_THREAD_NAME_STR_SIZE); + + /* thread-id is the core-id of this core inside the SMP group plus 1 */ + rtos->thread_details[thread_num].threadid = tid; + /* create the thread name */ + rtos->thread_details[thread_num].exists = true; + rtos->thread_details[thread_num].thread_name_str = strdup(target_name(curr)); + snprintf(tmp_str, HW_THREAD_NAME_STR_SIZE-1, "state: %s", debug_reason_name(curr)); + rtos->thread_details[thread_num].extra_info_str = strdup(tmp_str); + + return ERROR_OK; +} + +static int hwthread_update_threads(struct rtos *rtos) +{ + int threads_found = 0; + int thread_list_size = 0; + struct target_list *head; + struct target *target; + int64_t current_thread = 0; + enum target_debug_reason current_reason = DBG_REASON_UNDEFINED; + + if (rtos == NULL) + return -1; + + target = rtos->target; + + /* wipe out previous thread details if any */ + rtos_free_threadlist(rtos); + + /* determine the number of "threads" */ + if (target->smp) { + for (head = target->head; head != NULL; head = head->next) { + struct target *curr = head->target; + + if (!target_was_examined(curr)) + continue; + + ++thread_list_size; + } + } else + thread_list_size = 1; + + /* create space for new thread details */ + rtos->thread_details = malloc(sizeof(struct thread_detail) * thread_list_size); + + if (target->smp) { + /* loop over all threads */ + for (head = target->head; head != NULL; head = head->next) { + struct target *curr = head->target; + + if (!target_was_examined(curr)) + continue; + + threadid_t tid = threadid_from_target(curr); + + hwthread_fill_thread(rtos, curr, threads_found); + + /* find an interesting thread to set as current */ + switch (current_reason) { + case DBG_REASON_UNDEFINED: + current_reason = curr->debug_reason; + current_thread = tid; + break; + case DBG_REASON_SINGLESTEP: + /* single-step can only be overridden by itself */ + if (curr->debug_reason == DBG_REASON_SINGLESTEP) { + if (tid == rtos->current_threadid) + current_thread = tid; + } + break; + case DBG_REASON_BREAKPOINT: + /* single-step overrides breakpoint */ + if (curr->debug_reason == DBG_REASON_SINGLESTEP) { + current_reason = curr->debug_reason; + current_thread = tid; + } else + /* multiple breakpoints, prefer gdbs' threadid */ + if (curr->debug_reason == DBG_REASON_BREAKPOINT) { + if (tid == rtos->current_threadid) + current_thread = tid; + } + break; + case DBG_REASON_WATCHPOINT: + /* breakpoint and single-step override watchpoint */ + if (curr->debug_reason == DBG_REASON_SINGLESTEP || + curr->debug_reason == DBG_REASON_BREAKPOINT) { + current_reason = curr->debug_reason; + current_thread = tid; + } + break; + case DBG_REASON_DBGRQ: + /* all other reasons override debug-request */ + if (curr->debug_reason == DBG_REASON_SINGLESTEP || + curr->debug_reason == DBG_REASON_WATCHPOINT || + curr->debug_reason == DBG_REASON_BREAKPOINT) { + current_reason = curr->debug_reason; + current_thread = tid; + } else + if (curr->debug_reason == DBG_REASON_DBGRQ) { + if (tid == rtos->current_threadid) + current_thread = tid; + } + + break; + + default: + break; + } + + threads_found++; + } + } else { + hwthread_fill_thread(rtos, target, threads_found); + current_thread = threadid_from_target(target); + threads_found++; + } + + rtos->thread_count = threads_found; + + /* we found an interesting thread, set it as current */ + if (current_thread != 0) + rtos->current_thread = current_thread; + else if (rtos->current_threadid != 0) + rtos->current_thread = rtos->current_threadid; + else + rtos->current_thread = threadid_from_target(target); + + LOG_DEBUG("%s current_thread=%i", __func__, (int)rtos->current_thread); + return 0; +} + +static int hwthread_smp_init(struct target *target) +{ + return hwthread_update_threads(target->rtos); +} + +static int hwthread_get_thread_reg_list(struct rtos *rtos, int64_t thread_id, + struct rtos_reg **rtos_reg_list, int *num_regs) +{ + struct target_list *head; + struct target *target; + struct target *curr; + struct reg **reg_list; + int retval; + + if (rtos == NULL) + return ERROR_FAIL; + + target = rtos->target; + + /* Find the thread with that thread_id */ + if (target->smp) { + curr = NULL; + for (head = target->head; head != NULL; head = head->next) { + curr = head->target; + + if (thread_id == threadid_from_target(curr)) + break; + } + + if (head == NULL) + return ERROR_FAIL; + } else { + curr = target; + if (thread_id != threadid_from_target(curr)) + return ERROR_FAIL; + + } + + if (!target_was_examined(curr)) + return ERROR_FAIL; + + retval = target_get_gdb_reg_list(curr, ®_list, num_regs, + REG_CLASS_GENERAL); + if (retval != ERROR_OK) + return retval; + + *rtos_reg_list = calloc(*num_regs, sizeof(struct rtos_reg)); + if (*rtos_reg_list == NULL) { + free(reg_list); + return ERROR_FAIL; + } + + for (int i = 0; i < *num_regs; i++) { + (*rtos_reg_list)[i].number = (*reg_list)[i].number; + (*rtos_reg_list)[i].size = (*reg_list)[i].size; + memcpy((*rtos_reg_list)[i].value, (*reg_list)[i].value, + ((*reg_list)[i].size + 7) / 8); + } + + free(reg_list); + + return ERROR_OK; + +} + +static int hwthread_get_symbol_list_to_lookup(symbol_table_elem_t *symbol_list[]) +{ + /* return an empty list, we don't have any symbols to look up */ + *symbol_list = calloc(1, sizeof(symbol_table_elem_t)); + (*symbol_list)[0].symbol_name = NULL; + return 0; +} + +static int hwthread_target_for_threadid(struct connection *connection, int64_t thread_id, struct target **p_target) +{ + struct target *target = get_target_from_connection(connection); + struct target_list *head; + struct target *curr; + + if (target->smp) { + /* Find the thread with that thread_id */ + curr = NULL; + for (head = target->head; head != NULL; head = head->next) { + curr = head->target; + + if (thread_id == threadid_from_target(curr)) + break; + } + + if (head == NULL) + return ERROR_FAIL; + } else { + curr = target; + if (thread_id != threadid_from_target(curr)) + return ERROR_FAIL; + } + + *p_target = curr; + + return ERROR_OK; +} + +static bool hwthread_detect_rtos(struct target *target) +{ + /* always return 0, avoid auto-detection */ + return false; +} + +static int hwthread_thread_packet(struct connection *connection, const char *packet, int packet_size) +{ + struct target *target = get_target_from_connection(connection); + + struct target *curr = NULL; + int64_t current_threadid; + + if (packet[0] == 'H' && packet[1] == 'g') { + sscanf(packet, "Hg%16" SCNx64, ¤t_threadid); + + if (current_threadid > 0) { + if (hwthread_target_for_threadid(connection, current_threadid, &curr) != ERROR_OK) { + LOG_ERROR("hwthread: cannot find thread id %"PRId64, current_threadid); + gdb_put_packet(connection, "E01", 3); + return ERROR_FAIL; + } + target->rtos->current_thread = current_threadid; + } else + if (current_threadid == 0 || current_threadid == -1) + target->rtos->current_thread = threadid_from_target(target); + + target->rtos->current_threadid = current_threadid; + + gdb_put_packet(connection, "OK", 2); + return ERROR_OK; + } + + return rtos_thread_packet(connection, packet, packet_size); +} + +static int hwthread_create(struct target *target) +{ + LOG_INFO("Hardware thread awareness created"); + + target->rtos->rtos_specific_params = NULL; + target->rtos->current_thread = 0; + target->rtos->thread_details = NULL; + target->rtos->gdb_target_for_threadid = hwthread_target_for_threadid; + target->rtos->gdb_thread_packet = hwthread_thread_packet; + return 0; +} diff --git a/src/rtos/rtos.c b/src/rtos/rtos.c index cd2e271f68..da0a503629 100644 --- a/src/rtos/rtos.c +++ b/src/rtos/rtos.c @@ -37,6 +37,7 @@ extern struct rtos_type embKernel_rtos; extern struct rtos_type mqx_rtos; extern struct rtos_type uCOS_III_rtos; extern struct rtos_type nuttx_rtos; +extern struct rtos_type hwthread_rtos; static struct rtos_type *rtos_types[] = { &ThreadX_rtos, @@ -49,6 +50,7 @@ static struct rtos_type *rtos_types[] = { &mqx_rtos, &uCOS_III_rtos, &nuttx_rtos, + &hwthread_rtos, NULL }; -- 2.30.2