parse CIE and FDE out of runtime ELF

This commit is contained in:
Nikita Smith
2025-11-07 14:39:45 -08:00
committed by Ryan Fleury
parent 4e2c983b0f
commit 7f705923e6
3 changed files with 441 additions and 310 deletions
+206 -76
View File
@@ -1901,11 +1901,13 @@ DW_REG_WRITE(ctrl_unwind_reg_write_dwarf_x64)
internal CTRL_UnwindStepResult internal CTRL_UnwindStepResult
ctrl_unwind_step__dwarf(CTRL_Handle process_handle, CTRL_Handle module_handle, Arch arch, void *regs, U64 endt_us) ctrl_unwind_step__dwarf(CTRL_Handle process_handle, CTRL_Handle module_handle, Arch arch, void *regs, U64 endt_us)
{ {
Temp scratch = scratch_begin(0, 0);
CTRL_UnwindStepResult result = { .flags = CTRL_UnwindFlag_Error }; CTRL_UnwindStepResult result = { .flags = CTRL_UnwindFlag_Error };
// gather context for virtual stack unwinder // gather context for virtual stack unwinder
U64 rebase = 0; U64 rebase = 0;
B32 is_unwind_data_eh = 0; B32 is_unwind_eh = 0;
String8 unwind_data = {0}; String8 unwind_data = {0};
EH_FrameHdr eh_frame_hdr = {0}; EH_FrameHdr eh_frame_hdr = {0};
EH_PtrCtx eh_ptr_ctx = {0}; EH_PtrCtx eh_ptr_ctx = {0};
@@ -1920,7 +1922,7 @@ ctrl_unwind_step__dwarf(CTRL_Handle process_handle, CTRL_Handle module_handle, A
if(ctrl_handle_match(n->module, module_handle)) if(ctrl_handle_match(n->module, module_handle))
{ {
rebase = n->rebase; rebase = n->rebase;
is_unwind_data_eh = n->is_dwarf_unwind_data_eh; is_unwind_eh = n->is_unwind_eh;
unwind_data = n->dwarf_unwind_data; unwind_data = n->dwarf_unwind_data;
eh_frame_hdr = n->eh_frame_hdr; eh_frame_hdr = n->eh_frame_hdr;
eh_ptr_ctx = n->eh_ptr_ctx; eh_ptr_ctx = n->eh_ptr_ctx;
@@ -1934,25 +1936,103 @@ ctrl_unwind_step__dwarf(CTRL_Handle process_handle, CTRL_Handle module_handle, A
U64 cfi_ip = ip + rebase; U64 cfi_ip = ip + rebase;
// use .eh_frame_hdr to quickly locate nearest FDE offset // use .eh_frame_hdr to quickly locate nearest FDE offset
U64 fde_offset = eh_find_nearest_fde(eh_frame_hdr, &eh_ptr_ctx, cfi_ip); U64 fde_addr = eh_find_nearest_fde(eh_frame_hdr, &eh_ptr_ctx, cfi_ip);
if(fde_offset < unwind_data.size) if(fde_addr != max_U64)
{ {
// parse call frame info // parse call frame info
DW_CIE cie = {0}; DW_CIE cie = {0};
DW_FDE fde = {0}; DW_FDE fde = {0};
B32 is_cfi_parsed = is_unwind_data_eh ? B32 is_cfi_parsed = 0;
eh_parse_cfi(unwind_data, fde_offset, arch, &eh_ptr_ctx, &cie, &fde) : if(is_unwind_eh)
dw_parse_cfi(unwind_data, fde_offset, arch, &cie, &fde); {
B32 is_stale = 0;
// extract FDE info
Rng1U64 fde_vrange = {0};
DW_Format fde_format = DW_Format_Null;
String8 fde_data = {0};
U64 cie_addr = 0;
{
// parse FDE length
U32 first_four_bytes = 0;
if(!ctrl_process_memory_read_struct(process_handle, fde_addr, &is_stale, &first_four_bytes, endt_us)) { goto eh_parse_exit; }
if(first_four_bytes == max_U32)
{
U64 length = 0;
if(!ctrl_process_memory_read_struct(process_handle, fde_addr + sizeof(first_four_bytes), &is_stale, &length, endt_us)) { goto eh_parse_exit; }
fde_vrange = r1u64(fde_addr, fde_addr + sizeof(first_four_bytes) + length);
fde_format = DW_Format_64Bit;
}
else
{
fde_vrange = r1u64(fde_addr, fde_addr + sizeof(first_four_bytes) + first_four_bytes);
fde_format = DW_Format_32Bit;
}
// read out whole FDE
void *fde_raw = push_array(scratch.arena, U8, dim_1u64(fde_vrange));
if(!ctrl_process_memory_read(process_handle, fde_vrange, &is_stale, fde_raw, endt_us)) { goto eh_parse_exit; }
fde_data = str8(fde_raw, dim_1u64(fde_vrange));
// compute CIE address
U64 cie_delta_off = fde_format == DW_Format_32Bit ? 4 : 12;
U64 cie_delta = 0;
U64 cie_delta_size = str8_deserial_read_dwarf_uint(fde_data, cie_delta_off, fde_format, &cie_delta);
if (cie_delta_size == 0) { goto eh_parse_exit; }
cie_addr = (fde_addr + cie_delta_off) - cie_delta;
}
// extract CIE info
Rng1U64 cie_vrange = {0};
DW_Format cie_format = DW_Format_Null;
String8 cie_data = {0};
{
// parse CIE length
U32 first_four_bytes = 0;
if(!ctrl_process_memory_read_struct(process_handle, cie_addr, &is_stale, &first_four_bytes, endt_us)) { goto eh_parse_exit; }
if(first_four_bytes == max_U32)
{
U64 length = 0;
if(!ctrl_process_memory_read_struct(process_handle, cie_addr + sizeof(first_four_bytes), &is_stale, &length, endt_us)) { goto eh_parse_exit; }
cie_vrange = r1u64(cie_addr, cie_addr + sizeof(first_four_bytes) + length);
cie_format = DW_Format_64Bit;
}
else
{
cie_vrange = r1u64(cie_addr, cie_addr + sizeof(first_four_bytes) + first_four_bytes);
cie_format = DW_Format_32Bit;
}
// read out whole CIE
void *cie_raw = push_array(scratch.arena, U8, dim_1u64(cie_vrange));
if(!ctrl_process_memory_read(process_handle, cie_vrange, &is_stale, cie_raw, endt_us)) { goto eh_parse_exit; }
cie_data = str8(cie_raw, dim_1u64(cie_vrange));
}
// parse CIE and FDE
if(eh_parse_cie(cie_data, cie_format, arch, cie_vrange.min, &eh_ptr_ctx, &cie))
{
is_cfi_parsed = eh_parse_fde(fde_data, fde_format, fde_vrange.min, &cie, &eh_ptr_ctx, &fde);
}
eh_parse_exit:;
if(is_stale)
{
result.flags = CTRL_UnwindFlag_Stale;
}
}
else
{
is_cfi_parsed = dw_parse_cfi(unwind_data, fde_addr, arch, &cie, &fde);
}
if(is_cfi_parsed) if(is_cfi_parsed)
{ {
Temp scratch = scratch_begin(0, 0);
// setup pointer decoder ops // setup pointer decoder ops
DW_DecodePtr *decode_ptr_func = 0; DW_DecodePtr *decode_ptr_func = 0;
void *decode_ptr_ctx = 0; void *decode_ptr_ctx = 0;
if(is_unwind_data_eh) if(is_unwind_eh)
{ {
EH_DecodePtrCtx *decode_ptr_ctx_eh = push_array(scratch.arena, EH_DecodePtrCtx, 1); EH_DecodePtrCtx *decode_ptr_ctx_eh = push_array(scratch.arena, EH_DecodePtrCtx, 1);
decode_ptr_ctx_eh->ptr_ctx = &eh_ptr_ctx; decode_ptr_ctx_eh->ptr_ctx = &eh_ptr_ctx;
@@ -2034,12 +2114,6 @@ ctrl_unwind_step__dwarf(CTRL_Handle process_handle, CTRL_Handle module_handle, A
default: { InvalidPath; } break; default: { InvalidPath; } break;
} }
} }
scratch_end(scratch);
}
else
{
Assert(0 && "failed to parse CFI");
} }
} }
else else
@@ -2047,6 +2121,7 @@ ctrl_unwind_step__dwarf(CTRL_Handle process_handle, CTRL_Handle module_handle, A
// TODO: if IP does not have FDE, does this mean function is a leaf? // TODO: if IP does not have FDE, does this mean function is a leaf?
} }
scratch_end(scratch);
return result; return result;
} }
@@ -3639,28 +3714,21 @@ ctrl_thread__append_program_defined_bp_traps(Arena *arena, CTRL_Entity *bp, DMN_
//- rjf: module lifetime open/close work //- rjf: module lifetime open/close work
internal void internal
ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_range, String8 path) DW_MEM_READ(ctrl_dmn_mem_read)
{ {
////////////////////////////// U64 read = dmn_process_read(((CTRL_Handle *)ud)->dmn_handle, r1u64(addr, addr + size), buffer);
//- rjf: parse module image info return read == size ? DW_UnwindStatus_Ok : DW_UnwindStatus_Fail;
// }
internal void
ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_range, String8 path, Rng1U64 elf_phdr_vrange, U64 elf_phdr_entsize)
{
Temp scratch = scratch_begin(0,0);
Arena *arena = arena_alloc(); Arena *arena = arena_alloc();
COFF_MachineType machine = COFF_MachineType_Unknown;
PE_ImageFileCharacteristics file_characteristics = 0;
PE_IntelPdata *pdatas = 0;
U64 pdatas_count = 0;
U64 rebase = 0;
B32 is_dwarf_unwind_data_eh = 0;
String8 dwarf_unwind_data = {0};
EH_FrameHdr eh_frame_hdr = {0};
EH_PtrCtx eh_ptr_ctx = {0};
U64 entry_point_voff = 0; U64 entry_point_voff = 0;
Rng1U64 tls_vaddr_range = {0}; Rng1U64 tls_vaddr_range = {0};
U32 pdb_dbg_time = 0;
U32 pdb_dbg_age = 0;
Guid pdb_dbg_guid = {0};
String8 pdb_dbg_path = {0};
U32 rdi_dbg_time = 0; U32 rdi_dbg_time = 0;
Guid rdi_dbg_guid = {0}; Guid rdi_dbg_guid = {0};
String8 exe_dbg_path = {0}; String8 exe_dbg_path = {0};
@@ -3668,6 +3736,30 @@ ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_
String8 raddbg_data = {0}; String8 raddbg_data = {0};
Rng1U64 raddbg_section_voff_range = r1u64(0, 0); Rng1U64 raddbg_section_voff_range = r1u64(0, 0);
Rng1U64 raddbg_is_attached_section_voff_range = r1u64(0, 0); Rng1U64 raddbg_is_attached_section_voff_range = r1u64(0, 0);
PE_IntelPdata *pdatas = 0;
U64 pdatas_count = 0;
U32 pdb_dbg_time = 0;
U32 pdb_dbg_age = 0;
Guid pdb_dbg_guid = {0};
String8 pdb_dbg_path = {0};
U64 rebase = 0;
B32 is_unwind_eh = 0;
EH_FrameHdr eh_frame_hdr = {0};
EH_PtrCtx eh_ptr_ctx = { .raw_base_vaddr = max_U64, .text_vaddr = max_U64, .data_vaddr = max_U64, .func_vaddr = max_U64, .ptr_align = 0 };
// read module's signature bytes
U64 module_sig_size = Max(elf_magic_string.size, sizeof(PE_DosMagic));
U8 *module_sig_bytes = push_array(scratch.arena, U8, module_sig_size);
dmn_process_read(process.dmn_handle, rng_1u64(vaddr_range.min, vaddr_range.min + module_sig_size), module_sig_bytes);
//////////////////////////////
//- parse PE module
//
PE_DosMagic dos_magic = *(PE_DosMagic *)module_sig_bytes;
if(dos_magic == PE_DOS_MAGIC)
{
ProfScope("unpack relevant PE info") ProfScope("unpack relevant PE info")
{ {
B32 is_valid = 1; B32 is_valid = 1;
@@ -3699,12 +3791,7 @@ ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_
COFF_FileHeader file_header = {0}; COFF_FileHeader file_header = {0};
if(is_valid) if(is_valid)
{ {
if(dmn_process_read_struct(process.dmn_handle, vaddr_range.min + file_header_off, &file_header)) if(!dmn_process_read_struct(process.dmn_handle, vaddr_range.min + file_header_off, &file_header))
{
machine = file_header.machine;
file_characteristics = file_header.flags;
}
else
{ {
is_valid = 0; is_valid = 0;
} }
@@ -3720,8 +3807,6 @@ ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_
U32 data_dir_count = 0; U32 data_dir_count = 0;
if(opt_ext_size > 0) if(opt_ext_size > 0)
{ {
Temp scratch = scratch_begin(0, 0);
// rjf: read magic number // rjf: read magic number
U16 opt_ext_magic = 0; U16 opt_ext_magic = 0;
dmn_process_read_struct(process.dmn_handle, vaddr_range.min + opt_ext_off_range.min, &opt_ext_magic); dmn_process_read_struct(process.dmn_handle, vaddr_range.min + opt_ext_off_range.min, &opt_ext_magic);
@@ -3902,44 +3987,90 @@ ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_
U8 new_value = 1; U8 new_value = 1;
dmn_process_write_struct(process.dmn_handle, vaddr_range.min + raddbg_is_attached_section_voff_range.min, &new_value); dmn_process_write_struct(process.dmn_handle, vaddr_range.min + raddbg_is_attached_section_voff_range.min, &new_value);
} }
}
}
}
//////////////////////////////
//- parse ELF module
//
else if(str8_match(str8(module_sig_bytes, elf_magic_string.size), elf_magic_string, 0))
{
U64 e_entry = 0;
ELF_Type e_type = ELF_Type_None;
{
U8 *elf_sig = dmn_process_read_raw(scratch.arena, process.dmn_handle, rng_1u64(vaddr_range.min, vaddr_range.min + sizeof(elf_sig[0]) * ELF_Identifier_Max));
if(elf_sig == 0) { goto elf_exit; }
switch(elf_sig[ELF_Identifier_Class])
{
default:
case ELF_Class_None:{}break;
case ELF_Class_32:
{
NotImplemented;
}break;
case ELF_Class_64:
{
ELF_Hdr64 *ehdr = dmn_process_read_raw(scratch.arena, process.dmn_handle, rng_1u64(vaddr_range.min, vaddr_range.min + sizeof(*ehdr)));
if(ehdr == 0) { goto elf_exit; }
e_entry = ehdr->e_entry;
e_type = ehdr->e_type;
}break;
}
}
#if 0 // find and parse .eh_frame_hdr
// TODO(ns): temporary hack to test DWARF unwinder on windows -- remove when done Rng1U64 eh_frame_hdr_vrange = {0};
String8 eh_frame_hdr_data = {0};
{ {
for EachIndex(idx, sec_count) void *phdrs_raw = dmn_process_read_raw(scratch.arena, process.dmn_handle, elf_phdr_vrange);
if(phdrs_raw == 0) { goto elf_exit; }
if(elf_phdr_entsize == sizeof(ELF_Phdr32))
{ {
String8 section_name = str8_cstring((char *)sec[idx].name); NotImplemented;
if(str8_match(section_name, str8_lit("/130"), 0))
{
U64 restore_pos = arena_pos(arena);
Rng1U64 debug_frame_voff_range = rng_1u64(sec[idx].voff, sec[idx].voff + sec[idx].vsize);
Rng1U64 debug_frame_vaddr_range = shift_1u64(debug_frame_voff_range, vaddr_range.min);
void *debug_frame_buffer = push_array(arena, U8, sec[idx].vsize);
U64 debug_frame_read_size = dmn_process_read(process.dmn_handle, debug_frame_vaddr_range, debug_frame_buffer);
if(debug_frame_read_size == sec[idx].vsize)
{
U64 default_base = pe_get_default_base_addr(file_characteristics, machine);
Arch arch = ctrl_arch_from_process_handle(process);
String8 debug_frame = str8(debug_frame_buffer, debug_frame_read_size);
DW_CallFrameInfo dwarf_cfi = dw_call_frame_info_from_data(scratch.arena, arch, debug_frame);
String8 eh_frame_hdr_data = eh_frame_hdr_from_call_frame_info(arena, dwarf_cfi.fde_count, dwarf_cfi.fde_offsets, dwarf_cfi.fde);
rebase = default_base - vaddr_range.min; // DL overwrites image base in the optional header, if image is linked with a custom base this breaks
dwarf_unwind_data = debug_frame;
is_dwarf_unwind_data_eh = 0;
eh_frame_hdr = eh_parse_frame_hdr(eh_frame_hdr_data, byte_size_from_arch(arch), 0);
} }
else else if(elf_phdr_entsize == sizeof(ELF_Phdr64))
{ {
arena_pop_to(arena, restore_pos); U64 elf_phcount = dim_1u64(elf_phdr_vrange) / elf_phdr_entsize;
} ELF_Phdr64 *phdrs64 = phdrs_raw;
for EachIndex(phdr_idx, elf_phcount)
{
ELF_Phdr64 *phdr = phdrs64 + phdr_idx;
if(phdr->p_type == ELF_PType_GnuEHFrame)
{
eh_frame_hdr_vrange = r1u64(phdr->p_vaddr, phdr->p_vaddr + phdr->p_memsz);
eh_frame_hdr_data = dmn_process_read_block(arena, process.dmn_handle, eh_frame_hdr_vrange);
is_unwind_eh = 1;
break; break;
} }
} }
} }
#endif
scratch_end(scratch);
} }
// parse .eh_frame_hdr
Arch arch = ctrl_arch_from_process_handle(process);
eh_ptr_ctx.raw_base_vaddr = eh_frame_hdr_vrange.min;
eh_ptr_ctx.data_vaddr = eh_frame_hdr_vrange.min;
eh_frame_hdr = eh_parse_frame_hdr(eh_frame_hdr_data, byte_size_from_arch(arch), &eh_ptr_ctx);
// set entry point
if (e_entry != 0)
{
entry_point_voff = e_entry - vaddr_range.min;
}
// TODO: is there a way to detect DWARF in runtime ELF?
if(1)
{
exe_dbg_path = path;
}
if(e_type == ELF_Type_Dyn)
{
rebase = -vaddr_range.min;
}
elf_exit:;
} }
////////////////////////////// //////////////////////////////
@@ -3947,7 +4078,6 @@ ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_
// //
String8 initial_debug_info_path = str8_zero(); String8 initial_debug_info_path = str8_zero();
{ {
Temp scratch = scratch_begin(0, 0);
String8 exe_folder = str8_chop_last_slash(path); String8 exe_folder = str8_chop_last_slash(path);
String8List dbg_path_candidates = {0}; String8List dbg_path_candidates = {0};
// //
@@ -3988,7 +4118,6 @@ ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_
break; break;
} }
} }
scratch_end(scratch);
} }
////////////////////////////// //////////////////////////////
@@ -4003,7 +4132,7 @@ ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_
MutexScopeW(stripe->rw_mutex) MutexScopeW(stripe->rw_mutex)
{ {
CTRL_ModuleImageInfoCacheNode *node = 0; CTRL_ModuleImageInfoCacheNode *node = 0;
for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next) for EachNode(n, CTRL_ModuleImageInfoCacheNode, slot->first)
{ {
if(ctrl_handle_match(n->module, module)) if(ctrl_handle_match(n->module, module))
{ {
@@ -4020,8 +4149,7 @@ ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_
node->pdatas = pdatas; node->pdatas = pdatas;
node->pdatas_count = pdatas_count; node->pdatas_count = pdatas_count;
node->rebase = rebase; node->rebase = rebase;
node->is_dwarf_unwind_data_eh = is_dwarf_unwind_data_eh; node->is_unwind_eh = is_unwind_eh;
node->dwarf_unwind_data = dwarf_unwind_data;
node->eh_frame_hdr = eh_frame_hdr; node->eh_frame_hdr = eh_frame_hdr;
node->eh_ptr_ctx = eh_ptr_ctx; node->eh_ptr_ctx = eh_ptr_ctx;
node->entry_point_voff = entry_point_voff; node->entry_point_voff = entry_point_voff;
@@ -4029,6 +4157,8 @@ ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_
} }
} }
} }
scratch_end(scratch);
} }
internal void internal void
@@ -4362,7 +4492,7 @@ ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg,
CTRL_Event *out_evt1 = ctrl_event_list_push(scratch.arena, &evts); CTRL_Event *out_evt1 = ctrl_event_list_push(scratch.arena, &evts);
String8 module_path = path_normalized_from_string(scratch.arena, event->string); String8 module_path = path_normalized_from_string(scratch.arena, event->string);
U64 exe_timestamp = os_properties_from_file_path(module_path).modified; U64 exe_timestamp = os_properties_from_file_path(module_path).modified;
ctrl_thread__module_open(process_handle, module_handle, r1u64(event->address, event->address+event->size), module_path); ctrl_thread__module_open(process_handle, module_handle, r1u64(event->address, event->address+event->size), module_path, event->elf_phdr_vrange, event->elf_phdr_entsize);
out_evt1->kind = CTRL_EventKind_NewModule; out_evt1->kind = CTRL_EventKind_NewModule;
out_evt1->msg_id = msg->msg_id; out_evt1->msg_id = msg->msg_id;
out_evt1->entity = module_handle; out_evt1->entity = module_handle;
+2 -2
View File
@@ -598,7 +598,7 @@ struct CTRL_ModuleImageInfoCacheNode
PE_IntelPdata *pdatas; PE_IntelPdata *pdatas;
U64 pdatas_count; U64 pdatas_count;
U64 rebase; U64 rebase;
B32 is_dwarf_unwind_data_eh; B32 is_unwind_eh;
String8 dwarf_unwind_data; String8 dwarf_unwind_data;
EH_FrameHdr eh_frame_hdr; EH_FrameHdr eh_frame_hdr;
EH_PtrCtx eh_ptr_ctx; EH_PtrCtx eh_ptr_ctx;
@@ -987,7 +987,7 @@ internal void ctrl_thread__append_resolved_process_user_bp_traps(Arena *arena, C
internal void ctrl_thread__append_program_defined_bp_traps(Arena *arena, CTRL_Entity *bp, DMN_TrapChunkList *traps_out); internal void ctrl_thread__append_program_defined_bp_traps(Arena *arena, CTRL_Entity *bp, DMN_TrapChunkList *traps_out);
//- rjf: module lifetime open/close work //- rjf: module lifetime open/close work
internal void ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_range, String8 path); internal void ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_range, String8 path, Rng1U64 elf_phdr_vrange, U64 elf_phdr_entsize);
internal void ctrl_thread__module_close(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_range); internal void ctrl_thread__module_close(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_range);
//- rjf: attached process running/event gathering //- rjf: attached process running/event gathering
+1
View File
@@ -486,6 +486,7 @@ struct PE_LoadConfig64
#define PE_MAGIC 0x00004550u #define PE_MAGIC 0x00004550u
#define PE_PE32_MAGIC 0x010bu #define PE_PE32_MAGIC 0x010bu
#define PE_PE32PLUS_MAGIC 0x020bu #define PE_PE32PLUS_MAGIC 0x020bu
typedef U16 PE_DosMagic;
typedef struct PE_MipsPdata PE_MipsPdata; typedef struct PE_MipsPdata PE_MipsPdata;
struct PE_MipsPdata struct PE_MipsPdata