Following code is part of software - written by me - that is used to analyze memory data.
Even if this just small part of software, I think this can help understand the way of unwinding stack by using DWARF information. Following sample code is to analyze DWARF information generated from C code.
For more details about unwinding stack. See this post.

const CDwf_Dbg*
CFrameS::_Get_dwf_dbg(DvAddr pc) const
{
  CDwf_Dbg*   dwf_dbg = NULL;

  for(int i=0; i<_dwfs->Size(); i++) {
    if( (*_dwfs)[i]->Is_in_fde(pc) ) { dwf_dbg = (*_dwfs)[i]; break; }
  }
  return dwf_dbg;
}

// "frm->regs" should have valid values before call this function.
int
CFrameS::_Set_frame_info(_CFrm_Info* fi) const
{
  if(REG_VAL_UNDEF==fi->regs[_tgt->Pci()].from) { return -1; }

  const CDwf_Dbg* dwf_dbg = _Get_dwf_dbg(REG_PC(fi->regs[_tgt->Pci()].val));

  if(dwf_dbg)
  {
    if( dwf_dbg->ArangeS() ) {
      ASSERT(REG_VAL_UNDEF != fi->regs[_tgt->Pci()].from);
      fi->fdie = dwf_dbg->ArangeS()->Subprogram_die(REG_PC(fi->regs[_tgt->Pci()].val), &fi->cudie);
    }
    else {
      fi->cudie = NULL;
      fi->fdie = NULL;
    }

    if( dwf_dbg->FdeS() ) {
      ASSERT(REG_VAL_UNDEF != fi->regs[_tgt->Pci()].from)
      fi->fde = dwf_dbg->FdeS()->Get_fde(REG_PC(fi->regs[_tgt->Pci()].val), NULL, NULL);
    }
    else { fi->fde = NULL; }

    if(fi->cudie)
    {
      CLine*  ln = CLine::Create(*fi->cudie);
      ln->Lineno(REG_PC(fi->regs[_tgt->Pci()].val), &fi->line, &fi->column);
      if(ln) { MDELETE(ln); }
    }
  }
  else
  {
    // There is no debugging information about this.
    // But we have address value of PC. So, it's not fail!
    fi->cudie = NULL;
    fi->fdie = NULL;
    fi->fde = NULL;
  }

  return 0;
}

