#include #include #include #include #include #include #define ASSERT assert typedef struct Arena Arena; struct Arena { Arena *prev; Arena *next; Arena *current; int64_t base; int64_t cursor; int64_t reserved; int64_t committed; // The data continues here }; #define ARENA_DEFAULT_RESERVE_SIZE (64ll*1024ll*1024ll) #define ARENA_DEFAULT_COMMIT_SIZE (64ll*1024) #define ARENA_MIN_SIZE (sizeof(Arena)) Arena * Arena_Allocate_sized(int64_t reserved, int64_t committed) { ASSERT(committed >= ARENA_MIN_SIZE); if (reserved < committed) { reserved = committed; } Arena *result = NULL; int64_t size = sizeof(Arena)+(sizeof(void*)-1); uint8_t *allocated_data = VirtualAlloc(0, reserved, MEM_RESERVE, PAGE_READWRITE); VirtualAlloc(allocated_data, committed, MEM_COMMIT, PAGE_READWRITE); result = (Arena *)allocated_data; memset(result, 0, sizeof(Arena)); result->cursor = ARENA_MIN_SIZE; result->reserved = reserved; result->committed = committed; result->current = result; return result; } Arena * Arena_Allocate(void) { return Arena_Allocate_sized(ARENA_DEFAULT_RESERVE_SIZE, ARENA_DEFAULT_COMMIT_SIZE); } void Arena_Release(Arena *arena) { for (; arena; arena = arena->next) { VirtualFree(arena, 0, MEM_RELEASE); } } void * Arena_Push_aligned(Arena *arena, int64_t total_bytes, int64_t alignement) { Arena *arena_curr = arena->current; uintptr_t start_address = ((uintptr_t)arena_curr)+arena_curr->cursor; uintptr_t start_address_aligned = (start_address+(alignement-1))-((start_address+(alignement-1))%alignement); int64_t new_cursor = arena_curr->cursor+(start_address_aligned+total_bytes)-start_address; void *result = (void *)start_address_aligned; // Not enough memory in the current arena, allocate a new arena if (new_cursor > arena_curr->committed) { if (new_cursor > arena_curr->reserved) { int64_t committed = ARENA_MIN_SIZE+total_bytes+alignement-1; if (committed < ARENA_DEFAULT_COMMIT_SIZE) { committed = ARENA_DEFAULT_COMMIT_SIZE; } Arena *arena_new = Arena_Allocate_sized(ARENA_DEFAULT_RESERVE_SIZE, committed); start_address = ((uintptr_t)arena_new) + arena_new->cursor; start_address_aligned = (start_address+(alignement-1))-((start_address+(alignement-1))%alignement); new_cursor = arena_new->cursor + (start_address_aligned+total_bytes)-start_address; result = (void *)start_address_aligned; arena_curr->next = arena_new; arena_new->prev = arena_curr; arena_new->base = arena_curr->base + arena_curr->cursor; arena->current = arena_new; } else { int64_t committed = arena_curr->committed+ARENA_DEFAULT_COMMIT_SIZE; if (committed < new_cursor+1) { committed = new_cursor+1; } if (committed > arena_curr->reserved) { committed = arena_curr->reserved; } VirtualAlloc(arena_curr, committed, MEM_COMMIT, PAGE_READWRITE); arena_curr->committed = committed; } } arena->current->cursor = new_cursor; return result; } #define ARENA_NEW(arena, T) ((T*)Arena_Push_aligned(arena, sizeof(T), sizeof(void*))) #define ARENA_NEW_ZERO(arena, T) ((T*)memset(Arena_Push_aligned(arena, sizeof(T), sizeof(void*)),0,sizeof(T))) #define ARENA_NEW_ARRAY(arena, elements, T) ((T*)Arena_Push_aligned(arena, sizeof(T)*elements, sizeof(void*))) #define ARRLEN(a) (sizeof(a)/sizeof(a[0])) int64_t Arena_Get_cursor(Arena *arena) { Arena *arena_curr = arena->current; int64_t result = arena_curr->base+arena_curr->cursor; return result; } void Arena_Pop_to_cursor(Arena *arena, int64_t cursor) { Arena *arena_curr = arena->current; while (arena_curr) { if (arena_curr->base < cursor) { int64_t new_cursor = cursor - arena_curr->base; ASSERT(new_cursor <= arena->cursor); ASSERT(new_cursor >= ARENA_MIN_SIZE); arena_curr->cursor = new_cursor; break; } else { Arena *arena_prev = arena_curr->prev; VirtualFree(arena_curr, 0, MEM_RELEASE); arena_curr = arena_prev; arena_curr->next = NULL; } } arena->current = arena_curr; } typedef struct { Arena *arena; int64_t cursor; } ArenaTemp; #define SCRATCH_POOL_COUNT 4 Arena *scratch_pool[SCRATCH_POOL_COUNT] = {0}; ArenaTemp Arena_Get_scratch(Arena *conflicts[], int64_t conflicts_count) { ArenaTemp result = {0}; int64_t selected_scratch = -1; for (int64_t scratch_i=0; scratch_i < SCRATCH_POOL_COUNT; scratch_i+=1) { if (scratch_pool[scratch_i] == NULL) { scratch_pool[scratch_i] = Arena_Allocate(); selected_scratch = scratch_i; break; } bool we_have_conflict = false; for (int64_t conflict_i=0; conflict_i < conflicts_count; conflict_i+=1) { if (scratch_pool[scratch_i] == conflicts[conflict_i]) { we_have_conflict = true; break; } } if (!we_have_conflict) { selected_scratch = scratch_i; break; } } if (selected_scratch != -1) { result.arena = scratch_pool[selected_scratch]; result.cursor = Arena_Get_cursor(result.arena); } return result; } void Arena_Release_scratch(ArenaTemp scratch) { // TODO: Release the arena if is empty Arena_Pop_to_cursor(scratch.arena, scratch.cursor); } #define WITH_SCRATCH(scratch, conflicts, conflicts_count) \ int _i_=(scratch=Arena_Get_scratch(conflicts,conflicts_count),0); !_i_; _i_+=1,Arena_Release_scratch(scratch) #define LIST_PUSH(first,last,node) do { \ if (last) { \ (last)->next=(node); \ (last)=(last)->next; \ } \ else (last) = (node); \ if (!first) (first) = (last); \ }while(0) typedef struct { const uint8_t *data; int64_t len; } String; #define STR_LIT(s) (String){.data = (s), .len = (sizeof(s)-1)} #define STR_VARG(s) ((int)(s).len), ((s).data) bool String_match(String a, String b) { if (a.len != b.len) { return false; } for (int64_t cursor = 0; cursor < a.len; cursor+=1) { if (a.data[cursor] != a.data[cursor]) { return false; } } return true; } bool String_matches_end(String str, String end) { if (str.len < end.len) { return false; } int64_t end_cursor = end.len-1; int64_t str_cursor = str.len-1; for (; end_cursor >= 0; (end_cursor -= 1),(str_cursor -= 1)) { if (str.data[str_cursor] != end.data[end_cursor]) { return false; } } return true; } String String_concatenate(Arena *arena, String first, String second) { uint8_t *data = ARENA_NEW_ARRAY(arena, first.len+second.len, uint8_t); memcpy(data, first.data, first.len); memcpy(&data[first.len], second.data, second.len); String result = {0}; result.data = data; result.len = first.len+second.len; return result; } String String_path_concatenate(Arena *arena, String first, String second) { uint8_t *data = ARENA_NEW_ARRAY(arena, first.len+second.len+1, uint8_t); uint8_t *dst = data; memcpy(dst, first.data, first.len); dst += first.len; dst[0] = '\\'; dst += 1; memcpy(dst, second.data, second.len); String result = {0}; result.data = data; result.len = first.len+second.len+1; return result; } String String_clone(Arena *arena, String src) { uint8_t *data = ARENA_NEW_ARRAY(arena, src.len, uint8_t); memcpy(data, src.data, src.len); String result = {0}; result.data = data; result.len = src.len; return result; } String String_clone_from_cstring(Arena *arena, const char *src) { int64_t len = strlen(src); uint8_t *data = ARENA_NEW_ARRAY(arena, len, uint8_t); memcpy(data, src, len); String result = {0}; result.data = data; result.len = len; return result; } const char * String_clone_to_cstring(Arena *arena, String src) { char *result = ARENA_NEW_ARRAY(arena, src.len+1, char); memcpy(result, src.data, src.len); result[src.len] = '\0'; return result; } // djb2 http://www.cse.yorku.ca/~oz/hash.html uint64_t String_hash(String s) { uint64_t hash = 5381; for (int cursor = 0; cursor < s.len; cursor+=1) { uint64_t c = s.data[cursor]; hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ } return hash; } String String_format(Arena *arena, const char *fmt, ...) { int64_t arena_prev_cursor = Arena_Get_cursor(arena); char *buffer = ARENA_NEW_ARRAY(arena, 1024*1024, char); va_list argptr; va_start(argptr, fmt); int64_t chars_written = vsnprintf(buffer, 1024*1024, fmt, argptr); va_end(argptr); String result = {0}; if (chars_written <= 0) { Arena_Pop_to_cursor(arena, arena_prev_cursor); return result; } int64_t arena_current_cursor = Arena_Get_cursor(arena); Arena_Pop_to_cursor(arena, arena_current_cursor - (1024*1024-chars_written)); result.len = chars_written; result.data = buffer; return result; } typedef struct StringNode StringNode; struct StringNode { String value; StringNode *next; }; typedef struct { StringNode *first; StringNode *last; int64_t count; } StringList; typedef struct { StringList strings; } StringBuilder; void StringBuilder_Append(Arena *arena, StringBuilder *sb, String s) { StringNode *node = ARENA_NEW_ZERO(arena, StringNode); node->value = String_clone(arena, s); LIST_PUSH(sb->strings.first, sb->strings.last, node); sb->strings.count += 1; } bool StringBuilder_Write_to_file(StringBuilder *sb, String filepath) { bool success = false; ArenaTemp scratch; for (WITH_SCRATCH(scratch, NULL, 0)) { const char *cfilepath = String_clone_to_cstring(scratch.arena, filepath); HANDLE file_handle = CreateFileA( cfilepath, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); if (file_handle == INVALID_HANDLE_VALUE) { continue; } for (StringNode *string_node = sb->strings.first; string_node; string_node = string_node->next) { if (string_node->value.len > 0) { WriteFile(file_handle, string_node->value.data, string_node->value.len, NULL, NULL); } } success = true; } return success; } // HASHMAP Stuff #define HASHMAP_MAX_ITEMS_PER_BUCKET 16 typedef struct HashMapBucket HashMapBucket; struct HashMapBucket { uint64_t items_count; uint64_t keys[HASHMAP_MAX_ITEMS_PER_BUCKET]; HashMapBucket *next; uint64_t values[HASHMAP_MAX_ITEMS_PER_BUCKET]; }; typedef struct { uint64_t total_slots; HashMapBucket *slots[]; } HashMap; static inline uint64_t HashMap_String_key(String s) { return String_hash(s); } HashMap * HashMap_New(Arena *arena, uint64_t total_slots); uint64_t * HashMap_Lookup(HashMap *hashmap, uint64_t key); bool HashMap_Delete(HashMap *hashmap, uint64_t key); void HashMap_Delete_all_values(HashMap *hashmap); uint64_t * HashMap_Lookup_or_create(Arena *arena, HashMap *hashmap, uint64_t key, bool *found); bool HashMap_Insert(Arena *arena, HashMap *hashmap, uint64_t key, uint64_t value, bool overwrite); HashMap * HashMap_New(Arena *arena, uint64_t total_slots) { HashMap *result = Arena_Push_aligned(arena, sizeof(HashMap)+sizeof(void*)*total_slots, 8); if (result != NULL) { result->total_slots = total_slots; for (int64_t i = 0; i < (int64_t)total_slots; i += 1) { result->slots[i] = NULL; } } return result; } uint64_t * HashMap_Lookup(HashMap *hashmap, uint64_t key) { uint64_t slot_pos = key%hashmap->total_slots; for (HashMapBucket *bucket = hashmap->slots[slot_pos]; bucket; bucket = bucket->next) { for (int64_t i = 0; i < bucket->items_count; i += 1) { if (bucket->keys[i] == key) { return &(bucket->values[i]); } } } return NULL; } bool HashMap_Delete(HashMap *hashmap, uint64_t key) { uint64_t slot_pos = key%hashmap->total_slots; for (HashMapBucket *bucket = hashmap->slots[slot_pos]; bucket; bucket = bucket->next) { for (int64_t i = 0; i < bucket->items_count; i += 1) { if (bucket->keys[i] == key) { int64_t last_item_index = bucket->items_count-1; bucket->keys[i] = bucket->keys[last_item_index]; bucket->values[i] = bucket->values[last_item_index]; bucket->items_count -= 1; return true; } } } return false; } void HashMap_Delete_all_values(HashMap *hashmap) { int64_t total_slots = hashmap->total_slots; for (int64_t slot_i = 0; slot_i < total_slots; slot_i+=1) { for (HashMapBucket *bucket = hashmap->slots[slot_i]; bucket; bucket = bucket->next) { bucket->items_count = 0; } } } uint64_t * HashMap_Lookup_or_create(Arena *arena, HashMap *hashmap, uint64_t key, bool *found) { uint64_t slot_pos = key%hashmap->total_slots; HashMapBucket **append_bucket = NULL; HashMapBucket *target_bucket = NULL; if (hashmap->slots[slot_pos]) { HashMapBucket *bucket = hashmap->slots[slot_pos]; for (;;) { // Iterate over the bucket items for (int64_t i = 0; i < bucket->items_count; i += 1) { if (bucket->keys[i] == key) { if (found) *found = true; return &(bucket->values[i]); } } // Check if the bucket has empty items to target it in case the key is not found if (!target_bucket && bucket->items_count < HASHMAP_MAX_ITEMS_PER_BUCKET) { target_bucket = bucket; } // If the next bucket is NULL we end if (bucket->next == NULL) { break; } // Advance the bucket bucket = bucket->next; } if (!target_bucket) { append_bucket = &(bucket->next); } } else { append_bucket = &(hashmap->slots[slot_pos]); } // If append bucket is not null we have to create a new bucket and append it to the // append_bucket pointer if (append_bucket) { target_bucket = ARENA_NEW_ZERO(arena, HashMapBucket); if (target_bucket) { target_bucket->items_count = 0; target_bucket->next = NULL; *append_bucket = target_bucket; } } uint64_t *result = NULL; // If a target bucket exist we add a new item to it if (target_bucket) { target_bucket->keys[target_bucket->items_count] = key; target_bucket->values[target_bucket->items_count] = 0; result = &(target_bucket->values[target_bucket->items_count]); target_bucket->items_count += 1; } // If we are here we didn't find the key if (found) *found = false; return result; } bool HashMap_Insert(Arena *arena, HashMap *hashmap, uint64_t key, uint64_t value, bool overwrite) { bool found = false; uint64_t *val_ptr = HashMap_Lookup_or_create(arena, hashmap, key, &found); if (!val_ptr) return false; if (found && !overwrite) return false; *val_ptr = value; return true; } typedef struct { const uint8_t *data; int64_t cursor; int64_t size; } ProcessBinaryContext; static inline bool Get_U8(ProcessBinaryContext *ctx, uint8_t *out) { if (ctx->cursor+1 <= ctx->size) { if (out) { (*out) = ctx->data[ctx->cursor]; } ctx->cursor += 1; return true; } else { return false; } } static inline bool Peek_U16(ProcessBinaryContext *ctx, uint16_t *out) { if (ctx->cursor+2 <= ctx->size) { if (out) { uint16_t low = ctx->data[ctx->cursor]; uint16_t high = ctx->data[ctx->cursor+1]; (*out) = (high << 8)|low; } return true; } else { return false; } } static inline bool Get_U16(ProcessBinaryContext *ctx, uint16_t *out) { if (Peek_U16(ctx, out)) { ctx->cursor+=2; return true; } else { return false; } } static inline bool Peek_U32(ProcessBinaryContext *ctx, uint32_t *out) { if (ctx->cursor+4 <= ctx->size) { if (out) { uint32_t c0 = (uint8_t)(ctx->data[ctx->cursor]); uint32_t c1 = (uint8_t)(ctx->data[ctx->cursor+1]); uint32_t c2 = (uint8_t)(ctx->data[ctx->cursor+2]); uint32_t c3 = (uint8_t)(ctx->data[ctx->cursor+3]); (*out) = (c3 << 24)|(c2 << 16)|(c1 << 8)|(c0); } return true; } else { return false; } } static inline bool Get_U32(ProcessBinaryContext *ctx, uint32_t *out) { if (Peek_U32(ctx, out)) { ctx->cursor+=4; return true; } return false; } static inline bool Get_U64(ProcessBinaryContext *ctx, uint64_t *out) { if (ctx->cursor+8 <= ctx->size) { if (out) { uint64_t c0 = (uint8_t)(ctx->data[ctx->cursor]); uint64_t c1 = (uint8_t)(ctx->data[ctx->cursor+1]); uint64_t c2 = (uint8_t)(ctx->data[ctx->cursor+2]); uint64_t c3 = (uint8_t)(ctx->data[ctx->cursor+3]); uint64_t c4 = (uint8_t)(ctx->data[ctx->cursor+4]); uint64_t c5 = (uint8_t)(ctx->data[ctx->cursor+5]); uint64_t c6 = (uint8_t)(ctx->data[ctx->cursor+6]); uint64_t c7 = (uint8_t)(ctx->data[ctx->cursor+7]); (*out) = (c7<<56)|(c6<<48)|(c5<<40)|(c4<<32)|(c3 << 24)|(c2 << 16)|(c1 << 8)|(c0); ctx->cursor += 8; } return true; } else { return false; } } static inline const uint8_t * Get_bytes(ProcessBinaryContext *ctx, uint32_t count) { const uint8_t *result = NULL; if (ctx->cursor+count <= ctx->size) { result = &(ctx->data[ctx->cursor]); ctx->cursor += count; } return result; } static inline bool Seek_to(ProcessBinaryContext *ctx, int64_t cursor) { if (cursor < 0 && cursor > ctx->size) { return false; } ctx->cursor = cursor; return true; } static inline const uint8_t * Get_current_addr(ProcessBinaryContext *ctx) { if (ctx->cursor >= ctx->size) { return NULL; } return &(ctx->data[ctx->cursor]); } void Get_all_obj_in_directory_ex(Arena *arena, String path, StringList *list_result) { String path_wildcard = String_path_concatenate(arena, path, STR_LIT("*")); WIN32_FIND_DATAA file_data; HANDLE hfind = FindFirstFile(String_clone_to_cstring(arena, path_wildcard), &file_data); if (hfind == INVALID_HANDLE_VALUE) { return; } for (bool there_are_more = true; there_are_more; there_are_more = FindNextFileA(hfind, &file_data)) { String filename = String_clone_from_cstring(arena, file_data.cFileName); if (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if (String_match(filename, STR_LIT("."))) { continue; } if (String_match(filename, STR_LIT(".."))) { continue; } String next_directory = String_path_concatenate(arena, path, filename); Get_all_obj_in_directory_ex(arena, next_directory, list_result); } else if (file_data.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) { if (String_matches_end(filename , STR_LIT(".obj"))) { StringNode *string_node = ARENA_NEW_ZERO(arena, StringNode); string_node->value = String_path_concatenate(arena, path, filename); LIST_PUSH(list_result->first, list_result->last, string_node); list_result->count += 1; } } } FindClose(hfind); return; } uint8_t * Read_entire_file(Arena *arena, String filepath, int64_t *size_out) { uint8_t *result_data = NULL; int64_t result_size = 0; int64_t arena_cursor = Arena_Get_cursor(arena); bool has_errors = false; const char *cfilepath = String_clone_to_cstring(arena, filepath); HANDLE file_handle = CreateFileA( cfilepath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if (file_handle == INVALID_HANDLE_VALUE) { has_errors = true; } int64_t size = 0; if (!has_errors) { DWORD get_size_result = GetFileSize(file_handle, NULL); if (get_size_result == INVALID_FILE_SIZE) { has_errors = true; } else { size = (int64_t)get_size_result; } } uint8_t *file_data = NULL; if (!has_errors) { file_data = ARENA_NEW_ARRAY(arena, size, uint8_t); if (!ReadFile(file_handle, file_data, size, NULL, NULL)) { has_errors = true; } } CloseHandle(file_handle); if (has_errors) { Arena_Pop_to_cursor(arena, arena_cursor); } else { result_data = file_data; result_size = size; } *size_out = result_size; return result_data; } StringList Get_all_obj_in_directory(Arena *arena, String path) { StringList result = {0}; Get_all_obj_in_directory_ex(arena, path, &result); return result; } enum { COFF_MACHINE_UNKNOWN = 0x0, // The content of this field is assumed to be applicable to any machine type }; typedef enum { TYPE_NULL = 0, TYPE_POINTER = 1, TYPE_FUNCTION = 2, TYPE_ARRAY = 3 }ImageSymDtype; enum { CLASS_END_OF_FUNCTION = -1, // A special symbol that represents the end of function, for debugging purposes. CLASS_NULL = 0, // No assigned storage class. CLASS_AUTOMATIC = 1, // The automatic (stack) variable. The Value field specifies the stack frame offset. CLASS_EXTERNAL = 2, // A value that Microsoft tools use for external symbols. The Value field indicates the size if // the section number is IMAGE_SYM_UNDEFINED (0). If the section number is not zero, then the // Value field specifies the offset within the section. CLASS_STATIC = 3, // The offset of the symbol within the section. If the Value field is zero, then the symbol // represents a section name. CLASS_REGISTER = 4, // A register variable. The Value field specifies the register number. CLASS_EXTERNAL_DEF = 5, // A symbol that is defined externally. CLASS_LABEL = 6, // A code label that is defined within the module. The Value field specifies the offset of the // symbol within the section. CLASS_UNDEFINED_LABEL = 7, // A reference to a code label that is not defined. CLASS_MEMBER_OF_STRUCT = 8, // The structure member. The Value field specifies the n th member. CLASS_ARGUMENT = 9, // A formal argument (parameter) of a function. The Value field specifies the n th argument. CLASS_STRUCT_TAG = 10, // The structure tag-name entry. CLASS_MEMBER_OF_UNION = 11, // A union member. The Value field specifies the n th member. CLASS_UNION_TAG = 12, // The Union tag-name entry. CLASS_TYPE_DEFINITION = 13, // A Typedef entry. CLASS_UNDEFINED_STATIC = 14, // A static data declaration. CLASS_ENUM_TAG = 15, // An enumerated type tagname entry. CLASS_MEMBER_OF_ENUM = 16, // A member of an enumeration. The Value field specifies the n th member. CLASS_REGISTER_PARAM = 17, // A register parameter. CLASS_BIT_FIELD = 18, // A bit-field reference. The Value field specifies the n th bit in the bit field. CLASS_BLOCK = 100, // A .bb (beginning of block) or .eb (end of block) record. The Value field is the relocatable // address of the code location. CLASS_FUNCTION = 101, // A value that Microsoft tools use for symbol records that define the extent of a function: // begin function (.bf ), end function ( .ef ), and lines in function ( .lf ). For .lf records, // the Value field gives the number of source lines in the function. For .ef records, the Value // field gives the size of the function code. CLASS_END_OF_STRUCT = 102, // An end-of-structure entry. CLASS_FILE = 103, // A value that Microsoft tools, as well as traditional COFF format, use for the source-file // symbol record. The symbol is followed by auxiliary records that name the file. CLASS_SECTION = 104, // A definition of a section (Microsoft tools use STATIC storage class instead). CLASS_WEAK_EXTERNAL = 105, // A weak external. For more information, see Auxiliary Format 3: Weak Externals. CLASS_CLR_TOKEN = 107, // A CLR token symbol. The name is an ASCII string that consists of the hexadecimal value of the // token. For more information, see CLR Token Definition (Object Only). }; typedef struct { bool success; int64_t time_stamp; StringList exported_symbols; StringList imported_symbols; } ProcessCoffResult; ProcessCoffResult Process_coff_from_memory(Arena *arena, const uint8_t *data, int64_t size) { ProcessCoffResult result = {0}; ProcessBinaryContext ctx = {0}; ctx.data = data; ctx.size = size; typedef struct { bool big_obj; uint16_t machine; uint32_t time_stamp; uint32_t number_of_sections; uint32_t pointer_to_symbol_table; uint32_t number_of_symbols; } COFFInfo; COFFInfo info = {0}; if (!Get_U16(&ctx, &(info.machine))) { return result; } if (info.machine == COFF_MACHINE_UNKNOWN) { // ASUME ANON_OBJECT_HEADER info.big_obj = true; uint16_t magic2; if (!Get_U16(&ctx, &magic2)) { return result; } if (magic2 != 0xFFFF) { return result; } uint16_t version; if (!Get_U16(&ctx, &version)) { return result; } if (version < 2) { return result; } if (version < 2) { return result; } // Get the real machine if (!Get_U16(&ctx, &(info.machine))) { return result; } // Time date stamp if (!Get_U32(&ctx, &info.time_stamp)) { return result; } // CLSID if (!Get_bytes(&ctx, 16)) { return result; } // Size of data if (!Get_U32(&ctx, NULL)) { return result; } // Flags if (!Get_U32(&ctx, NULL)) { return result; } // Metadata size if (!Get_U32(&ctx, NULL)) { return result; } // Metadata offset if (!Get_U32(&ctx, NULL)) { return result; } // Number of sections (32 bits) if (!Get_U32(&ctx, &info.number_of_sections)) { return result; } // Pointer to symbol table if (!Get_U32(&ctx, &info.pointer_to_symbol_table)) { return result; } // Number of symbols if (!Get_U32(&ctx, &info.number_of_symbols)) { return result; } } else { // Normal COFF header // Number of sections uint16_t number_of_sections = 0; if (!Get_U16(&ctx, &number_of_sections)) { return result; } info.number_of_sections = number_of_sections; // Time date stamp if (!Get_U32(&ctx, &info.time_stamp)) { return result; } // Pointer to symbol table if (!Get_U32(&ctx, &info.pointer_to_symbol_table)) { return result; } // Number of symbols if (!Get_U32(&ctx, &info.number_of_symbols)) { return result; } // Size of optional header if (!Get_U16(&ctx, NULL)) { return result; } // Characteristics if (!Get_U16(&ctx, NULL)) { return result; } } if (info.pointer_to_symbol_table == 0) { return result; } if (!Seek_to(&ctx, info.pointer_to_symbol_table)) { return result; } // NOTE(Tano): This is very bad we have to validate the size of the string table uint32_t pointer_to_string_table = 0; if (info.big_obj) { pointer_to_string_table = info.pointer_to_symbol_table+info.number_of_symbols*20; } else { pointer_to_string_table = info.pointer_to_symbol_table+info.number_of_symbols*18; } for (int64_t i = 0; i < info.number_of_symbols; i+=1) { typedef struct { union { const char str[8]; uint64_t pack; struct { uint32_t packl; uint32_t packh; }; }; } MaybeName; MaybeName maybe_name = {0}; if (!Get_U64(&ctx, &(maybe_name.pack))) { return result; } const char *name = NULL; if (maybe_name.packl == 0) { // NOTE(Tano): This is very bad we have to validate if is inside the string table // and if it is null terminated uint32_t prev_cursor = ctx.cursor; if (!Seek_to(&ctx, pointer_to_string_table+maybe_name.packh)) { return result; } name = Get_current_addr(&ctx); if (!name) { return result; } if (!Seek_to(&ctx, prev_cursor)) { return result; } } else { name = maybe_name.str; } uint32_t value = 0; if (!Get_U32(&ctx, &value)) { return result; } int32_t section_number = 0; if (info.big_obj) { if (!Get_U32(&ctx, (uint32_t*)§ion_number)) { return result; } } else { int16_t section_number_16 = 0; if (!Get_U16(&ctx, (uint16_t*)§ion_number_16)) { return result; } section_number = section_number_16; } uint16_t type = 0; if (!Get_U16(&ctx, &type)) { return result; } int8_t storage_class = 0; if (!Get_U8(&ctx, (uint8_t *)&storage_class)) { return result; } if (!Get_U8(&ctx, NULL)) { return result; } if (storage_class != CLASS_EXTERNAL) { // Only care about external continue; } StringNode *string_node = ARENA_NEW_ZERO(arena, StringNode); string_node->value = String_clone_from_cstring(arena, name); if (section_number == 0) { // UNDEFINED (Is imported) LIST_PUSH(result.imported_symbols.first, result.imported_symbols.last, string_node); result.imported_symbols.count += 1; } else {// Otherwise exported LIST_PUSH(result.exported_symbols.first, result.exported_symbols.last, string_node); result.exported_symbols.count += 1; } } result.time_stamp = info.time_stamp; result.success = true; return result; } typedef struct ObjectDependency ObjectDependency; struct ObjectDependency { int64_t id; int64_t weight; ObjectDependency *next; }; typedef struct { String filename; int64_t size_in_bytes; bool valid; int64_t time_stamp; StringList exported_symbols; StringList imported_symbols; ObjectDependency *first_dependency; ObjectDependency *last_dependency; int64_t dependency_count; int64_t dependents_count; int64_t group_id; } ObjectInfo; bool Assign_group_if_depends_on_it(ObjectInfo objects[], int64_t object_id, bool visited[], int64_t group_id) { if (objects[object_id].group_id == group_id) { return true; } if (objects[object_id].group_id != 0) { return false; } if (visited[object_id]) { return false; } visited[object_id] = true; bool depends = false; for (ObjectDependency *dep = objects[object_id].first_dependency; dep; dep = dep->next) { depends |= Assign_group_if_depends_on_it(objects, dep->id, visited, group_id); } if (depends) { objects[object_id].group_id = group_id; return true; } else { return false; } } bool Try_assign_group(ObjectInfo objects[], int64_t objects_count, int64_t object_id, int64_t group_id) { if (objects[object_id].group_id != 0) { return false; } objects[object_id].group_id = group_id; ArenaTemp scratch; for (WITH_SCRATCH(scratch, NULL, 0)) { bool *visited = ARENA_NEW_ARRAY(scratch.arena, objects_count, bool); memset(visited, 0, objects_count*sizeof(bool)); visited[object_id] = true; for (ObjectDependency *dep = objects[object_id].first_dependency; dep; dep = dep->next) { Assign_group_if_depends_on_it(objects, dep->id, visited, group_id); } } return true; } int main(int argc, const char *argv[]) { Arena *arena = Arena_Allocate(); if (argc != 3) { const char *exec = "disaster"; if (argc > 0) { exec = argv[0]; } printf("USAGE: %s path output\n", exec); return -1; } String path = String_clone_from_cstring(arena, argv[1]); String output = String_clone_from_cstring(arena, argv[2]); StringList list = Get_all_obj_in_directory(arena, path); int64_t total_size = 0; ObjectInfo *objects = ARENA_NEW_ARRAY(arena, list.count, ObjectInfo); int64_t object_i = 0; for (StringNode *node = list.first; node; (node = node->next),(object_i+=1)) { memset(&objects[object_i], 0, sizeof(ObjectInfo)); ArenaTemp scratch; for (WITH_SCRATCH(scratch, (Arena*[]){arena}, 1)) { int64_t size = 0; objects[object_i].filename = node->value; uint8_t *file_data = Read_entire_file(scratch.arena, node->value, &size); if (!file_data) { printf("Failed to read file %.*s\n", (int)(node->value.len), node->value.data); } else { objects[object_i].size_in_bytes = size; ProcessCoffResult process_coff_result = Process_coff_from_memory(arena, file_data, size); if (process_coff_result.success) { objects[object_i].valid = true; objects[object_i].time_stamp = process_coff_result.time_stamp; objects[object_i].exported_symbols = process_coff_result.exported_symbols; objects[object_i].imported_symbols = process_coff_result.imported_symbols; } } } } printf("Total object files: %lld\n", list.count); int64_t total_exported = 0; int64_t errors = 0; HashMap *exported_to_object = HashMap_New(arena, 1024*1024); for (int64_t object_i = 0; object_i < list.count; object_i+=1) { for (StringNode *snode = objects[object_i].exported_symbols.first; snode; snode = snode->next) { if (HashMap_Insert(arena, exported_to_object, HashMap_String_key(snode->value), object_i, false)) { int64_t first_obj_i = *HashMap_Lookup(exported_to_object, HashMap_String_key(snode->value)); if (first_obj_i != object_i) { ObjectInfo *first_obj = &objects[first_obj_i]; printf("Error: symbol %.*s exported by more than one object file, first object file: %.*s , current object file: %.*s\n", (int)(snode->value.len), snode->value.data, (int)(first_obj->filename.len), first_obj->filename.data, (int)(objects[object_i].filename.len), objects[object_i].filename.data ); errors += 1; } } total_exported += 1; } } // printf("TOTAL_EXPORTED: %lld\n", total_exported); // printf("ERRORS: %lld\n", errors); // Find for each object file which object files have his imported symbols for (int64_t object_i = 0; object_i < list.count; object_i+=1) { ObjectInfo *current_object = &objects[object_i]; for (StringNode *snode = current_object->imported_symbols.first; snode; snode = snode->next) { int64_t *dependency_ptr = HashMap_Lookup(exported_to_object, HashMap_String_key(snode->value)); if (!dependency_ptr) { continue; } int64_t dependency_id = *dependency_ptr; // Linear search bool found = false; for (ObjectDependency *dep = current_object->first_dependency; dep; dep = dep->next) { if (dep->id == dependency_id) { dep->weight += 1; found = true; break; } } if (!found) { ObjectDependency *new_dep = ARENA_NEW_ZERO(arena, ObjectDependency); new_dep->id = dependency_id; new_dep->weight = 1; LIST_PUSH(current_object->first_dependency, current_object->last_dependency, new_dep); current_object->dependency_count += 1; // Increase by one the dependants count of the dependency objects[dependency_id].dependents_count += 1; } } } // Assign groups based on dependency cycles { int64_t group_id = 1; for (int64_t object_i = 0; object_i < list.count; object_i+=1) { if (Try_assign_group(objects, list.count, object_i, group_id)) { group_id += 1; } } } ArenaTemp scratch; for (WITH_SCRATCH(scratch, (Arena*[]){arena}, 0)) { StringBuilder string_builder = {0}; StringBuilder_Append(scratch.arena, &string_builder, STR_LIT("digraph {\n")); for (int64_t object_i = 0; object_i < list.count; object_i+=1) { ObjectInfo *current_object = &objects[object_i]; String src_filename = current_object->filename; String line_group = String_format(scratch.arena, "\"%.*s\" [class = \"%lld\"]\n", STR_VARG(src_filename), current_object->group_id); StringBuilder_Append(scratch.arena, &string_builder, line_group); for (ObjectDependency *dep = current_object->first_dependency; dep; dep = dep->next) { ObjectInfo *dep_object = &objects[dep->id]; String dst_filename = dep_object->filename; String line = String_format(scratch.arena, "\"%.*s\" -> \"%.*s\"\n", STR_VARG(src_filename), STR_VARG(dst_filename)); StringBuilder_Append(scratch.arena, &string_builder, line); } } StringBuilder_Append(scratch.arena, &string_builder, STR_LIT("}\n")); String output_object_info = String_concatenate(scratch.arena, output, STR_LIT("_graph.dot")); if (!StringBuilder_Write_to_file(&string_builder, output_object_info)) { printf("Failed to write file %.*s\n", STR_VARG(output_object_info)); } } for (WITH_SCRATCH(scratch, (Arena*[]){arena}, 0)) { StringBuilder string_builder = {0}; StringBuilder_Append(scratch.arena, &string_builder, STR_LIT("From, To, Weight\n")); for (int64_t object_i = 0; object_i < list.count; object_i+=1) { ObjectInfo *current_object = &objects[object_i]; String src_filename = current_object->filename; for (ObjectDependency *dep = current_object->first_dependency; dep; dep = dep->next) { ObjectInfo *dep_object = &objects[dep->id]; String dst_filename = dep_object->filename; String line = String_format(scratch.arena, "\"%.*s\", \"%.*s\", \"%lld\"\n", STR_VARG(src_filename), STR_VARG(dst_filename), dep->weight); StringBuilder_Append(scratch.arena, &string_builder, line); } } String output_object_info = String_concatenate(scratch.arena, output, STR_LIT("_edge_table.csv")); if (!StringBuilder_Write_to_file(&string_builder, output_object_info)) { printf("Failed to write file %.*s\n", STR_VARG(output_object_info)); } } for (WITH_SCRATCH(scratch, (Arena*[]){arena}, 0)) { StringBuilder string_builder = {0}; StringBuilder_Append(scratch.arena, &string_builder, STR_LIT("File, Valid, Time stamp, Size, Dependencies, Dependents, Group id\n")); for (int64_t object_i = 0; object_i < list.count; object_i+=1) { ObjectInfo *current_object = &objects[object_i]; String line = String_format(scratch.arena, "\"%.*s\", \"%s\", \"%lld\", \"%lld\", \"%lld\", \"%lld\", \"%lld\"\n", STR_VARG(current_object->filename), current_object->valid ? "true" : "false", current_object->time_stamp, current_object->size_in_bytes, current_object->dependency_count, current_object->dependents_count, current_object->group_id); StringBuilder_Append(scratch.arena, &string_builder, line); } String output_object_info = String_concatenate(scratch.arena, output, STR_LIT("_objects_info.csv")); if (!StringBuilder_Write_to_file(&string_builder, output_object_info)) { printf("Failed to write file %.*s\n", STR_VARG(output_object_info)); } } return 0; }