// Copyright (c) 2024 Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)

////////////////////////////////
//~ rjf: Build Options

#define BUILD_VERSION_MAJOR 0
#define BUILD_VERSION_MINOR 9
#define BUILD_VERSION_PATCH 12
#define BUILD_RELEASE_PHASE_STRING_LITERAL "ALPHA"
#define BUILD_TITLE "rdi_from_dwarf"
#define BUILD_CONSOLE_INTERFACE 1

////////////////////////////////
//~ rjf: Includes

//- rjf: [lib]
#include "lib_rdi_format/rdi_format.h"
#include "lib_rdi_format/rdi_format.c"
#include "third_party/rad_lzb_simple/rad_lzb_simple.h"
#include "third_party/rad_lzb_simple/rad_lzb_simple.c"

//- rjf: [h]
#include "base/base_inc.h"
#include "os/os_inc.h"
#include "rdi_make/rdi_make_local.h"
#include "rdi_elf.h"
#include "rdi_dwarf.h"
#include "rdi_dwarf_stringize.h"
#include "rdi_from_dwarf.h"

//- rjf: [c]
#include "base/base_inc.c"
#include "os/os_inc.c"
#include "rdi_make/rdi_make_local.c"
#include "rdi_elf.c"
#include "rdi_dwarf.c"
#include "rdi_dwarf_stringize.c"

// TODO(allen): 
// [ ] need sample data for .debug_names

////////////////////////////////
//~ Program Parameters Parser

static DWARFCONV_Params*
dwarf_convert_params_from_cmd_line(Arena *arena, CmdLine *cmdline){
  DWARFCONV_Params *result = push_array(arena, DWARFCONV_Params, 1);
  result->unit_idx_max = ~0ull;
  
  // get input pdb
  {
    String8 input_name = cmd_line_string(cmdline, str8_lit("elf"));
    if (input_name.size == 0){
      str8_list_push(arena, &result->errors,
                     str8_lit("missing required parameter '--elf:<elf_file>'"));
    }
    
    if (input_name.size > 0){
      String8 input_data = os_data_from_file_path(arena, input_name);
      
      if (input_data.size == 0){
        str8_list_pushf(arena, &result->errors,
                        "could not load input file '%.*s'", str8_varg(input_name));
      }
      
      if (input_data.size != 0){
        result->input_elf_name = input_name;
        result->input_elf_data = input_data;
      }
    }
  }
  
  // get output name
  {
    result->output_name = cmd_line_string(cmdline, str8_lit("out"));
  }
  
  // error options
  if (cmd_line_has_flag(cmdline, str8_lit("hide_errors"))){
    String8List vals = cmd_line_strings(cmdline, str8_lit("hide_errors"));
    
    // if no values - set all to hidden
    if (vals.node_count == 0){
      B8 *ptr  = (B8*)&result->hide_errors;
      B8 *opl = ptr + sizeof(result->hide_errors);
      for (;ptr < opl; ptr += 1){
        *ptr = 1;
      }
    }
    
    // for each explicit value set the corresponding flag to hidden
    for (String8Node *node = vals.first;
         node != 0;
         node = node->next){
      if (str8_match(node->string, str8_lit("input"), 0)){
        result->hide_errors.input = 1;
      }
    }
    
  }
  
  // unit idx selector
  if (cmd_line_has_flag(cmdline, str8_lit("unit_idx"))){
    String8List vals = cmd_line_strings(cmdline, str8_lit("unit_idx"));
    
    // single value unit index
    if (vals.node_count == 1){
      U64 idx = u64_from_str8(vals.first->string, 10);
      result->unit_idx_min = idx;
      result->unit_idx_max = idx;
    }
    
    // range value unit index
    else if (vals.node_count >= 2){
      U64 idx_a = u64_from_str8(vals.first->string, 10);
      U64 idx_b = u64_from_str8(vals.first->next->string, 10);
      result->unit_idx_min = Min(idx_a, idx_b);
      result->unit_idx_max = Max(idx_a, idx_b);
    }
  }
  
  // dump options
  if (cmd_line_has_flag(cmdline, str8_lit("dump"))){
    result->dump = 1;
    
    String8List vals = cmd_line_strings(cmdline, str8_lit("dump"));
    if (vals.first == 0){
      B8 *ptr = &result->dump__first;
      for (; ptr < &result->dump__last; ptr += 1){
        *ptr = 1;
      }
    }
    else{
      for (String8Node *node = vals.first;
           node != 0;
           node = node->next){
        if (str8_match(node->string, str8_lit("header"), 0)){
          result->dump_header = 1;
        }
        else if (str8_match(node->string, str8_lit("sections"), 0)){
          result->dump_sections = 1;
        }
        else if (str8_match(node->string, str8_lit("segments"), 0)){
          result->dump_segments = 1;
        }
        else if (str8_match(node->string, str8_lit("symtab"), 0)){
          result->dump_symtab = 1;
        }
        else if (str8_match(node->string, str8_lit("dynsym"), 0)){
          result->dump_dynsym = 1;
        }
        else if (str8_match(node->string, str8_lit("debug_sections"), 0)){
          result->dump_debug_sections = 1;
        }
        else if (str8_match(node->string, str8_lit("debug_info"), 0)){
          result->dump_debug_info = 1;
        }
        else if (str8_match(node->string, str8_lit("debug_abbrev"), 0)){
          result->dump_debug_abbrev = 1;
        }
        else if (str8_match(node->string, str8_lit("debug_pubnames"), 0)){
          result->dump_debug_pubnames = 1;
        }
        else if (str8_match(node->string, str8_lit("debug_pubtypes"), 0)){
          result->dump_debug_pubtypes = 1;
        }
        else if (str8_match(node->string, str8_lit("debug_names"), 0)){
          result->dump_debug_names = 1;
        }
        else if (str8_match(node->string, str8_lit("debug_aranges"), 0)){
          result->dump_debug_aranges = 1;
        }
        else if (str8_match(node->string, str8_lit("debug_addr"), 0)){
          result->dump_debug_addr = 1;
        }
      }
    }
  }
  
  return(result);
}