int
CFrameS::_Create_previous_frame_info(_CFrm_Info* outfi, const _CFrm_Info* fi) const
{
  ASSERT(outfi && fi);

  bool          b_exact = true;
#ifdef __RT_DEBUG__
  DvAddr        fde_lopc, fdie_lopc;
#endif // __RT_DEBUG__

  if( (REG_VAL_UNDEF == fi->regs[_tgt->Pci()].from)
      || (REG_VAL_UNDEF == fi->regs[_tgt->Spi()].from) ) { goto no_info; }

  const CDwf_Dbg* dwf_dbg = _Get_dwf_dbg(REG_PC(fi->regs[_tgt->Pci()].val));

  if(dwf_dbg && dwf_dbg->FdeS() && fi->fde)
  {
    DvHalf      lri = dwf_dbg->FdeS()->Get_return_addr_register(fi->fde);

    // initial reg values of this frame
    if(0 > dwf_dbg->FdeS()->Get_all_regs(outfi->regs, fi->regs,
                                          fi->fde, REG_PC(fi->regs[_tgt->Pci()].val)))
    { goto fail; }

    if(REG_VAL_UNDEF == outfi->regs[lri].from) { goto fail; }

    outfi->regs[_tgt->Pci()] = outfi->regs[lri];
    COMPENSATE_PC_AHEAD_OF_LR(outfi->regs[_tgt->Pci()].val);

    if(fi->fdie && fi->cudie)
    {
#ifdef __RT_DEBUG__
      fde_lopc = dwf_dbg->FdeS()->Fde_lopc(fi->fde);
      if(0 > fi->fdie->LoPC(&fdie_lopc)) { goto fail; }

      if(fde_lopc != fdie_lopc) { ASSERT(FALSE); goto fail; }
#endif // __RT_DEBUG__

      if(fi->fdie->Has(DW_AT_frame_base))
      {
        DvUSign             val;
        int                 val_type;
        DvAddr              culo;
        bool                b_exact = false;
        CArr<CLoc_Desc*> locarr;
        if(0 > fi->fdie->Frame_base(&locarr) ) {goto fail; }
        ASSERT(REG_VAL_UNDEF != fi->regs[_tgt->Pci()].from);
        if(0 > fi->cudie->LoPC(&culo)) { goto fail; }

        if(0 > CLocS::Interpret(&val_type, &val, &b_exact,_tgt,
                                REG_PC(fi->regs[_tgt->Pci()].val) - culo,
                                INVALID_FRAME_BASE, fi->regs, locarr) ) { goto fail; }
        if(!b_exact){ goto fail; } // this should be exact value.!

        //ASSERT( (LOC_VAL_REG == val_type) || (LOC_VAL_UCONST == val_type) );
        outfi->regs[_tgt->Spi()].val = val;
        outfi->regs[_tgt->Spi()].from = REG_VAL_FROM_REG;
      } // if(fi->fdie->Has(DW_AT_frame_base))
      else if(fi->fdie->Has(DW_AT_abstract_origin) )
      { // this may be inlined function
        CDieI* ofdie = fi->fdie->Original_die(ORI_PURE);

#ifdef  __RT_DEBUG__
        ODPRINTF(("========*********=========\n"));
        ODPRINTF(("Tag: %s\n", dwarf_get_tagCN(fi->fdie->Tag()) ));
        fi->fdie->ODPrint_attributes();
        ODPRINTF(("=========*********============\n"));
        ODPRINTF(("Tag:%s\n", dwarf_get_tagCN(ofdie->Tag()) ));
        ofdie->ODPrint_attributes();
        //ASSERT(FALSE);
#endif // __RT_DEBUG__
        if(ofdie->Has(DW_AT_inline)) {
          outfi->regs[_tgt->Spi()] = fi->regs[_tgt->Spi()];
        }
        else { ASSERT(FALSE); }
        MDELETE(ofdie);
      }
      else { ASSERT(FALSE); }
    }
    else
    {
#ifdef COMPILER_ADS12
      DvSign off;
      if( 0 <= dwf_dbg->FdeS()->Get_cfa_offset_via_sp(&off, fi->fde, fi->regs[_tgt->Pci()].val) )
      {
        if(0 == off) { goto fail; } // If value of stack pointer is not changed, than we can assume that virtual unwinding fails.
        else {
          outfi->regs[_tgt->Spi()].val = fi->regs[_tgt->Spi()].val+off;
          outfi->regs[_tgt->Spi()].from = fi->regs[_tgt->Spi()].from;
        }
      }
      else
#endif // COMPILER_ADS12
      { goto no_info; }
    }
  }
  else  // if(dwf_dbg && dwf_dbg->FdeS() && fi->fde)
  {
    static const int  __UNW_INST_CNT  = 1000;

    bool b_success = false;
    // try to unwind stack with raw machine instruction!!
    if(_unw)
    {
      UnwRT*    unwr;
      MMNEW(unwr, , UnwRT, _tgt->Num_regs());
      for(int i=0; i<_tgt->Num_regs(); i++) {
        unwr[i].v = fi->regs[i].val;
        unwr[i].o = (REG_VAL_UNDEF==fi->regs[i].from)? UNWR_INVALID: UNWR_VALID;
      }

      if( UNW_OK == _unw->Unw(unwr, __UNW_INST_CNT, _stk_base))
      {
        for(int i=0; i<_tgt->Num_regs(); i++) {
          ASSERT( (UNWR_VALID==unwr[_tgt->Pci()].o)&&(UNWR_VALID==unwr[_tgt->Spi()].o) );
          outfi->regs[i].val = unwr[i].v;
          // We can assume that valid register value comes from stack.
          outfi->regs[i].from = (UNWR_VALID==unwr[i].o)? REG_VAL_FROM_ADDR: REG_VAL_UNDEF;
        }
        b_success = true;;
      }
      MMDELETE(unwr);
      COMPENSATE_PC_AHEAD_OF_LR(outfi->regs[_tgt->Pci()].val);
    } // if(_unw)
    if(!b_success) { goto fail; }
  } // else  // if(dwf_dbg && dwf_dbg->FdeS() && fi->fde)

  // Unexpected case. - infinite loop!! if this happened
  // If PC is on 'very strange position' this can be happened
  // previous frame is exactly same with current frame! This cannot happened in normal situation!
  if( (REG_VAL_UNDEF != outfi->regs[_tgt->Pci()].from)
      && (outfi->regs[_tgt->Spi()].val == fi->regs[_tgt->Spi()].val)
      && (outfi->regs[_tgt->Pci()].val == fi->regs[_tgt->Pci()].val)
      && (outfi->regs[_tgt->Lri()].val == fi->regs[_tgt->Lri()].val) )
  { goto fail; }

  if(0 > _Set_frame_info(outfi) ) { goto fail; }

  return (b_exact)? FRAME_OK: FRAME_NOT_EXACT;

fail:
  return FRAME_FAIL;

no_info:
  return FRAME_NO_DBG_INFO;

}

