[ARM] Sample code for unwinding stack in Thumb mode.

Domain/ARM 2010.04.07 13:56

Following code is part of software - written by me - that is used to analyze memory data.
I think this can be good sample to help understand the way of "unwinding stack by pseudo execution". For more details see this post.

static void
_invalidate_variable_registers(UnwRT* r)
{
  for(int i=0; i<13; i++) { r[i].o = UNWR_INVALID; }
}

int
CRawunw::_Unw_thumb(UnwRT* r, unsigned int inst_cnt, unsigned int stack_base) const
{
#define __IS_IN_DUMPED_STACK(aDDR) ( (stack_base>=(aDDR)) && ((aDDR)<=osp) )

#ifdef UNW_ESCAPE_INFINITE_LOOP
  CArr<_CBInfo>   cbi;
#endif // UNW_ESCAPE_INFINITE_LOOP
  unsigned short instr;
  unsigned int   osp = r[13].v; // original sp.
  for(;;)
  {
    if(inst_cnt==0) {
      return UNW_INST_CNT_EXPIRED;
    }
    // PC is still on thumb.
    if(!(r[15].v&0x1)) { return UNW_FAIL; }
    //ASSERT(r[15].v&0x1);

    // Check PC & SP is OK.
    if((UNWR_INVALID==r[15].o)||(UNWR_INVALID==r[13].o)) {
		//ASSERT(FALSE);
		return UNW_FAIL;
    }

    if(stack_base <= r[13].v) {
      return UNW_END_OF_FRAME;
    }

    if(0 > _read2(_user, r[15].v&(~0x1), &instr)) {
      /*ASSERT(FALSE);*/ return UNW_FAIL;
    }

    // Undefined instruction
    if( ((instr & 0xf801) == 0xe801)
#if 0 // followings can be 'defined instrucion' in future!
        || ((instr & 0xff00) == 0xbf00)
        || ((instr & 0xff00) == 0xb100)
        || ((instr & 0xfa00) == 0xb200)
        || ((instr & 0xfc00) == 0xb800)
#endif
        ) {
      // undefined instruction. Unwinding SHOULD BE Stopped!
      return UNW_FAIL;
    }

    //Hi register operations/branch exchange
    else if((instr & 0xfc00) == 0x4400)
    {
      unsigned char    op  = (instr & 0x0300) >> 8;
      char h1  = (instr & 0x0080) ? TRUE: FALSE;
      char h2  = (instr & 0x0040) ? TRUE: FALSE;
      unsigned char    rs = (instr & 0x0038) >> 3;
      unsigned char    rd = (instr & 0x0007);

      // Adjust the register numbers
      if(h2) { rs += 8; }
      if(h1) { rd += 8; }

      if(op != 3 && !h1 && !h2) {
        ODPRINTF(("Invalid Instruction:0x%x", instr));
        return UNW_FAIL;
      }

      switch(op)
      {
        case 0: // ADD
          if((15==rd)&&(13==rd)) { ASSERT(FALSE); return UNW_FAIL; }
          else {
            r[rd].v += r[rs].v;
            r[rd].o = ((UNWR_VALID == r[rd].o) && (UNWR_VALID == r[rs].o) )? UNWR_VALID: UNWR_INVALID;
            ASSERT(UNWR_VALID == r[13].o);
          }
        break;

        case 1: // CMP
          ; // nothing to do!
        break;

        case 2: // MOV
          if((15==rd)&&(14==rs)&&(UNWR_VALID==r[rs].o)) {
            r[15]=r[14];
            return UNW_OK;
          }
          else {
            r[rd] = r[rs];
            ASSERT(UNWR_VALID == r[13].o);
          }
        break;

        case 3: // BX
          // if "14==rs" and it is valid than this is return!
          if(14==rs)
          {
            if(UNWR_VALID==r[rs].o) {
              ODPRINTF(("!!!Return PC=0x%x\n", r[rs].v));
              r[15]=r[rs];
              return UNW_OK;
            }
            else {
              ASSERT(FALSE);
              ODPRINTF(("!!!BX Fail\n"));
              return UNW_FAIL;
            }
          }
          // BX to somewhere. Just ignore it! - do nothing!!
          ODPRINTF(("!!!BX is ignored! [%s] 0x%x\n", (UNWR_VALID==r[rs].o)? "Valid": "Invalid", r[rs].v));
        break;
      }
    }
    //  ADD sp,#+imm
    //  ADD sp,#-imm
    else if((instr & 0xff00) == 0xb000)
    {
      unsigned short value = (instr & 0x7f) * 4;
      /* Check the negative bit */
      if(instr & 0x80) {
        r[13].v -= value;
        ODPRINTF(("*** SP(-) : 0x%x\n", r[13].v));
      }
      else {
        r[13].v += value;
        ODPRINTF(("*** SP(+) : 0x%x\n", r[13].v));
      }
    }
    //  PUSH {Rlist}
    //  PUSH {Rlist, LR}
    //  POP {Rlist}
    //  POP {Rlist, PC}
    else if((instr & 0xf600) == 0xb400)
    {
      char  L     = (instr & 0x0800) ? TRUE : FALSE;
      char  R     = (instr & 0x0100) ? TRUE : FALSE;
      unsigned char     r_list = (instr & 0x00ff);

      if(L) // stack grows in negative direction in ARM.
      {
        for(int i=0; i<8; i++)
        {
          if(r_list & (0x1 << i))
          {
            // value only in dumped stack is valid!
            if( __IS_IN_DUMPED_STACK(r[13].v) ) {
              // Read the word
              if(0 > _read4(_user, r[13].v, &(r[i].v)) ) { ASSERT(FALSE); return UNW_FAIL; }
              r[i].o = UNWR_VALID;
              ODPRINTF(("  r%d = 0x%08x\n", i, r[i].v));
            }
            else { r[i].o = UNWR_INVALID; }
            r[13].v += 4;
          }
        }

        // Check if the PC is to be popped
        if(R)
        {
          // Get the return address
          if(0 > _read4(_user, r[13].v, &(r[15].v)) ) { ASSERT(FALSE); return UNW_FAIL; }
          r[15].o = UNWR_VALID;
          ODPRINTF((" Return PC=%x\n", r[15].v));
          r[13].v += 4;
          return UNW_OK; // success.
        } // if(R)
        ODPRINTF(("*** SP(L) : 0x%x\n", r[13].v));
      } // if(L)
      else
      {
        // Stack grows. But we can't believe the values. So, just increase stack pointer!!
        // Check if the LR is to be pushed
        if(R) { r[13].v -= 4; }
        for(int i=7; i>=0; i--) {
          if(r_list & (0x1 << i)) { r[13].v -= 4; }
        }
        ODPRINTF(("*** SP(!L) : 0x%x\n", r[13].v));
      }
    }
    //  B label
    else if((instr&0xf800) == 0xe000)
    {
      short brv = (instr&0x07ff);
      if(brv & 0x400) { brv |= 0xf800; }

      // Branch distance is twice that specified in the instruction.
      brv *= 2;

#ifdef UNW_ESCAPE_INFINITE_LOOP
      int  idx = -1;
      for(int i=0; i<cbi.Size(); i++) {
        if(cbi[i].pc==r[15].v) { idx=i; break; }
      }
      if(idx>=0)
      { // found
        if(cbi[idx].b_jump) { cbi[idx].b_jump = false; }
        else {
          cbi[idx].b_jump = true;
          // branch
          r[15].v += brv; r[15].v += 2; // prefetch advance.
        }
      }
      else
      {
        // default is "Jump"
        _CBInfo  info(r[15].v, true);
        cbi.Append(info);
        // branch
        r[15].v += brv; r[15].v += 2; // prefetch advance.
      }
#else // UNW_ESCAPE_INFINITE_LOOP
      r[15].v += brv;
      // Need to advance by a word to account for pre-fetch.
      //  Advance by a half word here, allowing the normal address
      //  advance to account for the other half word.
      r[15].v += 2;
#endif // UNW_ESCAPE_INFINITE_LOOP
    }
#ifdef UNW_ESCAPE_INFINITE_LOOP
    // B<cond> label
    else if((instr&0xf000) == 0xd000)
    {
      short immed8  = instr&0x00ff;
      int  idx = -1;
      for(int i=0; i<cbi.Size(); i++) {
        if(cbi[i].pc==r[15].v) { idx=i; break; }
      }
      if(idx>=0)
      { // found
        if(cbi[idx].b_jump) { cbi[idx].b_jump = false; }
        else {
          cbi[idx].b_jump = true;
          // try to jump
          r[15].v += immed8*2;
          r[15].v += 2; // prefetch advance.
        }
      }
      else {
        // default is "Pass"
        _CBInfo  info(r[15].v, false);
        cbi.Append(info);
      }
    }
#endif // UNW_ESCAPE_INFINITE_LOOP
    // ADD <Rd>, PC/SP, #<immed_8>*4
    // It is not essential But, because sp and pc are always 'valid', result is valid.
    // So, I put this. BUT IT'S NOT ESSENTIAL!
    else if((instr&0xf000) == 0xa000)
    {
      unsigned char rd = (instr&0x0700)>>8;
      unsigned short imm8 = (instr&0x00ff)<<2;
      if(instr&0x0800) { r[rd].v = r[13].v + imm8; }    // SP
      else { r[rd].v = (r[15].v & 0xfffffffc) + imm8; } // PC
    }
    // LDR <Rd> [SP, #<immd_8>*4]
    // It is not essential. But, because original stack is 'valid', in most case, result is valid.
    // So, I put this. BUT IT'S NOT ESSENTIAL!
    else if((instr&0xf800) == 0x9800)
    {
      char  rd = instr&0x0700 >> 8;
      short imm8d = instr&0x00ff;
      unsigned int addr = r[13].v+imm8d*4;
      if(addr%4) { ASSERT(FALSE); return UNW_FAIL; }
      if(osp<=addr) { // valid stack.
        if(0 > _read4(_user, addr, &(r[rd].v))) { ODPRINTF(("Cannot read from stack\n")); ASSERT(FALSE); return UNW_FAIL; }
        r[rd].o = UNWR_VALID;  // assume that value from stack or constant!.
      }
      else {
        r[rd].o = UNWR_INVALID;
      }
    }
    else  // invalidate registers
    {
      _invalidate_variable_registers(r);
    }

    r[15].v += 2;
    inst_cnt--;
  }

#undef __IS_IN_DUMPED_STACK
}
신고

'Domain > ARM' 카테고리의 다른 글

[ARM] Multi-core optimization test...  (0) 2011.05.03
[ARM] Sample code for unwinding stack with DWARF2 information.  (0) 2010.04.07
[ARM] Sample code for unwinding stack in Thumb mode.  (0) 2010.04.07
[ARM] Unwinding Stack.  (0) 2009.09.15
[ARM] Long jump.  (0) 2007.06.05
[ARM] .init_array section  (0) 2007.03.18
tags : , ,
Trackback 0 : Comment 0

티스토리 툴바