From 1bcd66329f7003f8e5aa607b7a9259d80cbda9a3 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Tue, 15 Oct 2024 16:28:39 -0700 Subject: [PATCH] factored parser code out of pdb --- src/pdb/pdb.c | 1006 +------------------------- src/pdb/pdb.h | 519 ++++++------- src/pdb/pdb_parse.c | 982 +++++++++++++++++++++++++ src/pdb/pdb_parse.h | 244 +++++++ src/raddbg/raddbg_main.c | 2 + src/rdi_from_pdb/rdi_from_pdb_main.c | 2 + 6 files changed, 1485 insertions(+), 1270 deletions(-) create mode 100644 src/pdb/pdb_parse.c create mode 100644 src/pdb/pdb_parse.h diff --git a/src/pdb/pdb.c b/src/pdb/pdb.c index 01f8ca3a..ad2b1e10 100644 --- a/src/pdb/pdb.c +++ b/src/pdb/pdb.c @@ -1,1003 +1,27 @@ // Copyright (c) 2024 Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -//////////////////////////////// -//~ PDB Parser Functions - -internal PDB_Info* -pdb_info_from_data(Arena *arena, String8 data){ - ProfBegin("pdb_info_from_data"); - - // get header - PDB_InfoHeader *header = 0; - if (data.size >= sizeof(*header)){ - header = (PDB_InfoHeader*)data.str; - } - - PDB_Info *result = 0; - if (header != 0){ - // read guid - COFF_Guid *auth_guid = 0; - U32 after_auth_guid_off = sizeof(*header); - switch (header->version){ - case PDB_Version_VC70_DEP: - case PDB_Version_VC70: - case PDB_Version_VC80: - case PDB_Version_VC110: - case PDB_Version_VC140: - { - auth_guid = (COFF_Guid*)(data.str + after_auth_guid_off); - after_auth_guid_off = sizeof(*header) + sizeof(*auth_guid); - }break; - - default: - {}break; - } - - if (header->version != 0){ - // table layout: names - U32 names_len_off = after_auth_guid_off; - U32 names_len = 0; - if (names_len_off + 4 <= data.size){ - names_len = *(U32*)(data.str + names_len_off); - } - - U32 names_base_off = names_len_off + 4; - U32 names_base_opl = names_base_off + names_len; - - // table layout: hash table - U32 hash_table_count_off = names_base_opl; - U32 hash_table_max_off = hash_table_count_off + 4; - - U32 hash_table_count = 0; - U32 hash_table_max = 0; - if (hash_table_max_off + 4 <= data.size){ - hash_table_count = *(U32*)(data.str + hash_table_count_off); - hash_table_max = *(U32*)(data.str + hash_table_max_off); - } - - // table layout: words - U32 num_present_words_off = hash_table_max_off + 4; - U32 num_present_words = 0; - if (hash_table_max_off + 4 <= data.size){ - num_present_words = *(U32*)(data.str + num_present_words_off); - } - U32 present_words_array_off = num_present_words_off + 4; - - U32 num_deleted_words_off = present_words_array_off + num_present_words*sizeof(U32); - U32 num_deleted_words = 0; - if (num_deleted_words_off + 4 <= data.size){ - num_deleted_words = *(U32*)(data.str + num_deleted_words_off); - } - U32 deleted_words_array_off = num_deleted_words_off + 4; - - // table layout: epilogue - U32 epilogue_base_off = deleted_words_array_off + num_deleted_words*sizeof(U32); - - // read table - if (hash_table_count > 0 && epilogue_base_off <= data.size){ - PDB_InfoNode *first = 0; - PDB_InfoNode *last = 0; - - U32 record_off = epilogue_base_off; - for (U32 i = 0; i < hash_table_count; i += 1, record_off += 8){ - U32 *record = (U32*)(data.str + record_off); - U32 relative_name_off = record[0]; - MSF_StreamNumber sn = (MSF_StreamNumber)record[1]; - - U32 name_off = names_base_off + relative_name_off; - String8 name = str8_cstring_capped((char*)(data.str + name_off), - (char*)(data.str + names_base_opl)); - - // push info node - PDB_InfoNode *node = push_array(arena, PDB_InfoNode, 1); - SLLQueuePush(first, last, node); - node->string = name; - node->sn = sn; - } - - result = push_array(arena, PDB_Info, 1); - result->first = first; - result->last = last; - result->auth_guid = *auth_guid; - } - - } - } - - ProfEnd(); - - return(result); -} - -internal PDB_NamedStreamTable* -pdb_named_stream_table_from_info(Arena *arena, PDB_Info *info){ - ProfBegin("pdb_named_stream_table_from_info"); - - // mapping "NamedStream" indexes to strings - struct StreamNameIndexPair{ - PDB_NamedStream index; - String8 name; - }; - struct StreamNameIndexPair pairs[] = { - {PDB_NamedStream_HEADER_BLOCK, str8_lit("/src/headerblock")}, - {PDB_NamedStream_STRTABLE , str8_lit("/names")}, - {PDB_NamedStream_LINK_INFO , str8_lit("/LinkInfo")}, - }; - - // build baked table - PDB_NamedStreamTable *result = push_array(arena, PDB_NamedStreamTable, 1); - struct StreamNameIndexPair *p = pairs; - for (U64 i = 0; i < ArrayCount(pairs); i += 1, p += 1){ - String8 name = p->name; - - // get info node with this name - PDB_InfoNode *match = 0; - for (PDB_InfoNode *node = info->first; - node != 0; - node = node->next){ - if (str8_match(name, node->string, 0)){ - match = node; - break; - } - } - - // if match found save stream number - if (match != 0){ - result->sn[p->index] = match->sn; - } - else{ - result->sn[p->index] = 0xFFFF; - } - } - - ProfEnd(); - - return(result); -} - -internal PDB_Strtbl* -pdb_strtbl_from_data(Arena *arena, String8 data){ - ProfBegin("pdb_strtbl_from_data"); - - // get header - PDB_StrtblHeader *header = 0; - if (sizeof(*header) <= data.size){ - header = (PDB_StrtblHeader*)data.str; - } - - PDB_Strtbl *result = 0; - if (header != 0 && header->magic == PDB_StrtblHeader_MAGIC && header->version == 1){ - U32 strblock_size_off = sizeof(*header); - U32 strblock_size = 0; - if (strblock_size_off + 4 <= data.size){ - strblock_size = *(U32*)(data.str + strblock_size_off); - } - U32 strblock_off = strblock_size_off + 4; - - U32 bucket_count_off = strblock_off + strblock_size; - U32 bucket_count = 0; - if (bucket_count_off + 4 <= data.size){ - bucket_count = *(U32*)(data.str + bucket_count_off); - } - - U32 bucket_array_off = bucket_count_off + 4; - U32 bucket_array_size = bucket_count*sizeof(PDB_StringIndex); - - if (bucket_array_off + bucket_array_size <= data.size){ - result = push_array(arena, PDB_Strtbl, 1); - result->data = data; - result->bucket_count = bucket_count; - result->strblock_min = strblock_off; - result->strblock_max = strblock_off + strblock_size; - result->buckets_min = bucket_array_off; - result->buckets_max = bucket_array_off + bucket_array_size; - } - } - - ProfEnd(); - - return(result); -} - -internal PDB_DbiParsed* -pdb_dbi_from_data(Arena *arena, String8 data){ - ProfBegin("pdb_dbi_from_data"); - - // get header - PDB_DbiHeader *header = 0; - if (sizeof(*header) <= data.size){ - header = (PDB_DbiHeader*)data.str; - } - - PDB_DbiParsed *result = 0; - if (header != 0 && header->sig == PDB_DbiHeaderSignature_V1){ - // extract range sizes - U64 range_size[PDB_DbiRange_COUNT]; - range_size[PDB_DbiRange_ModuleInfo] = header->module_info_size; - range_size[PDB_DbiRange_SecCon] = header->sec_con_size; - range_size[PDB_DbiRange_SecMap] = header->sec_map_size; - range_size[PDB_DbiRange_FileInfo] = header->file_info_size; - range_size[PDB_DbiRange_TSM] = header->tsm_size; - range_size[PDB_DbiRange_EcInfo] = header->ec_info_size; - range_size[PDB_DbiRange_DbgHeader] = header->dbg_header_size; - - // fill result - result = push_array(arena, PDB_DbiParsed, 1); - result->data = data; - result->machine_type = header->machine; - result->gsi_sn = header->gsi_sn; - result->psi_sn = header->psi_sn; - result->sym_sn = header->sym_sn; - - - // fill result's range offsets - { - U64 cursor = sizeof(*header); - for (U64 i = 0; i < (U64)(PDB_DbiRange_COUNT); i += 1){ - result->range_off[i] = cursor; - cursor += range_size[i]; - cursor = ClampTop(cursor, data.size); - } - result->range_off[PDB_DbiRange_COUNT] = cursor; - } - - // fill result's debug streams - U64 dbg_streams_min = result->range_off[PDB_DbiRange_DbgHeader]; - U64 dbg_streams_max = result->range_off[PDB_DbiRange_DbgHeader + 1]; - U64 dbg_streams_size_raw = dbg_streams_max - dbg_streams_min; - U64 dbg_streams_size = ClampTop(dbg_streams_size_raw, sizeof(result->dbg_streams)); - MemoryCopy(result->dbg_streams, data.str + dbg_streams_min, dbg_streams_size); - if (dbg_streams_size < sizeof(result->dbg_streams)){ - U64 filled_count = dbg_streams_size/sizeof(MSF_StreamNumber); - MemorySet(result->dbg_streams + filled_count, 0xff, - (ArrayCount(result->dbg_streams) - filled_count)*sizeof(MSF_StreamNumber)); - } - } - - ProfEnd(); - - return(result); -} - -internal PDB_TpiParsed* -pdb_tpi_from_data(Arena *arena, String8 data){ - ProfBegin("pdb_tpi_from_data"); - - // get header - PDB_TpiHeader *header = 0; - if (sizeof(*header) <= data.size){ - header = (PDB_TpiHeader*)data.str; - } - - PDB_TpiParsed *result = 0; - if (header != 0 && header->version == PDB_TpiVersion_IMPV80){ - U64 leaf_first_raw = header->header_size; - U64 leaf_first = ClampTop(leaf_first_raw, data.size); - U64 leaf_opl_raw = leaf_first + header->leaf_data_size; - U64 leaf_opl = ClampTop(leaf_opl_raw, data.size); - - result = push_array(arena, PDB_TpiParsed, 1); - result->data = data; - - result->leaf_first = leaf_first; - result->leaf_opl = leaf_opl; - result->itype_first = header->ti_lo; - result->itype_opl = header->ti_hi; - - result->hash_sn = header->hash_sn; - result->hash_sn_aux = header->hash_sn_aux; - result->hash_key_size = header->hash_key_size; - result->hash_bucket_count = header->hash_bucket_count; - result->hash_vals_off = header->hash_vals_off; - result->hash_vals_size = header->hash_vals_size; - result->itype_off = header->itype_off; - result->itype_size = header->itype_size; - result->hash_adj_off = header->hash_adj_off; - result->hash_adj_size = header->hash_adj_size; - } - - ProfEnd(); - - return(result); -} - -internal PDB_TpiHashParsed* -pdb_tpi_hash_from_data(Arena *arena, PDB_Strtbl *strtbl, PDB_TpiParsed *tpi, String8 data, String8 aux_data){ - ProfBegin("pdb_tpi_hash_from_data"); - - PDB_TpiHashParsed *result = 0; - - U32 stride = tpi->hash_key_size; - U32 bucket_count = tpi->hash_bucket_count; - if (1 <= stride && stride <= 8 && bucket_count > 0 && data.str != 0){ - - // allocate buckets - PDB_TpiHashBlock **buckets = push_array(arena, PDB_TpiHashBlock*, bucket_count); - - // extract "hash" array - U8 *hashes = data.str + tpi->hash_vals_off; - U8 *hash_opl = hashes + tpi->hash_vals_size; - - // for each index in the array... - CV_TypeId itype = tpi->itype_first; - U8 *hash_cursor = hashes; - for (;hash_cursor + stride <= hash_opl;){ - - // read index - U64 bucket_idx = 0; - MemoryCopy(&bucket_idx, hash_cursor, stride); - - // save to map - if (bucket_idx < bucket_count){ - PDB_TpiHashBlock *block = buckets[bucket_idx]; - if (block == 0 || block->local_count == ArrayCount(block->itypes)){ - block = push_array(arena, PDB_TpiHashBlock, 1); - SLLStackPush(buckets[bucket_idx], block); - } - if(block->local_count != 0) - { - MemoryCopy(block->itypes+1, block->itypes, sizeof(CV_TypeId)*block->local_count); - } - block->itypes[0] = itype; - block->local_count += 1; - } - - // advance cursor - hash_cursor += stride; - itype += 1; - } - - //- rjf: compute bucket mask - U32 bucket_mask = 0; - if(IsPow2OrZero(bucket_count)) - { - bucket_mask = bucket_count-1; - } - - //- rjf: apply hash adjustments, to pull correct type IDs to the front of - // the chains - if(tpi->hash_adj_size != 0) - { - // NOTE(rjf): this table is laid out in the following format: - // - // pair_count: U32 -> # of name_index/type_index pairs - // slot_count: U32 -> # of slots in this hash table - // present_bit_array_count: U32 -> count for next array - // present_bit_array: U32[present_bit_array_count] -> 1 bit per slot, "is present" - // deleted_bit_array_count: U32 -> count for next array - // deleted_bit_array: U32[deleted_bit_array_count] -> 1 bit per slot, "is deleted" - // (U32, U32)[pair_count] -> array of name_index/type_index pairs - // - U8 *adjs = data.str + tpi->hash_adj_off; - U8 *adjs_opl = adjs + tpi->hash_adj_size; - U8 *adjs_cursor = adjs; - U32 pair_count = *(U32 *)adjs_cursor; - adjs_cursor += sizeof(U32); - U32 slot_count = *(U32 *)adjs_cursor; - adjs_cursor += sizeof(U32); - U32 present_bit_array_count = *(U32 *)adjs_cursor; // skip present_bit_array - adjs_cursor += sizeof(U32); - adjs_cursor += present_bit_array_count*sizeof(U32); - U32 deleted_bit_array_count = *(U32 *)adjs_cursor; // skip deleted_bit_array - adjs_cursor += sizeof(U32); - adjs_cursor += deleted_bit_array_count*sizeof(U32); - U32 adjs_stride = sizeof(U32)*2; - U32 pair_idx = 0; - for(;adjs_cursor < adjs_opl && pair_idx < pair_count; - adjs_cursor += adjs_stride, pair_idx += 1) - { - U32 name_off = ((U32 *)adjs_cursor)[0]; - CV_TypeId type_id = ((CV_TypeId *)adjs_cursor)[1]; - String8 string = pdb_strtbl_string_from_off(strtbl, name_off); - U32 hash = pdb_string_hash1(string); - U32 bucket_idx = ((bucket_mask != 0) ? hash&bucket_mask : hash%bucket_count); - PDB_TpiHashBlock *prev_block = 0; - for(PDB_TpiHashBlock *block = buckets[bucket_idx]; - block != 0; - prev_block = block, block = block->next) - { - for(U32 local_idx = 0; - local_idx < block->local_count && local_idx < ArrayCount(block->itypes); - local_idx += 1) - { - if(block->itypes[local_idx] == type_id) - { - if(prev_block != 0) - { - prev_block->next = block->next; - block->next = buckets[bucket_idx]; - buckets[bucket_idx] = block; - } - if(local_idx != 0) - { - Swap(CV_TypeId, block->itypes[0], block->itypes[local_idx]); - } - break; - } - } - } - } - } - - // fill result - result = push_array(arena, PDB_TpiHashParsed, 1); - result->data = data; - result->aux_data = aux_data; - result->buckets = buckets; - result->bucket_count = bucket_count; - result->bucket_mask = bucket_mask; - } - - ProfEnd(); - - return(result); -} - -internal PDB_GsiParsed* -pdb_gsi_from_data(Arena *arena, String8 data){ - ProfBegin("pdb_gsi_from_data"); - - // get header - PDB_GsiHeader *header = 0; - if (sizeof(*header) <= data.size){ - header = (PDB_GsiHeader*)data.str; - } - - PDB_GsiParsed *result = 0; - if (header != 0 && header->signature == PDB_GsiSignature_Basic && - header->version == PDB_GsiVersion_V70 && header->num_buckets != 0){ - Temp scratch = scratch_begin(&arena, 1); - - // hash offset - U32 hash_record_array_off = sizeof(*header); - - // bucket count - U32 slot_count = 4097; - - // array offsets - U32 bitmask_u32_count = CeilIntegerDiv(slot_count, 32); - U32 bitmask_byte_size = bitmask_u32_count*4; - U32 bitmask_off = hash_record_array_off + header->hr_len; - U32 offsets_off = bitmask_off + bitmask_byte_size; - - // get bitmask & packed offset arrays - U8 *bitmasks = 0; - U8 *packed_offsets = 0; - if (bitmask_off + bitmask_byte_size <= data.size){ - bitmasks = (data.str + bitmask_off); - packed_offsets = (data.str + offsets_off); - } - U32 packed_offset_count = (data.size - offsets_off)/4; - - // unpack - U32 *unpacked_offsets = 0; - if (packed_offsets != 0){ - unpacked_offsets = push_array(scratch.arena, U32, slot_count); - - U32 *bitmask_ptr = (U32*)bitmasks; - U32 *bitmask_opl = bitmask_ptr + bitmask_u32_count; - U32 *src_ptr = (U32*)packed_offsets; - U32 *src_opl = src_ptr + packed_offset_count; - U32 *dst_ptr = unpacked_offsets; - U32 *dst_opl = dst_ptr + slot_count; - for (; bitmask_ptr < bitmask_opl && src_ptr < src_opl; bitmask_ptr += 1){ - U32 bits = *bitmask_ptr; - U32 src_max = (U32)(src_opl - src_ptr); - U32 dst_max = (U32)(dst_opl - dst_ptr); - U32 k_max0 = ClampTop(32, dst_max); - U32 k_max = ClampTop(k_max0, src_max); - for (U32 k = 0; k < k_max; k += 1){ - if ((bits & 1) == 1){ - *dst_ptr = *src_ptr; - src_ptr += 1; - } - else{ - *dst_ptr = 0xFFFFFFFF; - } - dst_ptr += 1; - bits >>= 1; - } - } - for (; dst_ptr < dst_opl; dst_ptr += 1){ - *dst_ptr = 0xFFFFFFFF; - } - } - - // construct table - B32 bad_table = 0; - if (unpacked_offsets != 0){ - result = push_array(arena, PDB_GsiParsed, 1); - - // hash records - PDB_GsiHashRecord *hash_records = (PDB_GsiHashRecord*)(data.str + hash_record_array_off); - U32 hash_record_count = header->hr_len/sizeof(PDB_GsiHashRecord); - - // * We unpack hash records into the the table by scanning backwards through the - // * hash records. Neighboring values in unpacked_offsets *sort of* form counts, but we - // * have to skip the max-U32s (sloppy PDB nonsense). - - // * PDBs put one extra slot at the beginning of the encoded buckets that is mean - // * to be padding for modifying the buffer in place. After decoding there are 4096 buckets, - // * in the encoded buckets there are 4097. We are meant to drop the first one. - - // build table - PDB_GsiHashRecord *hash_record_ptr = hash_records + hash_record_count - 1; - U32 prev_n = hash_record_count; - for (U32 i = slot_count; i > 1;){ - i -= 1; - if (unpacked_offsets[i] != 0xFFFFFFFF){ - // determine hash record range to use - // * The "12" here is the result of some really sloppy PDB magic. - U32 n = unpacked_offsets[i]/12; - if (n > prev_n){ - bad_table = 1; - break; - } - U32 num_steps = prev_n - n; - - // fill this bucket - U32 *bucket_offs = push_array_aligned(arena, U32, num_steps, 4); - for (U32 j = num_steps; j > 0;){ - j -= 1; - // * The "- 1" is more sloppy PDB magic. - bucket_offs[j] = hash_record_ptr->symbol_off - 1; - hash_record_ptr -= 1; - } - PDB_GsiBucket *bucket = &result->buckets[i - 1]; - bucket->count = num_steps; - bucket->offs = bucket_offs; - - // update prev_n - prev_n = n; - } - } - } - - scratch_end(scratch); - } - - ProfEnd(); - - return(result); -} - -internal PDB_CoffSectionArray* -pdb_coff_section_array_from_data(Arena *arena, String8 data){ - U64 count = data.size/sizeof(COFF_SectionHeader); - - PDB_CoffSectionArray *result = push_array(arena, PDB_CoffSectionArray, 1); - result->sections = (COFF_SectionHeader*)data.str; - result->count = count; - return(result); -} - -internal PDB_CompUnitArray* -pdb_comp_unit_array_from_data(Arena *arena, String8 data){ - PDB_CompUnitNode *first = 0; - PDB_CompUnitNode *last = 0; - U64 count = 0; - - U64 cursor = 0; - for (;cursor + sizeof(PDB_DbiCompUnitHeader) <= data.size;){ - // get header - PDB_DbiCompUnitHeader *header = (PDB_DbiCompUnitHeader*)(data.str + cursor); - - // get names - U64 name_off = cursor + sizeof(*header); - String8 name = str8_cstring_capped((char *)(data.str + name_off), (char *)(data.str + data.size)); - - U64 name2_off = name_off + name.size + 1; - String8 name2 = str8_cstring_capped((char *)(data.str + name2_off), (char *)(data.str + data.size)); - - U64 after_name2_off = name2_off + name2.size + 1; - - // save mod info - PDB_CompUnitNode *node = push_array_no_zero(arena, PDB_CompUnitNode, 1); - SLLQueuePush(first, last, node); - count += 1; - node->unit.sn = header->sn; - node->unit.obj_name = name; - node->unit.group_name = name2; - - // fill range offsets - U32 *range_buf = node->unit.range_off; - { - // fill the buffer with size of each range - range_buf[PDB_DbiCompUnitRange_Symbols] = header->symbols_size; - range_buf[PDB_DbiCompUnitRange_C11] = header->c11_lines_size; - range_buf[PDB_DbiCompUnitRange_C13] = header->c13_lines_size; - Assert(PDB_DbiCompUnitRange_C13 + 1 == PDB_DbiCompUnitRange_COUNT); - - // in-place sizes -> offs conversion - U64 i = 0; - U32 range_cursor = 0; - for (; i < (U64)(PDB_DbiCompUnitRange_COUNT); i += 1){ - U64 adv = range_buf[i]; - range_buf[i] = range_cursor; - range_cursor += adv; - } - range_buf[i] = range_cursor; - - // skip 4 byte signature in symbols range - if (range_buf[1] >= 4){ - range_buf[0] += 4; - } - } - - // update cursor - cursor = AlignPow2(after_name2_off, 4); - } - - - // fill result - PDB_CompUnit **units = push_array_no_zero(arena, PDB_CompUnit*, count); - { - U64 idx = 0; - for (PDB_CompUnitNode *node = first; - node != 0; - node = node->next, idx += 1){ - units[idx] = &node->unit; - } - } - - PDB_CompUnitArray *result = push_array(arena, PDB_CompUnitArray, 1); - result->units = units; - result->count = count; - - return(result); -} - -internal PDB_CompUnitContributionArray* -pdb_comp_unit_contribution_array_from_data(Arena *arena, String8 data, - PDB_CoffSectionArray *sections){ - PDB_CompUnitContribution *contributions = 0; - U64 count = 0; - if (data.size >= sizeof(PDB_DbiSectionContribVersion)){ - PDB_DbiSectionContribVersion *version = (PDB_DbiSectionContribVersion*)data.str; - - // determine array layout from version - U32 item_size = 0; - U32 array_off = 0; - switch (*version){ - default: - { - // TODO(allen): do we have a test case for this? - item_size = sizeof(PDB_DbiSectionContrib40); - }break; - case PDB_DbiSectionContribVersion_1: - { - item_size = sizeof(PDB_DbiSectionContrib); - array_off = sizeof(*version); - }break; - case PDB_DbiSectionContribVersion_2: - { - item_size = sizeof(PDB_DbiSectionContrib2); - array_off = sizeof(*version); - }break; - } - - // allocate ranges - U64 max_count = (data.size - array_off)/item_size; - contributions = push_array_no_zero(arena, PDB_CompUnitContribution, max_count); - - // binary section info - U64 section_count = sections->count; - COFF_SectionHeader* section_headers = sections->sections; - - // fill array - PDB_CompUnitContribution *contribution_ptr = contributions; - U64 cursor = array_off; - for (; cursor + item_size <= data.size; cursor += item_size){ - PDB_DbiSectionContrib40 *sc = (PDB_DbiSectionContrib40*)(data.str + cursor); - if (sc->size > 0 && 1 <= sc->sec && sc->sec <= section_count){ - U64 voff = section_headers[sc->sec - 1].voff + sc->sec_off; - - contribution_ptr->mod = sc->mod; - contribution_ptr->voff_first = voff; - contribution_ptr->voff_opl = voff + sc->size; - contribution_ptr += 1; - } - } - count = (U64)(contribution_ptr - contributions); - } - - // fill result - PDB_CompUnitContributionArray *result = push_array(arena, PDB_CompUnitContributionArray, 1); - result->contributions = contributions; - result->count = count; - - return(result); -} - - -//////////////////////////////// -//~ PDB Definition Functions - internal U32 -pdb_string_hash1(String8 string){ +pdb_hash_v1(String8 string) +{ U32 result = 0; U8 *ptr = string.str; U8 *opl = ptr + (string.size&(~3)); - for (; ptr < opl; ptr += 4){ result ^= *(U32*)ptr; } - if ((string.size&2) != 0){ result ^= *(U16*)ptr; ptr += 2; } - if ((string.size&1) != 0){ result ^= *ptr; } + for(; ptr < opl; ptr += 4) + { + result ^= *(U32*)ptr; + } + if((string.size&2) != 0) + { + result ^= *(U16*)ptr; ptr += 2; + } + if((string.size&1) != 0) + { + result ^= *ptr; + } result |= 0x20202020; result ^= (result >> 11); result ^= (result >> 16); - return(result); + return result; } - -//////////////////////////////// -//~ PDB Dbi Functions - -internal String8 -pdb_data_from_dbi_range(PDB_DbiParsed *dbi, PDB_DbiRange range){ - String8 result = {0}; - if (range < PDB_DbiRange_COUNT){ - U64 first = dbi->range_off[range]; - U64 opl = dbi->range_off[range + 1]; - result.str = dbi->data.str + first; - result.size = opl - first; - } - return(result); -} - -internal String8 -pdb_data_from_unit_range(MSF_Parsed *msf, PDB_CompUnit *unit, PDB_DbiCompUnitRange range){ - String8 result = {0}; - if (range < PDB_DbiCompUnitRange_COUNT){ - String8 full_stream_data = msf_data_from_stream(msf, unit->sn); - - U64 first_raw = unit->range_off[range]; - U64 opl_raw = unit->range_off[range + 1]; - U64 opl = ClampTop(opl_raw, full_stream_data.size); - U64 first = ClampTop(first_raw, opl); - - result.str = full_stream_data.str + first; - result.size = opl - first; - } - return(result); -} - -//////////////////////////////// -//~ PDB Tpi Functions - -internal String8 -pdb_leaf_data_from_tpi(PDB_TpiParsed *tpi){ - String8 data = tpi->data; - U8 *first = data.str + tpi->leaf_first; - U8 *opl = data.str + tpi->leaf_opl; - String8 result = str8_range(first, opl); - return(result); -} - -internal CV_TypeIdArray -pdb_tpi_itypes_from_name(Arena *arena, PDB_TpiHashParsed *tpi_hash, CV_LeafParsed *leaf, - String8 name, B32 compare_unique_name, U32 output_cap){ - U32 hash = pdb_string_hash1(name); - U32 bucket_idx = ((tpi_hash->bucket_mask != 0) ? - hash&tpi_hash->bucket_mask : - hash%tpi_hash->bucket_count); - - CV_TypeId itype_first = leaf->itype_first; - CV_TypeId itype_opl = leaf->itype_opl; - String8 data = leaf->data; - - Temp scratch = scratch_begin(&arena, 1); - struct Chain{ - struct Chain *next; - CV_TypeId itype; - }; - struct Chain *first = 0; - struct Chain *last = 0; - U32 count = 0; - - for (PDB_TpiHashBlock *block = tpi_hash->buckets[bucket_idx]; - block != 0; - block = block->next){ - U32 local_count = block->local_count; - CV_TypeId *itype_ptr = block->itypes; - for (U32 i = 0; i < local_count; i += 1, itype_ptr += 1){ - - String8 extracted_name = {0}; - - CV_TypeId itype = *itype_ptr; - if (itype_first <= itype && itype < itype_opl){ - CV_RecRange *range = &leaf->leaf_ranges.ranges[itype - leaf->itype_first]; - if (range->off + range->hdr.size <= data.size){ - U8 *first = data.str + range->off + 2; - U64 cap = range->hdr.size - 2; - - switch (range->hdr.kind){ - default:break; - - case CV_LeafKind_CLASS: - case CV_LeafKind_STRUCTURE: - { - if (sizeof(CV_LeafStruct) <= cap){ - CV_LeafStruct *lf_struct = (CV_LeafStruct*)first; - - if (!(lf_struct->props & CV_TypeProp_FwdRef)){ - // size - U8 *numeric_ptr = (U8*)(lf_struct + 1); - CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, first + cap); - - // name - U8 *name_ptr = numeric_ptr + size.encoded_size; - String8 name = str8_cstring_capped((char*)name_ptr, (char *)(first + cap)); - - // unique name - if (compare_unique_name){ - if (lf_struct->props & CV_TypeProp_HasUniqueName) { - U8 *unique_name_ptr = name_ptr + name.size + 1; - String8 unique_name = str8_cstring_capped((char*)unique_name_ptr, (char *)(first + cap)); - extracted_name = unique_name; - } - } - else{ - extracted_name = name; - } - } - } - }break; - - case CV_LeafKind_CLASS2: - case CV_LeafKind_STRUCT2: - { - if (sizeof(CV_LeafStruct2) <= cap){ - CV_LeafStruct2 *lf_struct = (CV_LeafStruct2*)first; - - if (!(lf_struct->props & CV_TypeProp_FwdRef)){ - // size - U8 *numeric_ptr = (U8*)(lf_struct + 1); - CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, first + cap); - - // name - U8 *name_ptr = numeric_ptr + size.encoded_size; - String8 name = str8_cstring_capped((char*)name_ptr, (char *)(first + cap)); - - // unique name - if (compare_unique_name){ - if (lf_struct->props & CV_TypeProp_HasUniqueName) { - U8 *unique_name_ptr = name_ptr + name.size + 1; - String8 unique_name = str8_cstring_capped((char*)unique_name_ptr, (char *)(first + cap)); - extracted_name = unique_name; - } - } - else{ - extracted_name = name; - } - } - } - }break; - - case CV_LeafKind_UNION: - { - if (sizeof(CV_LeafUnion) <= cap){ - CV_LeafUnion *lf_union = (CV_LeafUnion*)first; - - if (!(lf_union->props & CV_TypeProp_FwdRef)){ - // size - U8 *numeric_ptr = (U8*)(lf_union + 1); - CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, first + cap); - - // name - U8 *name_ptr = numeric_ptr + size.encoded_size; - String8 name = str8_cstring_capped((char*)name_ptr, (char *)(first + cap)); - - // unique name - if (compare_unique_name){ - if (lf_union->props & CV_TypeProp_HasUniqueName) { - U8 *unique_name_ptr = name_ptr + name.size + 1; - String8 unique_name = str8_cstring_capped((char*)unique_name_ptr, (char *)(first + cap)); - extracted_name = unique_name; - } - } - else{ - extracted_name = name; - } - } - } - }break; - - case CV_LeafKind_ENUM: - { - if (sizeof(CV_LeafEnum) <= cap){ - CV_LeafEnum *lf_enum = (CV_LeafEnum*)first; - - if (!(lf_enum->props & CV_TypeProp_FwdRef)){ - // name - U8 *name_ptr = (U8*)(lf_enum + 1); - String8 name = str8_cstring_capped((char*)name_ptr, (char *)(first + cap)); - - // unique name - if (compare_unique_name){ - if (lf_enum->props & CV_TypeProp_HasUniqueName) { - U8 *unique_name_ptr = name_ptr + name.size + 1; - String8 unique_name = str8_cstring_capped((char*)unique_name_ptr, (char *)(first + cap)); - extracted_name = unique_name; - } - } - else{ - extracted_name = name; - } - } - } - }break; - } - } - } - - if (str8_match(extracted_name, name, 0)){ - struct Chain *chain = push_array(scratch.arena, struct Chain, 1); - SLLQueuePush(first, last, chain); - count += 1; - chain->itype = itype; - if (count == output_cap){ - goto dblbreak; - } - } - } - } - - dblbreak:; - - - // assemble result - CV_TypeId *itypes = push_array_aligned(arena, CV_TypeId, count, 8); - { - CV_TypeId *itype_ptr = itypes; - for (struct Chain *node = first; - node != 0; - node = node->next, itype_ptr += 1){ - *itype_ptr = node->itype; - } - } - CV_TypeIdArray result = {0}; - result.itypes = itypes; - result.count = count; - - scratch_end(scratch); - - return(result); -} - -internal CV_TypeId -pdb_tpi_first_itype_from_name(PDB_TpiHashParsed *tpi_hash, CV_LeafParsed *tpi_leaf, - String8 name, B32 compare_unique_name){ - Temp scratch = scratch_begin(0, 0); - CV_TypeIdArray array = pdb_tpi_itypes_from_name(scratch.arena, tpi_hash, tpi_leaf, - name, compare_unique_name, 1); - CV_TypeId result = 0; - if (array.count > 0){ - result = array.itypes[0]; - } - - scratch_end(scratch); - return(result); -} - -//////////////////////////////// -//~ PDB Strtbl Functions - -internal String8 -pdb_strtbl_string_from_off(PDB_Strtbl *strtbl, U32 off){ - U32 strblock_max = strtbl->strblock_max; - U32 full_off_raw = strtbl->strblock_min + off; - U32 full_off = ClampTop(full_off_raw, strblock_max); - String8 result = str8_cstring_capped((char*)(strtbl->data.str + full_off), - (char*)(strtbl->data.str + strblock_max)); - return(result); -} - -internal String8 -pdb_strtbl_string_from_index(PDB_Strtbl *strtbl, PDB_StringIndex idx){ - String8 result = {0}; - if (idx < strtbl->bucket_count){ - U32 off = *(U32*)(strtbl->data.str + strtbl->buckets_min + idx*4); - result = pdb_strtbl_string_from_off(strtbl, off); - } - return(result); -} diff --git a/src/pdb/pdb.h b/src/pdb/pdb.h index 187ff2a7..e8faa4ac 100644 --- a/src/pdb/pdb.h +++ b/src/pdb/pdb.h @@ -7,59 +7,145 @@ // https://github.com/microsoft/microsoft-pdb/tree/master/PDB //////////////////////////////// -//~ PDB Format Types +//~ PDB String Table Types -typedef U32 PDB_Version; -enum{ - PDB_Version_VC2 = 19941610, - PDB_Version_VC4 = 19950623, - PDB_Version_VC41 = 19950814, - PDB_Version_VC50 = 19960307, - PDB_Version_VC98 = 19970604, - PDB_Version_VC70_DEP = 19990604, - PDB_Version_VC70 = 20000404, - PDB_Version_VC80 = 20030901, - PDB_Version_VC110 = 20091201, - PDB_Version_VC140 = 20140508 -}; - -typedef U16 PDB_ModIndex; +#define PDB_INVALID_STRING_INDEX max_U32 typedef U32 PDB_StringIndex; +typedef U32 PDB_StringOffset; -typedef enum PDB_FixedStream{ - PDB_FixedStream_PdbInfo = 1, - PDB_FixedStream_Tpi = 2, - PDB_FixedStream_Dbi = 3, - PDB_FixedStream_Ipi = 4 -} PDB_FixedStream; - -typedef enum PDB_NamedStream{ - PDB_NamedStream_HEADER_BLOCK, - PDB_NamedStream_STRTABLE, - PDB_NamedStream_LINK_INFO, - PDB_NamedStream_COUNT -} PDB_NamedStream; - -typedef struct PDB_InfoHeader{ - PDB_Version version; - U32 time; - U32 age; -} PDB_InfoHeader; - -enum{ - PDB_StrtblHeader_MAGIC = 0xEFFEEFFE +enum +{ + PDB_StringTableHeader_MAGIC = 0xEFFEEFFE }; -typedef struct PDB_StrtblHeader{ +enum +{ + PDB_StringTableHeader_Version1 = 1, + PDB_StringTableHeader_CurrentVersion = PDB_StringTableHeader_Version1 +}; + +typedef struct PDB_StringTableHeader +{ U32 magic; U32 version; -} PDB_StrtblHeader; +} PDB_StringTableHeader; + +//////////////////////////////// + +typedef enum PDB_FixedStream +{ + PDB_FixedStream_Info = 1, + PDB_FixedStream_Tpi = 2, + PDB_FixedStream_Dbi = 3, + PDB_FixedStream_Ipi = 4 +} PDB_FixedStream; + +//////////////////////////////// +//~ PDB Info Types + +typedef U32 PDB_InfoVersion; +enum{ + PDB_InfoVersion_VC2 = 19941610, + PDB_InfoVersion_VC4 = 19950623, + PDB_InfoVersion_VC41 = 19950814, + PDB_InfoVersion_VC50 = 19960307, + PDB_InfoVersion_VC98 = 19970604, + PDB_InfoVersion_VC70_DEP = 19990604, + PDB_InfoVersion_VC70 = 20000404, + PDB_InfoVersion_VC80 = 20030901, + PDB_InfoVersion_VC110 = 20091201, + PDB_InfoVersion_VC140 = 20140508 +}; + +// referenced in PDB1::loadPdbStream +enum +{ + PDB_FeatureSig_NULL = 0, + PDB_FeatureSig_VC110 = PDB_InfoVersion_VC110, + PDB_FeatureSig_VC140 = PDB_InfoVersion_VC140, + PDB_FeatureSig_NO_TYPE_MERGE = 0x4D544F4E, + PDB_FeatureSig_MINIMAL_DEBUG_INFO = 0x494E494D, +}; +typedef U32 PDB_FeatureSig; + +enum +{ + PDB_FeatureFlag_HAS_ID_STREAM = (1 << 0), + PDB_FeatureFlag_NO_TYPE_MERGE = (1 << 1), + PDB_FeatureFlag_MINIMAL_DBG_INFO = (1 << 2), +}; +typedef U32 PDB_FeatureFlags; + +#pragma pack(push,1) +typedef struct PDB_InfoHeaderV70 +{ + PDB_InfoVersion version; + COFF_TimeStamp time_stamp; + U32 age; + OS_Guid guid; + // PDB_HashTable named_stream_hash_table + // PDB_FeatureFlag features[*] +} PDB_InfoHeaderV70; + +#pragma pack(pop) +StaticAssert(sizeof(PDB_InfoHeaderV70) == 28, pdb_info_header_v70_size_check); + +#define PDB_SRC_HEADER_BLOCK_STREAM_NAME str8_lit("/src/headerblock") +#define PDB_LINK_INFO_STREAM_NAME str8_lit("/LinkInfo") +#define PDB_NAMES_STREAM_NAME str8_lit("/names") + +//////////////////////////////// +// SRC Header Block + +#define PDB_SRC_HEADER_BLOCK_MAGIC_V1 19980827 + +typedef struct PDB_SrcHeaderBlockHeader +{ + U32 version; + U32 stream_size; + U64 file_time; + U32 age; + U8 pad[44]; +} PDB_SrcHeaderBlockHeader; + +enum +{ + PDB_SrcComp_NULL, + PDB_SrcComp_RUN_LENGTH_RECORD, + PDB_SrcComp_HUFFMAN, + PDB_SrcComp_LZ, + PDB_SrcComp_DOTNET +}; +typedef U8 PDB_SrcCompType; + +enum +{ + PDB_SrcHeaderBlockEntryFlag_IS_VIRTUAL = (1 << 0) +}; +typedef U8 PDB_SrcHeaderFlags; + +// (PDB/include/pdb.h: SrcHeaderOut) +typedef struct PDB_SrcHeaderBlockEntry +{ + U32 size; + U32 version; + U32 file_crc; + U32 file_size; + PDB_StringOffset file_path; + PDB_StringOffset obj; + PDB_StringOffset virt_path; + PDB_SrcCompType comp; + PDB_SrcHeaderFlags flags; + U8 pad[2]; + U8 reserved[8]; +} PDB_SrcHeaderBlockEntry; //////////////////////////////// //~ PDB Format DBI Types typedef U32 PDB_DbiStream; -enum{ +enum +{ PDB_DbiStream_FPO, PDB_DbiStream_EXCEPTION, PDB_DbiStream_FIXUP, @@ -75,12 +161,14 @@ enum{ }; typedef U32 PDB_DbiHeaderSignature; -enum{ +enum +{ PDB_DbiHeaderSignature_V1 = 0xFFFFFFFF }; typedef U32 PDB_DbiVersion; -enum{ +enum +{ PDB_DbiVersion_41 = 930803, PDB_DbiVersion_50 = 19960307, PDB_DbiVersion_60 = 19970606, @@ -90,20 +178,21 @@ enum{ typedef U16 PDB_DbiBuildNumber; #define PDB_DbiBuildNumberNewFormatFlag 0x8000 -#define PDB_DbiBuildNumberMinor(bn) ((bn)&0xFF) -#define PDB_DbiBuildNumberMajor(bn) (((bn) >> 8)&0x7F) +#define PDB_DbiBuildNumberMinor(bn) ((bn)&0xFF) +#define PDB_DbiBuildNumberMajor(bn) (((bn) >> 8)&0x7F) #define PDB_DbiBuildNumberNewFormat(bn) (!!((bn)&PDB_DbiBuildNumberNewFormatFlag)) -#define PDB_DbiBuildNumber(maj, min) \ -(PDB_DbiBuildNumberNewFormatFlag | ((min)&0xFF) | (((maj)&0x7F) << 16)) +#define PDB_DbiMakeBuildNumber(maj, min) (PDB_DbiBuildNumber)(PDB_DbiBuildNumberNewFormatFlag | ((min)&0xFF) | (((maj)&0x7F) << 16)) typedef U16 PDB_DbiHeaderFlags; -enum{ +enum +{ PDB_DbiHeaderFlag_Incremental = 0x1, PDB_DbiHeaderFlag_Stripped = 0x2, PDB_DbiHeaderFlag_CTypes = 0x4 }; -typedef struct PDB_DbiHeader{ +typedef struct PDB_DbiHeader +{ PDB_DbiHeaderSignature sig; PDB_DbiVersion version; U32 age; @@ -132,46 +221,40 @@ typedef struct PDB_DbiHeader{ U32 reserved; } PDB_DbiHeader; -// (this is not "literally" defined by the format - but helpful to have) -typedef enum PDB_DbiRange{ - PDB_DbiRange_ModuleInfo, - PDB_DbiRange_SecCon, - PDB_DbiRange_SecMap, - PDB_DbiRange_FileInfo, - PDB_DbiRange_TSM, - PDB_DbiRange_EcInfo, - PDB_DbiRange_DbgHeader, - PDB_DbiRange_COUNT -} PDB_DbiRange; - // "ModuleInfo" DBI range typedef U32 PDB_DbiSectionContribVersion; #define PDB_DbiSectionContribVersion_1 (0xeffe0000u + 19970605u) #define PDB_DbiSectionContribVersion_2 (0xeffe0000u + 20140516u) -typedef struct PDB_DbiSectionContrib40{ +typedef struct PDB_DbiSectionContrib40 +{ CV_SectionIndex sec; + U16 pad0; U32 sec_off; U32 size; U32 flags; - PDB_ModIndex mod; + CV_ModIndex mod; + U16 pad1; } PDB_DbiSectionContrib40; -typedef struct PDB_DbiSectionContrib{ +typedef struct PDB_DbiSectionContrib +{ PDB_DbiSectionContrib40 base; U32 data_crc; U32 reloc_crc; } PDB_DbiSectionContrib; -typedef struct PDB_DbiSectionContrib2{ +typedef struct PDB_DbiSectionContrib2 +{ PDB_DbiSectionContrib40 base; U32 data_crc; U32 reloc_crc; U32 sec_coff; } PDB_DbiSectionContrib2; -typedef struct PDB_DbiCompUnitHeader{ +typedef struct PDB_DbiCompUnitHeader +{ U32 unused; PDB_DbiSectionContrib contribution; U16 flags; // unknown @@ -192,29 +275,81 @@ typedef struct PDB_DbiCompUnitHeader{ // U8[] obj_name (null terminated) } PDB_DbiCompUnitHeader; -// (this is not "literally" defined by the format - but helpful to have) -typedef enum{ - PDB_DbiCompUnitRange_Symbols, - PDB_DbiCompUnitRange_C11, - PDB_DbiCompUnitRange_C13, - PDB_DbiCompUnitRange_COUNT -} PDB_DbiCompUnitRange; +//////////////////////////////// + +enum +{ + PDB_DbiOMF_NONE = 0, + PDB_DbiOMF_READ = (1 << 0), + PDB_DbiOMF_WRITE = (1 << 1), + PDB_DbiOMF_EXEC = (1 << 2), + PDB_DbiOMF_IS_32BIT_ADDR = (1 << 3), // Descritor is 32-bit address + PDB_DbiOMF_IS_SELECTOR = (1 << 8), // Frame is a selector + PDB_DbiOMF_IS_ABS_ADDR = (1 << 9), // Frame is absolute address + PDB_DbiOMF_IS_GROUP = (1 << 10) // Descriptor is a group +}; +typedef U16 PDB_DbiOMF; + +typedef struct PDB_DbiSecMapEntry +{ + PDB_DbiOMF flags; + U16 ovl; + U16 group; + U16 frame; + U16 sec_name; + U16 class_name; + U32 offset; + U32 sec_size; +} PDB_DbiSecMapEntry; + +typedef struct PDB_DbiSecMapHeader +{ + U16 section_count; + U16 segment_count; +} PDB_DbiSecMapHeader; //////////////////////////////// -//~ PDB Format TPI Types +//~ PDB Format TPI/IPI Types typedef U32 PDB_TpiVersion; -enum{ - PDB_TpiVersion_INTV_VC2 = 920924, - PDB_TpiVersion_IMPV40 = 19950410, - PDB_TpiVersion_IMPV41 = 19951122, +enum +{ + PDB_TpiVersion_INTV_VC2 = 920924, + PDB_TpiVersion_IMPV40 = 19950410, + PDB_TpiVersion_IMPV41 = 19951122, PDB_TpiVersion_IMPV50_INTERIM = 19960307, - PDB_TpiVersion_IMPV50 = 19961031, - PDB_TpiVersion_IMPV70 = 19990903, - PDB_TpiVersion_IMPV80 = 20040203, + PDB_TpiVersion_IMPV50 = 19961031, + PDB_TpiVersion_IMPV70 = 19990903, + PDB_TpiVersion_IMPV80 = 20040203, }; -typedef struct PDB_TpiHeader{ +enum +{ + PDB_TYPE_SERVER_HASH_BUCKET_COUNT_V7 = 0x1000, + PDB_TYPE_SERVER_HASH_BUCKET_COUNT_V8 = 0x3FFF, + PDB_TYPE_SERVER_HASH_BUCKET_COUNT_INIT = 0x1000, + PDB_TYPE_SERVER_HASH_BUCKET_COUNT_MAX = 0x40000, + + PDB_TYPE_SERVER_HASH_BUCKET_COUNT_CURRENT = PDB_TYPE_SERVER_HASH_BUCKET_COUNT_V8, +}; + +#define PDB_TYPE_OFFSET_MAX max_U32 +typedef U32 PDB_TypeOffset; + +typedef struct PDB_TpiOffHint +{ + CV_TypeId itype; + PDB_TypeOffset off; +} PDB_TpiOffHint; + +typedef struct PDB_OffsetSize +{ + U32 off; + U32 size; +} PDB_OffsetSize; + +typedef struct PDB_TpiHeader +{ // (HDR) PDB_TpiVersion version; U32 header_size; @@ -227,46 +362,50 @@ typedef struct PDB_TpiHeader{ MSF_StreamNumber hash_sn_aux; U32 hash_key_size; U32 hash_bucket_count; - U32 hash_vals_off; - U32 hash_vals_size; - U32 itype_off; - U32 itype_size; - U32 hash_adj_off; - U32 hash_adj_size; + PDB_OffsetSize hash_vals; + PDB_OffsetSize itype_offs; + PDB_OffsetSize hash_adj; } PDB_TpiHeader; -typedef struct PDB_TpiOffHint{ - CV_TypeId itype; - U32 off; -} PDB_TpiOffHint; - //////////////////////////////// //~ PDB Format GSI Types typedef U32 PDB_GsiSignature; -enum{ +enum +{ PDB_GsiSignature_Basic = 0xffffffff, }; typedef U32 PDB_GsiVersion; -enum{ +enum +{ PDB_GsiVersion_V70 = 0xeffe0000 + 19990810, }; -typedef struct PDB_GsiHeader{ +typedef struct PDB_GsiHeader +{ PDB_GsiSignature signature; PDB_GsiVersion version; - U32 hr_len; - U32 num_buckets; + U32 hash_record_arr_size; + U32 bucket_data_size; } PDB_GsiHeader; -typedef struct PDB_GsiHashRecord{ +typedef struct PDB_GsiHashRecord +{ U32 symbol_off; U32 cref; } PDB_GsiHashRecord; -typedef struct PDB_PsiHeader{ +typedef struct PDB_GsiHashRecordOffsetCalc +{ + U32 next; + U32 off; + U32 cref; +} PDB_GsiHashRecordOffsetCalc; + +typedef struct PDB_PsiHeader +{ U32 sym_hash_size; U32 addr_map_size; U32 thunk_count; @@ -278,185 +417,7 @@ typedef struct PDB_PsiHeader{ } PDB_PsiHeader; //////////////////////////////// -//~ PDB Parser Types -typedef struct PDB_InfoNode{ - struct PDB_InfoNode *next; - String8 string; - MSF_StreamNumber sn; -} PDB_InfoNode; - -typedef struct PDB_Info{ - PDB_InfoNode *first; - PDB_InfoNode *last; - COFF_Guid auth_guid; -} PDB_Info; - -typedef struct PDB_NamedStreamTable{ - MSF_StreamNumber sn[PDB_NamedStream_COUNT]; -} PDB_NamedStreamTable; - -typedef struct PDB_Strtbl{ - String8 data; - U32 bucket_count; - U32 strblock_min; - U32 strblock_max; - U32 buckets_min; - U32 buckets_max; -} PDB_Strtbl; - -typedef struct PDB_DbiParsed{ - String8 data; - COFF_MachineType machine_type; - MSF_StreamNumber gsi_sn; - MSF_StreamNumber psi_sn; - MSF_StreamNumber sym_sn; - - U64 range_off[(U64)(PDB_DbiRange_COUNT) + 1]; - MSF_StreamNumber dbg_streams[PDB_DbiStream_COUNT]; -} PDB_DbiParsed; - -typedef struct PDB_TpiParsed{ - String8 data; - - // leaf info - U64 leaf_first; - U64 leaf_opl; - U32 itype_first; - U32 itype_opl; - - // hash info - MSF_StreamNumber hash_sn; - MSF_StreamNumber hash_sn_aux; - U32 hash_key_size; - U32 hash_bucket_count; - U32 hash_vals_off; - U32 hash_vals_size; - U32 itype_off; - U32 itype_size; - U32 hash_adj_off; - U32 hash_adj_size; - -} PDB_TpiParsed; - -typedef struct PDB_TpiHashBlock{ - struct PDB_TpiHashBlock *next; - U32 local_count; - CV_TypeId itypes[13]; // 13 = (64 - 12)/4 -} PDB_TpiHashBlock; - -typedef struct PDB_TpiHashParsed{ - String8 data; - String8 aux_data; - - PDB_TpiHashBlock **buckets; - U32 bucket_count; - U32 bucket_mask; -} PDB_TpiHashParsed; - -typedef struct PDB_GsiBucket{ - U32 *offs; - U64 count; -} PDB_GsiBucket; - -typedef struct PDB_GsiParsed{ - PDB_GsiBucket buckets[4096]; -} PDB_GsiParsed; - -typedef struct PDB_CompUnit{ - MSF_StreamNumber sn; - U32 range_off[(U32)(PDB_DbiCompUnitRange_COUNT) + 1]; - - String8 obj_name; - String8 group_name; -} PDB_CompUnit; - -typedef struct PDB_CoffSectionArray{ - COFF_SectionHeader *sections; - U64 count; -} PDB_CoffSectionArray; - -typedef struct PDB_CompUnitNode{ - struct PDB_CompUnitNode *next; - PDB_CompUnit unit; -} PDB_CompUnitNode; - -typedef struct PDB_CompUnitArray{ - PDB_CompUnit **units; - U64 count; -} PDB_CompUnitArray; - -typedef struct PDB_CompUnitContribution{ - U32 mod; - U64 voff_first; - U64 voff_opl; -} PDB_CompUnitContribution; - -typedef struct PDB_CompUnitContributionArray{ - PDB_CompUnitContribution *contributions; - U64 count; -} PDB_CompUnitContributionArray; - -//////////////////////////////// -//~ PDB Parser Functions - -internal PDB_Info* pdb_info_from_data(Arena *arena, String8 pdb_info_data); -internal PDB_NamedStreamTable*pdb_named_stream_table_from_info(Arena *arena, PDB_Info *info); -internal PDB_Strtbl* pdb_strtbl_from_data(Arena *arena, String8 strtbl_data); - -internal PDB_DbiParsed* pdb_dbi_from_data(Arena *arena, String8 dbi_data); -internal PDB_TpiParsed* pdb_tpi_from_data(Arena *arena, String8 tpi_data); -internal PDB_TpiHashParsed* pdb_tpi_hash_from_data(Arena *arena, - PDB_Strtbl *strtbl, - PDB_TpiParsed *tpi, - String8 tpi_hash_data, - String8 tpi_hash_aux_data); -internal PDB_GsiParsed* pdb_gsi_from_data(Arena *arena, String8 gsi_data); - -internal PDB_CoffSectionArray*pdb_coff_section_array_from_data(Arena *arena, - String8 section_data); - -internal PDB_CompUnitArray* pdb_comp_unit_array_from_data(Arena *arena, - String8 module_info_data); - -internal PDB_CompUnitContributionArray* -pdb_comp_unit_contribution_array_from_data(Arena *arena, String8 seccontrib_data, - PDB_CoffSectionArray *sections); - -//////////////////////////////// -//~ PDB Definition Functions - -internal U32 pdb_string_hash1(String8 string); - -//////////////////////////////// -//~ PDB Dbi Functions - -internal String8 pdb_data_from_dbi_range(PDB_DbiParsed *dbi, PDB_DbiRange range); -internal String8 pdb_data_from_unit_range(MSF_Parsed *msf, PDB_CompUnit *unit, - PDB_DbiCompUnitRange range); - -//////////////////////////////// -//~ PDB Tpi Functions - -internal String8 pdb_leaf_data_from_tpi(PDB_TpiParsed *tpi); - -internal CV_TypeIdArray pdb_tpi_itypes_from_name(Arena *arena, - PDB_TpiHashParsed *tpi_hash, - CV_LeafParsed *tpi_leaf, - String8 name, - B32 compare_unique_name, - U32 output_cap); - -internal CV_TypeId pdb_tpi_first_itype_from_name(PDB_TpiHashParsed *tpi_hash, - CV_LeafParsed *tpi_leaf, - String8 name, - B32 compare_unique_name); - -//////////////////////////////// -//~ PDB Strtbl Functions - -internal String8 pdb_strtbl_string_from_off(PDB_Strtbl *strtbl, U32 off); -internal String8 pdb_strtbl_string_from_index(PDB_Strtbl *strtbl, - PDB_StringIndex idx); +internal U32 pdb_hash_v1(String8 string); #endif // PDB_H diff --git a/src/pdb/pdb_parse.c b/src/pdb/pdb_parse.c new file mode 100644 index 00000000..1c2d6b78 --- /dev/null +++ b/src/pdb/pdb_parse.c @@ -0,0 +1,982 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ PDB Parser Functions + +internal PDB_Info* +pdb_info_from_data(Arena *arena, String8 data){ + ProfBegin("pdb_info_from_data"); + + // get header + PDB_InfoHeader *header = 0; + if (data.size >= sizeof(*header)){ + header = (PDB_InfoHeader *)data.str; + } + + PDB_Info *result = 0; + if (header != 0){ + // read guid + COFF_Guid *auth_guid = 0; + U32 after_auth_guid_off = sizeof(*header); + switch (header->version){ + case PDB_InfoVersion_VC70_DEP: + case PDB_InfoVersion_VC70: + case PDB_InfoVersion_VC80: + case PDB_InfoVersion_VC110: + case PDB_InfoVersion_VC140: + { + auth_guid = (COFF_Guid*)(data.str + after_auth_guid_off); + after_auth_guid_off = sizeof(*header) + sizeof(*auth_guid); + }break; + + default: + {}break; + } + + if (header->version != 0){ + // table layout: names + U32 names_len_off = after_auth_guid_off; + U32 names_len = 0; + if (names_len_off + 4 <= data.size){ + names_len = *(U32*)(data.str + names_len_off); + } + + U32 names_base_off = names_len_off + 4; + U32 names_base_opl = names_base_off + names_len; + + // table layout: hash table + U32 hash_table_count_off = names_base_opl; + U32 hash_table_max_off = hash_table_count_off + 4; + + U32 hash_table_count = 0; + U32 hash_table_max = 0; + if (hash_table_max_off + 4 <= data.size){ + hash_table_count = *(U32*)(data.str + hash_table_count_off); + hash_table_max = *(U32*)(data.str + hash_table_max_off); + } + + // table layout: words + U32 num_present_words_off = hash_table_max_off + 4; + U32 num_present_words = 0; + if (hash_table_max_off + 4 <= data.size){ + num_present_words = *(U32*)(data.str + num_present_words_off); + } + U32 present_words_array_off = num_present_words_off + 4; + + U32 num_deleted_words_off = present_words_array_off + num_present_words*sizeof(U32); + U32 num_deleted_words = 0; + if (num_deleted_words_off + 4 <= data.size){ + num_deleted_words = *(U32*)(data.str + num_deleted_words_off); + } + U32 deleted_words_array_off = num_deleted_words_off + 4; + + // table layout: epilogue + U32 epilogue_base_off = deleted_words_array_off + num_deleted_words*sizeof(U32); + + // read table + if (hash_table_count > 0 && epilogue_base_off <= data.size){ + PDB_InfoNode *first = 0; + PDB_InfoNode *last = 0; + + U32 record_off = epilogue_base_off; + for (U32 i = 0; i < hash_table_count; i += 1, record_off += 8){ + U32 *record = (U32*)(data.str + record_off); + U32 relative_name_off = record[0]; + MSF_StreamNumber sn = (MSF_StreamNumber)record[1]; + + U32 name_off = names_base_off + relative_name_off; + String8 name = str8_cstring_capped((char*)(data.str + name_off), + (char*)(data.str + names_base_opl)); + + // push info node + PDB_InfoNode *node = push_array(arena, PDB_InfoNode, 1); + SLLQueuePush(first, last, node); + node->string = name; + node->sn = sn; + } + + result = push_array(arena, PDB_Info, 1); + result->first = first; + result->last = last; + result->auth_guid = *auth_guid; + } + + } + } + + ProfEnd(); + + return(result); +} + +internal PDB_NamedStreamTable* +pdb_named_stream_table_from_info(Arena *arena, PDB_Info *info){ + ProfBegin("pdb_named_stream_table_from_info"); + + // mapping "NamedStream" indexes to strings + struct StreamNameIndexPair{ + PDB_NamedStream index; + String8 name; + }; + struct StreamNameIndexPair pairs[] = { + {PDB_NamedStream_HeaderBlock, str8_lit("/src/headerblock")}, + {PDB_NamedStream_StringTable, str8_lit("/names")}, + {PDB_NamedStream_LinkInfo, str8_lit("/LinkInfo")}, + }; + + // build baked table + PDB_NamedStreamTable *result = push_array(arena, PDB_NamedStreamTable, 1); + struct StreamNameIndexPair *p = pairs; + for (U64 i = 0; i < ArrayCount(pairs); i += 1, p += 1){ + String8 name = p->name; + + // get info node with this name + PDB_InfoNode *match = 0; + for (PDB_InfoNode *node = info->first; + node != 0; + node = node->next){ + if (str8_match(name, node->string, 0)){ + match = node; + break; + } + } + + // if match found save stream number + if (match != 0){ + result->sn[p->index] = match->sn; + } + else{ + result->sn[p->index] = 0xFFFF; + } + } + + ProfEnd(); + + return(result); +} + +internal PDB_Strtbl* +pdb_strtbl_from_data(Arena *arena, String8 data){ + ProfBegin("pdb_strtbl_from_data"); + + // get header + PDB_StringTableHeader *header = 0; + if (sizeof(*header) <= data.size){ + header = (PDB_StringTableHeader *)data.str; + } + + PDB_Strtbl *result = 0; + if (header != 0 && header->magic == PDB_StringTableHeader_MAGIC && header->version == 1){ + U32 strblock_size_off = sizeof(*header); + U32 strblock_size = 0; + if (strblock_size_off + 4 <= data.size){ + strblock_size = *(U32*)(data.str + strblock_size_off); + } + U32 strblock_off = strblock_size_off + 4; + + U32 bucket_count_off = strblock_off + strblock_size; + U32 bucket_count = 0; + if (bucket_count_off + 4 <= data.size){ + bucket_count = *(U32*)(data.str + bucket_count_off); + } + + U32 bucket_array_off = bucket_count_off + 4; + U32 bucket_array_size = bucket_count*sizeof(PDB_StringIndex); + + if (bucket_array_off + bucket_array_size <= data.size){ + result = push_array(arena, PDB_Strtbl, 1); + result->data = data; + result->bucket_count = bucket_count; + result->strblock_min = strblock_off; + result->strblock_max = strblock_off + strblock_size; + result->buckets_min = bucket_array_off; + result->buckets_max = bucket_array_off + bucket_array_size; + } + } + + ProfEnd(); + + return(result); +} + +internal PDB_DbiParsed* +pdb_dbi_from_data(Arena *arena, String8 data){ + ProfBegin("pdb_dbi_from_data"); + + // get header + PDB_DbiHeader *header = 0; + if (sizeof(*header) <= data.size){ + header = (PDB_DbiHeader*)data.str; + } + + PDB_DbiParsed *result = 0; + if (header != 0 && header->sig == PDB_DbiHeaderSignature_V1){ + // extract range sizes + U64 range_size[PDB_DbiRange_COUNT]; + range_size[PDB_DbiRange_ModuleInfo] = header->module_info_size; + range_size[PDB_DbiRange_SecCon] = header->sec_con_size; + range_size[PDB_DbiRange_SecMap] = header->sec_map_size; + range_size[PDB_DbiRange_FileInfo] = header->file_info_size; + range_size[PDB_DbiRange_TSM] = header->tsm_size; + range_size[PDB_DbiRange_EcInfo] = header->ec_info_size; + range_size[PDB_DbiRange_DbgHeader] = header->dbg_header_size; + + // fill result + result = push_array(arena, PDB_DbiParsed, 1); + result->data = data; + result->machine_type = header->machine; + result->gsi_sn = header->gsi_sn; + result->psi_sn = header->psi_sn; + result->sym_sn = header->sym_sn; + + + // fill result's range offsets + { + U64 cursor = sizeof(*header); + for (U64 i = 0; i < (U64)(PDB_DbiRange_COUNT); i += 1){ + result->range_off[i] = cursor; + cursor += range_size[i]; + cursor = ClampTop(cursor, data.size); + } + result->range_off[PDB_DbiRange_COUNT] = cursor; + } + + // fill result's debug streams + U64 dbg_streams_min = result->range_off[PDB_DbiRange_DbgHeader]; + U64 dbg_streams_max = result->range_off[PDB_DbiRange_DbgHeader + 1]; + U64 dbg_streams_size_raw = dbg_streams_max - dbg_streams_min; + U64 dbg_streams_size = ClampTop(dbg_streams_size_raw, sizeof(result->dbg_streams)); + MemoryCopy(result->dbg_streams, data.str + dbg_streams_min, dbg_streams_size); + if (dbg_streams_size < sizeof(result->dbg_streams)){ + U64 filled_count = dbg_streams_size/sizeof(MSF_StreamNumber); + MemorySet(result->dbg_streams + filled_count, 0xff, + (ArrayCount(result->dbg_streams) - filled_count)*sizeof(MSF_StreamNumber)); + } + } + + ProfEnd(); + + return(result); +} + +internal PDB_TpiParsed* +pdb_tpi_from_data(Arena *arena, String8 data){ + ProfBegin("pdb_tpi_from_data"); + + // get header + PDB_TpiHeader *header = 0; + if (sizeof(*header) <= data.size){ + header = (PDB_TpiHeader*)data.str; + } + + PDB_TpiParsed *result = 0; + if (header != 0 && header->version == PDB_TpiVersion_IMPV80){ + U64 leaf_first_raw = header->header_size; + U64 leaf_first = ClampTop(leaf_first_raw, data.size); + U64 leaf_opl_raw = leaf_first + header->leaf_data_size; + U64 leaf_opl = ClampTop(leaf_opl_raw, data.size); + + result = push_array(arena, PDB_TpiParsed, 1); + result->data = data; + + result->leaf_first = leaf_first; + result->leaf_opl = leaf_opl; + result->itype_first = header->ti_lo; + result->itype_opl = header->ti_hi; + + result->hash_sn = header->hash_sn; + result->hash_sn_aux = header->hash_sn_aux; + result->hash_key_size = header->hash_key_size; + result->hash_bucket_count = header->hash_bucket_count; + result->hash_vals_off = header->hash_vals.off; + result->hash_vals_size = header->hash_vals.size; + result->itype_off = header->itype_offs.off; + result->itype_size = header->itype_offs.size; + result->hash_adj_off = header->hash_adj.off; + result->hash_adj_size = header->hash_adj.size; + } + + ProfEnd(); + + return(result); +} + +internal PDB_TpiHashParsed* +pdb_tpi_hash_from_data(Arena *arena, PDB_Strtbl *strtbl, PDB_TpiParsed *tpi, String8 data, String8 aux_data){ + ProfBegin("pdb_tpi_hash_from_data"); + + PDB_TpiHashParsed *result = 0; + + U32 stride = tpi->hash_key_size; + U32 bucket_count = tpi->hash_bucket_count; + if (1 <= stride && stride <= 8 && bucket_count > 0 && data.str != 0){ + + // allocate buckets + PDB_TpiHashBlock **buckets = push_array(arena, PDB_TpiHashBlock*, bucket_count); + + // extract "hash" array + U8 *hashes = data.str + tpi->hash_vals_off; + U8 *hash_opl = hashes + tpi->hash_vals_size; + + // for each index in the array... + CV_TypeId itype = tpi->itype_first; + U8 *hash_cursor = hashes; + for (;hash_cursor + stride <= hash_opl;){ + + // read index + U64 bucket_idx = 0; + MemoryCopy(&bucket_idx, hash_cursor, stride); + + // save to map + if (bucket_idx < bucket_count){ + PDB_TpiHashBlock *block = buckets[bucket_idx]; + if (block == 0 || block->local_count == ArrayCount(block->itypes)){ + block = push_array(arena, PDB_TpiHashBlock, 1); + SLLStackPush(buckets[bucket_idx], block); + } + if(block->local_count != 0) + { + MemoryCopy(block->itypes+1, block->itypes, sizeof(CV_TypeId)*block->local_count); + } + block->itypes[0] = itype; + block->local_count += 1; + } + + // advance cursor + hash_cursor += stride; + itype += 1; + } + + //- rjf: compute bucket mask + U32 bucket_mask = 0; + if(IsPow2OrZero(bucket_count)) + { + bucket_mask = bucket_count-1; + } + + //- rjf: apply hash adjustments, to pull correct type IDs to the front of + // the chains + if(tpi->hash_adj_size != 0) + { + // NOTE(rjf): this table is laid out in the following format: + // + // pair_count: U32 -> # of name_index/type_index pairs + // slot_count: U32 -> # of slots in this hash table + // present_bit_array_count: U32 -> count for next array + // present_bit_array: U32[present_bit_array_count] -> 1 bit per slot, "is present" + // deleted_bit_array_count: U32 -> count for next array + // deleted_bit_array: U32[deleted_bit_array_count] -> 1 bit per slot, "is deleted" + // (U32, U32)[pair_count] -> array of name_index/type_index pairs + // + U8 *adjs = data.str + tpi->hash_adj_off; + U8 *adjs_opl = adjs + tpi->hash_adj_size; + U8 *adjs_cursor = adjs; + U32 pair_count = *(U32 *)adjs_cursor; + adjs_cursor += sizeof(U32); + U32 slot_count = *(U32 *)adjs_cursor; + adjs_cursor += sizeof(U32); + U32 present_bit_array_count = *(U32 *)adjs_cursor; // skip present_bit_array + adjs_cursor += sizeof(U32); + adjs_cursor += present_bit_array_count*sizeof(U32); + U32 deleted_bit_array_count = *(U32 *)adjs_cursor; // skip deleted_bit_array + adjs_cursor += sizeof(U32); + adjs_cursor += deleted_bit_array_count*sizeof(U32); + U32 adjs_stride = sizeof(U32)*2; + U32 pair_idx = 0; + for(;adjs_cursor < adjs_opl && pair_idx < pair_count; + adjs_cursor += adjs_stride, pair_idx += 1) + { + U32 name_off = ((U32 *)adjs_cursor)[0]; + CV_TypeId type_id = ((CV_TypeId *)adjs_cursor)[1]; + String8 string = pdb_strtbl_string_from_off(strtbl, name_off); + U32 hash = pdb_hash_v1(string); + U32 bucket_idx = ((bucket_mask != 0) ? hash&bucket_mask : hash%bucket_count); + PDB_TpiHashBlock *prev_block = 0; + for(PDB_TpiHashBlock *block = buckets[bucket_idx]; + block != 0; + prev_block = block, block = block->next) + { + for(U32 local_idx = 0; + local_idx < block->local_count && local_idx < ArrayCount(block->itypes); + local_idx += 1) + { + if(block->itypes[local_idx] == type_id) + { + if(prev_block != 0) + { + prev_block->next = block->next; + block->next = buckets[bucket_idx]; + buckets[bucket_idx] = block; + } + if(local_idx != 0) + { + Swap(CV_TypeId, block->itypes[0], block->itypes[local_idx]); + } + break; + } + } + } + } + } + + // fill result + result = push_array(arena, PDB_TpiHashParsed, 1); + result->data = data; + result->aux_data = aux_data; + result->buckets = buckets; + result->bucket_count = bucket_count; + result->bucket_mask = bucket_mask; + } + + ProfEnd(); + + return(result); +} + +internal PDB_GsiParsed* +pdb_gsi_from_data(Arena *arena, String8 data){ + ProfBegin("pdb_gsi_from_data"); + + // get header + PDB_GsiHeader *header = 0; + if (sizeof(*header) <= data.size){ + header = (PDB_GsiHeader*)data.str; + } + + PDB_GsiParsed *result = 0; + if (header != 0 && header->signature == PDB_GsiSignature_Basic && + header->version == PDB_GsiVersion_V70 && header->bucket_data_size != 0){ + Temp scratch = scratch_begin(&arena, 1); + + // hash offset + U32 hash_record_array_off = sizeof(*header); + + // bucket count + U32 slot_count = 4097; + + // array offsets + U32 bitmask_u32_count = CeilIntegerDiv(slot_count, 32); + U32 bitmask_byte_size = bitmask_u32_count*4; + U32 bitmask_off = hash_record_array_off + header->hash_record_arr_size; + U32 offsets_off = bitmask_off + bitmask_byte_size; + + // get bitmask & packed offset arrays + U8 *bitmasks = 0; + U8 *packed_offsets = 0; + if (bitmask_off + bitmask_byte_size <= data.size){ + bitmasks = (data.str + bitmask_off); + packed_offsets = (data.str + offsets_off); + } + U32 packed_offset_count = (data.size - offsets_off)/4; + + // unpack + U32 *unpacked_offsets = 0; + if (packed_offsets != 0){ + unpacked_offsets = push_array(scratch.arena, U32, slot_count); + + U32 *bitmask_ptr = (U32*)bitmasks; + U32 *bitmask_opl = bitmask_ptr + bitmask_u32_count; + U32 *src_ptr = (U32*)packed_offsets; + U32 *src_opl = src_ptr + packed_offset_count; + U32 *dst_ptr = unpacked_offsets; + U32 *dst_opl = dst_ptr + slot_count; + for (; bitmask_ptr < bitmask_opl && src_ptr < src_opl; bitmask_ptr += 1){ + U32 bits = *bitmask_ptr; + U32 src_max = (U32)(src_opl - src_ptr); + U32 dst_max = (U32)(dst_opl - dst_ptr); + U32 k_max0 = ClampTop(32, dst_max); + U32 k_max = ClampTop(k_max0, src_max); + for (U32 k = 0; k < k_max; k += 1){ + if ((bits & 1) == 1){ + *dst_ptr = *src_ptr; + src_ptr += 1; + } + else{ + *dst_ptr = 0xFFFFFFFF; + } + dst_ptr += 1; + bits >>= 1; + } + } + for (; dst_ptr < dst_opl; dst_ptr += 1){ + *dst_ptr = 0xFFFFFFFF; + } + } + + // construct table + B32 bad_table = 0; + if (unpacked_offsets != 0){ + result = push_array(arena, PDB_GsiParsed, 1); + + // hash records + PDB_GsiHashRecord *hash_records = (PDB_GsiHashRecord*)(data.str + hash_record_array_off); + U32 hash_record_count = header->hash_record_arr_size/sizeof(PDB_GsiHashRecord); + + // * We unpack hash records into the the table by scanning backwards through the + // * hash records. Neighboring values in unpacked_offsets *sort of* form counts, but we + // * have to skip the max-U32s (sloppy PDB nonsense). + + // * PDBs put one extra slot at the beginning of the encoded buckets that is mean + // * to be padding for modifying the buffer in place. After decoding there are 4096 buckets, + // * in the encoded buckets there are 4097. We are meant to drop the first one. + + // build table + PDB_GsiHashRecord *hash_record_ptr = hash_records + hash_record_count - 1; + U32 prev_n = hash_record_count; + for (U32 i = slot_count; i > 1;){ + i -= 1; + if (unpacked_offsets[i] != 0xFFFFFFFF){ + // determine hash record range to use + // * The "12" here is the result of some really sloppy PDB magic. + U32 n = unpacked_offsets[i]/12; + if (n > prev_n){ + bad_table = 1; + break; + } + U32 num_steps = prev_n - n; + + // fill this bucket + U32 *bucket_offs = push_array_aligned(arena, U32, num_steps, 4); + for (U32 j = num_steps; j > 0;){ + j -= 1; + // * The "- 1" is more sloppy PDB magic. + bucket_offs[j] = hash_record_ptr->symbol_off - 1; + hash_record_ptr -= 1; + } + PDB_GsiBucket *bucket = &result->buckets[i - 1]; + bucket->count = num_steps; + bucket->offs = bucket_offs; + + // update prev_n + prev_n = n; + } + } + } + + scratch_end(scratch); + } + + ProfEnd(); + + return(result); +} + +internal COFF_SectionHeaderArray +pdb_coff_section_array_from_data(Arena *arena, String8 data){ + COFF_SectionHeaderArray result = {0}; + result.count = data.size/sizeof(COFF_SectionHeader); + result.v = (COFF_SectionHeader*)data.str; + return(result); +} + +internal PDB_CompUnitArray* +pdb_comp_unit_array_from_data(Arena *arena, String8 data){ + PDB_CompUnitNode *first = 0; + PDB_CompUnitNode *last = 0; + U64 count = 0; + + U64 cursor = 0; + for (;cursor + sizeof(PDB_DbiCompUnitHeader) <= data.size;){ + // get header + PDB_DbiCompUnitHeader *header = (PDB_DbiCompUnitHeader*)(data.str + cursor); + + // get names + U64 name_off = cursor + sizeof(*header); + String8 name = str8_cstring_capped((char *)(data.str + name_off), (char *)(data.str + data.size)); + + U64 name2_off = name_off + name.size + 1; + String8 name2 = str8_cstring_capped((char *)(data.str + name2_off), (char *)(data.str + data.size)); + + U64 after_name2_off = name2_off + name2.size + 1; + + // save mod info + PDB_CompUnitNode *node = push_array_no_zero(arena, PDB_CompUnitNode, 1); + SLLQueuePush(first, last, node); + count += 1; + node->unit.sn = header->sn; + node->unit.obj_name = name; + node->unit.group_name = name2; + + // fill range offsets + U32 *range_buf = node->unit.range_off; + { + // fill the buffer with size of each range + range_buf[PDB_DbiCompUnitRange_Symbols] = header->symbols_size; + range_buf[PDB_DbiCompUnitRange_C11] = header->c11_lines_size; + range_buf[PDB_DbiCompUnitRange_C13] = header->c13_lines_size; + Assert(PDB_DbiCompUnitRange_C13 + 1 == PDB_DbiCompUnitRange_COUNT); + + // in-place sizes -> offs conversion + U64 i = 0; + U32 range_cursor = 0; + for (; i < (U64)(PDB_DbiCompUnitRange_COUNT); i += 1){ + U64 adv = range_buf[i]; + range_buf[i] = range_cursor; + range_cursor += adv; + } + range_buf[i] = range_cursor; + + // skip 4 byte signature in symbols range + if (range_buf[1] >= 4){ + range_buf[0] += 4; + } + } + + // update cursor + cursor = AlignPow2(after_name2_off, 4); + } + + + // fill result + PDB_CompUnit **units = push_array_no_zero(arena, PDB_CompUnit*, count); + { + U64 idx = 0; + for (PDB_CompUnitNode *node = first; + node != 0; + node = node->next, idx += 1){ + units[idx] = &node->unit; + } + } + + PDB_CompUnitArray *result = push_array(arena, PDB_CompUnitArray, 1); + result->units = units; + result->count = count; + + return(result); +} + +internal PDB_CompUnitContributionArray* +pdb_comp_unit_contribution_array_from_data(Arena *arena, String8 data, + COFF_SectionHeaderArray sections){ + PDB_CompUnitContribution *contributions = 0; + U64 count = 0; + if (data.size >= sizeof(PDB_DbiSectionContribVersion)){ + PDB_DbiSectionContribVersion *version = (PDB_DbiSectionContribVersion*)data.str; + + // determine array layout from version + U32 item_size = 0; + U32 array_off = 0; + switch (*version){ + default: + { + // TODO(allen): do we have a test case for this? + item_size = sizeof(PDB_DbiSectionContrib40); + }break; + case PDB_DbiSectionContribVersion_1: + { + item_size = sizeof(PDB_DbiSectionContrib); + array_off = sizeof(*version); + }break; + case PDB_DbiSectionContribVersion_2: + { + item_size = sizeof(PDB_DbiSectionContrib2); + array_off = sizeof(*version); + }break; + } + + // allocate ranges + U64 max_count = (data.size - array_off)/item_size; + contributions = push_array_no_zero(arena, PDB_CompUnitContribution, max_count); + + // binary section info + U64 section_count = sections.count; + COFF_SectionHeader* section_headers = sections.v; + + // fill array + PDB_CompUnitContribution *contribution_ptr = contributions; + U64 cursor = array_off; + for (; cursor + item_size <= data.size; cursor += item_size){ + PDB_DbiSectionContrib40 *sc = (PDB_DbiSectionContrib40*)(data.str + cursor); + if (sc->size > 0 && 1 <= sc->sec && sc->sec <= section_count){ + U64 voff = section_headers[sc->sec - 1].voff + sc->sec_off; + + contribution_ptr->mod = sc->mod; + contribution_ptr->voff_first = voff; + contribution_ptr->voff_opl = voff + sc->size; + contribution_ptr += 1; + } + } + count = (U64)(contribution_ptr - contributions); + } + + // fill result + PDB_CompUnitContributionArray *result = push_array(arena, PDB_CompUnitContributionArray, 1); + result->contributions = contributions; + result->count = count; + + return(result); +} + +//////////////////////////////// +//~ PDB Dbi Functions + +internal String8 +pdb_data_from_dbi_range(PDB_DbiParsed *dbi, PDB_DbiRange range){ + String8 result = {0}; + if (range < PDB_DbiRange_COUNT){ + U64 first = dbi->range_off[range]; + U64 opl = dbi->range_off[range + 1]; + result.str = dbi->data.str + first; + result.size = opl - first; + } + return(result); +} + +internal String8 +pdb_data_from_unit_range(MSF_Parsed *msf, PDB_CompUnit *unit, PDB_DbiCompUnitRange range){ + String8 result = {0}; + if (range < PDB_DbiCompUnitRange_COUNT){ + String8 full_stream_data = msf_data_from_stream(msf, unit->sn); + + U64 first_raw = unit->range_off[range]; + U64 opl_raw = unit->range_off[range + 1]; + U64 opl = ClampTop(opl_raw, full_stream_data.size); + U64 first = ClampTop(first_raw, opl); + + result.str = full_stream_data.str + first; + result.size = opl - first; + } + return(result); +} + +//////////////////////////////// +//~ PDB Tpi Functions + +internal String8 +pdb_leaf_data_from_tpi(PDB_TpiParsed *tpi){ + String8 data = tpi->data; + U8 *first = data.str + tpi->leaf_first; + U8 *opl = data.str + tpi->leaf_opl; + String8 result = str8_range(first, opl); + return(result); +} + +internal CV_TypeIdArray +pdb_tpi_itypes_from_name(Arena *arena, PDB_TpiHashParsed *tpi_hash, CV_LeafParsed *leaf, + String8 name, B32 compare_unique_name, U32 output_cap){ + U32 hash = pdb_hash_v1(name); + U32 bucket_idx = ((tpi_hash->bucket_mask != 0) ? + hash&tpi_hash->bucket_mask : + hash%tpi_hash->bucket_count); + + CV_TypeId itype_first = leaf->itype_first; + CV_TypeId itype_opl = leaf->itype_opl; + String8 data = leaf->data; + + Temp scratch = scratch_begin(&arena, 1); + struct Chain{ + struct Chain *next; + CV_TypeId itype; + }; + struct Chain *first = 0; + struct Chain *last = 0; + U32 count = 0; + + for (PDB_TpiHashBlock *block = tpi_hash->buckets[bucket_idx]; + block != 0; + block = block->next){ + U32 local_count = block->local_count; + CV_TypeId *itype_ptr = block->itypes; + for (U32 i = 0; i < local_count; i += 1, itype_ptr += 1){ + + String8 extracted_name = {0}; + + CV_TypeId itype = *itype_ptr; + if (itype_first <= itype && itype < itype_opl){ + CV_RecRange *range = &leaf->leaf_ranges.ranges[itype - leaf->itype_first]; + if (range->off + range->hdr.size <= data.size){ + U8 *first = data.str + range->off + 2; + U64 cap = range->hdr.size - 2; + + switch (range->hdr.kind){ + default:break; + + case CV_LeafKind_CLASS: + case CV_LeafKind_STRUCTURE: + { + if (sizeof(CV_LeafStruct) <= cap){ + CV_LeafStruct *lf_struct = (CV_LeafStruct*)first; + + if (!(lf_struct->props & CV_TypeProp_FwdRef)){ + // size + U8 *numeric_ptr = (U8*)(lf_struct + 1); + CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, first + cap); + + // name + U8 *name_ptr = numeric_ptr + size.encoded_size; + String8 name = str8_cstring_capped((char*)name_ptr, (char *)(first + cap)); + + // unique name + if (compare_unique_name){ + if (lf_struct->props & CV_TypeProp_HasUniqueName) { + U8 *unique_name_ptr = name_ptr + name.size + 1; + String8 unique_name = str8_cstring_capped((char*)unique_name_ptr, (char *)(first + cap)); + extracted_name = unique_name; + } + } + else{ + extracted_name = name; + } + } + } + }break; + + case CV_LeafKind_CLASS2: + case CV_LeafKind_STRUCT2: + { + if (sizeof(CV_LeafStruct2) <= cap){ + CV_LeafStruct2 *lf_struct = (CV_LeafStruct2*)first; + + if (!(lf_struct->props & CV_TypeProp_FwdRef)){ + // size + U8 *numeric_ptr = (U8*)(lf_struct + 1); + CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, first + cap); + + // name + U8 *name_ptr = numeric_ptr + size.encoded_size; + String8 name = str8_cstring_capped((char*)name_ptr, (char *)(first + cap)); + + // unique name + if (compare_unique_name){ + if (lf_struct->props & CV_TypeProp_HasUniqueName) { + U8 *unique_name_ptr = name_ptr + name.size + 1; + String8 unique_name = str8_cstring_capped((char*)unique_name_ptr, (char *)(first + cap)); + extracted_name = unique_name; + } + } + else{ + extracted_name = name; + } + } + } + }break; + + case CV_LeafKind_UNION: + { + if (sizeof(CV_LeafUnion) <= cap){ + CV_LeafUnion *lf_union = (CV_LeafUnion*)first; + + if (!(lf_union->props & CV_TypeProp_FwdRef)){ + // size + U8 *numeric_ptr = (U8*)(lf_union + 1); + CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, first + cap); + + // name + U8 *name_ptr = numeric_ptr + size.encoded_size; + String8 name = str8_cstring_capped((char*)name_ptr, (char *)(first + cap)); + + // unique name + if (compare_unique_name){ + if (lf_union->props & CV_TypeProp_HasUniqueName) { + U8 *unique_name_ptr = name_ptr + name.size + 1; + String8 unique_name = str8_cstring_capped((char*)unique_name_ptr, (char *)(first + cap)); + extracted_name = unique_name; + } + } + else{ + extracted_name = name; + } + } + } + }break; + + case CV_LeafKind_ENUM: + { + if (sizeof(CV_LeafEnum) <= cap){ + CV_LeafEnum *lf_enum = (CV_LeafEnum*)first; + + if (!(lf_enum->props & CV_TypeProp_FwdRef)){ + // name + U8 *name_ptr = (U8*)(lf_enum + 1); + String8 name = str8_cstring_capped((char*)name_ptr, (char *)(first + cap)); + + // unique name + if (compare_unique_name){ + if (lf_enum->props & CV_TypeProp_HasUniqueName) { + U8 *unique_name_ptr = name_ptr + name.size + 1; + String8 unique_name = str8_cstring_capped((char*)unique_name_ptr, (char *)(first + cap)); + extracted_name = unique_name; + } + } + else{ + extracted_name = name; + } + } + } + }break; + } + } + } + + if (str8_match(extracted_name, name, 0)){ + struct Chain *chain = push_array(scratch.arena, struct Chain, 1); + SLLQueuePush(first, last, chain); + count += 1; + chain->itype = itype; + if (count == output_cap){ + goto dblbreak; + } + } + } + } + + dblbreak:; + + + // assemble result + CV_TypeId *itypes = push_array_aligned(arena, CV_TypeId, count, 8); + { + CV_TypeId *itype_ptr = itypes; + for (struct Chain *node = first; + node != 0; + node = node->next, itype_ptr += 1){ + *itype_ptr = node->itype; + } + } + CV_TypeIdArray result = {0}; + result.itypes = itypes; + result.count = count; + + scratch_end(scratch); + + return(result); +} + +internal CV_TypeId +pdb_tpi_first_itype_from_name(PDB_TpiHashParsed *tpi_hash, CV_LeafParsed *tpi_leaf, + String8 name, B32 compare_unique_name){ + Temp scratch = scratch_begin(0, 0); + CV_TypeIdArray array = pdb_tpi_itypes_from_name(scratch.arena, tpi_hash, tpi_leaf, + name, compare_unique_name, 1); + CV_TypeId result = 0; + if (array.count > 0){ + result = array.itypes[0]; + } + + scratch_end(scratch); + return(result); +} + +//////////////////////////////// +//~ PDB Strtbl Functions + +internal String8 +pdb_strtbl_string_from_off(PDB_Strtbl *strtbl, U32 off){ + U32 strblock_max = strtbl->strblock_max; + U32 full_off_raw = strtbl->strblock_min + off; + U32 full_off = ClampTop(full_off_raw, strblock_max); + String8 result = str8_cstring_capped((char*)(strtbl->data.str + full_off), + (char*)(strtbl->data.str + strblock_max)); + return(result); +} + +internal String8 +pdb_strtbl_string_from_index(PDB_Strtbl *strtbl, PDB_StringIndex idx){ + String8 result = {0}; + if (idx < strtbl->bucket_count){ + U32 off = *(U32*)(strtbl->data.str + strtbl->buckets_min + idx*4); + result = pdb_strtbl_string_from_off(strtbl, off); + } + return(result); +} diff --git a/src/pdb/pdb_parse.h b/src/pdb/pdb_parse.h new file mode 100644 index 00000000..5855dd48 --- /dev/null +++ b/src/pdb/pdb_parse.h @@ -0,0 +1,244 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef PDB_PARSE_H +#define PDB_PARSE_H + +//////////////////////////////// +//~ PDB Parser String Table Types + +typedef struct PDB_Strtbl +{ + String8 data; + U32 bucket_count; + U32 strblock_min; + U32 strblock_max; + U32 buckets_min; + U32 buckets_max; +} PDB_Strtbl; + +//////////////////////////////// +//~ PDB Parser Info Types + +typedef enum PDB_NamedStream +{ + PDB_NamedStream_HeaderBlock, + PDB_NamedStream_StringTable, + PDB_NamedStream_LinkInfo, + PDB_NamedStream_Count +} PDB_NamedStream; + +typedef struct PDB_NamedStreamTable +{ + MSF_StreamNumber sn[PDB_NamedStream_Count]; +} PDB_NamedStreamTable; + +typedef struct PDB_InfoNode +{ + struct PDB_InfoNode *next; + String8 string; + MSF_StreamNumber sn; +} PDB_InfoNode; + +typedef struct PDB_Info +{ + PDB_InfoNode *first; + PDB_InfoNode *last; + COFF_Guid auth_guid; +} PDB_Info; + +typedef struct PDB_InfoHeader +{ + PDB_InfoVersion version; + U32 time; + U32 age; +} PDB_InfoHeader; + +//////////////////////////////// +//~ PDB Parser DBI Types + +// (this is not "literally" defined by the format - but helpful to have) +typedef enum PDB_DbiRange +{ + PDB_DbiRange_ModuleInfo, + PDB_DbiRange_SecCon, + PDB_DbiRange_SecMap, + PDB_DbiRange_FileInfo, + PDB_DbiRange_TSM, + PDB_DbiRange_EcInfo, + PDB_DbiRange_DbgHeader, + PDB_DbiRange_COUNT +} PDB_DbiRange; + +// (this is not "literally" defined by the format - but helpful to have) +typedef enum +{ + PDB_DbiCompUnitRange_Symbols, + PDB_DbiCompUnitRange_C11, + PDB_DbiCompUnitRange_C13, + PDB_DbiCompUnitRange_COUNT +} PDB_DbiCompUnitRange; + +typedef struct PDB_DbiParsed +{ + String8 data; + COFF_MachineType machine_type; + MSF_StreamNumber gsi_sn; + MSF_StreamNumber psi_sn; + MSF_StreamNumber sym_sn; + U64 range_off[(U64)(PDB_DbiRange_COUNT) + 1]; + MSF_StreamNumber dbg_streams[PDB_DbiStream_COUNT]; +} PDB_DbiParsed; + +typedef struct PDB_CompUnit +{ + MSF_StreamNumber sn; + U32 range_off[(U32)(PDB_DbiCompUnitRange_COUNT) + 1]; + + String8 obj_name; + String8 group_name; +} PDB_CompUnit; + +typedef struct PDB_CompUnitNode +{ + struct PDB_CompUnitNode *next; + PDB_CompUnit unit; +} PDB_CompUnitNode; + +typedef struct PDB_CompUnitArray +{ + PDB_CompUnit **units; + U64 count; +} PDB_CompUnitArray; + +typedef struct PDB_CompUnitContribution +{ + U32 mod; + U64 voff_first; + U64 voff_opl; +} PDB_CompUnitContribution; + +typedef struct PDB_CompUnitContributionArray +{ + PDB_CompUnitContribution *contributions; + U64 count; +} PDB_CompUnitContributionArray; + + +//////////////////////////////// +//~ PDB Parser TPI/IPI Types + +typedef struct PDB_TpiParsed +{ + String8 data; + + // leaf info + U64 leaf_first; + U64 leaf_opl; + U32 itype_first; + U32 itype_opl; + + // hash info + MSF_StreamNumber hash_sn; + MSF_StreamNumber hash_sn_aux; + U32 hash_key_size; + U32 hash_bucket_count; + U32 hash_vals_off; + U32 hash_vals_size; + U32 itype_off; + U32 itype_size; + U32 hash_adj_off; + U32 hash_adj_size; + +} PDB_TpiParsed; + +typedef struct PDB_TpiHashBlock +{ + struct PDB_TpiHashBlock *next; + U32 local_count; + CV_TypeId itypes[13]; // 13 = (64 - 12)/4 +} PDB_TpiHashBlock; + +typedef struct PDB_TpiHashParsed +{ + String8 data; + String8 aux_data; + + PDB_TpiHashBlock **buckets; + U32 bucket_count; + U32 bucket_mask; +} PDB_TpiHashParsed; + +//////////////////////////////// +//~ PDB Parser GSI Types + +typedef struct PDB_GsiBucket +{ + U32 *offs; + U64 count; +} PDB_GsiBucket; + +typedef struct PDB_GsiParsed +{ + PDB_GsiBucket buckets[4096]; +} PDB_GsiParsed; + +//////////////////////////////// +//~ PDB Parser Functions + +internal PDB_Info* pdb_info_from_data(Arena *arena, String8 pdb_info_data); +internal PDB_NamedStreamTable*pdb_named_stream_table_from_info(Arena *arena, PDB_Info *info); +internal PDB_Strtbl* pdb_strtbl_from_data(Arena *arena, String8 strtbl_data); + +internal PDB_DbiParsed* pdb_dbi_from_data(Arena *arena, String8 dbi_data); +internal PDB_TpiParsed* pdb_tpi_from_data(Arena *arena, String8 tpi_data); +internal PDB_TpiHashParsed* pdb_tpi_hash_from_data(Arena *arena, + PDB_Strtbl *strtbl, + PDB_TpiParsed *tpi, + String8 tpi_hash_data, + String8 tpi_hash_aux_data); +internal PDB_GsiParsed* pdb_gsi_from_data(Arena *arena, String8 gsi_data); + +internal COFF_SectionHeaderArray pdb_coff_section_array_from_data(Arena *arena, String8 section_data); + +internal PDB_CompUnitArray* pdb_comp_unit_array_from_data(Arena *arena, + String8 module_info_data); + +internal PDB_CompUnitContributionArray* +pdb_comp_unit_contribution_array_from_data(Arena *arena, String8 seccontrib_data, + COFF_SectionHeaderArray sections); + +//////////////////////////////// +//~ PDB Dbi Functions + +internal String8 pdb_data_from_dbi_range(PDB_DbiParsed *dbi, PDB_DbiRange range); +internal String8 pdb_data_from_unit_range(MSF_Parsed *msf, PDB_CompUnit *unit, + PDB_DbiCompUnitRange range); + +//////////////////////////////// +//~ PDB Tpi Functions + +internal String8 pdb_leaf_data_from_tpi(PDB_TpiParsed *tpi); + +internal CV_TypeIdArray pdb_tpi_itypes_from_name(Arena *arena, + PDB_TpiHashParsed *tpi_hash, + CV_LeafParsed *tpi_leaf, + String8 name, + B32 compare_unique_name, + U32 output_cap); + +internal CV_TypeId pdb_tpi_first_itype_from_name(PDB_TpiHashParsed *tpi_hash, + CV_LeafParsed *tpi_leaf, + String8 name, + B32 compare_unique_name); + +//////////////////////////////// +//~ PDB Strtbl Functions + +internal String8 pdb_strtbl_string_from_off(PDB_Strtbl *strtbl, U32 off); +internal String8 pdb_strtbl_string_from_index(PDB_Strtbl *strtbl, + PDB_StringIndex idx); + + +#endif // PDB_PARSE_H + diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index dbe6c594..04545bc7 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -575,6 +575,7 @@ #include "msf/msf.h" #include "msf/msf_parse.h" #include "pdb/pdb.h" +#include "pdb/pdb_parse.h" #include "pdb/pdb_stringize.h" #include "rdi_from_pdb/rdi_from_pdb.h" #include "regs/regs.h" @@ -616,6 +617,7 @@ #include "msf/msf.c" #include "msf/msf_parse.c" #include "pdb/pdb.c" +#include "pdb/pdb_parse.c" #include "pdb/pdb_stringize.c" #include "rdi_from_pdb/rdi_from_pdb.c" #include "regs/regs.c" diff --git a/src/rdi_from_pdb/rdi_from_pdb_main.c b/src/rdi_from_pdb/rdi_from_pdb_main.c index f3f83d97..8c542141 100644 --- a/src/rdi_from_pdb/rdi_from_pdb_main.c +++ b/src/rdi_from_pdb/rdi_from_pdb_main.c @@ -31,6 +31,7 @@ #include "msf/msf.h" #include "msf/msf_parse.h" #include "pdb/pdb.h" +#include "pdb/pdb_parse.h" #include "pdb/pdb_stringize.h" #include "rdi_from_pdb.h" @@ -45,6 +46,7 @@ #include "msf/msf.c" #include "msf/msf_parse.c" #include "pdb/pdb.c" +#include "pdb/pdb_parse.c" #include "pdb/pdb_stringize.c" #include "rdi_from_pdb.c"