int
CFrameS::Construct_frame_stack(const RegT* regs)
{
  for(int i=0; i<_frame.Size(); i++) { MDELETE(_frame[i]); }
  _frame.Reset();

  int         frm_ret;
  bool        b_end = false;

  // flag to retry frame unwind
  bool        b_retry = true;
  _CFrm_Info* prev_fi = NULL;
  _CFrm_Info* next_fi = NULL;

  MNEW(prev_fi, , _CFrm_Info, (_num_regs));
  for(int i=0; i<_num_regs; i++) {
    prev_fi->regs[i].from = regs[i].from;
    prev_fi->regs[i].val = regs[i].val;
  }

  if(0 > _Set_frame_info(prev_fi) ) { goto fail; }
  _frame.Append(prev_fi);
  _ODPrint_frame_name(prev_fi);
  prev_fi = prev_fi->Clone();

  for(;;)
  {
    ASSERT(REG_VAL_UNDEF != prev_fi->regs[_tgt->Pci()].from);

    MNEW(next_fi, , _CFrm_Info, (_num_regs));
    frm_ret = _Create_previous_frame_info(next_fi, prev_fi);

    switch(frm_ret)
    {
      case FRAME_NOT_EXACT:
      case FRAME_OK:
        ASSERT(prev_fi && next_fi);
        _frame.Append(next_fi);
        MDELETE(prev_fi);
        prev_fi = next_fi->Clone();
        next_fi = NULL;
#ifdef __RT_DEBUG__
        if(prev_fi->fdie) {
          _ODPrint_frame_name(prev_fi);
        }
        else if(REG_VAL_UNDEF != prev_fi->regs[_tgt->Pci()].from) {
          ODPRINTF(("-- [0x%8x]\n", prev_fi->regs[_tgt->Pci()].val));
        }
#endif // __RT_DEBUG__

#ifdef ARCHITECTURE_ARM
        // In ARM, stack grows in negative direction.
        if( (REG_VAL_UNDEF != prev_fi->regs[_tgt->Spi()].from)
          && (prev_fi->regs[_tgt->Spi()].val >= _stk_base ) ) {
          frm_ret = FRAME_END;
          goto end_of_loop;
        }
#endif // ARCHITECTURE_ARM
      break;

      case FRAME_FAIL:
      case FRAME_NO_DBG_INFO:
        ASSERT(prev_fi);
        MDELETE(next_fi); 

        // Check whether previous frame is top of frame or not.
        if( (1 == _frame.Size()) && b_retry )
        {
          b_retry = false;
          ///////////////////////////////////
          // Let's try again with LR!! if previous frame is top of stack!!
          // (There are lots of cases that pc is corrupted in the top of stack!! (ex. prefetch abort))

          /////////////////////////////////////////////////////////////
          // CHECK IT! : "Try with LR" is good for approximation?!
          //  - Careful investigation is needed!
          /////////////////////////////////////////////////////////////
          DvAddr  lrpc = prev_fi->regs[_tgt->Lri()].val;
          COMPENSATE_PC_AHEAD_OF_LR(lrpc);

          if( (REG_VAL_UNDEF != prev_fi->regs[_tgt->Lri()].from)
              && (lrpc != prev_fi->regs[_tgt->Pci()].val) )
          {
            prev_fi->regs[_tgt->Pci()].val = lrpc;
            if(0 > _Set_frame_info(prev_fi) ) { goto fail; }  // set new frame info for retrying with LR.
          }
          else { goto end_of_loop; }
        }
        else { goto end_of_loop; }
      break;

      default:
        ASSERT(FALSE);
    } // switch
    continue;

end_of_loop:
    break;
  } // for(;;)

  if(prev_fi) { MDELETE(prev_fi); }
  if(next_fi) { MDELETE(next_fi); }

  _frame_status = frm_ret;
  return 0;

fail:
  if(prev_fi) { MDELETE(prev_fi); }
  if(next_fi) { MDELETE(next_fi); }
  return -1;
}

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

