summaryrefslogblamecommitdiffstats
path: root/cpukit/libdebugger/rtems-debugger-target.c
blob: 2b55c93513d66ccfeda0cc61d31a966d1305d0a2 (plain) (tree)
1
2
3
  
                                                          
                       







































                                                                             

                                                                    
   





                             






                                                                          
                                            

                       
                                            
                      
                                              






































































                                                                                  


                                                         



           
                                          
 




















                                                                 


           

























                                                                 
   
                                                                                

                                                                 
                                          




                                                    

                                                         



                
                                    






                                                                    















                                                                     
























































                                                                               




                                                                       





















































                                                                               

















































                                                                                


                                                           



















                                                                          

                                                                           
                                                  
                                            
                                                 
                                                          






                                                            

                                                                          
       








                                                                    

                                                           


                                                                     















                                                                             
                                                               
                                











                                                             
                                                             
                              


                                            
                                                             

      























                                                                             
       
                            
 
                                                           



                                              
                                                                               
 

                                                         

 

                                                                     
 
                         
                       

























                                                                                  
     
   














                                                         





                                               
/*
 * 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;
}

bool
rtems_debugger_target_swbreak_is_configured( uintptr_t addr )
{
  size_t                         i;
  rtems_debugger_target_swbreak *swbreaks;
  rtems_debugger_target         *target = rtems_debugger->target;

  if ( target == NULL ) {
    return false;
  }

  swbreaks = target->swbreaks.block;

  if ( swbreaks == NULL ) {
    return false;
  }

  for ( i = 0; i < target->swbreaks.level; ++i ) {
    if ( (uintptr_t) swbreaks[ i ].address == addr ) {
      return true;
    }
  }

  return false;
}

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;
}

uintptr_t saved_break_address = 0;
rtems_id saved_tid = 0;

static rtems_debugger_target_exc_action
soft_step_and_continue(CPU_Exception_frame* frame)
{
  uintptr_t              break_address;
  rtems_debugger_target *target = rtems_debugger->target;
  Thread_Control        *thread = _Thread_Get_executing();
  const rtems_id         tid = thread->Object.id;
  rtems_debugger_thread  fake_debugger_thread;

  /*
   * If this was a hwbreak, cascade. If this is a swbreak replace the contents
   * of the instruction, step then return the swbreak's contents.
   */
  if ((target->capabilities & RTEMS_DEBUGGER_TARGET_CAP_SWBREAK) == 0) {
    target_printk("rtems-db: exception in an interrupt, cascading\n");
    rtems_debugger_unlock();
    return rtems_debugger_target_exc_cascade;
  }

  break_address = rtems_debugger_target_frame_pc( frame );
  if ( rtems_debugger_target_swbreak_is_configured( break_address ) == false ) {
    target_printk("rtems-db: exception in an interrupt, cascading\n");
    rtems_debugger_unlock();
    return rtems_debugger_target_exc_cascade;
  }

  /* Remove the current breakpoint */
  rtems_debugger_target_swbreak_control(
    false,
    break_address,
    target->breakpoint_size
  );

  /* Save off thread ID and break address for later usage */
  saved_tid = tid;
  saved_break_address = break_address;

  /* Populate the fake rtems_debugger_thread */
  fake_debugger_thread.flags |= RTEMS_DEBUGGER_THREAD_FLAG_STEP;
  fake_debugger_thread.frame = frame;
  target_printk("rtems-db: stepping to the next instruction\n");
  rtems_debugger_target_thread_stepping(&fake_debugger_thread);

  /* rtems_debugger_unlock() not called until the step is resolved */
  return rtems_debugger_target_exc_step;
}

rtems_debugger_target_exc_action
rtems_debugger_target_exception(CPU_Exception_frame* frame)
{
  Thread_Control* thread = _Thread_Get_executing();
  const rtems_id  tid = thread->Object.id;

  /* Resolve outstanding step+continue */
  if ( saved_break_address != 0 && tid == saved_tid ) {
    rtems_debugger_target_swbreak_control(
      true,
      saved_break_address,
      rtems_debugger->target->breakpoint_size
    );
    saved_break_address = saved_tid = 0;

    /* Release the debugger lock now that the step+continue is complete */
    target_printk("rtems-db: resuming after step\n");
    rtems_debugger_unlock();
    return rtems_debugger_target_exc_consumed;
  }

  rtems_debugger_lock();

  if (!rtems_interrupt_is_in_progress()) {
    rtems_debugger_threads*              threads = rtems_debugger->threads;
    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);

    /*
     * 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;
  }

  target_printk("[} tid:%08" PRIx32 ": exception in interrupt context\n", tid);

  /* soft_step_and_continue releases the debugger lock */
  return soft_step_and_continue( frame );
}

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;
}