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 <matthias.welwarsky@sysgo.com>
Reviewed-on: http://openocd.zylin.com/3999
Tested-by: jenkins
Reviewed-by: Tim Newsome <tim@sifive.com>
Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de>
Reviewed-by: Graham Sanderson <graham.sanderson@gmail.com>
This commit is contained in:
Matthias Welwarsky 2017-02-17 16:16:51 +01:00 committed by Matthias Welwarsky
parent 56568b7996
commit 85ba2dc4c6
4 changed files with 417 additions and 51 deletions

View File

@ -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 Do not use this mode under an IDE like Eclipse as it caches values of
previously shown varibles. 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 @section RTOS Support
@cindex RTOS Support @cindex RTOS Support
@anchor{gdbrtossupport} @anchor{gdbrtossupport}
@ -10278,12 +10229,11 @@ Currently supported rtos's include:
@item @option{mqx} @item @option{mqx}
@item @option{uCOS-III} @item @option{uCOS-III}
@item @option{nuttx} @item @option{nuttx}
@item @option{hwthread} (This is not an actual RTOS. @xref{usingopenocdsmpwithgdb,,Using OpenOCD SMP with GDB}.)
@end itemize @end itemize
@quotation Note
Before an RTOS can be detected, it must export certain symbols; otherwise, it cannot 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. be used by OpenOCD. Below is a list of the required symbols for each supported RTOS.
@end quotation
@table @code @table @code
@item eCos symbols @item eCos symbols
@ -10330,6 +10280,72 @@ contrib/rtos-helpers/FreeRTOS-openocd.c
contrib/rtos-helpers/uCOS-III-openocd.c contrib/rtos-helpers/uCOS-III-openocd.c
@end table @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 @node Tcl Scripting API
@chapter Tcl Scripting API @chapter Tcl Scripting API
@cindex Tcl Scripting API @cindex Tcl Scripting API

View File

@ -17,6 +17,7 @@ noinst_LTLIBRARIES += %D%/librtos.la
%D%/mqx.c \ %D%/mqx.c \
%D%/uCOS-III.c \ %D%/uCOS-III.c \
%D%/nuttx.c \ %D%/nuttx.c \
%D%/hwthread.c \
%D%/rtos.h \ %D%/rtos.h \
%D%/rtos_standard_stackings.h \ %D%/rtos_standard_stackings.h \
%D%/rtos_ecos_stackings.h \ %D%/rtos_ecos_stackings.h \

347
src/rtos/hwthread.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>. *
***************************************************************************/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <helper/time_support.h>
#include <jtag/jtag.h>
#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, &reg_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, &current_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;
}

View File

@ -37,6 +37,7 @@ extern struct rtos_type embKernel_rtos;
extern struct rtos_type mqx_rtos; extern struct rtos_type mqx_rtos;
extern struct rtos_type uCOS_III_rtos; extern struct rtos_type uCOS_III_rtos;
extern struct rtos_type nuttx_rtos; extern struct rtos_type nuttx_rtos;
extern struct rtos_type hwthread_rtos;
static struct rtos_type *rtos_types[] = { static struct rtos_type *rtos_types[] = {
&ThreadX_rtos, &ThreadX_rtos,
@ -49,6 +50,7 @@ static struct rtos_type *rtos_types[] = {
&mqx_rtos, &mqx_rtos,
&uCOS_III_rtos, &uCOS_III_rtos,
&nuttx_rtos, &nuttx_rtos,
&hwthread_rtos,
NULL NULL
}; };