object-disaster/main.c

1394 lines
41 KiB
C

#include <stdio.h>
#include <windows.h>
#include <fileapi.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#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*)&section_number)) {
return result;
}
}
else {
int16_t section_number_16 = 0;
if (!Get_U16(&ctx, (uint16_t*)&section_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;
}