[ARM] Multi-core optimization test...  (0) 2011.05.03
[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

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] Unwinding Stack.  (0) 2009.09.15
[ARM] Long jump.  (0) 2007.06.05
[ARM] .init_array section  (0) 2007.03.18

The most important thing of OOP is "Making objects that developer don't need to look inside of it.". Reliable objects are foundation of OOP. Then let's think about this more.

* Object should respond at all cases.

That is, crashing inside object should not be happened! object should be in valid state at any case. If software is crashed inside object, developer should analyze internal code of object, and this is what we want to avoid.

* Reducing possibility of misuse interface should be considered when interface is design.

That is, usage of interfaces should be easy and intuitive. If not, developer need to investigate for object's internal parts to know correct usage. And, usually, interdependent interfaces leads to misuse. For example, "Interface B should be used after interface A is called", "Interface A should not be called after interface B is called" and so on. So, if possible, reduce dependency among interfaces of objects.

* Interface should be well-commented.

In the same context with above, developer don't need to look into the object that is well-commented. In my opinion, comments of interface should include at least following things.
  + behavior of interface.
  + prerequisite to use the interface.
  + explanation about each parameters.
  + description about return value.
And, adding usage example is recommended.

* For object to starts supporting minimal set of interface is better.

Adding interface is easy. But deleting interface is difficult because it may affect to number of other objects that already use the interface. And, stabilizing object having small number of interface, is easier. Starting from minimal set can also help developer escape from temptation of over-engineering

 
Next important point is relations among objects.
Software created by OOP consists of lots of objects and relations. To understand software's behavior, knowing relations and inter-operations among objects is significant.

* Software documentation.

Design concept, policy, block diagram, used patterns and so on is needed to be documented to help reader to understand overall shape of software.

 
There is also disadvantage of OOP.

* Performance drop.

In every object, there are lots of routines for checking and handling unexpected cases. And in general, software in OOP consists of lots of objects. So, inevitably, number of duplicated check and handling are unavoidable - all objects may check same thing. And sometimes this drops performance very much.

[[ blog 이사 과정에서 정확한 posting날짜가 분실됨. 년도와 분기 정도는 맞지 않을까? ]]

+++ 내가 Lead했던 첫번째 SW Project가 끝났다... 이 경험을 보존하기 위해 Review를 적어본다. +++

Project인원 : 나를 포함해서 3명
프로젝트 기간 : 약 4개월..
규모 : 한 10,000 라인 정도 되나??

내 관점에서 보기에, 다른 두명의 Programming능력 수준은 기대 이하였다.(아니면, 나 자신이 그들의 능력을 보지 못할 정도로 미숙했을 수도...)
Programming 경험이 많지 않은 사람과 같이 일하면서 크게 문제가 되었던 점들은 아래와 같았다.

+ 기본적으로... Code의 Stability가 떨어진다. (많은 Bug를 내포하고 있다. 일정이 짧아서 그럴수도 있지만, 일단 돌아가는 코드만 만들고 Program의 구조는 신경쓰지 않는다.)

