commit 61d40d9ceffa11053d63ff28fa8509fe0a569f07 Author: tanoxyz Date: Mon Apr 17 17:33:13 2023 +0200 First commit diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..7cb9b8a --- /dev/null +++ b/build.bat @@ -0,0 +1,4 @@ + +cl /Fe"disaster" /Zi main.c /link /DEBUG:FULL + + diff --git a/main.c b/main.c new file mode 100644 index 0000000..110748f --- /dev/null +++ b/main.c @@ -0,0 +1,1393 @@ + +#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; +} + diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..6ca81d9 --- /dev/null +++ b/readme.md @@ -0,0 +1,7 @@ + +# Object Disaster + +Command-line tool to analyze C/C++ projects structure based on the generated object files. + + +