Add MicroBlaze support for libdebugger. This uses only software break instructions to provide self-hosted GDB debugging support for applications since internal control of debug hardware is not possible. --- .../libdebugger/rtems-debugger-microblaze.c | 1440 +++++++++++++++++ .../cpu/microblaze/include/rtems/score/cpu.h | 24 + spec/build/cpukit/libdebugger.yml | 2 + spec/build/cpukit/objdbgmicroblaze.yml | 15 + spec/build/cpukit/optlibdebugger.yml | 1 + 5 files changed, 1482 insertions(+) create mode 100644 cpukit/libdebugger/rtems-debugger-microblaze.c create mode 100644 spec/build/cpukit/objdbgmicroblaze.yml
diff --git a/cpukit/libdebugger/rtems-debugger-microblaze.c b/cpukit/libdebugger/rtems-debugger-microblaze.c new file mode 100644 index 0000000000..3e7f9a141d --- /dev/null +++ b/cpukit/libdebugger/rtems-debugger-microblaze.c @@ -0,0 +1,1440 @@ +/* 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: brki r16, 0x18 + */ +static const uint8_t breakpoint[ 4 ] = { 0x18, 0x00, 0x0c, 0xba }; + +/** + * 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; + bool loaded; +} microblaze_soft_step; + +microblaze_soft_step next_soft_break = { 0, false }; +microblaze_soft_step target_soft_break = { 0, false }; + +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 + ); + soft_break->loaded = true; +} + +static void restore_soft_step( microblaze_soft_step *bp, bool clear_address ) +{ + /* + * Only restore if the breakpoint is active and the instruction at the address + * is the breakpoint instruction. + */ + if ( bp->loaded == true && bp->address != NULL ) { + rtems_debugger_target_swbreak_control( false, (uintptr_t) bp->address, 4 ); + } + + bp->loaded = false; + + if ( clear_address == true ) { + 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; + } + + /* + * Remove single step software breakpoints since they will need to be + * recalculated for the current instruction. + */ + restore_soft_step( &next_soft_break, true ); + restore_soft_step( &target_soft_break, true ); + + /* disable all software breakpoints */ + rtems_debugger_target_swbreak_remove(); + + if ( target_exception( ef ) == true ) { + /* 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 void preint_hook( void ) +{ + Per_CPU_Control *cpu_self = _Per_CPU_Get(); + + /* All software breakpoints must be disabled on entry to interrupt context */ + if ( cpu_self->isr_nest_level != 0 ) { + return; + } + + /* Handle software breaks */ + rtems_debugger_target_swbreak_remove(); +} + +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 postint_hook( void ) +{ + Per_CPU_Control *cpu_self = _Per_CPU_Get(); + Thread_Control *thread = _Thread_Get_executing(); + const rtems_id tid = thread->Object.id; + + /* All software breakpoints must be enabled on exit from interrupt context */ + if ( cpu_self->isr_nest_level != 0 ) { + return; + } + + if ( tid_is_excluded( tid ) == true ) { + return; + } + + /* Enable single-step software breaks */ + if ( next_soft_break.loaded == false && next_soft_break.address != NULL ) { + set_soft_break( &next_soft_break, next_soft_break.address ); + } + + if ( target_soft_break.loaded == false && + target_soft_break.address != NULL ) { + set_soft_break( &target_soft_break, target_soft_break.address ); + } + + /* Insert all software breaks */ + rtems_debugger_target_swbreak_insert(); +} + +static void mb_thread_switch( Thread_Control *executing, Thread_Control *heir ) +{ + /* + * R13 is meant for TLS usage, but isn't used by RTEMS. It is currently being + * coopted as an interrupt context flag. R2 could also serve this purpose, but + * it would need to be added to Context_Control. + */ + if ( heir->Registers.r13 != 0 ) { + rtems_debugger_target_swbreak_remove(); + return; + } + + if ( tid_is_excluded( heir->Object.id ) == true ) { + rtems_debugger_target_swbreak_remove(); + return; + } + + /* Enable single-step software breaks */ + if ( next_soft_break.loaded == false && next_soft_break.address != NULL ) { + set_soft_break( &next_soft_break, next_soft_break.address ); + } + + if ( target_soft_break.loaded == false && + target_soft_break.address != NULL ) { + set_soft_break( &target_soft_break, target_soft_break.address ); + } + + /* 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 ); + _MicroBlaze_Pre_Interrupt_install_hook( preint_hook, NULL ); + _MicroBlaze_Post_Interrupt_install_hook( postint_hook, 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 ); + } + } + + 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) */ + 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; +} diff --git a/cpukit/score/cpu/microblaze/include/rtems/score/cpu.h b/cpukit/score/cpu/microblaze/include/rtems/score/cpu.h index 9c6b213e20..63f64ada2a 100644 --- a/cpukit/score/cpu/microblaze/include/rtems/score/cpu.h +++ b/cpukit/score/cpu/microblaze/include/rtems/score/cpu.h @@ -208,6 +208,30 @@ typedef struct { #define _CPU_MSR_SET( _msr_value ) \ { __asm__ volatile ("mts rmsr, %0" : "=&r" ((_msr_value)) : "0" ((_msr_value))); } +#define MICROBLAZE_PVR0_VERSION_GET( _pvr0_value ) \ + ( ( _pvr0_value >> 8 ) & 0xff ) + +#define _CPU_PVR0_GET( _pvr0_value ) \ + do { \ + ( _pvr0_value ) = 0; \ + __asm__ volatile ( "mfs %0, rpvr0" : "=&r" ( ( _pvr0_value ) ) ); \ + } while ( 0 ) + +#define MICROBLAZE_PVR3_BP_GET( _pvr3_value ) \ + ( ( _pvr3_value >> 25 ) & 0xf ) + +#define MICROBLAZE_PVR3_RWP_GET( _pvr3_value ) \ + ( ( _pvr3_value >> 19 ) & 0x7 ) + +#define MICROBLAZE_PVR3_WWP_GET( _pvr3_value ) \ + ( ( _pvr3_value >> 13 ) & 0x7 ) + +#define _CPU_PVR3_GET( _pvr3_value ) \ + do { \ + ( _pvr3_value ) = 0; \ + __asm__ volatile ( "mfs %0, rpvr3" : "=&r" ( ( _pvr3_value ) ) ); \ + } while ( 0 ) + #define _CPU_ISR_Disable( _isr_cookie ) \ { \ unsigned int _new_msr; \ diff --git a/spec/build/cpukit/libdebugger.yml b/spec/build/cpukit/libdebugger.yml index 9b50adbe77..263a4994f0 100644 --- a/spec/build/cpukit/libdebugger.yml +++ b/spec/build/cpukit/libdebugger.yml @@ -16,6 +16,8 @@ links: uid: objdbgarm - role: build-dependency uid: objdbgi386 +- role: build-dependency + uid: objdbgmicroblaze source: - cpukit/libdebugger/rtems-debugger-block.c - cpukit/libdebugger/rtems-debugger-bsp.c diff --git a/spec/build/cpukit/objdbgmicroblaze.yml b/spec/build/cpukit/objdbgmicroblaze.yml new file mode 100644 index 0000000000..59219ae507 --- /dev/null +++ b/spec/build/cpukit/objdbgmicroblaze.yml @@ -0,0 +1,15 @@ +SPDX-License-Identifier: CC-BY-SA-4.0 OR BSD-2-Clause +build-type: objects +cflags: [] +copyrights: +- Copyright (C) 2022 On-Line Applications Research (OAR) +cppflags: [] +cxxflags: [] +enabled-by: +- microblaze +includes: [] +install: [] +links: [] +source: +- cpukit/libdebugger/rtems-debugger-microblaze.c +type: build diff --git a/spec/build/cpukit/optlibdebugger.yml b/spec/build/cpukit/optlibdebugger.yml index a89a495528..c657db4ba4 100644 --- a/spec/build/cpukit/optlibdebugger.yml +++ b/spec/build/cpukit/optlibdebugger.yml @@ -12,6 +12,7 @@ enabled-by: - aarch64 - arm - i386 +- microblaze links: [] name: BUILD_LIBDEBUGGER type: build -- 2.30.2 _______________________________________________ devel mailing list devel@rtems.org http://lists.rtems.org/mailman/listinfo/devel