Files
rtems/cpukit/libdebugger/rtems-debugger-target.c
Kinsey Moore 924993a4bc cpukit/libdebugger: Avoid missed swbreak removal
It is possible to remove software breaks without actually restoring the
original instruction to memory. When this happens, the original
instruction is lost. This ensures that the original instruction is
restored when a software break is removed.
2022-02-23 08:35:45 -06:00

492 lines
14 KiB
C

/*
* Copyright (c) 2016-2019 Chris Johns <chrisj@rtems.org>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#define TARGET_DEBUG 0
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
#include <rtems.h>
#include <rtems/score/threadimpl.h>
#include "rtems-debugger-target.h"
#include "rtems-debugger-threads.h"
/**
* Exception local stack frame data to synchronise with the debugger
* server's events loop processor.
*/
typedef struct {
rtems_chain_node node;
rtems_id id;
CPU_Exception_frame* frame;
rtems_rx_cond cond;
} rtems_debugger_exception;
#if TARGET_DEBUG
#include <rtems/bspIo.h>
static void target_printk(const char* format, ...) RTEMS_PRINTFLIKE(1, 2);
static void
target_printk(const char* format, ...)
{
rtems_interrupt_lock_context lock_context;
va_list ap;
va_start(ap, format);
rtems_debugger_printk_lock(&lock_context);
vprintk(format, ap);
rtems_debugger_printk_unlock(&lock_context);
va_end(ap);
}
#else
#define target_printk(_fmt, ...)
#endif
int
rtems_debugger_target_create(void)
{
if (rtems_debugger->target == NULL) {
rtems_debugger_target* target;
int r;
target = calloc(1, sizeof(rtems_debugger_target));
if (target == NULL) {
errno = ENOMEM;
return -1;
}
r = rtems_debugger_target_configure(target);
if (r < 0) {
free(target);
return -1;
}
if (target->breakpoint_size > RTEMS_DEBUGGER_TARGET_SWBREAK_MAX_SIZE) {
free(target);
rtems_debugger_printf("error: rtems-db: target: breakpoint size too big\n");
return -1;
}
r = rtems_debugger_block_create(&target->swbreaks,
RTEMS_DEBUGGER_TARGET_SWBREAK_NUM,
sizeof(rtems_debugger_target_swbreak));
if (r < 0) {
free(target);
return -1;
}
rtems_debugger->target = target;
}
return 0;
}
int
rtems_debugger_target_destroy(void)
{
if (rtems_debugger->target != NULL) {
rtems_debugger_target* target = rtems_debugger->target;
rtems_debugger_target_swbreak_remove();
rtems_debugger_target_disable();
rtems_debugger_block_destroy(&target->swbreaks);
free(target);
rtems_debugger->target = NULL;
}
return 0;
}
uint32_t
rtems_debugger_target_capabilities(void)
{
if (rtems_debugger->target != NULL)
return rtems_debugger->target->capabilities;
return 0;
}
size_t
rtems_debugger_target_reg_num(void)
{
rtems_debugger_target* target = rtems_debugger->target;
if (target != NULL)
return target->reg_num;
return 0;
}
size_t
rtems_debugger_target_reg_size(size_t reg)
{
rtems_debugger_target* target = rtems_debugger->target;
if (target != NULL && reg < target->reg_num)
return target->reg_offset[reg + 1] - target->reg_offset[reg];
return 0;
}
size_t
rtems_debugger_target_reg_offset(size_t reg)
{
rtems_debugger_target* target = rtems_debugger->target;
if (target != NULL && reg < target->reg_num)
return target->reg_offset[reg];
return 0;
}
size_t
rtems_debugger_target_reg_table_size(void)
{
rtems_debugger_target* target = rtems_debugger->target;
if (target != NULL)
return target->reg_offset[target->reg_num];
return 0;
}
int
rtems_debugger_target_swbreak_control(bool insert, uintptr_t addr, DB_UINT kind)
{
rtems_debugger_target* target = rtems_debugger->target;
rtems_debugger_target_swbreak* swbreaks;
size_t swbreak_size;
uint8_t* loc = (void*) addr;
size_t i;
int r;
if (target == NULL || target->swbreaks.block == NULL ||
kind != target->breakpoint_size) {
errno = EIO;
return -1;
}
swbreaks = target->swbreaks.block;
swbreak_size =
sizeof(rtems_debugger_target_swbreak) + target->breakpoint_size;
for (i = 0; i < target->swbreaks.level; ++i) {
if (loc == swbreaks[i].address) {
size_t remaining;
if (!insert) {
if (target->breakpoint_size > 4)
memcpy(loc, swbreaks[i].contents, target->breakpoint_size);
else {
switch (target->breakpoint_size) {
case 4:
loc[3] = swbreaks[i].contents[3];
case 3:
loc[2] = swbreaks[i].contents[2];
case 2:
loc[1] = swbreaks[i].contents[1];
case 1:
loc[0] = swbreaks[i].contents[0];
break;
}
}
rtems_debugger_target_cache_sync(&swbreaks[i]);
--target->swbreaks.level;
remaining = (target->swbreaks.level - i) * swbreak_size;
memmove(&swbreaks[i], &swbreaks[i + 1], remaining);
}
return 0;
}
}
if (!insert)
return 0;
r = rtems_debugger_block_resize(&target->swbreaks);
if (r < 0)
return -1;
swbreaks = target->swbreaks.block;
swbreaks[target->swbreaks.level].address = loc;
if (target->breakpoint_size > 4)
memcpy(&swbreaks[target->swbreaks.level].contents[0],
loc,
target->breakpoint_size);
else {
uint8_t* contents = &swbreaks[target->swbreaks.level].contents[0];
switch (target->breakpoint_size) {
case 4:
contents[3] = loc[3];
case 3:
contents[2] = loc[2];
case 2:
contents[1] = loc[1];
case 1:
contents[0] = loc[0];
break;
}
}
++target->swbreaks.level;
return 0;
}
int
rtems_debugger_target_swbreak_insert(void)
{
rtems_debugger_target* target = rtems_debugger->target;
int r = -1;
if (target != NULL && target->swbreaks.block != NULL) {
rtems_debugger_target_swbreak* swbreaks = target->swbreaks.block;
size_t i;
r = 0;
for (i = 0; i < target->swbreaks.level; ++i) {
uint8_t* loc = swbreaks[i].address;
if (rtems_debugger_verbose())
rtems_debugger_printf("rtems-db: bp: in: %p\n", swbreaks[i].address);
if (target->breakpoint_size > 4)
memcpy(loc, &target->breakpoint[0], target->breakpoint_size);
else {
if (rtems_debugger_verbose())
rtems_debugger_printf("rtems-db: bp: in: %p %p %d %d %d\n",
loc, &target->breakpoint[0],
(int) target->breakpoint_size,
(int) i, (int) target->swbreaks.level);
switch (target->breakpoint_size) {
case 4:
loc[3] = target->breakpoint[3];
case 3:
loc[2] = target->breakpoint[2];
case 2:
loc[1] = target->breakpoint[1];
case 1:
loc[0] = target->breakpoint[0];
break;
}
}
r = rtems_debugger_target_cache_sync(&swbreaks[i]);
}
}
return r;
}
int
rtems_debugger_target_swbreak_remove(void)
{
rtems_debugger_target* target = rtems_debugger->target;
int r = -1;
if (target != NULL && target->swbreaks.block != NULL) {
rtems_debugger_target* target = rtems_debugger->target;
rtems_debugger_target_swbreak* swbreaks = target->swbreaks.block;
size_t i;
r = 0;
for (i = 0; i < target->swbreaks.level; ++i) {
uint8_t* loc = swbreaks[i].address;
uint8_t* contents = &swbreaks[i].contents[0];
if (rtems_debugger_verbose())
rtems_debugger_printf("rtems-db: bp: out: %p\n", swbreaks[i].address);
if (target->breakpoint_size > 4)
memcpy(loc, contents, target->breakpoint_size);
else {
switch (target->breakpoint_size) {
case 4:
loc[3] = contents[3];
case 3:
loc[2] = contents[2];
case 2:
loc[1] = contents[1];
case 1:
loc[0] = contents[0];
break;
}
}
r = rtems_debugger_target_cache_sync(&swbreaks[i]);
}
}
return r;
}
rtems_debugger_target_exc_action
rtems_debugger_target_exception(CPU_Exception_frame* frame)
{
if (!rtems_interrupt_is_in_progress()) {
rtems_debugger_threads* threads = rtems_debugger->threads;
Thread_Control* thread = _Thread_Get_executing();
const rtems_id tid = thread->Object.id;
rtems_id* excludes;
uintptr_t pc;
const rtems_debugger_thread_stepper* stepper;
rtems_debugger_exception target_exception;
size_t i;
target_printk("[} tid:%08" PRIx32 ": thread:%08" PRIxPTR
" frame:%08" PRIxPTR "\n",
tid, (intptr_t) thread, (intptr_t) frame);
rtems_debugger_lock();
/*
* If the thread is in the debugger recover. If the access is from gdb
* continue else shutdown and let the user know.
*/
if (tid == rtems_debugger->server_task ||
tid == rtems_debugger->events_task) {
bool memory_access = rtems_debugger_target_is_memory_access();
rtems_debugger_unlock();
/*
* Has GDB has asked us to write to an address?
*/
if (memory_access) {
target_printk("[} server fault: memory access\n");
longjmp(rtems_debugger->target->access_return, -1);
}
rtems_debugger_printf("rtems-db: server exception (report)\n");
rtems_debugger_target_exception_print(frame);
rtems_debugger_server_crash();
return rtems_debugger_target_exc_cascade;
}
/*
* See if the thread is excluded.
*/
excludes = rtems_debugger_thread_excludes(threads);
for (i = 0; i < threads->excludes.level; ++i) {
if (tid == excludes[i]) {
/*
* We do nothing with this condition and cascade the exception.
*
* @todo: if this is a hwbreak carry on, if this is a swbreak replace
* the contents of the instruction, step then return the
* swbreak's contents.
*/
target_printk("[} tid:%08" PRIx32 ": excluded\n", tid);
rtems_debugger_unlock();
return rtems_debugger_target_exc_cascade;
}
}
/*
* See if the thread is inside the stepping a range.
*/
pc = rtems_debugger_target_frame_pc(frame);
stepper = rtems_debugger_thread_is_stepping(tid, pc);
if (stepper != NULL) {
stepper->thread->frame = frame;
rtems_debugger_target_thread_stepping(stepper->thread);
target_printk("[} tid:%08" PRIx32 ": stepping\n", tid);
rtems_debugger_unlock();
return rtems_debugger_target_exc_step;
}
target_printk("[} tid:%08" PRIx32 ": suspending\n", tid);
/*
* Initialise the target exception data and queue ready for the debugger
* server's event processor to handle.
*/
rtems_chain_initialize_node(&target_exception.node);
target_exception.frame = frame;
target_exception.id = tid;
_Condition_Initialize(&target_exception.cond);
rtems_chain_append_unprotected(&rtems_debugger->exception_threads,
&target_exception.node);
/*
* Signal the debug server's thread.
*/
rtems_debugger_server_events_signal();
/*
* Block on the exception thread's condition variable unlocking the
* debugger's mutex and letting the server's thread run.
*/
_Condition_Wait_recursive(&target_exception.cond, &rtems_debugger->lock);
/*
* Unlock the debugger's lock now the exception is resuming.
*/
rtems_debugger_unlock();
target_printk("[} tid:%08" PRIx32 ": resuming\n", tid);
return rtems_debugger_target_exc_consumed;
}
rtems_debugger_printf("rtems-db: exception in an interrupt, cascading\n");
return rtems_debugger_target_exc_cascade;
}
void
rtems_debugger_target_exception_thread(rtems_debugger_thread* thread)
{
rtems_chain_node* node;
thread->frame = NULL;
thread->flags &= ~RTEMS_DEBUGGER_THREAD_FLAG_EXCEPTION;
for (node = rtems_chain_first(&rtems_debugger->exception_threads);
!rtems_chain_is_tail(&rtems_debugger->exception_threads, node);
node = rtems_chain_next(node)) {
rtems_debugger_exception* target_exception = (rtems_debugger_exception*) node;
if (target_exception->id == thread->id) {
thread->frame = target_exception->frame;
thread->flags |= RTEMS_DEBUGGER_THREAD_FLAG_EXCEPTION;
}
}
}
void
rtems_debugger_target_exception_thread_resume(rtems_debugger_thread* thread)
{
rtems_chain_node* node;
for (node = rtems_chain_first(&rtems_debugger->exception_threads);
!rtems_chain_is_tail(&rtems_debugger->exception_threads, node);
node = rtems_chain_next(node)) {
rtems_debugger_exception* target_exception = (rtems_debugger_exception*) node;
if (target_exception->id == thread->id) {
rtems_chain_extract(node);
thread->frame = NULL;
thread->flags &= ~RTEMS_DEBUGGER_THREAD_FLAG_EXCEPTION;
_Condition_Signal(&target_exception->cond);
break;
}
}
}
int
rtems_debugger_target_start_memory_access(void)
{
rtems_debugger_target* target = rtems_debugger->target;
target->memory_access = true;
return setjmp(target->access_return);
}
void
rtems_debugger_target_end_memory_access(void)
{
rtems_debugger->target->memory_access = false;
}
bool
rtems_debugger_target_is_memory_access(void)
{
return rtems_debugger->target->memory_access;
}