////////////////////////////////
//~ Entry Point

static void
dump_symtab(Arena *arena, String8List *out, ELF_SymArray *symbols, String8 strtab,
            U32 indent){
  static char spaces[] = "                                ";
  
  U8 *str_first = strtab.str;
  U8 *str_opl   = strtab.str + strtab.size;
  
  ELF_Sym64 *symbol = symbols->symbols;
  U64 count = symbols->count;
  for (U64 i = 0; i < count; i += 1, symbol += 1){
    U8 *name_first = str_first + symbol->st_name;
    U8 *name_opl = name_first;
    for (;name_opl < str_opl && *name_opl != 0;) name_opl += 1;
    String8 name = str8_range(name_first, name_opl);
    
    ELF_SymbolBinding binding = ELF_SymBindingFromInfo(symbol->st_info);
    String8 binding_string = elf_string_from_symbol_binding(binding);
    
    ELF_SymbolType type = ELF_SymTypeFromInfo(symbol->st_info);
    String8 type_string = elf_string_from_symbol_type(type);
    
    ELF_SymbolVisibility vis = ELF_SymVisibilityFromOther(symbol->st_other);
    String8 vis_string = elf_string_from_symbol_visibility(vis);
    
    str8_list_pushf(arena, out,
                    "%.*ssymbol[%5llu] %6.*s %7.*s %9.*s 0x%08llx size=%-5llu sec=%-5u "
                    "%.*s\n",
                    indent, spaces, i,
                    str8_varg(binding_string), str8_varg(type_string),
                    str8_varg(vis_string),
                    symbol->st_value, symbol->st_size,
                    symbol->st_shndx, str8_varg(name));
  }
}

