// Copyright (c) 2024 Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) //////////////////////////////// //~ RADDBG Parse API RDI_PROC RDI_ParseStatus rdi_parse(RDI_U8 *data, RDI_U64 size, RDI_Parsed *out) { RDI_ParseStatus result = RDI_ParseStatus_Good; // out header RDI_Header *hdr = 0; { if (sizeof(*hdr) <= size){ hdr = (RDI_Header*)data; } // (errors) if (hdr == 0 || hdr->magic != RDI_MAGIC_CONSTANT){ hdr = 0; result = RDI_ParseStatus_HeaderDoesNotMatch; } if (hdr != 0 && hdr->encoding_version != 1){ hdr = 0; result = RDI_ParseStatus_UnsupportedVersionNumber; } } // out data sections RDI_DataSection *dsecs = 0; RDI_U32 dsec_count = 0; if (hdr != 0){ RDI_U64 opl = (RDI_U64)hdr->data_section_off + (RDI_U64)hdr->data_section_count*sizeof(*dsecs); if (opl <= size){ dsecs = (RDI_DataSection*)(data + hdr->data_section_off); dsec_count = hdr->data_section_count; } // (errors) if (dsecs == 0){ result = RDI_ParseStatus_InvalidDataSecionLayout; } } // extract primary data section indexes RDI_U32 dsec_idx[RDI_DataSectionTag_PRIMARY_COUNT] = {0}; if (result == RDI_ParseStatus_Good){ RDI_DataSection *sec_ptr = dsecs; for (RDI_U32 i = 0; i < dsec_count; i += 1, sec_ptr += 1){ if (sec_ptr->tag < RDI_DataSectionTag_PRIMARY_COUNT){ dsec_idx[sec_ptr->tag] = i; } } } // fill out data block (part 1) if (result == RDI_ParseStatus_Good){ out->raw_data = data; out->raw_data_size = size; out->dsecs = dsecs; out->dsec_count = dsec_count; for (RDI_U32 i = 0; i < RDI_DataSectionTag_PRIMARY_COUNT; i += 1){ out->dsec_idx[i] = dsec_idx[i]; } } // out string table RDI_U8 *string_data = 0; RDI_U64 string_opl = 0; RDI_U32 *string_offs = 0; RDI_U64 string_count = 0; if (result == RDI_ParseStatus_Good){ rdi_parse__extract_primary(out, string_data, &string_opl, RDI_DataSectionTag_StringData); RDI_U64 table_entry_count = 0; rdi_parse__extract_primary(out, string_offs, &table_entry_count, RDI_DataSectionTag_StringTable); if (table_entry_count > 0){ string_count = table_entry_count - 1; } // (errors) if (string_data == 0){ result = RDI_ParseStatus_MissingStringDataSection; } else if (string_offs == 0){ result = RDI_ParseStatus_MissingStringTableSection; } } // out index runs RDI_U32 *idx_run_data = 0; RDI_U64 idx_run_count = 0; if (result == RDI_ParseStatus_Good){ rdi_parse__extract_primary(out, idx_run_data, &idx_run_count, RDI_DataSectionTag_IndexRuns); // (errors) if (idx_run_data == 0){ result = RDI_ParseStatus_MissingIndexRunSection; } } if (result == RDI_ParseStatus_Good){ // fill out primary data structures (part 2) out->string_data = string_data; out->string_offs = string_offs; out->string_data_size = string_opl; out->string_count = string_count; out->idx_run_data = idx_run_data; out->idx_run_count = idx_run_count; { RDI_TopLevelInfo *tli = 0; RDI_U64 dummy = 0; rdi_parse__extract_primary(out, tli, &dummy, RDI_DataSectionTag_TopLevelInfo); if (dummy != 1){ tli = 0; } out->top_level_info = tli; } rdi_parse__extract_primary(out, out->binary_sections, &out->binary_sections_count, RDI_DataSectionTag_BinarySections); rdi_parse__extract_primary(out, out->file_paths, &out->file_paths_count, RDI_DataSectionTag_FilePathNodes); rdi_parse__extract_primary(out, out->source_files, &out->source_files_count, RDI_DataSectionTag_SourceFiles); rdi_parse__extract_primary(out, out->units, &out->units_count, RDI_DataSectionTag_Units); rdi_parse__extract_primary(out, out->unit_vmap, &out->unit_vmap_count, RDI_DataSectionTag_UnitVmap); rdi_parse__extract_primary(out, out->unit_vmap, &out->unit_vmap_count, RDI_DataSectionTag_UnitVmap); rdi_parse__extract_primary(out, out->type_nodes, &out->type_nodes_count, RDI_DataSectionTag_TypeNodes); rdi_parse__extract_primary(out, out->udts, &out->udts_count, RDI_DataSectionTag_UDTs); rdi_parse__extract_primary(out, out->members, &out->members_count, RDI_DataSectionTag_Members); rdi_parse__extract_primary(out, out->enum_members, &out->enum_members_count, RDI_DataSectionTag_EnumMembers); rdi_parse__extract_primary(out, out->global_variables, &out->global_variables_count, RDI_DataSectionTag_GlobalVariables); rdi_parse__extract_primary(out, out->global_vmap, &out->global_vmap_count, RDI_DataSectionTag_GlobalVmap); rdi_parse__extract_primary(out, out->thread_variables, &out->thread_variables_count, RDI_DataSectionTag_ThreadVariables); rdi_parse__extract_primary(out, out->procedures, &out->procedures_count, RDI_DataSectionTag_Procedures); rdi_parse__extract_primary(out, out->scopes, &out->scopes_count, RDI_DataSectionTag_Scopes); rdi_parse__extract_primary(out, out->scope_voffs, &out->scope_voffs_count, RDI_DataSectionTag_ScopeVoffData); rdi_parse__extract_primary(out, out->scope_vmap, &out->scope_vmap_count, RDI_DataSectionTag_ScopeVmap); rdi_parse__extract_primary(out, out->locals, &out->locals_count, RDI_DataSectionTag_Locals); rdi_parse__extract_primary(out, out->location_blocks, &out->location_blocks_count, RDI_DataSectionTag_LocationBlocks); rdi_parse__extract_primary(out, out->location_data, &out->location_data_size, RDI_DataSectionTag_LocationData); { rdi_parse__extract_primary(out, out->name_maps, &out->name_maps_count, RDI_DataSectionTag_NameMaps); RDI_NameMap *name_map_ptr = out->name_maps; RDI_NameMap *name_map_opl = out->name_maps + out->name_maps_count; for (; name_map_ptr < name_map_opl; name_map_ptr += 1){ if (out->name_maps_by_kind[name_map_ptr->kind] == 0){ out->name_maps_by_kind[name_map_ptr->kind] = name_map_ptr; } } } } #if !defined(RDI_DISABLE_NILS) if(out->top_level_info == 0) { out->top_level_info = &rdi_top_level_info_nil; } if(out->binary_sections == 0) { out->binary_sections = &rdi_binary_section_nil; out->binary_sections_count = 1; } if(out->file_paths == 0) { out->file_paths = &rdi_file_path_node_nil; out->file_paths_count = 1; } if(out->source_files == 0) { out->source_files = &rdi_source_file_nil; out->source_files_count = 1; } if(out->units == 0) { out->units = &rdi_unit_nil; out->units_count = 1; } if(out->unit_vmap == 0) { out->unit_vmap = &rdi_vmap_entry_nil; out->unit_vmap_count = 1; } if(out->type_nodes == 0) { out->type_nodes = &rdi_type_node_nil; out->type_nodes_count = 1; } if(out->udts == 0) { out->udts = &rdi_udt_nil; out->udts_count = 1; } if(out->members == 0) { out->members = &rdi_member_nil; out->members_count = 1; } if(out->enum_members == 0) { out->enum_members = &rdi_enum_member_nil; out->enum_members_count = 1; } if(out->global_variables == 0) { out->global_variables = &rdi_global_variable_nil; out->global_variables_count = 1; } if(out->global_vmap == 0) { out->global_vmap = &rdi_vmap_entry_nil; out->global_vmap_count = 1; } if(out->thread_variables == 0) { out->thread_variables = &rdi_thread_variable_nil; out->thread_variables_count = 1; } if(out->procedures == 0) { out->procedures = &rdi_procedure_nil; out->procedures_count = 1; } if(out->scopes == 0) { out->scopes = &rdi_scope_nil; out->scopes_count = 1; } if(out->scope_voffs == 0) { out->scope_voffs = &rdi_voff_nil; out->scope_voffs_count = 1; } if(out->scope_vmap == 0) { out->scope_vmap = &rdi_vmap_entry_nil; out->scope_vmap_count = 1; } if(out->locals == 0) { out->locals = &rdi_local_nil; out->locals_count = 1; } if(out->location_blocks == 0) { out->location_blocks = &rdi_location_block_nil; out->location_blocks_count = 1; } #endif return(result); } RDI_PROC RDI_U64 rdi_decompressed_size_from_parsed(RDI_Parsed *rdi) { RDI_U64 decompressed_size = rdi->raw_data_size; for(RDI_U64 dsec_idx = 0; dsec_idx < rdi->dsec_count; dsec_idx += 1) { decompressed_size += (rdi->dsecs[dsec_idx].unpacked_size - rdi->dsecs[dsec_idx].encoded_size); } return decompressed_size; } RDI_PROC RDI_U8* rdi_string_from_idx(RDI_Parsed *parsed, RDI_U32 idx, RDI_U64 *len_out){ RDI_U8 *result = 0; RDI_U64 len_result = 0; if (idx < parsed->string_count){ RDI_U32 off_raw = parsed->string_offs[idx]; RDI_U32 opl_raw = parsed->string_offs[idx + 1]; RDI_U32 opl = rdi_parse__min(opl_raw, parsed->string_data_size); RDI_U32 off = rdi_parse__min(off_raw, opl); result = parsed->string_data + off; len_result = opl - off; } *len_out = len_result; return(result); } RDI_PROC RDI_U32* rdi_idx_run_from_first_count(RDI_Parsed *parsed, RDI_U32 raw_first, RDI_U32 raw_count, RDI_U32 *n_out){ RDI_U32 raw_opl = raw_first + raw_count; RDI_U32 opl = rdi_parse__min(raw_opl, parsed->idx_run_count); RDI_U32 first = rdi_parse__min(raw_first, opl); RDI_U32 *result = 0; if (first < parsed->idx_run_count){ result = parsed->idx_run_data + first; } *n_out = opl - first; return(result); } //- line info RDI_PROC void rdi_line_info_from_unit(RDI_Parsed *p, RDI_Unit *unit, RDI_ParsedLineInfo *out){ RDI_U64 line_info_voff_count = 0; RDI_U64 *voffs = (RDI_U64*) rdi_data_from_dsec(p, unit->line_info_voffs_data_idx, sizeof(RDI_U64), RDI_DataSectionTag_LineInfoVoffs, &line_info_voff_count); RDI_U64 line_info_count_raw = 0; RDI_Line *lines = (RDI_Line*) rdi_data_from_dsec(p, unit->line_info_data_idx, sizeof(RDI_Line), RDI_DataSectionTag_LineInfoData, &line_info_count_raw); RDI_U64 column_info_count_raw = 0; RDI_Column *cols = (RDI_Column*) rdi_data_from_dsec(p, unit->line_info_col_data_idx, sizeof(RDI_Column), RDI_DataSectionTag_LineInfoColumns, &column_info_count_raw); RDI_U32 line_info_count_a = (line_info_voff_count > 0)?line_info_voff_count - 1:0; RDI_U32 line_info_count = rdi_parse__min(line_info_count_a, line_info_count_raw); RDI_U32 column_info_count = rdi_parse__min(column_info_count_raw, line_info_count); out->voffs = voffs; out->lines = lines; out->cols = cols; out->count = line_info_count; out->col_count = column_info_count; } RDI_PROC RDI_U64 rdi_line_info_idx_from_voff(RDI_ParsedLineInfo *line_info, RDI_U64 voff) { RDI_U64 result = 0; if (line_info->count > 0 && line_info->voffs[0] <= voff && voff < line_info->voffs[line_info->count - 1]){ // assuming: (i < j) -> (vmap[i].voff < vmap[j].voff) // find i such that: (vmap[i].voff <= voff) && (voff < vmap[i + 1].voff) RDI_U32 first = 0; RDI_U32 opl = line_info->count; for (;;){ RDI_U32 mid = (first + opl)/2; if (line_info->voffs[mid] < voff){ first = mid; } else if (line_info->voffs[mid] > voff){ opl = mid; } else{ first = mid; break; } if (opl - first <= 1){ break; } } result = (RDI_U64)first; } return(result); } RDI_PROC void rdi_line_map_from_source_file(RDI_Parsed *p, RDI_SourceFile *srcfile, RDI_ParsedLineMap *out){ RDI_U64 num_count = 0; RDI_U32 *nums = (RDI_U32*) rdi_data_from_dsec(p, srcfile->line_map_nums_data_idx, sizeof(RDI_U32), RDI_DataSectionTag_LineMapNumbers, &num_count); RDI_U64 range_count = 0; RDI_U32 *ranges = (RDI_U32*) rdi_data_from_dsec(p, srcfile->line_map_range_data_idx, sizeof(RDI_U32), RDI_DataSectionTag_LineMapRanges, &range_count); RDI_U64 voff_count = 0; RDI_U64 *voffs = (RDI_U64*) rdi_data_from_dsec(p, srcfile->line_map_voff_data_idx, sizeof(RDI_U64), RDI_DataSectionTag_LineMapVoffs, &voff_count); RDI_U32 count_a = (range_count > 0)?(range_count - 1):0; RDI_U32 count_b = rdi_parse__min(count_a, num_count); RDI_U32 count = rdi_parse__min(count_b, srcfile->line_map_count); out->nums = nums; out->ranges = ranges; out->voffs = voffs; out->count = count; out->voff_count = voff_count; } RDI_PROC RDI_U64* rdi_line_voffs_from_num(RDI_ParsedLineMap *map, RDI_U32 linenum, RDI_U32 *n_out){ RDI_U64 *result = 0; *n_out = 0; RDI_U32 closest_i = 0; if (map->count > 0 && map->nums[0] <= linenum){ // assuming: (i < j) -> (nums[i] < nums[j]) // find i such that: (nums[i] <= linenum) && (linenum < nums[i + 1]) RDI_U32 *nums = map->nums; RDI_U32 first = 0; RDI_U32 opl = map->count; for (;;){ RDI_U32 mid = (first + opl)/2; if (nums[mid] < linenum){ first = mid; } else if (nums[mid] > linenum){ opl = mid; } else{ first = mid; break; } if (opl - first <= 1){ break; } } closest_i = first; } // round up instead of down if possible if (closest_i + 1 < map->count && map->nums[closest_i] < linenum){ closest_i += 1; } // set result if possible if (closest_i < map->count){ RDI_U32 first = map->ranges[closest_i]; RDI_U32 opl = map->ranges[closest_i + 1]; if (opl < map->voff_count){ result = map->voffs + first; *n_out = opl - first; } } return(result); } //- vmaps RDI_PROC RDI_U64 rdi_vmap_idx_from_voff(RDI_VMapEntry *vmap, RDI_U32 vmap_count, RDI_U64 voff){ RDI_U64 result = 0; if (vmap_count > 0 && vmap[0].voff <= voff && voff < vmap[vmap_count - 1].voff){ // assuming: (i < j) -> (vmap[i].voff < vmap[j].voff) // find i such that: (vmap[i].voff <= voff) && (voff < vmap[i + 1].voff) RDI_U32 first = 0; RDI_U32 opl = vmap_count; for (;;){ RDI_U32 mid = (first + opl)/2; if (vmap[mid].voff < voff){ first = mid; } else if (vmap[mid].voff > voff){ opl = mid; } else{ first = mid; break; } if (opl - first <= 1){ break; } } result = (RDI_U64)vmap[first].idx; } return(result); } //- name maps RDI_PROC RDI_NameMap* rdi_name_map_from_kind(RDI_Parsed *p, RDI_NameMapKind kind){ RDI_NameMap *result = 0; if (0 < kind && kind < RDI_NameMapKind_COUNT){ result = p->name_maps_by_kind[kind]; } return(result); } RDI_PROC void rdi_name_map_parse(RDI_Parsed *p, RDI_NameMap *mapptr, RDI_ParsedNameMap *out){ out->buckets = 0; out->bucket_count = 0; if (mapptr != 0){ out->buckets = (RDI_NameMapBucket*) rdi_data_from_dsec(p, mapptr->bucket_data_idx, sizeof(RDI_NameMapBucket), RDI_DataSectionTag_NameMapBuckets, &out->bucket_count); out->nodes = (RDI_NameMapNode*) rdi_data_from_dsec(p, mapptr->node_data_idx, sizeof(RDI_NameMapNode), RDI_DataSectionTag_NameMapNodes, &out->node_count); } } RDI_PROC RDI_NameMapNode* rdi_name_map_lookup(RDI_Parsed *p, RDI_ParsedNameMap *map, RDI_U8 *str, RDI_U64 len){ RDI_NameMapNode *result = 0; if (map->bucket_count > 0){ RDI_NameMapBucket *buckets = map->buckets; RDI_U64 bucket_count = map->bucket_count; RDI_U64 hash = rdi_hash(str, len); RDI_U64 bucket_index = hash%bucket_count; RDI_NameMapBucket *bucket = map->buckets + bucket_index; RDI_NameMapNode *node = map->nodes + bucket->first_node; RDI_NameMapNode *node_opl = node + bucket->node_count; for (;node < node_opl; node += 1){ // extract a string from this node RDI_U64 nlen = 0; RDI_U8 *nstr = rdi_string_from_idx(p, node->string_idx, &nlen); // compare this to the needle string RDI_S32 match = 0; if (nlen == len){ RDI_U8 *a = str; RDI_U8 *aopl = str + len; RDI_U8 *b = nstr; for (;a < aopl && *a == *b; a += 1, b += 1); match = (a == aopl); } // stop with a matching node in result if (match){ result = node; break; } } } return(result); } RDI_PROC RDI_U32* rdi_matches_from_map_node(RDI_Parsed *p, RDI_NameMapNode *node, RDI_U32 *n_out){ RDI_U32 *result = 0; *n_out = 0; if (node != 0){ if (node->match_count == 1){ result = &node->match_idx_or_idx_run_first; *n_out = 1; } else{ result = rdi_idx_run_from_first_count(p, node->match_idx_or_idx_run_first, node->match_count, n_out); } } return(result); } //- common helpers RDI_PROC RDI_U64 rdi_first_voff_from_proc(RDI_Parsed *p, RDI_U32 proc_id){ RDI_U64 result = 0; if (0 < proc_id && proc_id < p->procedures_count){ RDI_Procedure *proc = p->procedures + proc_id; RDI_U32 scope_id = proc->root_scope_idx; if (0 < scope_id && scope_id < p->scopes_count){ RDI_Scope *scope = p->scopes + scope_id; if (scope->voff_range_first < scope->voff_range_opl && scope->voff_range_first < p->scope_voffs_count){ result = p->scope_voffs[scope->voff_range_first]; } } } return(result); } //////////////////////////////// //~ RADDBG Parsing Helpers RDI_PROC void* rdi_data_from_dsec(RDI_Parsed *parsed, RDI_U32 idx, RDI_U32 item_size, RDI_DataSectionTag expected_tag, RDI_U64 *count_out) { void *result = 0; RDI_U32 count_result = 0; if(0 < idx && idx < parsed->dsec_count) { RDI_DataSection *ds = parsed->dsecs + idx; if(ds->tag == expected_tag) { RDI_U64 encoded_opl = ds->off + ds->encoded_size; if(encoded_opl <= parsed->raw_data_size) { count_result = ds->unpacked_size/item_size; result = (parsed->raw_data + ds->off); } } } *count_out = count_result; return(result); }