+ SW design의 중요성을 전혀 인식하지 못한다. (이게 가장 큰 문제다!!!) SW design의 중요성을 모르고, 상호간 Interface간 정의의 중요성을 모르기 때문에, 여기에 큰 관심을 기울이지 않는다. 아무리 이게 중요하다고 이야기해도 전혀 반응하지 않는다.(소 귀에 경읽기??) 이번 프로젝트의 경우, Interface에 대한 communication을 초반에 상당히 강조했음에도, 결국 Integration단계에서, 그들이 전혀 Interface의 중요성을 인지하지 못했고 또, 관심조차 기울이지 않았음을 알게 되었다. 그들은 자신의 module과 연결될 다른 모듈의 동작 방식으로, 모듈 작성자와 communication하지 않고, 자기 나름대로 상상해서 API를 정했다. 결국 Integration단계에서, 문제가 발생했다..

+ Code re-factoring을 하지 않는다.... 내 개인적인 생각으로는, 그들은 자신이 짠 코드를 버릴 줄 모른다. 첫번째 짠 코드보다는 두번째 짠 코드의 질이 좋을 수 밖에 없다. 그리고 필요한 경우, 첫번째 짠 코드의 전체를 버릴 수도 있어야 한다. 그런데, 어설픈 구조로 짠 코드를 벗어나지 못해, bug를 양산할 가능성이 있는 불안한 코드에 땜질하는 형태로 project를 진행시키고 있고, 그게 Project를 빨리 그리고, 완성도 높게 끝낼수 있는 길이라고 생각한다. 이는 결국, SW design을 모르는 것과 어찌보면 그 맥을 가치하는데 - 보통 re-factoring은 design이 좋지 않은 경우 수행되어 진다. - 자신의 코드의 design이 좋지 않다는 사실을 인지하지 못하므로, re-factoring을 해야한다는 사실 자체를 모른다.

그럼 이런 경우 어떻게 이를 극복해야 하는가?
궁극적으로는 같이 일하는 사람의 능력을 향상시키는 것이 가장 중요하겠지만, 당장 일을하기 위해서는 결국 SW design과 Interface, API등을 모두 정의한 상태에서 상당한 수준까지 design을 구체화 한다음, 나머지 부분을 채워 넣으라는 방식으로 일을 할당했다.
(결국 이렇게라도 일을 시켜야, 생산성을 이끌어 낼 수 있었다.)

반성
+ 사람마다 능력과 경험이 다르고 일하는 방식이 다르다. "내가 아는 것은 다른 사람도 알 것이다."라는 전제 자체가 잘못 되었으므로, co-work에 어려움이 있었다. 사람들간의 차이를 알고, 상황에 맞게 생산성을 이끌어낼 수 있는 방법을 찾는 노력을 기울이지 않았다.

[[ blog 이사 과정에서 정확한 posting날짜가 분실됨. 년도와 분기 정도는 맞지 않을까? ]]
 

Strange!