#if 0
static void
dump_entry_tree(Arena *arena, String8List *out,
                DWARF_Parsed *dwarf, DWARF_InfoUnit *unit,
                DWARF_InfoEntry *entry, U32 indent){
  static char spaces[] = "                                ";
  
  DWARF_AbbrevDecl *abbrev_decl = entry->abbrev_decl;
  
  // tag
  DWARF_Tag tag = abbrev_decl->tag;
  String8 tag_string = dwarf_string_from_tag(tag);
  str8_list_pushf(arena, out, "%.*sentry(@%llx) TAG %.*s\n",
                  indent, spaces, entry->info_offset, str8_varg(tag_string));
  
  // attributes
  U32                     attrib_count = abbrev_decl->attrib_count;
  DWARF_AbbrevAttribSpec *attrib_spec  = abbrev_decl->attrib_specs;
  DWARF_InfoAttribVal    *attrib_val   = entry->attrib_vals;
  for (U32 i = 0; i < attrib_count; i += 1, attrib_spec += 1, attrib_val += 1){
    // attribute name
    DWARF_AttributeName name = attrib_spec->name;
    String8 name_string = dwarf_string_from_attribute_name(name);
    str8_list_pushf(arena, out, "%.*sATTR %.*s ", indent + 4, spaces, str8_varg(name_string));
    
    // attribute value
    switch (attrib_spec->form){
      default:
      {
        String8 form_string = dwarf_string_from_attribute_form(attrib_spec->form);
        str8_list_pushf(arena, out, "<form: %.*s> {%llu, 0x%p}\n",
                        str8_varg(form_string), attrib_val->val, attrib_val->dataptr);
      }break;
      
      case DWARF_AttributeForm_strp:
      {
        String8 str = {0};
        
        String8 data = dwarf->debug_data[DWARF_SectionCode_Str];
        U64 off = attrib_val->val;
        if (off < data.size){
          U8 *start = data.str + off;
          U8 *opl = data.str + data.size;
          U8 *ptr = start;
          for (;ptr < opl && *ptr != 0;) ptr += 1;
          str = str8_range(start, ptr);
        }
        
        str8_list_pushf(arena, out, "'%.*s'\n", str8_varg(str));
      }break;
      
      case DWARF_AttributeForm_sec_offset:
      {
        DWARF_AttributeClassFlags attr_classes1 = dwarf_attribute_class_from_name(name);
        DWARF_AttributeClassFlags attr_classes2 = DWARF_AttributeClassFlag_sec_offset_classes;
        DWARF_AttributeClassFlags attr_classes = attr_classes1&attr_classes2;
        
        DWARF_SectionCode sec_code = DWARF_SectionCode_Null;
        if (unit->dwarf_version == 5){
          switch (attr_classes){
            case DWARF_AttributeClassFlag_addrptr: sec_code = DWARF_SectionCode_Addr; break;
            case DWARF_AttributeClassFlag_lineptr: sec_code = DWARF_SectionCode_Line; break;
            case DWARF_AttributeClassFlag_loclist: sec_code = DWARF_SectionCode_LocLists; break;
            case DWARF_AttributeClassFlag_loclistsptr: sec_code = DWARF_SectionCode_LocLists; break;
            case DWARF_AttributeClassFlag_macptr:  sec_code = DWARF_SectionCode_Macro; break;
            case DWARF_AttributeClassFlag_rnglist: sec_code = DWARF_SectionCode_RngLists; break;
            case DWARF_AttributeClassFlag_rnglistsptr: sec_code = DWARF_SectionCode_RngLists; break;
            case DWARF_AttributeClassFlag_stroffsetsptr: sec_code = DWARF_SectionCode_StrOffsets; break;
          }
        }
        else if (unit->dwarf_version == 4){
          switch (attr_classes){
            case DWARF_AttributeClassFlag_lineptr: sec_code = DWARF_SectionCode_Line; break;
            case DWARF_AttributeClassFlag_loclist: sec_code = DWARF_SectionCode_Loc; break;
            case DWARF_AttributeClassFlag_macptr:  sec_code = DWARF_SectionCode_MacInfo; break;
            case DWARF_AttributeClassFlag_rnglist: sec_code = DWARF_SectionCode_Ranges; break;
          }
        }
        
        String8 sec_name = dwarf_name_from_debug_section(dwarf, sec_code);
        str8_list_pushf(arena, out, "sec(%.*s) + %llu\n", str8_varg(sec_name), attrib_val->val);
      }break;
      
      case DWARF_AttributeForm_ref1:
      case DWARF_AttributeForm_ref2:
      case DWARF_AttributeForm_ref4:
      case DWARF_AttributeForm_ref8:
      case DWARF_AttributeForm_ref_udata:
      {
        str8_list_pushf(arena, out, "entry(@%llx)\n", attrib_val->val);
      }break;
      
      case DWARF_AttributeForm_addr:
      {
        str8_list_pushf(arena, out, "0x%llx\n", attrib_val->val);
      }break;
      
      case DWARF_AttributeForm_exprloc:
      {
        str8_list_pushf(arena, out, "expression\n");
        // TODO(allen): dwarf expression dumping
      }break;
      
      case DWARF_AttributeForm_strx1:
      case DWARF_AttributeForm_strx2:
      case DWARF_AttributeForm_strx3:
      case DWARF_AttributeForm_strx4:
      {
        String8 str = {0};
        
        U32 idx = attrib_val->val;
        U64 str_offsets_off = unit->str_offsets_base + idx*unit->offset_size;
        
        String8 str_offsets = dwarf->debug_data[DWARF_SectionCode_StrOffsets];
        if (str_offsets_off + unit->offset_size < str_offsets.size){
          U64 off = 0;
          MemoryCopy(&off, str_offsets.str + str_offsets_off, unit->offset_size);
          
          String8 data = dwarf->debug_data[DWARF_SectionCode_Str];
          if (off < data.size){
            U8 *start = data.str + off;
            U8 *opl = data.str + data.size;
            U8 *ptr = start;
            for (;ptr < opl && *ptr != 0;) ptr += 1;
            str = str8_range(start, ptr);
          }
        }
        
        str8_list_pushf(arena, out, "'%.*s'\n", str8_varg(str));
      }break;
      
      case DWARF_AttributeForm_addrx:
      case DWARF_AttributeForm_addrx1:
      case DWARF_AttributeForm_addrx2:
      case DWARF_AttributeForm_addrx3:
      case DWARF_AttributeForm_addrx4:
      {
        U64 address = 0;
        
        U32 idx = attrib_val->val;
        U64 address_off = unit->addr_base + idx*unit->address_size;
        
        String8 data = dwarf->debug_data[DWARF_SectionCode_Addr];
        if (address_off + unit->address_size < data.size){
          MemoryCopy(&address, data.str + address_off, unit->address_size);
        }
        
        str8_list_pushf(arena, out, "0x%x\n", address);
      }break;
      
      case DWARF_AttributeForm_rnglistx:
      {
        U64 rnglist_off = unit->rnglists_base + attrib_val->val;
        int x = 0;
      }break;
      
      case DWARF_AttributeForm_data1:
      case DWARF_AttributeForm_data2:
      case DWARF_AttributeForm_data4:
      case DWARF_AttributeForm_data8:
      case DWARF_AttributeForm_data16:
      case DWARF_AttributeForm_udata:
      case DWARF_AttributeForm_implicit_const:
      case DWARF_AttributeForm_flag:
      case DWARF_AttributeForm_flag_present:
      {
        str8_list_pushf(arena, out, "%llu\n", attrib_val->val);
      }break;
      
      case DWARF_AttributeForm_sdata:
      {
        str8_list_pushf(arena, out, "%lld\n", (S64)attrib_val->val);
      }break;
      
      case DWARF_AttributeForm_string:
      {
        str8_list_pushf(arena, out, "'%.*s'\n", (int)attrib_val->val, attrib_val->dataptr);
      }break;
    }
  }
  
  // dump children
  for (DWARF_InfoEntry *child = entry->first_child;
       child != 0;
       child = child->next_sibling){
    dump_entry_tree(arena, out, dwarf, unit, child, indent + 1);
  }
}
#endif

