// VisualBoyAdvance - Nintendo Gameboy/GameboyAdvance (TM) emulator.
// Copyright (C) 1999-2003 Forgotten
// Copyright (C) 2004 Forgotten and the VBA development team

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2, or(at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "GBA.h"
#include "Port.h"
#include "elf.h"

#define elfReadMemory(addr) \
  READ32LE((&map[(addr)>>24].address[(addr) & map[(addr)>>24].mask]))

#define DW_TAG_array_type             0x01
#define DW_TAG_enumeration_type       0x04
#define DW_TAG_formal_parameter       0x05
#define DW_TAG_label                  0x0a
#define DW_TAG_lexical_block          0x0b
#define DW_TAG_member                 0x0d
#define DW_TAG_pointer_type           0x0f
#define DW_TAG_reference_type         0x10
#define DW_TAG_compile_unit           0x11
#define DW_TAG_structure_type         0x13
#define DW_TAG_subroutine_type        0x15
#define DW_TAG_typedef                0x16
#define DW_TAG_union_type             0x17
#define DW_TAG_unspecified_parameters 0x18
#define DW_TAG_inheritance            0x1c
#define DW_TAG_inlined_subroutine     0x1d
#define DW_TAG_subrange_type          0x21
#define DW_TAG_base_type              0x24
#define DW_TAG_const_type             0x26
#define DW_TAG_enumerator             0x28
#define DW_TAG_subprogram             0x2e
#define DW_TAG_variable               0x34
#define DW_TAG_volatile_type          0x35

#define DW_AT_sibling              0x01
#define DW_AT_location             0x02
#define DW_AT_name                 0x03 
#define DW_AT_byte_size            0x0b
#define DW_AT_bit_offset           0x0c
#define DW_AT_bit_size             0x0d
#define DW_AT_stmt_list            0x10
#define DW_AT_low_pc               0x11
#define DW_AT_high_pc              0x12
#define DW_AT_language             0x13
#define DW_AT_compdir              0x1b
#define DW_AT_const_value          0x1c
#define DW_AT_containing_type      0x1d
#define DW_AT_inline               0x20
#define DW_AT_producer             0x25
#define DW_AT_prototyped           0x27
#define DW_AT_upper_bound          0x2f
#define DW_AT_abstract_origin      0x31
#define DW_AT_accessibility        0x32
#define DW_AT_artificial           0x34
#define DW_AT_data_member_location 0x38
#define DW_AT_decl_file            0x3a
#define DW_AT_decl_line            0x3b
#define DW_AT_declaration          0x3c
#define DW_AT_encoding             0x3e
#define DW_AT_external             0x3f
#define DW_AT_frame_base           0x40
#define DW_AT_macro_info           0x43
#define DW_AT_specification        0x47
#define DW_AT_type                 0x49
#define DW_AT_virtuality           0x4c
#define DW_AT_vtable_elem_location 0x4d
// DWARF 2.1/3.0 extensions
#define DW_AT_entry_pc             0x52
#define DW_AT_ranges               0x55
// ARM Compiler extensions
#define DW_AT_proc_body            0x2000
#define DW_AT_save_offset          0x2001
#define DW_AT_user_2002            0x2002
// MIPS extensions
#define DW_AT_MIPS_linkage_name    0x2007

#define DW_FORM_addr      0x01
#define DW_FORM_data2     0x05
#define DW_FORM_data4     0x06
#define DW_FORM_string    0x08
#define DW_FORM_block     0x09
#define DW_FORM_block1    0x0a
#define DW_FORM_data1     0x0b
#define DW_FORM_flag      0x0c
#define DW_FORM_sdata     0x0d
#define DW_FORM_strp      0x0e
#define DW_FORM_udata     0x0f
#define DW_FORM_ref_addr  0x10
#define DW_FORM_ref4      0x13
#define DW_FORM_ref_udata 0x15
#define DW_FORM_indirect  0x16

#define DW_OP_addr        0x03
#define DW_OP_plus_uconst 0x23
#define DW_OP_reg0        0x50
#define DW_OP_reg1        0x51
#define DW_OP_reg2        0x52
#define DW_OP_reg3        0x53
#define DW_OP_reg4        0x54
#define DW_OP_reg5        0x55
#define DW_OP_reg6        0x56
#define DW_OP_reg7        0x57
#define DW_OP_reg8        0x58
#define DW_OP_reg9        0x59
#define DW_OP_reg10       0x5a
#define DW_OP_reg11       0x5b
#define DW_OP_reg12       0x5c
#define DW_OP_reg13       0x5d
#define DW_OP_reg14       0x5e
#define DW_OP_reg15       0x5f
#define DW_OP_fbreg       0x91

#define DW_LNS_extended_op      0x00
#define DW_LNS_copy             0x01
#define DW_LNS_advance_pc       0x02
#define DW_LNS_advance_line     0x03
#define DW_LNS_set_file         0x04
#define DW_LNS_set_column       0x05
#define DW_LNS_negate_stmt      0x06
#define DW_LNS_set_basic_block  0x07
#define DW_LNS_const_add_pc     0x08
#define DW_LNS_fixed_advance_pc 0x09

#define DW_LNE_end_sequence 0x01
#define DW_LNE_set_address  0x02
#define DW_LNE_define_file  0x03

#define DW_CFA_advance_loc      0x01
#define DW_CFA_offset           0x02
#define DW_CFA_restore          0x03
#define DW_CFA_set_loc          0x01
#define DW_CFA_advance_loc1     0x02
#define DW_CFA_advance_loc2     0x03
#define DW_CFA_advance_loc4     0x04
#define DW_CFA_offset_extended  0x05
#define DW_CFA_restore_extended 0x06
#define DW_CFA_undefined        0x07
#define DW_CFA_same_value       0x08
#define DW_CFA_register         0x09
#define DW_CFA_remember_state   0x0a
#define DW_CFA_restore_state    0x0b
#define DW_CFA_def_cfa          0x0c
#define DW_CFA_def_cfa_register 0x0d
#define DW_CFA_def_cfa_offset   0x0e
#define DW_CFA_nop              0x00

#define CASE_TYPE_TAG \
    case DW_TAG_const_type:\
    case DW_TAG_volatile_type:\
    case DW_TAG_pointer_type:\
    case DW_TAG_base_type:\
    case DW_TAG_array_type:\
    case DW_TAG_structure_type:\
    case DW_TAG_union_type:\
    case DW_TAG_typedef:\
    case DW_TAG_subroutine_type:\
    case DW_TAG_enumeration_type:\
    case DW_TAG_enumerator:\
    case DW_TAG_reference_type

struct ELFcie {
  ELFcie *next;
  uint32 offset;
  uint8 *augmentation;
  uint32 codeAlign;
  int32 dataAlign;
  int returnAddress;
  uint8 *data;
  uint32 dataLen;
};

struct ELFfde {
  ELFcie *cie;
  uint32 address;
  uint32 end;
  uint8 *data;
  uint32 dataLen;
};

enum ELFRegMode {
  REG_NOT_SET,
  REG_OFFSET,
  REG_REGISTER
};


struct ELFFrameStateRegister {
  ELFRegMode mode;
  int reg;
  int32 offset;
};

struct ELFFrameStateRegisters {
  ELFFrameStateRegister regs[16];
  ELFFrameStateRegisters *previous;
};

enum ELFCfaMode {
  CFA_NOT_SET,
  CFA_REG_OFFSET
};

struct ELFFrameState {
  ELFFrameStateRegisters registers;

  ELFCfaMode cfaMode;
  int cfaRegister;
  int32 cfaOffset;

  uint32 pc;

  int dataAlign;
  int codeAlign;
  int returnAddress;
};

extern bool8 cpuIsMultiBoot;

Symbol *elfSymbols = NULL;
char *elfSymbolsStrTab = NULL;
int elfSymbolsCount = 0;

ELFSectionHeader **elfSectionHeaders = NULL;
char *elfSectionHeadersStringTable = NULL;
int elfSectionHeadersCount = 0;
uint8 *elfFileData = NULL;

CompileUnit *elfCompileUnits = NULL;
DebugInfo *elfDebugInfo = NULL;
char *elfDebugStrings = NULL;

ELFcie *elfCies = NULL;
ELFfde **elfFdes = NULL;
int elfFdeCount = 0;

CompileUnit *elfCurrentUnit = NULL;

uint32 elfRead4Bytes(uint8 *);
uint16 elfRead2Bytes(uint8 *);

CompileUnit *elfGetCompileUnit(uint32 addr)
{
  if(elfCompileUnits) {
    CompileUnit *unit = elfCompileUnits;
    while(unit) {
      if(unit->lowPC) {
        if(addr >= unit->lowPC && addr < unit->highPC)
          return unit;
      } else {
        ARanges *r = unit->ranges;
        if(r) {
          int count = r->count;
          for(int j = 0; j < count; j++) {
            if(addr >= r->ranges[j].lowPC && addr < r->ranges[j].highPC)
              return unit;
          }
        }
      }
      unit = unit->next;
    }
  }
  return NULL;
}

char *elfGetAddressSymbol(uint32 addr)
{
  static char buffer[256];

  CompileUnit *unit = elfGetCompileUnit(addr);
  // found unit, need to find function
  if(unit) {
    Function *func = unit->functions;
    while(func) {
      if(addr >= func->lowPC && addr < func->highPC) {
        int offset = addr - func->lowPC;
        char *name = func->name;
        if(!name)
          name = "";
        if(offset)
          sprintf(buffer, "%s+%d", name, offset);
        else
          strcpy(buffer, name);
        return buffer;
      }
      func = func->next;
    }
  }

  if(elfSymbolsCount) {
    for(int i = 0; i < elfSymbolsCount; i++) {
      Symbol *s = &elfSymbols[i];
      if((addr >= s->value)  && addr < (s->value+s->size)) {
        int offset = addr-s->value;
        char *name = s->name;
        if(name == NULL)
          name = "";
        if(offset)
          sprintf(buffer, "%s+%d", name, addr-s->value);
        else
          strcpy(buffer, name);
        return buffer;
      } else if(addr == s->value) {
        if(s->name)
          strcpy(buffer, s->name);
        else
          strcpy(buffer, "");
        return buffer;
      }
    }
  }
  
  return "";
}

bool8 elfFindLineInModule(uint32 *addr, char *name, int line)
{
  CompileUnit *unit = elfCompileUnits;

  while(unit) {
    if(unit->lineInfoTable) {
      int i;
      int count = unit->lineInfoTable->fileCount;
      char *found = NULL;
      for(i = 0; i < count; i++) {
        if(strcmp(name, unit->lineInfoTable->files[i]) == 0) {
          found = unit->lineInfoTable->files[i];
          break;
        }
      }
      // found a matching filename... try to find line now
      if(found) {
        LineInfoItem *table = unit->lineInfoTable->lines;
        count = unit->lineInfoTable->number;
        for(i = 0; i < count; i++) {
          if(table[i].file == found && table[i].line == line) {
            *addr = table[i].address;
            return true;
          }
        }
        // we can only find a single match
        return false;
      }
    }
    unit = unit->next;
  }
  return false;
}

int elfFindLine(CompileUnit *unit, Function * /* func */, uint32 addr, char **f)
{
  int currentLine = -1;
  if(unit->hasLineInfo) {
    int count = unit->lineInfoTable->number;
    LineInfoItem *table = unit->lineInfoTable->lines;
    int i;
    for(i = 0; i < count; i++) {
      if(addr <= table[i].address)
        break;
    }
    if(i == count)
      i--;
    *f = table[i].file;
    currentLine = table[i].line;
  }
  return currentLine;
}

bool8 elfFindLineInUnit(uint32 *addr, CompileUnit *unit, int line)
{
  if(unit->hasLineInfo) {
    int count = unit->lineInfoTable->number;
    LineInfoItem *table = unit->lineInfoTable->lines;
    int i;
    for(i = 0; i < count; i++) {
      if(line == table[i].line) {
        *addr = table[i].address;
        return true;
      }
    }
  }
  return false;
}

bool8 elfGetCurrentFunction(uint32 addr, Function **f, CompileUnit **u)
{
  CompileUnit *unit = elfGetCompileUnit(addr);
  // found unit, need to find function
  if(unit) {
    Function *func = unit->functions;
    while(func) {
      if(addr >= func->lowPC && addr < func->highPC) {
        *f = func;
        *u = unit;
        return true;
      }
      func = func->next;
    }
  }
  return false;
}

bool8 elfGetObject(char *name, Function *f, CompileUnit *u, Object **o)
{
  if(f && u) {
    Object *v = f->variables;

    while(v) {
      if(strcmp(name, v->name) == 0) {
        *o = v;
        return true;
      }
      v = v->next;
    }
    v = f->parameters;
    while(v) {
      if(strcmp(name, v->name) == 0) {
        *o = v;
        return true;
      }
      v = v->next;
    }
    v = u->variables;
    while(v) {
      if(strcmp(name, v->name) == 0) {
        *o = v;
        return true;
      }
      v = v->next;
    }
  }
  
  CompileUnit *c = elfCompileUnits;
  
  while(c) {
    if(c != u) {
      Object *v = c->variables;
      while(v) {
        if(strcmp(name, v->name) == 0) {
          *o = v;
          return true;
        }
        v = v->next;
      }
    }
    c = c->next;
  }
  
  return false;
}

char *elfGetSymbol(int i, uint32 *value, uint32 *size, int *type)
{
  if(i < elfSymbolsCount) {
    Symbol *s = &elfSymbols[i];
    *value = s->value;
    *size = s->size;
    *type = s->type;
    return s->name;
  }
  return NULL;
}

bool8 elfGetSymbolAddress(char *sym, uint32 *addr, uint32 *size, int *type)
{
  if(elfSymbolsCount) {
    for(int i = 0; i < elfSymbolsCount; i++) {
      Symbol *s = &elfSymbols[i];
      if(strcmp(sym, s->name) == 0) {
        *addr = s->value;
        *size = s->size;
        *type = s->type;
        return true;
      }
    }
  }
  return false;
}

ELFfde *elfGetFde(uint32 address)
{
  if(elfFdes) {
    int i;
    for(i = 0; i < elfFdeCount; i++) {
      if(address >= elfFdes[i]->address &&
         address < elfFdes[i]->end) {
        return elfFdes[i];
      }
    }
  }

  return NULL;
}

void elfExecuteCFAInstructions(ELFFrameState *state, uint8 *data, uint32 len,
                               uint32 pc)
{
  uint8 *end = data + len;
  int bytes;
  int reg;
  ELFFrameStateRegisters *fs;

  while(data < end && state->pc < pc) {
    uint8 op = *data++;

    switch(op >> 6) {
    case DW_CFA_advance_loc:
      state->pc += (op & 0x3f) * state->codeAlign;
      break;
    case DW_CFA_offset:
      reg = op & 0x3f;
      state->registers.regs[reg].mode = REG_OFFSET;
      state->registers.regs[reg].offset = state->dataAlign *
        (int32)elfReadLEB128(data, &bytes);
      data += bytes;
      break;
    case DW_CFA_restore:
      // we don't care much about the other possible settings,
      // so just setting to unset is enough for now
      state->registers.regs[op & 0x3f].mode = REG_NOT_SET;
      break;
    case 0:
      switch(op & 0x3f) {
      case DW_CFA_nop:
          break;
      case DW_CFA_advance_loc1:
        state->pc += state->codeAlign * (*data++);
        break;
      case DW_CFA_advance_loc2:
        state->pc += state->codeAlign * elfRead2Bytes(data);
        data += 2;
        break;
      case DW_CFA_advance_loc4:
        state->pc += state->codeAlign * elfRead4Bytes(data);
        data += 4;
        break;
      case DW_CFA_offset_extended:
        reg = elfReadLEB128(data, &bytes);
        data += bytes;
        state->registers.regs[reg].mode = REG_OFFSET;
        state->registers.regs[reg].offset = state->dataAlign *
          (int32)elfReadLEB128(data, &bytes);
        data += bytes;
        break;
      case DW_CFA_restore_extended:
      case DW_CFA_undefined:
      case DW_CFA_same_value:
        reg = elfReadLEB128(data, &bytes);
        data += bytes;
        state->registers.regs[reg].mode = REG_NOT_SET;
        break;
      case DW_CFA_register:
        reg = elfReadLEB128(data, &bytes);
        data += bytes;
        state->registers.regs[reg].mode = REG_REGISTER;
        state->registers.regs[reg].reg = elfReadLEB128(data, &bytes);
        data += bytes;
        break;
      case DW_CFA_remember_state:
        fs = (ELFFrameStateRegisters *)calloc(1,
                                              sizeof(ELFFrameStateRegisters));
        memcpy(fs, &state->registers, sizeof(ELFFrameStateRegisters));
        state->registers.previous = fs;
        break;
      case DW_CFA_restore_state:
        if(state->registers.previous == NULL) {
          printf("Error: previous frame state is NULL.\n");
          return;
        }
        fs = state->registers.previous;
        memcpy(&state->registers, fs, sizeof(ELFFrameStateRegisters));
        free(fs);
        break;
      case DW_CFA_def_cfa:
        state->cfaRegister = elfReadLEB128(data, &bytes);
        data += bytes;
        state->cfaOffset = (int32)elfReadLEB128(data, &bytes);
        data += bytes;
        state->cfaMode = CFA_REG_OFFSET;
        break;
      case DW_CFA_def_cfa_register:
        state->cfaRegister = elfReadLEB128(data, &bytes);
        data += bytes;
        state->cfaMode = CFA_REG_OFFSET;
        break;
      case DW_CFA_def_cfa_offset:
        state->cfaOffset = (int32)elfReadLEB128(data, &bytes);
        data += bytes;
        state->cfaMode = CFA_REG_OFFSET;
        break;
      default:
        printf("Unknown CFA opcode %08x\n", op);
        return;
      }
      break;
    default:
      printf("Unknown CFA opcode %08x\n", op);
      return;
    }
  }
}

ELFFrameState *elfGetFrameState(ELFfde *fde, uint32 address)
{
  ELFFrameState *state = (ELFFrameState *)calloc(1, sizeof(ELFFrameState));
  state->pc = fde->address;
  state->dataAlign = fde->cie->dataAlign;
  state->codeAlign = fde->cie->codeAlign;
  state->returnAddress = fde->cie->returnAddress;
  
  elfExecuteCFAInstructions(state,
                            fde->cie->data,
                            fde->cie->dataLen,
                            0xffffffff);
  elfExecuteCFAInstructions(state,
                            fde->data,
                            fde->dataLen,
                            address);

  return state;
}

void elfPrintCallChain(uint32 address)
{
  int count = 1;

  reg_pair regs[15];
  reg_pair newRegs[15];

  memcpy(&regs[0], &reg[0], sizeof(reg_pair) * 15);
  
  while(count < 20) {
    char *addr = elfGetAddressSymbol(address);
    if(*addr == 0)
      addr = "???";
    
    printf("%08x %s\n", address, addr);
    
    ELFfde *fde = elfGetFde(address);
    
    if(fde == NULL) {
      break;
    }
    
    ELFFrameState *state = elfGetFrameState(fde, address);
    
    if(!state) {
      break;
    }
    
    if(state->cfaMode == CFA_REG_OFFSET) {
      memcpy(&newRegs[0], &regs[0], sizeof(reg_pair) * 15);      
      uint32 addr = 0;
      for(int i = 0; i < 15; i++) {
        ELFFrameStateRegister *r = &state->registers.
          regs[i];
        
        switch(r->mode) {
        case REG_NOT_SET:
          newRegs[i].I = regs[i].I;
          break;
        case REG_OFFSET:
          newRegs[i].I = elfReadMemory(regs[state->cfaRegister].I +
                                       state->cfaOffset +
                                       r->offset);
          break;
        case REG_REGISTER:
          newRegs[i].I = regs[r->reg].I;
          break;
        default:
          printf("Unknown register mode: %d\n", r->mode);
          break;
        }
      }
      memcpy(regs, newRegs, sizeof(reg_pair)*15);
      addr = newRegs[14].I;
      addr &= 0xfffffffe;
      address = addr;
      count++;
    } else {
      printf("CFA not set\n");
      break;
    }
    if(state->registers.previous) {
      ELFFrameStateRegisters *prev = state->registers.previous;

      while(prev) {
        ELFFrameStateRegisters *p = prev->previous;
        free(prev);
        prev = p;
      }
    }
    free(state);
  }
}

uint32 elfDecodeLocation(Function *f, ELFBlock *o, LocationType *type, uint32 base)
{
  uint32 framebase = 0;
  if(f && f->frameBase) {
    ELFBlock *b = f->frameBase;
    switch(*b->data) {
    case DW_OP_reg0:
    case DW_OP_reg1:
    case DW_OP_reg2:
    case DW_OP_reg3:
    case DW_OP_reg4:
    case DW_OP_reg5:
    case DW_OP_reg6:
    case DW_OP_reg7:
    case DW_OP_reg8:
    case DW_OP_reg9:
    case DW_OP_reg10:
    case DW_OP_reg11:
    case DW_OP_reg12:
    case DW_OP_reg13:
    case DW_OP_reg14:
    case DW_OP_reg15:
      framebase = reg[*b->data-0x50].I;
      break;
    default:
      fprintf(stderr, "Unknown frameBase %02x\n", *b->data);
      break;
    }
  }
  
  ELFBlock *loc = o;
  uint32 location = 0;
  int bytes = 0;
  if(loc) {
    switch(*loc->data) {
    case DW_OP_addr:
      location = elfRead4Bytes(loc->data+1);
      *type = LOCATION_memory;
      break;
    case DW_OP_plus_uconst:
      location = base + elfReadLEB128(loc->data+1, &bytes);
      *type = LOCATION_memory;
      break;
    case DW_OP_reg0:
    case DW_OP_reg1:
    case DW_OP_reg2:
    case DW_OP_reg3:
    case DW_OP_reg4:
    case DW_OP_reg5:
    case DW_OP_reg6:
    case DW_OP_reg7:
    case DW_OP_reg8:
    case DW_OP_reg9:
    case DW_OP_reg10:
    case DW_OP_reg11:
    case DW_OP_reg12:
    case DW_OP_reg13:
    case DW_OP_reg14:
    case DW_OP_reg15:      
      location = *loc->data - 0x50;
      *type = LOCATION_register;
      break;
    case DW_OP_fbreg:
      {
        int bytes;
        int32 off = elfReadSignedLEB128(loc->data+1, &bytes);
        location = framebase + off;
        *type = LOCATION_memory;
      }
      break;
    default:
      fprintf(stderr, "Unknown location %02x\n", *loc->data);
      break;
    }
  }
  return location;
}

uint32 elfDecodeLocation(Function *f, ELFBlock *o, LocationType *type)
{
  return elfDecodeLocation(f, o, type, 0);
}

// reading function

uint32 elfRead4Bytes(uint8 *data)
{
  uint32 value = *data++;
  value |= (*data++ << 8);
  value |= (*data++ << 16);
  value |= (*data << 24);
  return value;
}

uint16 elfRead2Bytes(uint8 *data)
{
  uint16 value = *data++;
  value |= (*data << 8);
  return value;
}

char *elfReadString(uint8 *data, int *bytesRead)
{
  if(*data == 0) {
    *bytesRead = 1;
    return NULL;
  }
  *bytesRead = strlen((char *)data) + 1;
  return (char *)data;
}

int32 elfReadSignedLEB128(uint8 *data, int *bytesRead)
{
  int32 result = 0;
  int shift = 0;
  int count = 0;
  
  uint8 byte;
  do {
    byte = *data++;
    count++;
    result |= (byte & 0x7f) << shift;
    shift += 7;
  } while(byte & 0x80);
  if((shift < 32) && (byte & 0x40))
    result |= -(1 << shift);
  *bytesRead = count;
  return result;
}

uint32 elfReadLEB128(uint8 *data, int *bytesRead)
{
  uint32 result = 0;
  int shift = 0;
  int count = 0;
  uint8 byte;
  do {
    byte = *data++;
    count++;
    result |= (byte & 0x7f) << shift;
    shift += 7;
  } while(byte & 0x80);
  *bytesRead = count;
  return result;
}

uint8 *elfReadSection(uint8 *data, ELFSectionHeader *sh)
{
  return data + READ32LE(&sh->offset);
}

ELFSectionHeader *elfGetSectionByName(char *name)
{
  for(int i = 0; i < elfSectionHeadersCount; i++) {
    if(strcmp(name,
              &elfSectionHeadersStringTable[READ32LE(&elfSectionHeaders[i]->
                                                     name)]) == 0) {
      return elfSectionHeaders[i];
    }
  }
  return NULL;
}

ELFSectionHeader *elfGetSectionByNumber(int number)
{
  if(number < elfSectionHeadersCount) {
    return elfSectionHeaders[number];
  }
  return NULL;
}

CompileUnit *elfGetCompileUnitForData(uint8 *data)
{
  uint8 *end = elfCurrentUnit->top + 4 + elfCurrentUnit->length;

  if(data >= elfCurrentUnit->top && data < end)
    return elfCurrentUnit;

  CompileUnit *unit = elfCompileUnits;

  while(unit) {
    end = unit->top + 4 + unit->length;

    if(data >= unit->top && data < end)
      return unit;

    unit = unit->next;
  }

  printf("Error: cannot find reference to compile unit at offset %08x\n",
         (int)(data - elfDebugInfo->infodata));
  exit(-1);
}

uint8 *elfReadAttribute(uint8 *data, ELFAttr *attr)
{
  int bytes;
  int form = attr->form;
 start:
  switch(form) {
  case DW_FORM_addr:
    attr->value = elfRead4Bytes(data);
    data += 4;
    break;
  case DW_FORM_data2:
    attr->value = elfRead2Bytes(data);
    data += 2;
    break;
  case DW_FORM_data4:
    attr->value = elfRead4Bytes(data);
    data += 4;
    break;
  case DW_FORM_string:
    attr->string = (char *)data;
    data += strlen(attr->string)+1;
    break;
  case DW_FORM_strp:
    attr->string = elfDebugStrings + elfRead4Bytes(data);
    data += 4;
    break;
  case DW_FORM_block:
    attr->block = (ELFBlock *)malloc(sizeof(ELFBlock));
    attr->block->length = elfReadLEB128(data, &bytes);
    data += bytes;
    attr->block->data = data;
    data += attr->block->length;
    break;
  case DW_FORM_block1:
    attr->block = (ELFBlock *)malloc(sizeof(ELFBlock));
    attr->block->length = *data++;
    attr->block->data = data;
    data += attr->block->length;
    break;
  case DW_FORM_data1:
    attr->value = *data++;
    break;
  case DW_FORM_flag:
    attr->flag = (*data++) ? true : false;
    break;
  case DW_FORM_sdata:
    attr->value = elfReadSignedLEB128(data, &bytes);
    data += bytes;
    break;
  case DW_FORM_udata:
    attr->value = elfReadLEB128(data, &bytes);
    data += bytes;
    break;
  case DW_FORM_ref_addr:
    attr->value = (elfDebugInfo->infodata + elfRead4Bytes(data)) -
      elfGetCompileUnitForData(data)->top;
    data += 4;
    break;
  case DW_FORM_ref4:
    attr->value = elfRead4Bytes(data);
    data += 4;
    break;
  case DW_FORM_ref_udata:
    attr->value = (elfDebugInfo->infodata +
                   (elfGetCompileUnitForData(data)->top -
                    elfDebugInfo->infodata) +
                   elfReadLEB128(data, &bytes)) -
      elfCurrentUnit->top;
    data += bytes;
    break;    
  case DW_FORM_indirect:
    form = elfReadLEB128(data, &bytes);
    data += bytes;
    goto start;
  default:
    fprintf(stderr, "Unsupported FORM %02x\n", form);
    exit(-1);
  }
  return data;
}

ELFAbbrev *elfGetAbbrev(ELFAbbrev **table, uint32 number)
{
  int hash = number % 121;

  ELFAbbrev *abbrev = table[hash];

  while(abbrev) {
    if(abbrev->number == number)
      return abbrev;
    abbrev = abbrev->next;
  }
  return NULL;
}

ELFAbbrev **elfReadAbbrevs(uint8 *data, uint32 offset)
{
  data += offset;
  ELFAbbrev **abbrevs = (ELFAbbrev **)calloc(sizeof(ELFAbbrev *)*121,1);
  int bytes = 0;
  uint32 number = elfReadLEB128(data, &bytes);
  data += bytes;
  while(number) {
    ELFAbbrev *abbrev = (ELFAbbrev *)calloc(sizeof(ELFAbbrev),1);

    // read tag information
    abbrev->number = number;
    abbrev->tag = elfReadLEB128(data, &bytes);
    data += bytes;
    abbrev->hasChildren = *data++ ? true: false;

    // read attributes
    int name = elfReadLEB128(data, &bytes);
    data += bytes;
    int form = elfReadLEB128(data, &bytes);
    data += bytes;

    while(name) {
      if((abbrev->numAttrs % 4) == 0) {
        abbrev->attrs = (ELFAttr *)realloc(abbrev->attrs,
                                           (abbrev->numAttrs + 4) *
                                           sizeof(ELFAttr));
      }
      abbrev->attrs[abbrev->numAttrs].name = name;
      abbrev->attrs[abbrev->numAttrs++].form = form;

      name = elfReadLEB128(data, &bytes);
      data += bytes;
      form = elfReadLEB128(data, &bytes);
      data += bytes;
    }

    int hash = number % 121;
    abbrev->next = abbrevs[hash];
    abbrevs[hash] = abbrev;

    number = elfReadLEB128(data, &bytes);
    data += bytes;

    if(elfGetAbbrev(abbrevs, number) != NULL)
      break;
  }

  return abbrevs;
}

void elfParseCFA(uint8 *top)
{
  ELFSectionHeader *h = elfGetSectionByName(".debug_frame");

  if(h == NULL) {
    return;
  }

  uint8 *data = elfReadSection(top, h);

  uint8 *topOffset = data;

  uint8 *end = data + READ32LE(&h->size);

  ELFcie *cies = NULL;
  
  while(data < end) {
    uint32 offset = data - topOffset;
    uint32 len = elfRead4Bytes(data);
    data += 4;

    uint8 *dataEnd = data + len;
    
    uint32 id = elfRead4Bytes(data);
    data += 4;
    
    if(id == 0xffffffff) {
      // skip version
      *data++;

      ELFcie *cie = (ELFcie *)calloc(1, sizeof(ELFcie));

      cie->next = cies;
      cies = cie;

      cie->offset = offset;
      
      cie->augmentation = data;
      while(*data)
        data++;
      data++;
      
      if(*cie->augmentation) {
        fprintf(stderr, "Error: augmentation not supported\n");
        exit(-1);
      }
      
      int bytes;
      cie->codeAlign = elfReadLEB128(data, &bytes);
      data += bytes;
      
      cie->dataAlign = elfReadSignedLEB128(data, &bytes);
      data += bytes;
      
      cie->returnAddress = *data++;

      cie->data = data;
      cie->dataLen = dataEnd - data;
    } else {
      ELFfde *fde = (ELFfde *)calloc(1, sizeof(ELFfde));

      ELFcie *cie = cies;

      while(cie != NULL) {
        if(cie->offset == id)
          break;
        cie = cie->next;
      }

      if(!cie) {
        fprintf(stderr, "Cannot find CIE %08x\n", id);
        exit(-1);
      }

      fde->cie = cie;
      
      fde->address = elfRead4Bytes(data);
      data += 4;
      
      fde->end = fde->address + elfRead4Bytes(data);
      data += 4;

      fde->data = data;
      fde->dataLen = dataEnd - data;

      if((elfFdeCount %10) == 0) {
        elfFdes = (ELFfde **)realloc(elfFdes, (elfFdeCount+10) *
                                    sizeof(ELFfde *));
      }
      elfFdes[elfFdeCount++] = fde;
    }
    data = dataEnd;
  }

  elfCies = cies;
}

void elfAddLine(LineInfo *l, uint32 a, int file, int line, int *max)
{
  if(l->number == *max) {
    *max += 1000;
    l->lines = (LineInfoItem *)realloc(l->lines, *max*sizeof(LineInfoItem));
  }
  LineInfoItem *li = &l->lines[l->number];
  li->file = l->files[file-1];
  li->address = a;
  li->line = line;
  l->number++;
}

void elfParseLineInfo(CompileUnit *unit, uint8 *top)
{
  ELFSectionHeader *h = elfGetSectionByName(".debug_line");
  if(h == NULL) {
    fprintf(stderr, "No line information found\n");
    return;
  }
  LineInfo *l = unit->lineInfoTable = (LineInfo *)calloc(1, sizeof(LineInfo));
  l->number = 0;
  int max = 1000;
  l->lines = (LineInfoItem *)malloc(1000*sizeof(LineInfoItem));
  
  uint8 *data = elfReadSection(top, h);
  data += unit->lineInfo;
  uint32 totalLen = elfRead4Bytes(data);
  data += 4;
  uint8 *end = data + totalLen;
  //  uint16 version = elfRead2Bytes(data);
  data += 2;
  //  uint32 offset = elfRead4Bytes(data);
  data += 4;
  int minInstrSize = *data++;
  int defaultIsStmt = *data++;
  int lineBase = (int8)*data++;
  int lineRange = *data++;
  int opcodeBase = *data++;
  uint8 *stdOpLen = (uint8 *)malloc(opcodeBase * sizeof(uint8));
  stdOpLen[0] = 1;
  int i;
  for(i = 1; i < opcodeBase; i++)
    stdOpLen[i] = *data++;

  free(stdOpLen);// todo
  int bytes = 0;

  char *s;
  while((s = elfReadString(data, &bytes)) != NULL) {
    data += bytes;
    //    fprintf(stderr, "Directory is %s\n", s);
  }
  data += bytes;
  int count = 4;
  int index = 0;
  l->files = (char **)malloc(sizeof(char *)*count);
  
  while((s = elfReadString(data, &bytes)) != NULL) {
    l->files[index++] = s;
    
    data += bytes;
    // directory
    elfReadLEB128(data, &bytes);
    data += bytes;
    // time
    elfReadLEB128(data, &bytes);
    data += bytes;
    // size
    elfReadLEB128(data, &bytes);
    data += bytes;
    //    fprintf(stderr, "File is %s\n", s);
    if(index == count) {
      count += 4;
      l->files = (char **)realloc(l->files, sizeof(char *)*count);
    }
  }
  l->fileCount = index;
  data += bytes;

  while(data < end) {
    uint32 address = 0;
    int file = 1;
    int line = 1;
    int col = 0;
    int isStmt = defaultIsStmt;
    int basicBlock = 0;
    int endSeq = 0;

    while(!endSeq) {
      int op = *data++;
      switch(op) {
      case DW_LNS_extended_op:
        {
          data++;
          op = *data++;
          switch(op) {
          case DW_LNE_end_sequence:
            endSeq = 1;
            break;
          case DW_LNE_set_address:
            address = elfRead4Bytes(data);
            data += 4;
            break;
          default:
            fprintf(stderr, "Unknown extended LINE opcode %02x\n", op);
            exit(-1);
          }
        }
        break;
      case DW_LNS_copy:
        //      fprintf(stderr, "Address %08x line %d (%d)\n", address, line, file);
        elfAddLine(l, address, file, line, &max);
        basicBlock = 0;
        break;
      case DW_LNS_advance_pc:
        address += minInstrSize * elfReadLEB128(data, &bytes);
        data += bytes;
        break;
      case DW_LNS_advance_line:
        line += elfReadSignedLEB128(data, &bytes);
        data += bytes;
        break;
      case DW_LNS_set_file:
        file = elfReadLEB128(data, &bytes);
        data += bytes;
        break;
      case DW_LNS_set_column:
        col = elfReadLEB128(data, &bytes);
        data += bytes;
        break;
      case DW_LNS_negate_stmt:
        isStmt = !isStmt;
        break;
      case DW_LNS_set_basic_block:
        basicBlock = 1;
        break;
      case DW_LNS_const_add_pc:
        address += (minInstrSize *((255 - opcodeBase)/lineRange));
        break;
      case DW_LNS_fixed_advance_pc:
        address += elfRead2Bytes(data);
        data += 2;
        break;
      default:
        op = op - opcodeBase;
        address += (op / lineRange) * minInstrSize;
        line += lineBase + (op % lineRange);
        elfAddLine(l, address, file, line, &max);        
        //        fprintf(stderr, "Address %08x line %d (%d)\n", address, line,file);
        basicBlock = 1;
        break;
      }
    }
  }
  l->lines = (LineInfoItem *)realloc(l->lines, l->number*sizeof(LineInfoItem));
}

uint8 *elfSkipData(uint8 *data, ELFAbbrev *abbrev, ELFAbbrev **abbrevs)
{
  int i;
  int bytes;
  
  for(i = 0; i < abbrev->numAttrs; i++) {
    data = elfReadAttribute(data,  &abbrev->attrs[i]);
    if(abbrev->attrs[i].form == DW_FORM_block1)
      free(abbrev->attrs[i].block);
  }
    
  if(abbrev->hasChildren) {
    int nesting = 1;
    while(nesting) {
      uint32 abbrevNum = elfReadLEB128(data, &bytes);
      data += bytes;
      
      if(!abbrevNum) {
        nesting--;
        continue;
      }
      
      abbrev = elfGetAbbrev(abbrevs, abbrevNum);
      
      for(i = 0; i < abbrev->numAttrs; i++) {
        data = elfReadAttribute(data,  &abbrev->attrs[i]);
        if(abbrev->attrs[i].form == DW_FORM_block1)
          free(abbrev->attrs[i].block);
      }
      
      if(abbrev->hasChildren) {
        nesting++;
      }
    }
  }
  return data;
}

Type *elfParseType(CompileUnit *unit, uint32);
uint8 *elfParseObject(uint8 *data, ELFAbbrev *abbrev, CompileUnit *unit,
                   Object **object);
uint8 *elfParseFunction(uint8 *data, ELFAbbrev *abbrev, CompileUnit *unit,
                     Function **function);
void elfCleanUp(Function *);

void elfAddType(Type *type, CompileUnit *unit, uint32 offset)
{
  if(type->next == NULL) {
    if(unit->types != type && type->offset == 0) {
      type->offset = offset;
      type->next = unit->types;
      unit->types = type;
    }
  }  
}

void elfParseType(uint8 *data, uint32 offset, ELFAbbrev *abbrev, CompileUnit *unit,
                  Type **type)
{
  switch(abbrev->tag) {
  case DW_TAG_typedef:
    {
      uint32 typeref = 0;
      char *name = NULL;      
      for(int i = 0; i < abbrev->numAttrs; i++) {
        ELFAttr *attr = &abbrev->attrs[i];
        data = elfReadAttribute(data, attr);
        switch(attr->name) {
        case DW_AT_name:
          name = attr->string;
          break;
        case DW_AT_type:
          typeref = attr->value;
          break;
        case DW_AT_decl_file:
        case DW_AT_decl_line:
          break;          
        default:
          fprintf(stderr, "Unknown attribute for typedef %02x\n", attr->name);
          break;
        }
      }
      if(abbrev->hasChildren)
        fprintf(stderr, "Unexpected children for typedef\n");
      *type = elfParseType(unit, typeref);
      if(name)
        (*type)->name = name;
      return;
    }
    break;
  case DW_TAG_union_type:
  case DW_TAG_structure_type:
    {
      Type *t = (Type *)calloc(sizeof(Type), 1);
      if(abbrev->tag == DW_TAG_structure_type)
        t->type = TYPE_struct;
      else
        t->type = TYPE_union;
      
      Struct *s = (Struct *)calloc(sizeof(Struct), 1);
      t->structure = s;
      elfAddType(t, unit, offset);
      
      for(int i = 0; i < abbrev->numAttrs; i++) {
        ELFAttr *attr = &abbrev->attrs[i];
        data = elfReadAttribute(data, attr);
        switch(attr->name) {
        case DW_AT_name:
          t->name = attr->string;
          break;
        case DW_AT_byte_size:
          t->size = attr->value;
          break;
        case DW_AT_decl_file:
        case DW_AT_decl_line:
        case DW_AT_sibling:
        case DW_AT_containing_type: // todo?
        case DW_AT_declaration:
  case DW_AT_specification: // TODO:
          break;
        default:
          fprintf(stderr, "Unknown attribute for struct %02x\n", attr->name);
          break;
        }
      }
      if(abbrev->hasChildren) {
        int bytes;
        uint32 num = elfReadLEB128(data, &bytes);
        data += bytes;
        int index = 0;
        while(num) {
          ELFAbbrev *abbr = elfGetAbbrev(unit->abbrevs, num);
          
          switch(abbr->tag) {
          case DW_TAG_member:
            {
              if((index % 4) == 0)
                s->members = (Member *)realloc(s->members,
                                               sizeof(Member)*(index+4));
              Member *m = &s->members[index];
              m->location = NULL;
              m->bitOffset = 0;
              m->bitSize = 0;
              m->byteSize = 0;
              for(int i = 0; i < abbr->numAttrs; i++) {
                ELFAttr *attr = &abbr->attrs[i];
                data = elfReadAttribute(data, attr);
                switch(attr->name) {
                case DW_AT_name:
                  m->name = attr->string;
                  break;
                case DW_AT_type:
                  m->type = elfParseType(unit, attr->value);
                  break;
                case DW_AT_data_member_location:
                  m->location = attr->block;
                  break;
                case DW_AT_byte_size:
                  m->byteSize = attr->value;
                  break;
                case DW_AT_bit_offset:
                  m->bitOffset = attr->value;
                  break;
                case DW_AT_bit_size:
                  m->bitSize = attr->value;
                  break;
                case DW_AT_decl_file:
                case DW_AT_decl_line:
                case DW_AT_accessibility:
                case DW_AT_artificial: // todo?
                  break;
                default:
                  fprintf(stderr, "Unknown member attribute %02x\n",
                          attr->name);
                }
              }
              index++;
            }
            break;
          case DW_TAG_subprogram:
            {
              Function *fnc = NULL;
              data = elfParseFunction(data, abbr, unit, &fnc);
              if(fnc != NULL) {
                if(unit->lastFunction)
                  unit->lastFunction->next = fnc;
                else
                  unit->functions = fnc;
                unit->lastFunction = fnc;
              }
            }
            break;
          case DW_TAG_inheritance:
            // TODO: add support
            data = elfSkipData(data, abbr, unit->abbrevs);
            break;
          CASE_TYPE_TAG:
            // skip types... parsed only when used
            data = elfSkipData(data, abbr, unit->abbrevs);
            break;
          case DW_TAG_variable:
            data = elfSkipData(data, abbr, unit->abbrevs);
            break;
          default:
            fprintf(stderr, "Unknown struct tag %02x %s\n", abbr->tag, t->name);
            data = elfSkipData(data, abbr, unit->abbrevs);
            break;
          }
          num = elfReadLEB128(data, &bytes);
          data += bytes;
        }
        s->memberCount = index;
      }
      *type = t;
      return;
    }
    break;
  case DW_TAG_base_type:
    {
      Type *t = (Type *)calloc(sizeof(Type), 1);

      t->type = TYPE_base;
      elfAddType(t, unit, offset);
      for(int i = 0; i < abbrev->numAttrs; i++) {
        ELFAttr *attr = &abbrev->attrs[i];
        data = elfReadAttribute(data, attr);
        switch(attr->name) {
        case DW_AT_name:
          t->name = attr->string;
          break;
        case DW_AT_encoding:
          t->encoding = attr->value;
          break;
        case DW_AT_byte_size:
          t->size = attr->value;
          break;
        case DW_AT_bit_size:
          t->bitSize = attr->value;
          break;
        default:
          fprintf(stderr, "Unknown attribute for base type %02x\n",
                  attr->name);
          break;
        }
      }
      if(abbrev->hasChildren)
        fprintf(stderr, "Unexpected children for base type\n");
      *type = t;
      return;
    }
    break;
  case DW_TAG_pointer_type:
    {
      Type *t = (Type *)calloc(sizeof(Type), 1);

      t->type = TYPE_pointer;

      elfAddType(t, unit, offset);
      
      for(int i = 0; i < abbrev->numAttrs; i++) {
        ELFAttr *attr = &abbrev->attrs[i];
        data =elfReadAttribute(data, attr);
        switch(attr->name) {
        case DW_AT_type:
          t->pointer = elfParseType(unit, attr->value);
          break;
        case DW_AT_byte_size:
          t->size = attr->value;
          break;
        default:
          fprintf(stderr, "Unknown pointer type attribute %02x\n", attr->name);
          break;
        }
      }
      if(abbrev->hasChildren)
        fprintf(stderr, "Unexpected children for pointer type\n");
      *type = t;
      return;
    }
    break;
  case DW_TAG_reference_type:
    {
      Type *t = (Type *)calloc(sizeof(Type), 1);

      t->type = TYPE_reference;

      elfAddType(t, unit, offset);
      
      for(int i = 0; i < abbrev->numAttrs; i++) {
        ELFAttr *attr = &abbrev->attrs[i];
        data =elfReadAttribute(data, attr);
        switch(attr->name) {
        case DW_AT_type:
          t->pointer = elfParseType(unit, attr->value);
          break;
        case DW_AT_byte_size:
          t->size = attr->value;
          break;
        default:
          fprintf(stderr, "Unknown ref type attribute %02x\n", attr->name);
          break;
        }
      }
      if(abbrev->hasChildren)
        fprintf(stderr, "Unexpected children for ref type\n");
      *type = t;
      return;
    }
    break;
  case DW_TAG_volatile_type:
    {
      uint32 typeref = 0;
      
      for(int i = 0; i < abbrev->numAttrs; i++) {
        ELFAttr *attr = &abbrev->attrs[i];
        data = elfReadAttribute(data, attr);
        switch(attr->name) {
        case DW_AT_type:
          typeref = attr->value;
          break;
        default:
          fprintf(stderr, "Unknown volatile attribute for type %02x\n",
                  attr->name);
          break;
        }
      }
      if(abbrev->hasChildren)
        fprintf(stderr, "Unexpected children for volatile type\n");
      *type = elfParseType(unit, typeref);
      return;
    }
    break;
  case DW_TAG_const_type:
    {
      uint32 typeref = 0;
      
      for(int i = 0; i < abbrev->numAttrs; i++) {
        ELFAttr *attr = &abbrev->attrs[i];
        data = elfReadAttribute(data, attr);
        switch(attr->name) {
        case DW_AT_type:
          typeref = attr->value;
          break;
        default:
          fprintf(stderr, "Unknown const attribute for type %02x\n",
                  attr->name);
          break;
        }
      }
      if(abbrev->hasChildren)
        fprintf(stderr, "Unexpected children for const type\n");
      *type = elfParseType(unit, typeref);
      return;
    }
    break;
  case DW_TAG_enumeration_type:
    {
      Type *t = (Type *)calloc(sizeof(Type), 1);
      t->type = TYPE_enum;
      Enum *e = (Enum *)calloc(sizeof(Enum), 1);
      t->enumeration = e;
      elfAddType(t, unit, offset);
      int count = 0;
      for(int i = 0; i < abbrev->numAttrs; i++) {
        ELFAttr *attr = &abbrev->attrs[i];
        data = elfReadAttribute(data, attr);
        switch(attr->name) {
        case DW_AT_name:
          t->name = attr->string;
          break;
        case DW_AT_byte_size:
          t->size = attr->value;
          break;
        case DW_AT_sibling:
        case DW_AT_decl_file:
        case DW_AT_decl_line:
          break;
        default:
          fprintf(stderr, "Unknown enum attribute %02x\n", attr->name);
        }
      }
      if(abbrev->hasChildren) {
        int bytes;
        uint32 num = elfReadLEB128(data, &bytes);
        data += bytes;
        while(num) {
          ELFAbbrev *abbr = elfGetAbbrev(unit->abbrevs, num);
          
          switch(abbr->tag) {
          case DW_TAG_enumerator:
            {
              count++;
              e->members = (EnumMember *)realloc(e->members,
                                                 count*sizeof(EnumMember));
              EnumMember *m = &e->members[count-1];
              for(int i = 0; i < abbr->numAttrs; i++) {
                ELFAttr *attr = &abbr->attrs[i];
                data = elfReadAttribute(data, attr);
                switch(attr->name) {
                case DW_AT_name:
                  m->name = attr->string;
                  break;
                case DW_AT_const_value:
                  m->value = attr->value;
                  break;
                default:
                  fprintf(stderr, "Unknown sub param attribute %02x\n",
                          attr->name);
                }
              }
            }
            break;
          default:
            fprintf(stderr, "Unknown enum tag %02x\n", abbr->tag);
            data = elfSkipData(data, abbr, unit->abbrevs);          
            break;
          }
          num = elfReadLEB128(data, &bytes);
          data += bytes;
        }
      }
      e->count = count;
      *type = t;
      return;
    }
    break;
  case DW_TAG_subroutine_type:
    {
      Type *t = (Type *)calloc(sizeof(Type), 1);
      t->type = TYPE_function;
      FunctionType *f = (FunctionType *)calloc(sizeof(FunctionType), 1);
      t->function = f;      
      elfAddType(t, unit, offset);
      for(int i = 0; i < abbrev->numAttrs; i++) {
        ELFAttr *attr = &abbrev->attrs[i];
        data = elfReadAttribute(data, attr);
        switch(attr->name) {
        case DW_AT_prototyped:
        case DW_AT_sibling:          
          break;
        case DW_AT_type:
          f->returnType = elfParseType(unit, attr->value);
          break;
        default:
          fprintf(stderr, "Unknown subroutine attribute %02x\n", attr->name);
        }
      }
      if(abbrev->hasChildren) {
        int bytes;
        uint32 num = elfReadLEB128(data, &bytes);
        data += bytes;
        Object *lastVar = NULL;        
        while(num) {
          ELFAbbrev *abbr = elfGetAbbrev(unit->abbrevs, num);
          
          switch(abbr->tag) {
          case DW_TAG_formal_parameter:
            {
              Object *o;
              data = elfParseObject(data, abbr, unit, &o);
              if(f->args)
                lastVar->next = o;
              else
                f->args = o;
              lastVar = o;
            }
            break;
          case DW_TAG_unspecified_parameters:
            // no use in the debugger yet
            data = elfSkipData(data, abbr, unit->abbrevs);
            break;
          CASE_TYPE_TAG:
            // skip types... parsed only when used
            data = elfSkipData(data, abbr, unit->abbrevs);
            break;
          default:
            fprintf(stderr, "Unknown subroutine tag %02x\n", abbr->tag);
            data = elfSkipData(data, abbr, unit->abbrevs);          
            break;
          }
          num = elfReadLEB128(data, &bytes);
          data += bytes;
        }
      }
      *type = t;
      return;
    }
    break;
  case DW_TAG_array_type:
    {
      uint32 typeref = 0;
      int i;
      Array *array = (Array *)calloc(sizeof(Array), 1);
      Type *t = (Type *)calloc(sizeof(Type), 1);
      t->type = TYPE_array;
      elfAddType(t, unit, offset);
      
      for(i = 0; i < abbrev->numAttrs; i++) {
        ELFAttr *attr = &abbrev->attrs[i];
        data = elfReadAttribute(data, attr);
        switch(attr->name) {
        case DW_AT_sibling:
          break;
        case DW_AT_type:
          typeref = attr->value;
          array->type = elfParseType(unit, typeref);
          break;
        default:
          fprintf(stderr, "Unknown array attribute %02x\n", attr->name);
        }
      }
      if(abbrev->hasChildren) {
        int bytes;
        uint32 num = elfReadLEB128(data, &bytes);
        data += bytes;
        int index = 0;
        int maxBounds = 0;
        while(num) {
          ELFAbbrev *abbr = elfGetAbbrev(unit->abbrevs, num);
          
          switch(abbr->tag) {
          case DW_TAG_subrange_type:
            {
              if(maxBounds == index) {
                maxBounds += 4;
                array->bounds = (int *)realloc(array->bounds,
                                               sizeof(int)*maxBounds);
              }
              for(int i = 0; i < abbr->numAttrs; i++) {
                ELFAttr *attr = &abbr->attrs[i];
                data = elfReadAttribute(data, attr);
                switch(attr->name) {
                case DW_AT_upper_bound:
                  array->bounds[index] = attr->value+1;
                  break;
                case DW_AT_type: // ignore
                  break;
                default:
                  fprintf(stderr, "Unknown subrange attribute %02x\n",
                          attr->name);
                }
              }
              index++;
            }
            break;
          default:
            fprintf(stderr, "Unknown array tag %02x\n", abbr->tag);
            data = elfSkipData(data, abbr, unit->abbrevs);          
            break;
          }
          num = elfReadLEB128(data, &bytes);
          data += bytes;
        }
        array->maxBounds = index;
      }
      t->size = array->type->size;
      for(i = 0; i < array->maxBounds; i++)
        t->size *= array->bounds[i];
      t->array = array;
      *type = t;
      return;
    }
    break;
  default:
    fprintf(stderr, "Unknown type TAG %02x\n", abbrev->tag);
    exit(-1);
  }
}

Type *elfParseType(CompileUnit *unit, uint32 offset)
{
  Type *t = unit->types;

  while(t) {
    if(t->offset == offset)
      return t;
    t = t->next;
  }
  if(offset == 0) {
    Type *t = (Type *)calloc(sizeof(Type), 1);
    t->type = TYPE_void;
    t->offset = 0;
    elfAddType(t, unit, 0);
    return t;
  }
  uint8 *data = unit->top + offset;
  int bytes;
  int abbrevNum = elfReadLEB128(data, &bytes);
  data += bytes;
  Type *type = NULL;

  ELFAbbrev *abbrev = elfGetAbbrev(unit->abbrevs, abbrevNum);
  
  elfParseType(data, offset, abbrev, unit, &type);
  return type;
}

void elfGetObjectAttributes(CompileUnit *unit, uint32 offset, Object *o)
{
  uint8 *data = unit->top + offset;
  int bytes;
  uint32 abbrevNum = elfReadLEB128(data, &bytes);
  data += bytes;
  
  if(!abbrevNum) {
    return;
  }

  ELFAbbrev *abbrev = elfGetAbbrev(unit->abbrevs, abbrevNum);
  
  for(int i = 0; i < abbrev->numAttrs; i++) {
    ELFAttr *attr = &abbrev->attrs[i];
    data = elfReadAttribute(data, attr);
    switch(attr->name) {
    case DW_AT_location:
      o->location = attr->block;
      break;
    case DW_AT_name:
      if(o->name == NULL)
        o->name = attr->string;
      break;
    case DW_AT_MIPS_linkage_name:
      o->name = attr->string;
      break;
    case DW_AT_decl_file:
      o->file = attr->value;
      break;
    case DW_AT_decl_line:
      o->line = attr->value;
      break;
    case DW_AT_type:
      o->type = elfParseType(unit, attr->value);
      break;
    case DW_AT_external:
      o->external = attr->flag;
      break;
    case DW_AT_const_value:      
    case DW_AT_abstract_origin:
    case DW_AT_declaration:
    case DW_AT_artificial:
      // todo
      break;
    case DW_AT_specification:
      // TODO:
      break;
    default:
      fprintf(stderr, "Unknown object attribute %02x\n", attr->name);
      break;
    }
  }  
}

uint8 *elfParseObject(uint8 *data, ELFAbbrev *abbrev, CompileUnit *unit,
                   Object **object)
{
  Object *o = (Object *)calloc(sizeof(Object), 1);

  o->next = NULL;

  for(int i = 0; i < abbrev->numAttrs; i++) {
    ELFAttr *attr = &abbrev->attrs[i];
    data = elfReadAttribute(data, attr);
    switch(attr->name) {
    case DW_AT_location:
      o->location = attr->block;
      break;
    case DW_AT_name:
      if(o->name == NULL)
        o->name = attr->string;
      break;
    case DW_AT_MIPS_linkage_name:
      o->name = attr->string;
      break;
    case DW_AT_decl_file:
      o->file = attr->value;
      break;
    case DW_AT_decl_line:
      o->line = attr->value;
      break;
    case DW_AT_type:
      o->type = elfParseType(unit, attr->value);
      break;
    case DW_AT_external:
      o->external = attr->flag;
      break;
    case DW_AT_abstract_origin:
      elfGetObjectAttributes(unit, attr->value, o);
      break;
    case DW_AT_const_value:      
    case DW_AT_declaration:
    case DW_AT_artificial:
      break;
    case DW_AT_specification:
      // TODO:
      break;
    default:
      fprintf(stderr, "Unknown object attribute %02x\n", attr->name);
      break;
    }
  }
  *object = o;
  return data;
}

uint8 *elfParseBlock(uint8 *data, ELFAbbrev *abbrev, CompileUnit *unit,
                  Function *func, Object **lastVar)
{
  int bytes;
  uint32 start = func->lowPC;
  uint32 end = func->highPC;
  
  for(int i = 0; i < abbrev->numAttrs; i++) {
    ELFAttr *attr = &abbrev->attrs[i];
    data = elfReadAttribute(data, attr);
    switch(attr->name) {
    case DW_AT_sibling:
      break;
    case DW_AT_low_pc:
      start = attr->value;
      break;
    case DW_AT_high_pc:
      end = attr->value;
      break;
    case DW_AT_ranges: // ignore for now
      break;
    default:
      fprintf(stderr, "Unknown block attribute %02x\n", attr->name);
      break;
    }
  }
  
  if(abbrev->hasChildren) {
    int nesting = 1;

    while(nesting) {
      uint32 abbrevNum = elfReadLEB128(data, &bytes);
      data += bytes;
      
      if(!abbrevNum) {
        nesting--;
        continue;
      }

      abbrev = elfGetAbbrev(unit->abbrevs, abbrevNum);
      
      switch(abbrev->tag) {
      CASE_TYPE_TAG: // types only parsed when used
      case DW_TAG_label: // not needed
        data = elfSkipData(data, abbrev, unit->abbrevs);
        break;  
      case DW_TAG_lexical_block:
        data = elfParseBlock(data, abbrev, unit, func, lastVar);
        break;
      case DW_TAG_subprogram:
        {
          Function *f = NULL;
          data = elfParseFunction(data, abbrev, unit, &f);
          if(f != NULL) {
            if(unit->lastFunction)
              unit->lastFunction->next = f;
            else
              unit->functions = f;
            unit->lastFunction = f;
          }
        }
        break;
      case DW_TAG_variable:
        {
          Object *o;
          data = elfParseObject(data, abbrev, unit, &o);
          if(o->startScope == 0)
            o->startScope = start;
          if(o->endScope == 0)
            o->endScope = 0;
          if(func->variables)
            (*lastVar)->next = o;
          else
            func->variables = o;
          *lastVar = o;   
        }
        break;
      case DW_TAG_inlined_subroutine:
        // TODO:
        data = elfSkipData(data, abbrev, unit->abbrevs);
        break;
      default:
        {
          fprintf(stderr, "Unknown block TAG %02x\n", abbrev->tag);
          data = elfSkipData(data, abbrev, unit->abbrevs);        
        }
        break;
      }
    }
  }
  return data;  
}

void elfGetFunctionAttributes(CompileUnit *unit, uint32 offset, Function *func)
{
  uint8 *data = unit->top + offset;
  int bytes;
  uint32 abbrevNum = elfReadLEB128(data, &bytes);
  data += bytes;
  
  if(!abbrevNum) {
    return;
  }

  ELFAbbrev *abbrev = elfGetAbbrev(unit->abbrevs, abbrevNum);

  for(int i = 0; i < abbrev->numAttrs; i++) {
    ELFAttr *attr = &abbrev->attrs[i];
    data = elfReadAttribute(data, attr);
    
    switch(attr->name) {
    case DW_AT_sibling:
      break;      
    case DW_AT_name:
      if(func->name == NULL)
        func->name = attr->string;
      break;
    case DW_AT_MIPS_linkage_name:
      func->name = attr->string;
      break;
    case DW_AT_low_pc:
      func->lowPC = attr->value;
      break;
    case DW_AT_high_pc:
      func->highPC = attr->value;
      break;
    case DW_AT_decl_file:
      func->file = attr->value;
      break;
    case DW_AT_decl_line:
      func->line = attr->value;
      break;
    case DW_AT_external:
      func->external = attr->flag;
      break;
    case DW_AT_frame_base:
      func->frameBase = attr->block;
      break;
    case DW_AT_type:
      func->returnType = elfParseType(unit, attr->value);
      break;
    case DW_AT_inline:
    case DW_AT_specification:
    case DW_AT_declaration:
    case DW_AT_artificial:
    case DW_AT_prototyped:
    case DW_AT_proc_body:
    case DW_AT_save_offset:
    case DW_AT_user_2002:
    case DW_AT_virtuality:
    case DW_AT_containing_type:
    case DW_AT_accessibility:
      // todo;
      break;
    case DW_AT_vtable_elem_location:
      free(attr->block);
      break;
    default:
      fprintf(stderr, "Unknown function attribute %02x\n", attr->name);
      break;    
    }
  }

  return;
}

uint8 *elfParseFunction(uint8 *data, ELFAbbrev *abbrev, CompileUnit *unit,
                     Function **f)
{
  Function *func = (Function *)calloc(sizeof(Function), 1);
  *f = func;
   
  int bytes;
  bool8 mangled = false;
  bool8 declaration = false;
  for(int i = 0; i < abbrev->numAttrs; i++) {
    ELFAttr *attr = &abbrev->attrs[i];
    data = elfReadAttribute(data, attr);
    switch(attr->name) {
    case DW_AT_sibling:
      break;
    case DW_AT_name:
      if(func->name == NULL)
        func->name = attr->string;
      break;
    case DW_AT_MIPS_linkage_name:
      func->name = attr->string;
      mangled = true;
      break;
    case DW_AT_low_pc:
      func->lowPC = attr->value;
      break;
    case DW_AT_high_pc:
      func->highPC = attr->value;
      break;
    case DW_AT_prototyped:
      break;
    case DW_AT_decl_file:
      func->file = attr->value;
      break;
    case DW_AT_decl_line:
      func->line = attr->value;
      break;
    case DW_AT_external:
      func->external = attr->flag;
      break;
    case DW_AT_frame_base:
      func->frameBase = attr->block;
      break;
    case DW_AT_type:
      func->returnType = elfParseType(unit, attr->value);
      break;
    case DW_AT_abstract_origin:
      elfGetFunctionAttributes(unit, attr->value, func);
      break;
    case DW_AT_declaration:
      declaration = attr->flag;
      break;
    case DW_AT_inline:
    case DW_AT_specification:
    case DW_AT_artificial:
    case DW_AT_proc_body:
    case DW_AT_save_offset:
    case DW_AT_user_2002:
    case DW_AT_virtuality:
    case DW_AT_containing_type:
    case DW_AT_accessibility:      
      // todo;
      break;
    case DW_AT_vtable_elem_location:
      free(attr->block);
      break;
    default:
      fprintf(stderr, "Unknown function attribute %02x\n", attr->name);
      break;
    }
  }

  if(declaration) {
    elfCleanUp(func);
    free(func);
    *f = NULL;

    while(1) {
      uint32 abbrevNum = elfReadLEB128(data, &bytes);
      data += bytes;

      if(!abbrevNum) {
        return data;
      }

      abbrev = elfGetAbbrev(unit->abbrevs, abbrevNum);

      data = elfSkipData(data, abbrev, unit->abbrevs);
    }
  }
  
  if(abbrev->hasChildren) {
    int nesting = 1;
    Object *lastParam = NULL;
    Object *lastVar = NULL;

    while(nesting) {
      uint32 abbrevNum = elfReadLEB128(data, &bytes);
      data += bytes;
      
      if(!abbrevNum) {
        nesting--;
        continue;
      }

      abbrev = elfGetAbbrev(unit->abbrevs, abbrevNum);
      
      switch(abbrev->tag) {
      CASE_TYPE_TAG: // no need to parse types. only parsed when used
      case DW_TAG_label: // not needed
        data = elfSkipData(data, abbrev, unit->abbrevs);
        break;
      case DW_TAG_subprogram:
        {
          Function *fnc=NULL;
          data = elfParseFunction(data, abbrev, unit, &fnc);
          if(fnc != NULL) {
            if(unit->lastFunction == NULL)
              unit->functions = fnc;
            else
              unit->lastFunction->next = fnc;
            unit->lastFunction = fnc;
          }
        }
        break;
      case DW_TAG_lexical_block:
        {
          data = elfParseBlock(data, abbrev, unit, func, &lastVar);
        }
        break;
      case DW_TAG_formal_parameter:
        {
          Object *o;
          data = elfParseObject(data, abbrev, unit, &o);
          if(func->parameters)
            lastParam->next = o;
          else
            func->parameters = o;
          lastParam = o;
        }
        break;
      case DW_TAG_variable:
        {
          Object *o;
          data = elfParseObject(data, abbrev, unit, &o);
          if(func->variables)
            lastVar->next = o;
          else
            func->variables = o;
          lastVar = o;
        }
        break;
      case DW_TAG_unspecified_parameters:
      case DW_TAG_inlined_subroutine:
        {
          // todo
          for(int i = 0; i < abbrev->numAttrs; i++) {
            data = elfReadAttribute(data,  &abbrev->attrs[i]);
            if(abbrev->attrs[i].form == DW_FORM_block1)
              free(abbrev->attrs[i].block);
          }
          
          if(abbrev->hasChildren)
            nesting++;
        }
        break;
      default:
        {
          fprintf(stderr, "Unknown function TAG %02x\n", abbrev->tag);
          data = elfSkipData(data, abbrev, unit->abbrevs);                
        }
        break;
      }
    }
  }
  return data;
}

uint8 *elfParseUnknownData(uint8 *data, ELFAbbrev *abbrev, ELFAbbrev **abbrevs)
{
  int i;
  int bytes;
  //  switch(abbrev->tag) {
  //  default:
    fprintf(stderr, "Unknown TAG %02x\n", abbrev->tag);
    
    for(i = 0; i < abbrev->numAttrs; i++) {
      data = elfReadAttribute(data,  &abbrev->attrs[i]);
      if(abbrev->attrs[i].form == DW_FORM_block1)
        free(abbrev->attrs[i].block);
    }
    
    if(abbrev->hasChildren) {
      int nesting = 1;
      while(nesting) {
        uint32 abbrevNum = elfReadLEB128(data, &bytes);
        data += bytes;
        
        if(!abbrevNum) {
          nesting--;
          continue;
        }
        
        abbrev = elfGetAbbrev(abbrevs, abbrevNum);
        
        fprintf(stderr, "Unknown TAG %02x\n", abbrev->tag);
        
        for(i = 0; i < abbrev->numAttrs; i++) {
          data = elfReadAttribute(data,  &abbrev->attrs[i]);
          if(abbrev->attrs[i].form == DW_FORM_block1)
            free(abbrev->attrs[i].block);
        }
        
        if(abbrev->hasChildren) {
          nesting++;
        }
      }
    }
    //  }
  return data;
}

uint8 *elfParseCompileUnitChildren(uint8 *data, CompileUnit *unit)
{
  int bytes;
  uint32 abbrevNum = elfReadLEB128(data, &bytes);
  data += bytes;
  Object *lastObj = NULL;
  while(abbrevNum) {
    ELFAbbrev *abbrev = elfGetAbbrev(unit->abbrevs, abbrevNum);
    switch(abbrev->tag) {
    case DW_TAG_subprogram:
      {
        Function *func = NULL;
        data = elfParseFunction(data, abbrev, unit, &func);
        if(func != NULL) {
          if(unit->lastFunction)
            unit->lastFunction->next = func;
          else
            unit->functions = func;
          unit->lastFunction = func;
        }
      }
      break;
    CASE_TYPE_TAG:
      data = elfSkipData(data, abbrev, unit->abbrevs);
      break;
    case DW_TAG_variable:
      {
        Object *var = NULL;
        data = elfParseObject(data, abbrev, unit, &var);
        if(lastObj)
          lastObj->next = var;
        else
          unit->variables = var;
        lastObj = var;
      }
      break;
    default:
      data = elfParseUnknownData(data, abbrev, unit->abbrevs);
      break;
    }
    
    abbrevNum = elfReadLEB128(data, &bytes);
    data += bytes;
  }
  return data;
}


CompileUnit *elfParseCompUnit(uint8 *data, uint8 *abbrevData)
{
  int bytes;
  uint8 *top = data;
  
  uint32 length = elfRead4Bytes(data);
  data += 4;
  
  uint16 version = elfRead2Bytes(data);
  data += 2;
  
  uint32 offset = elfRead4Bytes(data);
  data += 4;
  
  uint8 addrSize = *data++;

  if(version != 2) {
    fprintf(stderr, "Unsupported debugging information version %d\n", version);
    return NULL;
  }
  
  if(addrSize != 4) {
    fprintf(stderr, "Unsupported address size %d\n", addrSize);
    return NULL;
  }

  ELFAbbrev **abbrevs = elfReadAbbrevs(abbrevData, offset);

  uint32 abbrevNum = elfReadLEB128(data, &bytes);
  data += bytes;

  ELFAbbrev *abbrev = elfGetAbbrev(abbrevs, abbrevNum);

  CompileUnit *unit = (CompileUnit *)calloc(sizeof(CompileUnit), 1);
  unit->top = top;
  unit->length = length;
  unit->abbrevs = abbrevs;
  unit->next = NULL;

  elfCurrentUnit = unit;
  
  int i;
  
  for(i = 0; i < abbrev->numAttrs; i++) {
    ELFAttr *attr = &abbrev->attrs[i];
    data = elfReadAttribute(data, attr);

    switch(attr->name) {
    case DW_AT_name:
      unit->name = attr->string;
      break;
    case DW_AT_stmt_list:
      unit->hasLineInfo = true;
      unit->lineInfo = attr->value;
      break;
    case DW_AT_low_pc:
      unit->lowPC = attr->value;
      break;
    case DW_AT_high_pc:
      unit->highPC = attr->value;
      break;
    case DW_AT_compdir:
      unit->compdir = attr->string;
      break;
      // ignore
    case DW_AT_language:
    case DW_AT_producer:
    case DW_AT_macro_info:
    case DW_AT_entry_pc:
      break;
    default:
      fprintf(stderr, "Unknown attribute %02x\n", attr->name);
      break;
    }
  }

  if(abbrev->hasChildren)
    elfParseCompileUnitChildren(data, unit);

  return unit;
}

void elfParseAranges(uint8 *data)
{
  ELFSectionHeader *sh = elfGetSectionByName(".debug_aranges");
  if(sh == NULL) {
    fprintf(stderr, "No aranges found\n");
    return;
  }

  data = elfReadSection(data, sh);
  uint8 *end = data + READ32LE(&sh->size);

  int max = 4;
  ARanges *ranges = (ARanges *)calloc(sizeof(ARanges), 4);

  int index = 0;
  
  while(data < end) {
    uint32 len = elfRead4Bytes(data);
    data += 4;
    //    uint16 version = elfRead2Bytes(data);
    data += 2;
    uint32 offset = elfRead4Bytes(data);
    data += 4;
    //    uint8 addrSize = *data++;
    //    uint8 segSize = *data++;
    data += 2; // remove if uncommenting above
    data += 4;
    ranges[index].count = (len-20)/8;
    ranges[index].offset = offset;
    ranges[index].ranges = (ARange *)calloc(sizeof(ARange), (len-20)/8);
    int i = 0;
    while(true) {
      uint32 addr = elfRead4Bytes(data);
      data += 4;
      uint32 len = elfRead4Bytes(data);
      data += 4;
      if(addr == 0 && len == 0)
        break;
      ranges[index].ranges[i].lowPC = addr;
      ranges[index].ranges[i].highPC = addr+len;
      i++;
    }
    index++;
    if(index == max) {
      max += 4;
      ranges = (ARanges *)realloc(ranges, max*sizeof(ARanges));
    }
  }
  elfDebugInfo->numRanges = index;
  elfDebugInfo->ranges = ranges;
}

void elfReadSymtab(uint8 *data)
{
  ELFSectionHeader *sh = elfGetSectionByName(".symtab");
  int table = READ32LE(&sh->link);

  char *strtable = (char *)elfReadSection(data, elfGetSectionByNumber(table));
  
  ELFSymbol *symtab = (ELFSymbol *)elfReadSection(data, sh);

  int count = READ32LE(&sh->size) / sizeof(ELFSymbol);
  elfSymbolsCount = 0;

  elfSymbols = (Symbol *)malloc(sizeof(Symbol)*count);

  int i;
  
  for(i = 0; i < count; i++) {
    ELFSymbol *s = &symtab[i];
    int type = s->info & 15;
    int binding = s->info >> 4;

    if(binding) {
      Symbol *sym = &elfSymbols[elfSymbolsCount];
      sym->name = &strtable[READ32LE(&s->name)];
      sym->binding = binding;
      sym->type = type;
      sym->value = READ32LE(&s->value);
      sym->size = READ32LE(&s->size);
      elfSymbolsCount++;
    }
  }
  for(i = 0; i < count; i++) {
    ELFSymbol *s = &symtab[i];
    int bind = s->info>>4;
    int type = s->info & 15;

    if(!bind) {
      Symbol *sym = &elfSymbols[elfSymbolsCount];
      sym->name = &strtable[READ32LE(&s->name)];
      sym->binding = (s->info >> 4);
      sym->type = type;
      sym->value = READ32LE(&s->value);
      sym->size = READ32LE(&s->size);
      elfSymbolsCount++;
    }
  }  
  elfSymbolsStrTab = strtable;
  //  free(symtab);
}

bool8 elfReadProgram(ELFHeader *eh, uint8 *data, int& size, bool8 parseDebug)
{
  int count = READ16LE(&eh->e_phnum);
  int i;
  
  if(READ32LE(&eh->e_entry) == 0x2000000)
    cpuIsMultiBoot = true;

  // read program headers... should probably move this code down
  uint8 *p = data + READ32LE(&eh->e_phoff);
  size = 0;
  for(i = 0; i < count; i++) {
    ELFProgramHeader *ph = (ELFProgramHeader *)p;
    p += sizeof(ELFProgramHeader);
    if(READ16LE(&eh->e_phentsize) != sizeof(ELFProgramHeader)) {
      p += READ16LE(&eh->e_phentsize) - sizeof(ELFProgramHeader);
    }

    //    printf("PH %d %08x %08x %08x %08x %08x %08x %08x %08x\n",
    //     i, ph->type, ph->offset, ph->vaddr, ph->paddr,
    //     ph->filesz, ph->memsz, ph->flags, ph->align);
    if(cpuIsMultiBoot) {
      if(READ32LE(&ph->paddr) >= 0x2000000 &&
         READ32LE(&ph->paddr) <= 0x203ffff) {
        memcpy(&workRAM[READ32LE(&ph->paddr) & 0x3ffff],
               data + READ32LE(&ph->offset),
               READ32LE(&ph->filesz));
        size += READ32LE(&ph->filesz);
      }      
    } else {
      if(READ32LE(&ph->paddr) >= 0x8000000 &&
         READ32LE(&ph->paddr) <= 0x9ffffff) {
        memcpy(&rom[READ32LE(&ph->paddr) & 0x1ffffff],
               data + READ32LE(&ph->offset),
               READ32LE(&ph->filesz));
        size += READ32LE(&ph->filesz);
      }
    }
  }

  char *stringTable = NULL;

  // read section headers
  p = data + READ32LE(&eh->e_shoff);
  count = READ16LE(&eh->e_shnum);

  ELFSectionHeader **sh = (ELFSectionHeader **)
    malloc(sizeof(ELFSectionHeader *) * count);
  
  for(i = 0; i < count; i++) {
    sh[i] = (ELFSectionHeader *)p;
    p += sizeof(ELFSectionHeader);
    if(READ16LE(&eh->e_shentsize) != sizeof(ELFSectionHeader))
      p += READ16LE(&eh->e_shentsize) - sizeof(ELFSectionHeader);
  }

  if(READ16LE(&eh->e_shstrndx) != 0) {
    stringTable = (char *)elfReadSection(data,
                                         sh[READ16LE(&eh->e_shstrndx)]);
  }
  
  elfSectionHeaders = sh;
  elfSectionHeadersStringTable = stringTable;
  elfSectionHeadersCount = count;

  for(i = 0; i < count; i++) {
    //    printf("SH %d %-20s %08x %08x %08x %08x %08x %08x %08x %08x\n",
    //   i, &stringTable[sh[i]->name], sh[i]->name, sh[i]->type,
    //   sh[i]->flags, sh[i]->addr, sh[i]->offset, sh[i]->size,
    //   sh[i]->link, sh[i]->info);
    if(READ32LE(&sh[i]->flags) & 2) { // load section
      if(cpuIsMultiBoot) {
        if(READ32LE(&sh[i]->addr) >= 0x2000000 &&
           READ32LE(&sh[i]->addr) <= 0x203ffff) {
          memcpy(&workRAM[READ32LE(&sh[i]->addr) & 0x3ffff], data +
                 READ32LE(&sh[i]->offset),
                 READ32LE(&sh[i]->size));
                   size += READ32LE(&sh[i]->size);
        }      
      } else {
        if(READ32LE(&sh[i]->addr) >= 0x8000000 &&
           READ32LE(&sh[i]->addr) <= 0x9ffffff) {
          memcpy(&rom[READ32LE(&sh[i]->addr) & 0x1ffffff],
                 data + READ32LE(&sh[i]->offset),
                 READ32LE(&sh[i]->size));
          size += READ32LE(&sh[i]->size);
        }
      }      
    }
  }

  if(parseDebug) {
    fprintf(stderr, "Parsing debug info\n");

    ELFSectionHeader *dbgHeader = elfGetSectionByName(".debug_info");
    if(dbgHeader == NULL) {
      fprintf(stderr, "Cannot find debug information\n");
      goto end;      
    }

    ELFSectionHeader *h = elfGetSectionByName(".debug_abbrev");
    if(h == NULL) {
      fprintf(stderr, "Cannot find abbreviation table\n");
      goto end;
    }
    
    elfDebugInfo = (DebugInfo *)calloc(sizeof(DebugInfo), 1);
    uint8 *abbrevdata = elfReadSection(data, h);

    h = elfGetSectionByName(".debug_str");

    if(h == NULL)
      elfDebugStrings = NULL;
    else
      elfDebugStrings = (char *)elfReadSection(data, h);
    
    uint8 *debugdata = elfReadSection(data, dbgHeader);
    
    elfDebugInfo->debugdata = data;
    elfDebugInfo->infodata = debugdata;
    
    uint32 total = READ32LE(&dbgHeader->size);
    uint8 *end = debugdata + total;
    uint8 *ddata = debugdata;
    
    CompileUnit *last = NULL;
    CompileUnit *unit = NULL;
    
    while(ddata < end) {
      unit = elfParseCompUnit(ddata, abbrevdata);
      unit->offset = ddata-debugdata;
      elfParseLineInfo(unit, data);
      if(last == NULL)
        elfCompileUnits = unit;
      else
        last->next = unit;
      last = unit;
      ddata += 4 + unit->length;
    }
    elfParseAranges(data);
    CompileUnit *comp = elfCompileUnits;
    while(comp) {
      ARanges *r = elfDebugInfo->ranges;
      for(int i = 0; i < elfDebugInfo->numRanges; i++)
        if(r[i].offset == comp->offset) {
          comp->ranges = &r[i];
          break;
        }
      comp = comp->next;
    }
    elfParseCFA(data);
    elfReadSymtab(data);
  }
 end:  
  if(sh) {
    free(sh);
  }

  elfSectionHeaders = NULL;
  elfSectionHeadersStringTable = NULL;
  elfSectionHeadersCount = 0;
  
  return true;
}

extern bool8 parseDebug;

bool8 elfRead(const char *name, int& siz, FILE *f)
{
  fseek(f, 0, SEEK_END);
  long size = ftell(f);
  elfFileData = (uint8 *)malloc(size);
  fseek(f, 0, SEEK_SET);
  fread(elfFileData, 1, size, f);
  fclose(f);
  
  ELFHeader *header = (ELFHeader *)elfFileData;
  
  if(READ32LE(&header->magic) != 0x464C457F ||
     READ16LE(&header->e_machine) != 40 ||
     header->clazz != 1) {
    MDFN_PrintError(_("Not a valid ELF file %s"));
    free(elfFileData);
    elfFileData = NULL;
    return false;
  }

  if(!elfReadProgram(header, elfFileData, siz, parseDebug)) {
    free(elfFileData);
    elfFileData = NULL;
    return false;
  }
  
  return true;
}

void elfCleanUp(Object *o)
{
  free(o->location);
}

void elfCleanUp(Function *func)
{
  Object *o = func->parameters;
  while(o) {
    elfCleanUp(o);
    Object *next = o->next;
    free(o);
    o = next;
  }

  o = func->variables;
  while(o) {
    elfCleanUp(o);
    Object *next = o->next;
    free(o);
    o = next;
  }
  free(func->frameBase);
}

void elfCleanUp(ELFAbbrev **abbrevs)
{
  for(int i = 0; i < 121; i++) {
    ELFAbbrev *abbrev = abbrevs[i];

    while(abbrev) {
      free(abbrev->attrs);
      ELFAbbrev *next = abbrev->next;
      free(abbrev);
      
      abbrev = next;
    }
  }
}

void elfCleanUp(Type *t)
{
  switch(t->type) {
  case TYPE_function:
    if(t->function) {
      Object *o = t->function->args;
      while(o) {
        elfCleanUp(o);
        Object *next = o->next;
        free(o);
        o = next;
      }
      free(t->function);      
    }
    break;
  case TYPE_array:
    if(t->array) {
      free(t->array->bounds);
      free(t->array);
    }
    break;
  case TYPE_struct:
  case TYPE_union:
    if(t->structure) {
      for(int i = 0; i < t->structure->memberCount; i++) {
        free(t->structure->members[i].location);
      }
      free(t->structure->members);
      free(t->structure);
    }
    break;
  case TYPE_enum:
    if(t->enumeration) {
      free(t->enumeration->members);
      free(t->enumeration);
    }
    break;
  case TYPE_base:
  case TYPE_pointer:
  case TYPE_void:
  case TYPE_reference:
    break; // nothing to do
  }
}

void elfCleanUp(CompileUnit *comp)
{
  elfCleanUp(comp->abbrevs);
  free(comp->abbrevs);
  Function *func = comp->functions;
  while(func) {
    elfCleanUp(func);
    Function *next = func->next;
    free(func);
    func = next;
  }
  Type *t = comp->types;
  while(t) {
    elfCleanUp(t);
    Type *next = t->next;
    free(t);
    t = next;
  }
  Object *o = comp->variables;
  while(o) {
    elfCleanUp(o);
    Object *next = o->next;
    free(o);
    o = next;
  }
  if(comp->lineInfoTable) {
    free(comp->lineInfoTable->lines);
    free(comp->lineInfoTable->files);
    free(comp->lineInfoTable);
  }
}

void elfCleanUp()
{
  CompileUnit *comp = elfCompileUnits;

  while(comp) {
    elfCleanUp(comp);
    CompileUnit *next = comp->next;
    free(comp);
    comp = next;
  }
  elfCompileUnits = NULL;
  free(elfSymbols);
  elfSymbols = NULL;
  //  free(elfSymbolsStrTab);
  elfSymbolsStrTab = NULL;

  elfDebugStrings = NULL;
  if(elfDebugInfo) {
    int num = elfDebugInfo->numRanges;
    int i;
    for(i = 0; i < num; i++) {
      free(elfDebugInfo->ranges[i].ranges);
    }
    free(elfDebugInfo->ranges);
    free(elfDebugInfo);
    elfDebugInfo = NULL;
  }

  if(elfFdes) {
    if(elfFdeCount) {
      for(int i = 0; i < elfFdeCount; i++)
        free(elfFdes[i]);
    }
    free(elfFdes);
    
    elfFdes = NULL;
    elfFdeCount = 0;
  }

  ELFcie *cie = elfCies;
  while(cie) {
    ELFcie *next = cie->next;
    free(cie);
    cie = next;
  }
  elfCies = NULL;

  if(elfFileData) {
    free(elfFileData);
    elfFileData = NULL;
  }
}