'-p' option at gnumake is for making db. But, according to my experience, '$(shell xxxx)' seems not to described in the db generated by '-p' option.
I didn't check this in the gnumake document. This should be checked later!!!
[[ blog 이사 과정에서 정확한 posting날짜가 분실됨. 년도와 분기 정도는 맞지 않을까? ]]

 My personal definition of software development process. (There aren't any grounds for this except for my experience.)

1. Rough analysis
  - Analyzing requirements.
  - Analyzing features and functionality that are required.
  - Analyzing required schedule.

2. Detail analysis
  - Defining the way to measure project's completeness.(When can we say "We complete this project!")
  - Defining risks.
  - Estimating costs and time.
  - Deciding whether to go or not.

3. Planing
  - Making reasonable schedule.
  - Confirming resource plan.
  - Confirming the way to measure project's progress - including Milestone. (ex. if XXX is YYY, then project is oo% completed.)
  - Planning schedule, solution and alternatives etc about risk management.

4. Development
  - Determining development environment. (ex. language, CM tool, etc.)
  - Designing SW.
  - Making test plan and cases.
  - Implementation & debugging.

5. Maintenance

[[ blog 이사 과정에서 정확한 posting날짜가 분실됨. 년도와 분기 정도는 맞지 않을까? ]]

A study at the Software Engineering Laboratory - a cooperative project of NASA, Computer Sciences Corporation, and the University of Maryland - found that extensive computer use (edit, compile, link, test) is correlated with low productivity. Programmers who spent less time at their computers actually finished their projects faster. The implication is that heavy computer users didn't spend enough time on planning and design before coding and testing (Card, McGarry, and Page 1987; Card 1987).
[[ blog 이사 과정에서 정확한 posting날짜가 분실됨. 년도와 분기 정도는 맞지 않을까? ]]

I want to say only one sentence about software design.

"Software design starts from and ends with [ separating things that will be changed with high possibility, from others that will not. ]".

In macro point of view, software requirement is main subject to classify. In micro point of view, function parameter, algorithm, data structure and so one can be targets.

[[ blog 이사 과정에서 정확한 posting날짜가 분실됨. 년도와 분기 정도는 맞지 않을까? ]]

On Android, usually developer just set parameter of layout (LayoutParams.FILL_PARENT ... etc) for compatibility.

Then, when the exact size of each view is determined? The size of each View is decided when "onLayout()" is called.

The problem is, sometimes we need to know exact size of some Views and do something with this.
In this case, in my opinion, "onWindowFocusChanged()" is quite good place to do this. (in Activity).
At the moment when "onWindowFocusChanged()" is firstly called, all exact size of Views are decided. That is, we can get each View's exact size by calling "getWidth()/getHeigh()". - yes, I know. We need to handle quite many exceptional cases... :-(
But, until now, I cannot find any better place to do this.

Note!
Views added at "onLayout" of ViewGroup, are not drawn in Canvas before layout is re-updated!.

pseudo code)

    class View myView {
        onDraw(...) { // -- (*1)
            ...
        }
    }

    class LinearLayout layout {
        onLayout(...) {
            addView(myView)... // ---(*2)
        }
    }

At (*2), we can know exact size of 'layout'. So we can create myView's instance with layout's exact size, and add it to 'layout'.
In this case, even though (*1) is called, (process stops at (*1) when I set breakpoint.), it is not shown in the LCD screen.
Why? Because, newly added view - henceforth new View - is already excluded in the process of calculating View's exact size. So, layout of this new View is just empty rectangle! So, this cannot be drawn!.
So, all layout(by parameter) in View Tree should be decided, before starting framework's calculating-layout process (recursive calls of each View's 'onLayout()') to determine exact size of each View in View Tree.

[[ blog 이사 과정에서 정확한 posting날짜가 분실됨. 년도와 분기 정도는 맞지 않을까? ]]
 

======= 같은 실무자로서 일을 맡기기 (ex Lead Programmer) ========
일을 맡기기전에, "What"을 원하는지, 아니면, "What + How"를 원하는지 먼저 물어보고, 일을 시킬 필요가 있는 것 같다. 사람에 따라, "What"을 주고 일을 시킬 경우, 너무 막막해 하는 사람이 있고, "What + How"를 주고 일을 시킬 경우, 단순 노동을 시킨다고 짜증내는 사람이 있다.

따라서 일을 시키기 전에 미리, 이를 물어보고 선택의 여지를 주는 것이 좋아 보인다. 일에 자신이 있고, 자기 나름대로 무언가를 해 보고 싶은 사람 (사실 이런 사람을 원한다.) 에게는 "What"을 주고, 그렇지 않고, 아직까지는 일에 자신이 없고, 구체적인 일의 모습을 원하는 사람에게는 "What + How"를 주는 것이 좋다. 그러나 만약, 계속해서 "What + How"를 아주 상세한 level까지 줘야 하는 사람이라면, 같이 일하지 않는 편이 더 나아 보인다. 이런 사람은 관리하는데, 더 많은 시간이 소모되고, 또 더 많은 스트레스를 받게 된다.