internal void
entry_point(CmdLine *cmd_line)
{
  Arena *arena = arena_alloc();
  
  // parse arguments
  DWARFCONV_Params *params = dwarf_convert_params_from_cmd_line(arena, cmd_line);
  
  // show input errors
  if (params->errors.node_count > 0 &&
      !params->hide_errors.input){
    for (String8Node *node = params->errors.first;
         node != 0;
         node = node->next){
      fprintf(stdout, "error(input): %.*s\n", str8_varg(node->string));
    }
  }
  
  // will we try to parse an input file?
  B32 try_parse_input = (params->errors.node_count == 0);
  
  // track parse success
  B32 successful_parse = 1;
  
#define PARSE_CHECK_ERROR(p,fmt,...) do{ if ((p) == 0){ \
successful_parse = 0; \
fprintf(stdout, "error(parsing): " fmt "\n", __VA_ARGS__); \
} }while(0)
  
  // parse elf
  ELF_Parsed *elf = 0;
  if (try_parse_input) ProfScope("parse elf"){
    elf = elf_parsed_from_data(arena, params->input_elf_data);
    PARSE_CHECK_ERROR(elf, "ELF");
  }
  
  // parse strtab
  String8 strtab = {0};
  if (elf != 0) ProfScope("parse strtab"){
    strtab = elf_section_data_from_idx(elf, elf->strtab_idx);
  }
  
  // parse symtab
  ELF_SymArray symtab = {0};
  if (elf != 0) ProfScope("parse symtab"){
    String8 data = elf_section_data_from_idx(elf, elf->symtab_idx);
    symtab = elf_sym_array_from_data(arena, elf->elf_class, data);
  }
  
  // parse dynsym
  ELF_SymArray dynsym = {0};
  if (elf != 0) ProfScope("parse dynsym"){
    String8 data = elf_section_data_from_idx(elf, elf->dynsym_idx);
    dynsym = elf_sym_array_from_data(arena, elf->elf_class, data);
  }
  
  // parse dwarf
  DWARF_Parsed *dwarf = 0;
  if (elf != 0) ProfScope("parse dwarf"){
    dwarf = dwarf_parsed_from_elf(arena, elf);
    PARSE_CHECK_ERROR(dwarf, "DWARF");
  }
  
  // parse info
  DWARF_InfoParsed *info = 0;
  if (dwarf != 0){
    String8 data = dwarf->debug_data[DWARF_SectionCode_Info];
    if (data.size > 0) ProfScope("parse .debug_info"){
      info = dwarf_info_from_data(arena, data);
      PARSE_CHECK_ERROR(info, "DEBUG INFO");
    }
  }
  
  // parse pubnames
  DWARF_PubNamesParsed *pubnames = 0;
  if (dwarf != 0){
    String8 data = dwarf->debug_data[DWARF_SectionCode_PubNames];
    if (data.size) ProfScope("parse .debug_pubnames"){
      pubnames = dwarf_pubnames_from_data(arena, data);
      PARSE_CHECK_ERROR(pubnames, "DEBUG PUBNAMES");
    }
  }
  
  // parse pubtypes
  DWARF_PubNamesParsed *pubtypes = 0;
  if (dwarf != 0){
    String8 data = dwarf->debug_data[DWARF_SectionCode_PubTypes];
    if (data.size) ProfScope("parse .debug_pubtypes"){
      pubtypes = dwarf_pubnames_from_data(arena, data);
      PARSE_CHECK_ERROR(pubtypes, "DEBUG PUBTYPES");
    }
  }
  
  // parse names
  DWARF_NamesParsed *names = 0;
  if (dwarf != 0){
    String8 data = dwarf->debug_data[DWARF_SectionCode_Names];
    if (data.size) ProfScope("parse .debug_names"){
      names = dwarf_names_from_data(arena, data);
      PARSE_CHECK_ERROR(names, "DEBUG NAMES");
    }
  }
  
  // parse aranges
  DWARF_ArangesParsed *aranges = 0;
  if (dwarf != 0){
    String8 data = dwarf->debug_data[DWARF_SectionCode_Aranges];
    if (data.size) ProfScope("parse .debug_aranges"){
      aranges = dwarf_aranges_from_data(arena, data);
      PARSE_CHECK_ERROR(aranges, "DEBUG ARANGES");
    }
  }
  
  // parse addr
  DWARF_AddrParsed *addr = 0;
  if (dwarf != 0){
    String8 data = dwarf->debug_data[DWARF_SectionCode_Addr];
    if (data.size) ProfScope("parse .debug_addr"){
      addr = dwarf_addr_from_data(arena, data);
      PARSE_CHECK_ERROR(addr, "DEBUG ADDR");
    }
  }
  
