/* SPDX-License-Identifier: BSD-2-Clause */
/**
* @file
*
* @ingroup RTEMSLibdebugger
*
* @brief MicroBlaze libdebugger implementation
*/
/*
* Copyright (C) 2022 On-Line Applications Research Corporation (OAR)
*
* 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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>
/* Defined by linkcmds.base */
extern char bsp_section_text_begin[];
extern char bsp_section_text_end[];
extern char bsp_section_fast_text_begin[];
extern char bsp_section_fast_text_end[];
#include <rtems.h>
#include <rtems/score/cpu.h>
#include <rtems/score/threadimpl.h>
#include <rtems/score/userextimpl.h>
#include <rtems/debugger/rtems-debugger-bsp.h>
#include "rtems-debugger-target.h"
#include "rtems-debugger-threads.h"
#if TARGET_DEBUG
#include <rtems/bspIo.h>
#endif
/*
* Number of registers.
*/
#define RTEMS_DEBUGGER_NUMREGS 57
/*
* Number of bytes per type of register.
*/
#define RTEMS_DEBUGGER_REG_BYTES 4
/* Debugger registers layout. See microblaze-core.xml in GDB source. */
#define REG_R0 0
#define REG_R1 1
#define REG_R2 2
#define REG_R3 3
#define REG_R4 4
#define REG_R5 5
#define REG_R6 6
#define REG_R7 7
#define REG_R8 8
#define REG_R9 9
#define REG_R10 10
#define REG_R11 11
#define REG_R12 12
#define REG_R13 13
#define REG_R14 14
#define REG_R15 15
#define REG_R16 16
#define REG_R17 17
#define REG_R18 18
#define REG_R19 19
#define REG_R20 20
#define REG_R21 21
#define REG_R22 22
#define REG_R23 23
#define REG_R24 24
#define REG_R25 25
#define REG_R26 26
#define REG_R27 27
#define REG_R28 28
#define REG_R29 29
#define REG_R30 30
#define REG_R31 31
#define REG_PC 32
#define REG_MS 33
#define REG_EA 34
#define REG_ES 35
#define REG_FS 36
#define REG_BT 37
#define REG_PV0 38
#define REG_PV1 39
#define REG_PV2 40
#define REG_PV3 41
#define REG_PV4 42
#define REG_PV5 43
#define REG_PV6 44
#define REG_PV7 45
#define REG_PV8 46
#define REG_PV9 47
#define REG_PV10 48
#define REG_PV11 49
#define REG_ED 50
#define REG_PID 51
#define REG_ZP 52
#define REG_TBLX 53
#define REG_TBLSX 54
#define REG_TBLLO 55
#define REG_TBLHI 56
/**
* Register offset table with the total as the last entry.
*
* Check this table in gdb with the command:
*
* maint print registers
*/
static const size_t microblaze_reg_offsets[ RTEMS_DEBUGGER_NUMREGS + 1 ] = {
REG_R0 * 4,
REG_R1 * 4,
REG_R2 * 4,
REG_R3 * 4,
REG_R4 * 4,
REG_R5 * 4,
REG_R6 * 4,
REG_R7 * 4,
REG_R8 * 4,
REG_R9 * 4,
REG_R10 * 4,
REG_R11 * 4,
REG_R12 * 4,
REG_R13 * 4,
REG_R14 * 4,
REG_R15 * 4,
REG_R16 * 4,
REG_R17 * 4,
REG_R18 * 4,
REG_R19 * 4,
REG_R20 * 4,
REG_R21 * 4,
REG_R22 * 4,
REG_R23 * 4,
REG_R24 * 4,
REG_R25 * 4,
REG_R26 * 4,
REG_R27 * 4,
REG_R28 * 4,
REG_R29 * 4,
REG_R30 * 4,
REG_R31 * 4,
REG_PC * 4,
REG_MS * 4,
REG_EA * 4,
REG_ES * 4,
REG_FS * 4,
REG_BT * 4,
REG_PV0 * 4,
REG_PV1 * 4,
REG_PV2 * 4,
REG_PV3 * 4,
REG_PV4 * 4,
REG_PV5 * 4,
REG_PV6 * 4,
REG_PV7 * 4,
REG_PV8 * 4,
REG_PV9 * 4,
REG_PV10 * 4,
REG_PV11 * 4,
REG_ED * 4,
REG_PID * 4,
REG_ZP * 4,
REG_TBLX * 4,
REG_TBLSX * 4,
REG_TBLLO * 4,
REG_TBLHI * 4,
/* Total size */
REG_TBLHI * 4 + 4,
};
/*
* Number of bytes of registers.
*/
#define RTEMS_DEBUGGER_NUMREGBYTES \
microblaze_reg_offsets[ RTEMS_DEBUGGER_NUMREGS ]
/**
* Print the exception frame.
*/
#define EXC_FRAME_PRINT( _out, _prefix, _frame ) \
do { \
_out( \
_prefix " R0 = 0x%08" PRIx32 " R1 = 0x%08" PRIx32 \
" R2 = 0x%08" PRIx32 " R3 = 0x%08" PRIx32 "\n", \
0, \
_frame->r1, \
_frame->r2, \
_frame->r3 \
); \
_out( \
_prefix " R4 = 0x%08" PRIx32 " R5 = 0x%08" PRIx32 \
" R6 = 0x%08" PRIx32 " R7 = 0x%08" PRIx32 "\n", \
_frame->r4, \
_frame->r5, \
_frame->r6, \
_frame->r7 \
); \
_out( \
_prefix " R8 = 0x%08" PRIx32 " R9 = 0x%08" PRIx32 \
" R10 = 0x%08" PRIx32 " R11 = 0x%08" PRIx32 "\n", \
_frame->r8, \
_frame->r9, \
_frame->r10, \
_frame->r11 \
); \
_out( \
_prefix " R12 = 0x%08" PRIx32 " R13 = 0x%08" PRIx32 \
" R14 = 0x%08" PRIxPTR " R15 = 0x%08" PRIxPTR "\n", \
_frame->r12, \
_frame->r13, \
(uintptr_t) _frame->r14, \
(uintptr_t) _frame->r15 \
); \
_out( \
_prefix " R16 = 0x%08" PRIxPTR " R17 = 0x%08" PRIxPTR \
" R18 = 0x%08" PRIx32 " R19 = 0x%08" PRIx32 "\n", \
(uintptr_t) _frame->r16, \
(uintptr_t) _frame->r17, \
_frame->r18, \
_frame->r19 \
); \
_out( \
_prefix " R20 = 0x%08" PRIx32 " R21 = 0x%08" PRIx32 \
" R22 = 0x%08" PRIx32 " R23 = 0x%08" PRIx32 "\n", \
_frame->r20, \
_frame->r21, \
_frame->r22, \
_frame->r23 \
); \
_out( \
_prefix " R24 = 0x%08" PRIx32 " R25 = 0x%08" PRIx32 \
" R26 = 0x%08" PRIx32 " R27 = 0x%08" PRIx32 "\n", \
_frame->r24, \
_frame->r25, \
_frame->r26, \
_frame->r27 \
); \
_out( \
_prefix " R28 = 0x%08" PRIx32 " R29 = 0x%08" PRIx32 \
" R30 = 0x%08" PRIxPTR " R31 = 0x%08" PRIxPTR "\n", \
_frame->r28, \
_frame->r29, \
_frame->r30, \
_frame->r31 \
); \
_out( \
_prefix " EAR = %p ESR = 0x%08" PRIx32 "\n", \
_frame->ear, \
_frame->esr \
); \
_out( \
_prefix " PC = %p\n", \
_frame->r16 \
); \
_out( \
_prefix " MSR = 0x%08" PRIx32 " En:%c%c%c%c Prog:%c%c%c Mode:%c%c Arith:%c%c\n", \
_frame->msr, \
( _frame->msr & MICROBLAZE_MSR_IE ) != 0 ? 'I' : '-', \
( _frame->msr & MICROBLAZE_MSR_ICE ) != 0 ? 'C' : '-', \
( _frame->msr & MICROBLAZE_MSR_DCE ) != 0 ? 'D' : '-', \
( _frame->msr & MICROBLAZE_MSR_EE ) != 0 ? 'E' : '-', \
( _frame->msr & MICROBLAZE_MSR_BIP ) != 0 ? 'B' : '-', \
( _frame->msr & MICROBLAZE_MSR_FSL ) != 0 ? 'F' : '-', \
( _frame->msr & MICROBLAZE_MSR_EIP ) != 0 ? 'E' : '-', \
( _frame->msr & MICROBLAZE_MSR_UM ) != 0 ? 'U' : '-', \
( _frame->msr & MICROBLAZE_MSR_VM ) != 0 ? 'V' : '-', \
( _frame->msr & MICROBLAZE_MSR_C ) != 0 ? 'C' : '-', \
( _frame->msr & MICROBLAZE_MSR_DZO ) != 0 ? 'Z' : '-' \
); \
} while ( 0 )
/**
* The breakpoint instruction can be intercepted on hardware by an active JTAG
* connection. This instead uses an illegal opcode (0xdeadbeef) to trigger an
* exception as a mechanism to call into the debugger.
*/
static const uint8_t breakpoint[ 4 ] = { 0xef, 0xbe, 0xad, 0xde };
/**
* Target lock.
*/
RTEMS_INTERRUPT_LOCK_DEFINE( static, target_lock, "target_lock" )
/**
* Is a session active?
*/
static bool debug_session_active;
/*
* MicroBlaze debug hardware.
*/
static uint8_t hw_breakpoints;
static uint8_t hw_read_watchpoints;
static uint8_t hw_write_watchpoints;
/* Software breakpoints for single stepping */
typedef struct {
uint32_t *address;
} microblaze_soft_step;
microblaze_soft_step next_soft_break = { 0 };
microblaze_soft_step target_soft_break = { 0 };
static void set_soft_break(
microblaze_soft_step *soft_break,
uint32_t *next_ins
)
{
soft_break->address = next_ins;
rtems_debugger_target_swbreak_control(
true,
(uintptr_t) soft_break->address,
4
);
}
static void restore_soft_step( microblaze_soft_step *bp )
{
/*
* Only restore if the breakpoint is active and the instruction at the address
* is the breakpoint instruction.
*/
if ( bp->address != NULL ) {
rtems_debugger_target_swbreak_control( false, (uintptr_t) bp->address, 4 );
}
bp->address = NULL;
}
/*
* Target debugging support. Use this to debug the backend.
*/
#if TARGET_DEBUG
void rtems_debugger_printk_lock( rtems_interrupt_lock_context *lock_context );
void rtems_debugger_printk_unlock(
rtems_interrupt_lock_context *lock_context
);
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
static int microblaze_debug_probe( rtems_debugger_target *target )
{
uint32_t msr;
uint32_t pvr0;
uint32_t pvr3;
const char *version = NULL;
rtems_debugger_printf(
"rtems-db: MicroBlaze\n"
);
_CPU_MSR_GET( msr );
if ( ( msr & MICROBLAZE_MSR_PVR ) == 0 ) {
rtems_debugger_printf(
"rtems-db: Processor Version Registers not supported\n"
);
return 0;
}
_CPU_PVR0_GET( pvr0 );
switch ( MICROBLAZE_PVR0_VERSION_GET( pvr0 ) ) {
case 0x1:
version = "v5.00.a";
break;
case 0x2:
version = "v5.00.b";
break;
case 0x3:
version = "v5.00.c";
break;
case 0x4:
version = "v6.00.a";
break;
case 0x5:
version = "v7.00.a";
break;
case 0x6:
version = "v6.00.b";
break;
case 0x7:
version = "v7.00.b";
break;
case 0x8:
version = "v7.10.a";
break;
}
rtems_debugger_printf(
"rtems-db: Version: %s (%d)\n",
version,
MICROBLAZE_PVR0_VERSION_GET( pvr0 )
);
/* further PVR supported? */
if ( ( pvr0 >> 31 ) == 0 ) {
rtems_debugger_printf(
"rtems-db: Further Processor Version Registers not supported\n"
);
return 0;
}
_CPU_PVR3_GET( pvr3 );
hw_breakpoints = MICROBLAZE_PVR3_BP_GET( pvr3 );
hw_read_watchpoints = MICROBLAZE_PVR3_RWP_GET( pvr3 );
hw_write_watchpoints = MICROBLAZE_PVR3_WWP_GET( pvr3 );
rtems_debugger_printf(
"rtems-db: breakpoints:%" PRIu32
" read watchpoints:%" PRIu32 " write watchpoints:%" PRIu32 "\n",
hw_breakpoints,
hw_read_watchpoints,
hw_write_watchpoints
);
return 0;
}
int rtems_debugger_target_configure( rtems_debugger_target *target )
{
target->capabilities = ( RTEMS_DEBUGGER_TARGET_CAP_SWBREAK
| RTEMS_DEBUGGER_TARGET_CAP_PURE_SWBREAK );
target->reg_num = RTEMS_DEBUGGER_NUMREGS;
target->reg_offset = microblaze_reg_offsets;
target->breakpoint = &breakpoint[ 0 ];
target->breakpoint_size = sizeof( breakpoint );
return microblaze_debug_probe( target );
}
static void target_print_frame( CPU_Exception_frame *frame )
{
EXC_FRAME_PRINT( target_printk, "[} ", frame );
}
/* returns true if cascade is required */
static bool target_exception( CPU_Exception_frame *frame )
{
target_print_frame( frame );
switch ( rtems_debugger_target_exception( frame ) ) {
case rtems_debugger_target_exc_consumed:
default:
break;
case rtems_debugger_target_exc_step:
break;
case rtems_debugger_target_exc_cascade:
target_printk( "rtems-db: unhandled exception: cascading\n" );
/* Continue in fatal error handler chain */
return true;
}
target_printk(
"[} < resuming frame = %016" PRIxPTR "\n",
(uintptr_t) frame
);
target_print_frame( frame );
return false;
}
static void target_exception_handler( CPU_Exception_frame *ef )
{
if ( debug_session_active == false ) {
/* Falls into fatal error handler */
return;
}
/*
* Blindly roll back R17 in the exception frame due to exceptions resuming at
* the next instruction and not the instruction that caused the exception.
* TODO(kmoore): This does not apply in all cases for MicroBlaze cores that
* have a MMU and can generate MMU exceptions.
*/
ef->r17 = &ef->r17[ -1 ];
/*
* Remove single step software breakpoints since they will need to be
* recalculated for the current instruction.
*/
restore_soft_step( &next_soft_break );
restore_soft_step( &target_soft_break );
/* disable all software breakpoints */
rtems_debugger_target_swbreak_remove();
if ( target_exception( ef ) == true ) {
/* Roll R17 forward for an accurate frame in the fatal error handler */
ef->r17 = &ef->r17[ 1 ];
/* Falls into fatal error handler */
return;
}
/* Enable all software breakpoints including added single-step breakpoints */
rtems_debugger_target_swbreak_insert();
/* does not return */
_CPU_Exception_resume( ef );
}
static void rtems_debugger_set_int_reg(
rtems_debugger_thread *thread,
size_t reg,
const uint32_t value
)
{
const size_t offset = microblaze_reg_offsets[ reg ];
memcpy( &thread->registers[ offset ], &value, sizeof( uint32_t ) );
}
static const uint32_t rtems_debugger_get_int_reg(
rtems_debugger_thread *thread,
size_t reg
)
{
const size_t offset = microblaze_reg_offsets[ reg ];
uint32_t value;
memcpy( &value, &thread->registers[ offset ], sizeof( uint32_t ) );
return value;
}
static bool tid_is_excluded( const rtems_id tid )
{
rtems_debugger_threads *threads = rtems_debugger->threads;
rtems_id *excludes;
size_t i;
excludes = rtems_debugger_thread_excludes( threads );
for ( i = 0; i < threads->excludes.level; ++i ) {
if ( tid == excludes[ i ] ) {
return true;
}
}
/* DBSe is dynamically created and destroyed, so might not actually be in the excludes list */
char name[ RTEMS_DEBUGGER_THREAD_NAME_SIZE ];
rtems_object_get_name( tid, sizeof( name ), (char *) &name[ 0 ] );
if ( strcmp( "DBSe", name ) == 0 ) {
return true;
}
return false;
}
static void mb_thread_switch( Thread_Control *executing, Thread_Control *heir )
{
if ( tid_is_excluded( heir->Object.id ) == true ) {
rtems_debugger_target_swbreak_remove();
return;
}
/* Insert all software breaks */
rtems_debugger_target_swbreak_insert();
}
User_extensions_Control mb_ext = {
.Callouts = { .thread_switch = mb_thread_switch }
};
int rtems_debugger_target_enable( void )
{
debug_session_active = true;
rtems_interrupt_lock_context lock_context;
rtems_interrupt_lock_acquire( &target_lock, &lock_context );
_MicroBlaze_Debug_install_handler( target_exception_handler, NULL );
_MicroBlaze_Exception_install_handler( target_exception_handler, NULL );
_User_extensions_Add_set( &mb_ext );
rtems_interrupt_lock_release( &target_lock, &lock_context );
return RTEMS_SUCCESSFUL;
}
int rtems_debugger_target_disable( void )
{
debug_session_active = false;
rtems_interrupt_lock_context lock_context;
rtems_interrupt_lock_acquire( &target_lock, &lock_context );
_User_extensions_Remove_set( &mb_ext );
rtems_interrupt_lock_release( &target_lock, &lock_context );
return RTEMS_SUCCESSFUL;
}
int rtems_debugger_target_read_regs( rtems_debugger_thread *thread )
{
if (
rtems_debugger_thread_flag(
thread,
RTEMS_DEBUGGER_THREAD_FLAG_REG_VALID
) == 0
) {
static const uintptr_t good_address = (uintptr_t) &good_address;
int i;
memset( &thread->registers[ 0 ], 0, RTEMS_DEBUGGER_NUMREGBYTES );
/* set all integer register to a known valid address */
for ( i = 0; i < RTEMS_DEBUGGER_NUMREGS; ++i ) {
rtems_debugger_set_int_reg( thread, i, (uintptr_t) &good_address );
}
if ( thread->frame != NULL ) {
CPU_Exception_frame *frame = thread->frame;
*( (CPU_Exception_frame *) thread->registers ) = *frame;
rtems_debugger_set_int_reg( thread, REG_R0, 0 );
rtems_debugger_set_int_reg( thread, REG_R1, frame->r1 );
rtems_debugger_set_int_reg( thread, REG_R2, frame->r2 );
rtems_debugger_set_int_reg( thread, REG_R3, frame->r3 );
rtems_debugger_set_int_reg( thread, REG_R4, frame->r4 );
rtems_debugger_set_int_reg( thread, REG_R5, frame->r5 );
rtems_debugger_set_int_reg( thread, REG_R6, frame->r6 );
rtems_debugger_set_int_reg( thread, REG_R7, frame->r7 );
rtems_debugger_set_int_reg( thread, REG_R8, frame->r8 );
rtems_debugger_set_int_reg( thread, REG_R9, frame->r9 );
rtems_debugger_set_int_reg( thread, REG_R10, frame->r10 );
rtems_debugger_set_int_reg( thread, REG_R11, frame->r11 );
rtems_debugger_set_int_reg( thread, REG_R12, frame->r12 );
rtems_debugger_set_int_reg( thread, REG_R13, frame->r13 );
rtems_debugger_set_int_reg( thread, REG_R14, (uintptr_t) frame->r14 );
rtems_debugger_set_int_reg( thread, REG_R15, (uintptr_t) frame->r15 );
rtems_debugger_set_int_reg( thread, REG_R16, (uintptr_t) frame->r16 );
rtems_debugger_set_int_reg( thread, REG_R17, (uintptr_t) frame->r17 );
rtems_debugger_set_int_reg( thread, REG_R18, frame->r18 );
rtems_debugger_set_int_reg( thread, REG_R19, frame->r19 );
rtems_debugger_set_int_reg( thread, REG_R20, frame->r20 );
rtems_debugger_set_int_reg( thread, REG_R21, frame->r21 );
rtems_debugger_set_int_reg( thread, REG_R22, frame->r22 );
rtems_debugger_set_int_reg( thread, REG_R23, frame->r23 );
rtems_debugger_set_int_reg( thread, REG_R24, frame->r24 );
rtems_debugger_set_int_reg( thread, REG_R25, frame->r25 );
rtems_debugger_set_int_reg( thread, REG_R26, frame->r26 );
rtems_debugger_set_int_reg( thread, REG_R27, frame->r27 );
rtems_debugger_set_int_reg( thread, REG_R28, frame->r28 );
rtems_debugger_set_int_reg( thread, REG_R29, frame->r29 );
rtems_debugger_set_int_reg( thread, REG_R30, frame->r30 );
rtems_debugger_set_int_reg( thread, REG_R31, frame->r31 );
rtems_debugger_set_int_reg(
thread,
REG_PC,
rtems_debugger_target_frame_pc( frame )
);
rtems_debugger_set_int_reg( thread, REG_MS, frame->msr );
rtems_debugger_set_int_reg( thread, REG_EA, (uintptr_t) frame->ear );
rtems_debugger_set_int_reg( thread, REG_ES, frame->esr );
rtems_debugger_set_int_reg( thread, REG_BT, (uintptr_t) frame->btr );
/*
* Get the signal from the frame.
*/
thread->signal = rtems_debugger_target_exception_to_signal( frame );
} else {
rtems_debugger_set_int_reg(
thread,
REG_R1,
thread->tcb->Registers.r1
);
rtems_debugger_set_int_reg(
thread,
REG_R13,
thread->tcb->Registers.r13
);
rtems_debugger_set_int_reg(
thread,
REG_R14,
thread->tcb->Registers.r14
);
rtems_debugger_set_int_reg(
thread,
REG_R15,
thread->tcb->Registers.r15
);
rtems_debugger_set_int_reg(
thread,
REG_R16,
thread->tcb->Registers.r16
);
rtems_debugger_set_int_reg(
thread,
REG_R17,
thread->tcb->Registers.r17
);
rtems_debugger_set_int_reg(
thread,
REG_R18,
thread->tcb->Registers.r18
);
rtems_debugger_set_int_reg(
thread,
REG_R19,
thread->tcb->Registers.r19
);
rtems_debugger_set_int_reg(
thread,
REG_R20,
thread->tcb->Registers.r20
);
rtems_debugger_set_int_reg(
thread,
REG_R21,
thread->tcb->Registers.r21
);
rtems_debugger_set_int_reg(
thread,
REG_R22,
thread->tcb->Registers.r22
);
rtems_debugger_set_int_reg(
thread,
REG_R23,
thread->tcb->Registers.r23
);
rtems_debugger_set_int_reg(
thread,
REG_R24,
thread->tcb->Registers.r24
);
rtems_debugger_set_int_reg(
thread,
REG_R25,
thread->tcb->Registers.r25
);
rtems_debugger_set_int_reg(
thread,
REG_R26,
thread->tcb->Registers.r26
);
rtems_debugger_set_int_reg(
thread,
REG_R27,
thread->tcb->Registers.r27
);
rtems_debugger_set_int_reg(
thread,
REG_R28,
thread->tcb->Registers.r28
);
rtems_debugger_set_int_reg(
thread,
REG_R29,
thread->tcb->Registers.r29
);
rtems_debugger_set_int_reg(
thread,
REG_R30,
thread->tcb->Registers.r30
);
rtems_debugger_set_int_reg(
thread,
REG_R31,
thread->tcb->Registers.r31
);
rtems_debugger_set_int_reg(
thread,
REG_MS,
(intptr_t) thread->tcb->Registers.rmsr
);
/*
* Blocked threads have no signal.
*/
thread->signal = 0;
}
thread->flags |= RTEMS_DEBUGGER_THREAD_FLAG_REG_VALID;
thread->flags &= ~RTEMS_DEBUGGER_THREAD_FLAG_REG_DIRTY;
}
return 0;
}
int rtems_debugger_target_write_regs( rtems_debugger_thread *thread )
{
if (
rtems_debugger_thread_flag(
thread,
RTEMS_DEBUGGER_THREAD_FLAG_REG_DIRTY
) != 0
) {
/*
* Only write to debugger controlled exception threads. Do not touch the
* registers for threads blocked in the context switcher.
*/
if (
rtems_debugger_thread_flag(
thread,
RTEMS_DEBUGGER_THREAD_FLAG_EXCEPTION
) != 0
) {
CPU_Exception_frame *frame = thread->frame;
frame->r1 = rtems_debugger_get_int_reg( thread, REG_R1 );
frame->r2 = rtems_debugger_get_int_reg( thread, REG_R2 );
frame->r3 = rtems_debugger_get_int_reg( thread, REG_R3 );
frame->r4 = rtems_debugger_get_int_reg( thread, REG_R4 );
frame->r5 = rtems_debugger_get_int_reg( thread, REG_R5 );
frame->r6 = rtems_debugger_get_int_reg( thread, REG_R6 );
frame->r7 = rtems_debugger_get_int_reg( thread, REG_R7 );
frame->r8 = rtems_debugger_get_int_reg( thread, REG_R8 );
frame->r9 = rtems_debugger_get_int_reg( thread, REG_R9 );
frame->r10 = rtems_debugger_get_int_reg( thread, REG_R10 );
frame->r11 = rtems_debugger_get_int_reg( thread, REG_R11 );
frame->r12 = rtems_debugger_get_int_reg( thread, REG_R12 );
frame->r13 = rtems_debugger_get_int_reg( thread, REG_R13 );
frame->r14 = (uint32_t *) rtems_debugger_get_int_reg( thread, REG_R14 );
frame->r15 = (uint32_t *) rtems_debugger_get_int_reg( thread, REG_R15 );
frame->r16 = (uint32_t *) rtems_debugger_get_int_reg( thread, REG_R16 );
frame->r17 = (uint32_t *) rtems_debugger_get_int_reg( thread, REG_R17 );
frame->r18 = rtems_debugger_get_int_reg( thread, REG_R18 );
frame->r19 = rtems_debugger_get_int_reg( thread, REG_R19 );
frame->r20 = rtems_debugger_get_int_reg( thread, REG_R20 );
frame->r21 = rtems_debugger_get_int_reg( thread, REG_R21 );
frame->r22 = rtems_debugger_get_int_reg( thread, REG_R22 );
frame->r23 = rtems_debugger_get_int_reg( thread, REG_R23 );
frame->r24 = rtems_debugger_get_int_reg( thread, REG_R24 );
frame->r25 = rtems_debugger_get_int_reg( thread, REG_R25 );
frame->r26 = rtems_debugger_get_int_reg( thread, REG_R26 );
frame->r27 = rtems_debugger_get_int_reg( thread, REG_R27 );
frame->r28 = rtems_debugger_get_int_reg( thread, REG_R28 );
frame->r29 = rtems_debugger_get_int_reg( thread, REG_R29 );
frame->r30 = rtems_debugger_get_int_reg( thread, REG_R30 );
frame->r31 = rtems_debugger_get_int_reg( thread, REG_R31 );
frame->msr = rtems_debugger_get_int_reg( thread, REG_MS );
frame->ear = (uint32_t *) rtems_debugger_get_int_reg( thread, REG_EA );
frame->esr = rtems_debugger_get_int_reg( thread, REG_ES );
frame->btr = (uint32_t *) rtems_debugger_get_int_reg( thread, REG_BT );
}
thread->flags &= ~RTEMS_DEBUGGER_THREAD_FLAG_REG_DIRTY;
}
return 0;
}
uintptr_t rtems_debugger_target_reg_pc( rtems_debugger_thread *thread )
{
return thread->tcb->Registers.r15;
}
uintptr_t rtems_debugger_target_frame_pc( CPU_Exception_frame *frame )
{
return (uintptr_t) _MicroBlaze_Get_return_address( frame );
}
uintptr_t rtems_debugger_target_reg_sp( rtems_debugger_thread *thread )
{
int r;
r = rtems_debugger_target_read_regs( thread );
if ( r >= 0 ) {
return rtems_debugger_get_int_reg( thread, REG_R1 );
}
return 0;
}
uintptr_t rtems_debugger_target_tcb_sp( rtems_debugger_thread *thread )
{
return (uintptr_t) thread->tcb->Registers.r1;
}
#define IGROUP_MASK 0x3f
static uint32_t get_igroup( uint32_t ins )
{
return ( ins >> 26 ) & IGROUP_MASK;
}
#define REGISTER_MASK 0x1f
static uint32_t get_Ra( uint32_t ins )
{
return ( ins >> 16 ) & REGISTER_MASK;
}
static uint32_t get_Rb( uint32_t ins )
{
return ( ins >> 11 ) & REGISTER_MASK;
}
static uint32_t get_Rd( uint32_t ins )
{
return ( ins >> 21 ) & REGISTER_MASK;
}
#define IMM16_MASK 0xffff
static int32_t get_Imm16( uint32_t ins )
{
int16_t base = (int16_t) ins & IMM16_MASK;
return base;
}
#define IMM24_MASK 0xffffff
static int32_t get_Imm24( uint32_t ins )
{
int32_t base = ins & IMM24_MASK;
/* Sign-extend manually if necessary */
if ( ( base & 0x800000 ) != 0 ) {
base &= 0xFF000000;
}
return base;
}
static int64_t get_Imm( uint32_t ins )
{
if ( ( get_Rd( ins ) & 0x10 ) != 0 ) {
return get_Imm24( ins );
}
return get_Imm16( ins );
}
#define IMM_GROUP 0x2c
static bool is_imm( uint32_t ins )
{
return get_igroup( ins ) == IMM_GROUP;
}
#define RETURN_GROUP 0x2d
static bool is_return( uint32_t ins )
{
return get_igroup( ins ) == RETURN_GROUP;
}
/* Unconditional branch */
#define UBRANCH_GROUP 0x26
static bool is_ubranch( uint32_t ins )
{
return get_igroup( ins ) == UBRANCH_GROUP;
}
/* Comparison branch */
#define CBRANCH_GROUP 0x27
static bool is_cbranch( uint32_t ins )
{
return get_igroup( ins ) == CBRANCH_GROUP;
}
/* Unconditional Immediate branch */
#define UIBRANCH_GROUP 0x2e
static bool is_uibranch( uint32_t ins )
{
/* Ra == 0x2 is a memory barrier which continues at the next instruction */
return get_igroup( ins ) == UIBRANCH_GROUP && get_Ra( ins ) != 0x2;
}
/* Comparison Immediate branch */
#define CIBRANCH_GROUP 0x2f
static bool is_cibranch( uint32_t ins )
{
return get_igroup( ins ) == CIBRANCH_GROUP;
}
static bool branch_has_delay_slot( uint32_t ins )
{
if ( is_ubranch( ins ) == true && ( get_Ra( ins ) & 0x10 ) != 0 ) {
return true;
}
if ( is_cbranch( ins ) == true && ( get_Ra( ins ) & 0x10 ) != 0 ) {
return true;
}
if ( is_uibranch( ins ) == true && ( get_Ra( ins ) & 0x10 ) != 0 ) {
return true;
}
if ( is_cibranch( ins ) == true && ( get_Rd( ins ) & 0x10 ) != 0 ) {
return true;
}
return false;
}
/* All return instructions have a delay slot */
static bool branch_is_absolute( uint32_t ins )
{
return ( is_ubranch( ins ) == true || is_uibranch( ins ) == true ) &&
( get_Ra( ins ) & 0x8 ) != 0;
}
/* All returns are absolute */
static bool target_is_absolute( uint32_t ins )
{
return branch_is_absolute( ins ) == true || is_return( ins ) == true;
}
static bool is_branch( uint32_t ins )
{
if ( is_ubranch( ins ) == true ) {
return true;
}
if ( is_cbranch( ins ) == true ) {
return true;
}
if ( is_uibranch( ins ) == true ) {
return true;
}
if ( is_cibranch( ins ) == true ) {
return true;
}
return false;
}
#define BRK_RA 0xC
static bool is_brk( uint32_t ins )
{
return ( is_ubranch( ins ) == true || is_uibranch( ins ) == true ) &&
get_Ra( ins ) == BRK_RA;
}
static uint32_t get_register_value(
CPU_Exception_frame *frame,
uint32_t target_register
)
{
if ( target_register == 0 ) {
return 0;
}
/* Assumes all registers are contiguous and accounted for */
return ( &( frame->r1 ) )[ target_register - 1 ];
}
static void set_frame_pc( CPU_Exception_frame *frame, uint32_t *new_pc )
{
Per_CPU_Control *cpu_self = _Per_CPU_Get();
/* Break in progress */
if ( ( frame->msr & MICROBLAZE_MSR_BIP ) != 0 ) {
frame->r16 = (uint32_t *) new_pc;
return;
}
/* Exception in progress */
if ( ( frame->msr & MICROBLAZE_MSR_EIP ) != 0 ) {
frame->r17 = (uint32_t *) new_pc;
return;
}
/* Interrupt in progress must be determined by stack pointer location */
if (
frame->r1 >= (uint32_t) cpu_self->interrupt_stack_low
&& frame->r1 < (uint32_t) cpu_self->interrupt_stack_high
) {
frame->r14 = (uint32_t *) new_pc;
return;
}
/* Default to normal link register */
frame->r15 = (uint32_t *) new_pc;
}
static uint32_t bypass_swbreaks( uint32_t *addr )
{
rtems_debugger_target *target = rtems_debugger->target;
if ( target != NULL && target->swbreaks.block != NULL ) {
rtems_debugger_target_swbreak *swbreaks = target->swbreaks.block;
size_t i;
for ( i = 0; i < target->swbreaks.level; ++i ) {
if ( swbreaks[ i ].address == addr ) {
return *( (uint32_t *) &( swbreaks[ i ].contents[ 0 ] ) );
}
}
}
return *addr;
}
static int setup_single_step_breakpoints( CPU_Exception_frame *frame )
{
/*
* It may be necessary to evaluate the current instruction and next immediate
* instruction to determine the address of the "next" instruction and possible
* branch target instructions
*/
uint32_t *pc = (uint32_t *) rtems_debugger_target_frame_pc( frame );
int64_t imm = 0;
uint32_t *resume_pc;
/*
* Normalize PC address to the real instruction and not any IMM. This deals
* with a possible cascade of IMM.
*/
while ( is_imm( bypass_swbreaks( pc ) ) == true ) {
pc = &pc[ 1 ];
}
resume_pc = pc;
/*
* If execution ends up on a branch instruction that is preceeded by IMM, bad
* things can happen since it's not possible to know if the IMM was actually
* executed or something jumped to the branch directly. Exceptions treat IMM
* as part of the following instruction, so the RTEMS debugger will do so as
* well.
*/
uint32_t bypass_ins = bypass_swbreaks( &pc[ -1 ] );
if ( is_imm( bypass_ins ) == true ) {
imm = get_Imm( bypass_ins );
imm <<= 16;
resume_pc = &pc[ -1 ];
}
uint32_t ins = bypass_swbreaks( pc );
bool needs_target_break = false;
bool needs_next_break = true;
if ( is_brk( ins ) == true ) {
/*
* If the instruction being stepped is brk or brki, something bad has
* happened. If this instruction is stepped, the target of the branch (the
* debug vector) has a brki placed in it which results in an tight infinite
* recursive call. Under normal circumstances, this shouldn't happen.
*/
rtems_debugger_printf(
"rtems-db: Unable to set single-step breakpoints for brk/brki instructions\n"
);
return -1;
}
if ( is_branch( ins ) == true ) {
needs_target_break = true;
/*
* Unconditional branches (including returns) do not need to break on the
* next instruction.
*/
if (
is_ubranch( ins ) == true
|| is_uibranch( ins ) == true
|| is_return( ins ) == true
) {
needs_next_break = false;
}
}
if ( is_return( ins ) == true ) {
needs_target_break = true;
needs_next_break = false;
}
if ( needs_next_break == true ) {
uint32_t *next_ins = &pc[ 1 ];
if ( branch_has_delay_slot( ins ) == true ) {
next_ins = &pc[ 2 ];
}
if ( is_brk( *next_ins ) == false ) {
/* setup next instruction software break */
set_soft_break( &next_soft_break, next_ins );
}
}
if ( imm != 0 ) {
imm |= ( get_Imm16( ins ) & 0xFFFF );
} else {
imm = get_Imm16( ins );
}
if ( needs_target_break == true ) {
/* Calculate target address */
uintptr_t target_ins = 0;
if ( target_is_absolute( ins ) == false ) {
target_ins += (uintptr_t) pc;
}
if (
is_uibranch( ins ) == true || is_cibranch( ins ) == true ||
is_return( ins ) == true
) {
target_ins += imm;
}
if ( is_return( ins ) == true ) {
uint32_t target_register = get_Ra( ins );
target_ins += get_register_value( frame, target_register );
}
if ( is_ubranch( ins ) == true || is_cbranch( ins ) == true ) {
uint32_t target_register = get_Rb( ins );
target_ins += get_register_value( frame, target_register );
}
if ( is_brk( *( (uint32_t *) target_ins ) ) == false ) {
/* setup target instruction software break */
set_soft_break( &target_soft_break, (uint32_t *) target_ins );
}
}
/* Alter resume address */
set_frame_pc( frame, resume_pc );
return 0;
}
int rtems_debugger_target_thread_stepping( rtems_debugger_thread *thread )
{
CPU_Exception_frame *frame = thread->frame;
int ret = 0;
if (
rtems_debugger_thread_flag(
thread,
RTEMS_DEBUGGER_THREAD_FLAG_STEP_INSTR
) != 0
) {
/* Especially on first startup, frame isn't guaranteed to be non-NULL */
if ( frame == NULL ) {
return -1;
}
/* set software breakpoint(s) here */
ret = setup_single_step_breakpoints( frame );
}
return ret;
}
int rtems_debugger_target_exception_to_signal( CPU_Exception_frame *frame )
{
uint32_t BiP = frame->msr & MICROBLAZE_MSR_BIP;
uint32_t EiP = frame->msr & MICROBLAZE_MSR_EIP;
if ( BiP != 0 ) {
return RTEMS_DEBUGGER_SIGNAL_TRAP;
}
if ( EiP != 0 ) {
uint32_t EC = frame->esr & 0x1f;
switch ( EC ) {
case 0x0: /* FSL */
case 0x1: /* Unaligned data access */
case 0x3: /* instruction fetch */
case 0x4: /* data bus error */
case 0x10: /* MMU data storage */
case 0x11: /* MMU instruction storage */
case 0x12: /* MMU data TLB miss */
case 0x13: /* MMU instruction TLB miss */
return RTEMS_DEBUGGER_SIGNAL_SEGV;
case 0x7: /* priveleged */
return RTEMS_DEBUGGER_SIGNAL_TRAP;
case 0x5: /* div/0 */
case 0x6: /* FPU */
return RTEMS_DEBUGGER_SIGNAL_FPE;
case 0x2: /* illegal opcode (unknown instruction) */
/* Check for the illegal opcode being used in place of brki */
if ( rtems_debugger_target_swbreak_is_configured( (uintptr_t) frame->r17 ) ) {
return RTEMS_DEBUGGER_SIGNAL_TRAP;
}
default:
return RTEMS_DEBUGGER_SIGNAL_ILL;
}
}
/* Default to SIGILL */
return RTEMS_DEBUGGER_SIGNAL_ILL;
}
void rtems_debugger_target_exception_print( CPU_Exception_frame *frame )
{
EXC_FRAME_PRINT( rtems_debugger_printf, "", frame );
}
/*
* Debug hardware is inaccessible to the CPU, so hardware breaks and watchpoints
* are not supported.
*/
int rtems_debugger_target_hwbreak_insert( void )
{
return 0;
}
int rtems_debugger_target_hwbreak_remove( void )
{
return 0;
}
int rtems_debugger_target_hwbreak_control(
rtems_debugger_target_watchpoint wp,
bool insert,
uintptr_t addr,
DB_UINT kind
)
{
return 0;
}
int rtems_debugger_target_cache_sync( rtems_debugger_target_swbreak *swbreak )
{
/*
* Flush the data cache and invalidate the instruction cache.
*/
rtems_cache_flush_multiple_data_lines(
swbreak->address,
sizeof( breakpoint )
);
rtems_cache_instruction_sync_after_code_change(
swbreak->address,
sizeof( breakpoint )
);
return 0;
}