또 한 가지 중요한 점은, 'What'을 주고 일을 시킨다고 할 지라도, 그 'What'의 내용이 너무 포괄적이거나, 추상적이여서는 안된다. 예를 들면, "좋은 코드를 만들어라". 같은건 말이 안된다. "Performance에 초점을 맞추어서 개발해라" 라는 식의 구체적인 모습을 주어야 한다. 이것이 중요한 이유는 일을 진행하는 중에, 서로 다른 가치가 충돌할 때, 어디에 더 비중을 두어야 할 지를 결정하는 기준이 바로, 일의 목적인 'What'의 내용이 되기 때문이다.

더불어 일의 진행상황이나 결과물을 reporting받아야하는데, 이때, deadline과는 조금 여유를 두고 받는 편이 좋다. 왜냐하면, reporting된 결과물이 원하는 형태가 아닐 확률이 높기 때문이다. 따라서, 2~3번 정도의 수정/검토를 염두해 두고 reporting 일정을 계획하는것이 좋다.

========= 실무를 모르는 관리자의 입장에서 일을 맡기기 ==========
실무에서 막 관리자로 역할이 바뀐 사람을 제외하고, 대부분의 경우, 관리자는 실무자에 비해 실무의 내용을 모른다. 또 몰라야 한다. (관리자가 이를 알려고 시간을 낭비하게 되면, 그 만큼 관리자가 해야할 일을 못하게 되기 때문이다.)

그럼, 실무를 모르는 관리자가 실무를 자신보다 더 잘 아는 실무자에게 어떤 식으로 일을 시켜야 하는가?

먼저 관리자는 자신이 실무자에 비해, 실무에 대한 구체적인 지식이 부족함을 인정해야 한다. (종종, 좋지 않은 관리자는, 자신이 실무자보다 실무경험이 더 많다는 점을 내세워 실무에 대한 구체적인 지식이 모자람에도 불구하고 이를 인정하지 않고 잘못된 방법론을 강요하는 경우가 많다.) 이를 전제로 관리자는 실무자에게, 구체적인 실무의 "How"에 대한 부분은 전적으로 맡기는 것이 좋다. 그럼 관리자가 할 일은? 관리자는 실무자에게 일의 방향성과, 요구되는 schedule등을 지시해야 한다. 즉 관리자는 "어떤 일을 언제까지 끝내야 한다."를 지시하는 것이지 "어떻게"를 지시하지는 못한다.(해서는 안된다.)

이때, 많은 경우 "언제까지"를 충족시키기 힘든데, 관리자는 이를 위해 실무자와 논의해서 최상의 결과를 얻을 수 있는 절충안을 찾아야 한다. 필요한 resource, requirement의 수정 등등.

다시 한 번 생각해 보면, 위의 과정은 관리자가 실무자를 신뢰함을 전제로 하고 있음을 알 수 있다. 즉 관리자는 실무자가 자신의 편의를 위해서 관리자를 속이지 않음을 전제한다는 말이다. 그런데 이는 너무 이상적인 가정이다. 많은 실무자가 자신의 편의를 위해, 일의 양을 부풀리거나 더 많은 resource를 요구할 것이다. 그렇다면 관리자는 어떻게 이를 방지할 것인가?

가장 좋은 방법은, 속이지 않는 실무자를 고용하는 것이다. ("인사는 만사") 자신에게 모든 인사권이 있다면 이런 사람을 뽑는데, 대부분의 시간을 할애해야 한다. 그렇게만 된다면, 이 문제는 더 이상 고민할 필요가 없다.

차선은, 실무자의 '도덕적 헤이'를 막기 위해, 해당 실무를 잘 아는 여러명의 실무자에게 같은 질문을 함으로서, 특정 실무자의 진심을 파악하도록 노력하고 이에 맞는 조치를 취해야 한다..

각설하고, 중요한 것은, 관리자가 실무자에게 "How"를 지시해서는 안된다는 뜻이다.!!!
손자병법에서도 전쟁에서 패하는 요인중에 하나를 "군주가 진격하지 말아야 할 때 진격을 명하거나, 후퇴하지 말아야 할 때 후퇴를 명하는 것"으로 뽑았다. 비단 전쟁 뿐이겠는가???

+ Recent posts