#if 0
  // parse abbrev
  DWARF_AbbrevParsed *abbrev = 0;
  if (dwarf != 0){
    String8 data = dwarf->debug_data[DWARF_SectionCode_Abbrev];
    if (data.size > 0) ProfScope("parse .debug_abbrev"){
      DWARF_AbbrevParams abbrev_params = {0};
      abbrev_params.unit_idx_min = params->unit_idx_min;
      abbrev_params.unit_idx_max = params->unit_idx_max;
      abbrev = dwarf_abbrev_from_data(arena, data, &abbrev_params);
      PARSE_CHECK_ERROR(abbrev, "DEBUG ABBREV");
    }
  }
  
  // parse info
  DWARF_InfoParsed *info = 0;
  if (abbrev != 0){
    String8 data = dwarf->debug_data[DWARF_SectionCode_Info];
    if (data.size > 0) ProfScope("parse .debug_info"){
      DWARF_InfoParams info_params = {0};
      info_params.unit_idx_min = params->unit_idx_min;
      info_params.unit_idx_max = params->unit_idx_max;
      info = dwarf_info_from_data(arena, data, &info_params, abbrev);
      PARSE_CHECK_ERROR(info, "DEBUG INFO");
    }
  }
#endif
  
  // dump
  if (params->dump) ProfScope("dump"){
    String8List dump = {0};
    
    // ELF
    if (params->dump_header){
      if (elf != 0){
        str8_list_push(arena, &dump,
                       str8_lit("################################"
                                "################################\n"
                                "ELF:\n"));
        
        // TODO: better stringizers for fields here
        str8_list_pushf(arena, &dump, " elf_class=%u\n", elf->elf_class);
        str8_list_pushf(arena, &dump, " arch=%u\n", elf->arch);
        str8_list_pushf(arena, &dump, " section_count=%llu\n", elf->section_count);
        str8_list_pushf(arena, &dump, " segment_count=%llu\n", elf->segment_count);
        str8_list_pushf(arena, &dump, " vbase=0x%llx\n", elf->vbase);
        str8_list_pushf(arena, &dump, " entry_vaddr=0x%llx\n", elf->vbase);
        
        str8_list_push(arena, &dump, str8_lit("\n"));
      }
    }
    
    // SECTIONS
    if (params->dump_sections){
      if (elf != 0){
        ELF_SectionArray section_array = elf_section_array_from_elf(elf);
        String8Array section_name_array = elf_section_name_array_from_elf(elf);
        
        str8_list_push(arena, &dump,
                       str8_lit("################################"
                                "################################\n"
                                "SECTIONS:\n"));
        
        ELF_Shdr64 *sec = section_array.sections;
        String8 *sec_name = section_name_array.v;
        U64 count = section_array.count;
        for (U64 i = 0 ; i < count; i += 1, sec += 1, sec_name += 1){
          String8 type_string = elf_string_from_section_type(sec->sh_type);
          
          // TODO: better stringizers for fields here
          str8_list_pushf(arena, &dump, " section[%llu]:\n", i);
          str8_list_pushf(arena, &dump, "  name='%.*s'\n", str8_varg(*sec_name));
          str8_list_pushf(arena, &dump, "  type=%.*s\n", str8_varg(type_string));
          str8_list_pushf(arena, &dump, "  flags=0x%llx\n", sec->sh_flags);
          str8_list_pushf(arena, &dump, "  addr=0x%llx\n", sec->sh_addr);
          str8_list_pushf(arena, &dump, "  offset=0x%llx\n", sec->sh_offset);
          str8_list_pushf(arena, &dump, "  size=%llu\n", sec->sh_size);
          str8_list_pushf(arena, &dump, "  link=%u\n", sec->sh_link);
          str8_list_pushf(arena, &dump, "  info=%u\n", sec->sh_info);
          str8_list_pushf(arena, &dump, "  addralign=0x%llx\n", sec->sh_addralign);
          str8_list_pushf(arena, &dump, "  entsize=%llu\n", sec->sh_entsize);
          str8_list_push(arena, &dump, str8_lit("\n"));
        }
      }
    }
    
    // SYMTAB
    if (symtab.count > 0 && params->dump_symtab){
      str8_list_push(arena, &dump,
                     str8_lit("################################"
                              "################################\n"
                              "SYMTAB:\n"));
      str8_list_pushf(arena, &dump, " section: %llu\n", elf->symtab_idx);
      dump_symtab(arena, &dump, &symtab, strtab, 1);
      str8_list_push(arena, &dump, str8_lit("\n"));
    }
    
    // DYNSYM
    if (dynsym.count > 0 && params->dump_dynsym){
      str8_list_push(arena, &dump,
                     str8_lit("################################"
                              "################################\n"
                              "DYNSYM:\n"));
      str8_list_pushf(arena, &dump, " section: %llu\n", elf->dynsym_idx);
      dump_symtab(arena, &dump, &dynsym, strtab, 1);
      str8_list_push(arena, &dump, str8_lit("\n"));
    }
    
    // SEGMENTS
    if (params->dump_segments){
      if (elf != 0){
        ELF_SegmentArray segment_array = elf_segment_array_from_elf(elf);
        
        str8_list_push(arena, &dump,
                       str8_lit("################################"
                                "################################\n"
                                "SEGMENTS:\n"));
        
        ELF_Phdr64 *segments = segment_array.segments;
        U64 count = segment_array.count;
        for (U64 i = 0 ; i < count; i += 1){
          ELF_Phdr64 *seg = segments + i;
          
          // TODO: better stringizers for fields here
          str8_list_pushf(arena, &dump, " segment[%llu]:\n", i);
          str8_list_pushf(arena, &dump, "  p_type=%u\n", seg->p_type);
          str8_list_pushf(arena, &dump, "  p_flags=0x%x\n", seg->p_flags);
          str8_list_pushf(arena, &dump, "  p_offset=0x%llx\n", seg->p_offset);
          str8_list_pushf(arena, &dump, "  p_vaddr=0x%llx\n", seg->p_vaddr);
          str8_list_pushf(arena, &dump, "  p_paddr=0x%llx\n", seg->p_paddr);
          str8_list_pushf(arena, &dump, "  p_filesz=%llu\n", seg->p_filesz);
          str8_list_pushf(arena, &dump, "  p_memsz=%llu\n", seg->p_memsz);
          str8_list_pushf(arena, &dump, "  p_align=%llu\n", seg->p_align);
          str8_list_push(arena, &dump, str8_lit("\n"));
        }
      }
    }
    
    // DEBUG SECTIONS
    if (params->dump_debug_sections){
      if (dwarf != 0){
        str8_list_push(arena, &dump,
                       str8_lit("################################"
                                "################################\n"
                                "DEBUG SECTIONS:\n"));
        
        U32 *debug_section_idx = dwarf->debug_section_idx;
        String8 *debug_data = dwarf->debug_data;
        for (U32 i = 1; i < DWARF_SectionCode_COUNT; i += 1, debug_data += 1){
          U32 idx = debug_section_idx[i];
          String8 name = dwarf_string_from_section_code(i);
          str8_list_pushf(arena, &dump, " %-10.*s section_idx=%u\n", str8_varg(name), idx);
        }
        str8_list_push(arena, &dump, str8_lit("\n"));
      }
    }
    
    // DEBUG INFO
    if (params->dump_debug_info){
      if (info != 0){
        str8_list_push(arena, &dump,
                       str8_lit("################################"
                                "################################\n"
                                "DEBUG INFO:\n"));
        
        U32 i = 0;
        for (DWARF_InfoUnit *unit = info->unit_first;
             unit != 0;
             unit = unit->next, i += 1){
          str8_list_pushf(arena, &dump, " unit[%u]:\n", i);
          dwarf_stringize_info(arena, &dump, unit, 2);
          str8_list_push(arena, &dump, str8_lit("\n"));
        }
        
      }
    }
    
    // DEBUG PUBNAMES
    if (params->dump_debug_pubnames){
      if (pubnames != 0){
        str8_list_push(arena, &dump,
                       str8_lit("################################"
                                "################################\n"
                                "DEBUG PUBNAMES:\n"));
        
        U32 i = 0;
        for (DWARF_PubNamesUnit *unit = pubnames->unit_first;
             unit != 0;
             unit = unit->next, i += 1){
          str8_list_pushf(arena, &dump, " unit[%u]:\n", i);
          dwarf_stringize_pubnames(arena, &dump, unit, 2);
          str8_list_push(arena, &dump, str8_lit("\n"));
        }
        
      }
    }
    
    // DEBUG PUBTYPES
    if (params->dump_debug_pubtypes){
      if (pubtypes != 0){
        str8_list_push(arena, &dump,
                       str8_lit("################################"
                                "################################\n"
                                "DEBUG PUBTYPES:\n"));
        
        U32 i = 0;
        for (DWARF_PubNamesUnit *unit = pubtypes->unit_first;
             unit != 0;
             unit = unit->next, i += 1){
          str8_list_pushf(arena, &dump, " unit[%u]:\n", i);
          dwarf_stringize_pubnames(arena, &dump, unit, 2);
          str8_list_push(arena, &dump, str8_lit("\n"));
        }
        
      }
    }
    
    // DEBUG NAMES
    if (params->dump_debug_names){
      if (names != 0){
        str8_list_push(arena, &dump,
                       str8_lit("################################"
                                "################################\n"
                                "DEBUG NAMES:\n"));
        
        U32 i = 0;
        for (DWARF_NamesUnit *unit = names->unit_first;
             unit != 0;
             unit = unit->next, i += 1){
          str8_list_pushf(arena, &dump, " unit[%u]:\n", i);
          dwarf_stringize_names(arena, &dump, unit, 2);
          str8_list_push(arena, &dump, str8_lit("\n"));
        }
        
      }
    }
    
    // DEBUG ARANGES
    if (params->dump_debug_aranges){
      if (aranges != 0){
        str8_list_push(arena, &dump,
                       str8_lit("################################"
                                "################################\n"
                                "DEBUG ARANGES:\n"));
        
        U32 i = 0;
        for (DWARF_ArangesUnit *unit = aranges->unit_first;
             unit != 0;
             unit = unit->next, i += 1){
          str8_list_pushf(arena, &dump, " unit[%u]:\n", i);
          dwarf_stringize_aranges(arena, &dump, unit, 2);
          str8_list_push(arena, &dump, str8_lit("\n"));
        }
        
      }
    }
    
    // DEBUG ADDR
    if (params->dump_debug_addr){
      if (addr != 0){
        str8_list_push(arena, &dump,
                       str8_lit("################################"
                                "################################\n"
                                "DEBUG ADDR:\n"));
        
        U32 i = 0;
        for (DWARF_AddrUnit *unit = addr->unit_first;
             unit != 0;
             unit = unit->next, i += 1){
          str8_list_pushf(arena, &dump, " unit[%u]:\n", i);
          dwarf_stringize_addr(arena, &dump, unit, 2);
          str8_list_push(arena, &dump, str8_lit("\n"));
        }
        
      }
    }
    
#if 0
    // DEBUG ABBREV
    if (params->dump_debug_abbrev){
      if (abbrev != 0){
        str8_list_push(arena, &dump,
                       str8_lit("################################"
                                "################################\n"
                                "DEBUG ABBREV:\n"));
        
        U32 i = 0;
        for (DWARF_AbbrevUnit *unit = abbrev->unit_first;
             unit != 0;
             unit = unit->next, i += 1){
          U32 j = 0;
          for (DWARF_AbbrevDecl *abbrev_decl = unit->first;
               abbrev_decl != 0;
               abbrev_decl = abbrev_decl->next, j += 1){
            String8 tag_string = dwarf_string_from_tag(abbrev_decl->tag);
            
            str8_list_pushf(arena, &dump, " unit[%u],abbrev[%u]:\n", i, j);
            str8_list_pushf(arena, &dump, "  code=%llu\n", abbrev_decl->abbrev_code);
            str8_list_pushf(arena, &dump, "  tag=%.*s\n", str8_varg(tag_string));
            str8_list_pushf(arena, &dump, "  has_children=%u\n", abbrev_decl->has_children);
            str8_list_pushf(arena, &dump, "  attrib_count=%u\n", abbrev_decl->attrib_count);
            str8_list_pushf(arena, &dump, "  attribs:\n", abbrev_decl->attrib_count);
            
            U32 attrib_count = abbrev_decl->attrib_count;
            DWARF_AbbrevAttribSpec *attrib_spec = abbrev_decl->attrib_specs;
            for (U32 k = 0; k < attrib_count; k += 1, attrib_spec += 1){
              String8 name_string = dwarf_string_from_attribute_name(attrib_spec->name);
              String8 form_string = dwarf_string_from_attribute_form(attrib_spec->form);
              
              str8_list_pushf(arena, &dump, "   [%-14.*s %-10.*s]\n", 
                              str8_varg(name_string), str8_varg(form_string));
            }
            
            str8_list_push(arena, &dump, str8_lit("\n"));
          }
        }
        
      }
    }
#endif
    
#if 0
    // DEBUG INFO
    if (params->dump_debug_info){
      if (info != 0){
        str8_list_push(arena, &dump,
                       str8_lit("################################"
                                "################################\n"
                                "DEBUG INFO:\n"));
        
        U32 i = 0;
        for (DWARF_InfoUnit *unit = info->unit_first;
             unit != 0;
             unit = unit->next, i += 1){
          str8_list_pushf(arena, &dump, " unit[%u]:\n", i);
          str8_list_pushf(arena, &dump, "  [header]\n");
          str8_list_pushf(arena, &dump, "  version=%u\n", unit->dwarf_version);
          str8_list_pushf(arena, &dump, "  offset_size=%u\n", unit->offset_size);
          str8_list_pushf(arena, &dump, "  address_size=%u\n", unit->address_size);
          str8_list_pushf(arena, &dump, "  [extracted attributes]\n");
          str8_list_pushf(arena, &dump, "  langauge=%u\n", (U32)unit->language);
          str8_list_pushf(arena, &dump, "  line_info_offset=%llu\n", unit->line_info_offset);
          str8_list_pushf(arena, &dump, "  vbase=0x%llx\n", unit->vbase);
          str8_list_pushf(arena, &dump, "  str_offsets_base=%llu\n", unit->str_offsets_base);
          str8_list_pushf(arena, &dump, "  addr_base=%llu\n", unit->addr_base);
          str8_list_pushf(arena, &dump, "  rnglists_base=%llu\n", unit->rnglists_base);
          str8_list_pushf(arena, &dump, "  loclists_base=%llu\n", unit->loclists_base);
          dump_entry_tree(arena, &dump, dwarf, unit, unit->entry_root, 2);
          str8_list_push(arena, &dump, str8_lit("\n"));
        }
        
      }
    }
#endif
    
    // print dump
    for (String8Node *node = dump.first;
         node != 0;
         node = node->next){
      fwrite(node->string.str, 1, node->string.size, stdout);
    }
  }
}
