diff --git a/Makefile b/Makefile index 996c12893..ffefc9e5a 100644 --- a/Makefile +++ b/Makefile @@ -869,6 +869,7 @@ ifeq ($(WINDOWS_BUILD),1) ifeq ($(CROSS),) LDFLAGS += -no-pie endif + LDFLAGS += -T windows.ld else ifeq ($(TARGET_RPI),1) LDFLAGS := $(OPT_FLAGS) -lm $(BACKEND_LDFLAGS) -no-pie else ifeq ($(OSX_BUILD),1) @@ -986,7 +987,7 @@ else endif endif -IS_DEV_OR_DEBUG := $(or $(filter 1,$(DEVELOPMENT)),$(filter 1,$(DEBUG))) +IS_DEV_OR_DEBUG := $(or $(filter 1,$(DEVELOPMENT)),$(filter 1,$(DEBUG)),0) ifeq ($(IS_DEV_OR_DEBUG),0) CFLAGS += -fno-ident -fno-common -fno-asynchronous-unwind-tables -ffile-prefix-map=$(PWD)=. -D__DATE__="\"\"" -D__TIME__="\"\"" -Wno-builtin-macro-redefined LDFLAGS += -Wl,--build-id=none -Wl,--no-randomize-sections @@ -1139,8 +1140,15 @@ endef all: $(EXE) ifeq ($(WINDOWS_BUILD),1) +MAPFILE = $(BUILD_DIR)/coop.map exemap: $(EXE) - $(V)$(OBJDUMP) -t $(EXE) > $(BUILD_DIR)/coop.map + @$(PRINT) "$(GREEN)Creating map file: $(BLUE)$(MAPFILE) $(NO_COL)\n" + $(V)$(OBJDUMP) -t $(EXE) > $(MAPFILE) + @cp $(EXE) $(EXE).bak && cp $(MAPFILE) $(MAPFILE).bak + $(V)$(PYTHON) $(TOOLS_DIR)/clean_mapfile.py $(EXE) $(MAPFILE) +ifeq ($(IS_DEV_OR_DEBUG),0) + $(V)$(OBJCOPY) -p --strip-unneeded $(EXE) +endif all: exemap endif diff --git a/tools/clean_mapfile.py b/tools/clean_mapfile.py new file mode 100644 index 000000000..2c459aabf --- /dev/null +++ b/tools/clean_mapfile.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +# "tools/clean_mapfile.py" + +import sys +import os +import re +import json + +def extract_int(b: bytes, offset: int, size: int) -> int: + return int.from_bytes(b[offset:(offset+size)], 'little') + +def parse_exe_sections(exe_path: str) -> list[dict]: + print(f'* Parsing sections from EXE: "{exe_path}"') + + with open(exe_path, 'rb') as f: + b: bytes = f.read(4096) + + if b[0:(0+2)] != b'MZ': + raise ValueError(f'"{exe_path}": no MZ header') + + pe_offset = extract_int(b, 0x3C, 4) + if (pe_offset < 0x40) or (pe_offset > 4096) or (pe_offset & 7): + raise ValueError(f'"{exe_path}": bad LfaNew address') + if b[pe_offset:(pe_offset+4)] != b'PE\0\0': + raise ValueError(f'"{exe_path}": not a valid PE file') + + x = extract_int(b, pe_offset + 4, 2) + if x != 0x8664: + raise ValueError(f'"{exe_path}": not a x86_64 executable') + + num_sections = extract_int(b, pe_offset + 6, 2) + if (num_sections <= 0) or (num_sections > 64): + raise ValueError(f'"{exe_path}: bad NumberOfSections') + + x = extract_int(b, pe_offset + 20, 2) + if (x <= 0) or (x > 256): + raise ValueError(f'"{exe_path}: bad SizeOfOptionalHeader.') + + if extract_int(b, pe_offset + 24, 2) != 0x020B: + raise ValueError(f'"{exe_path}": invalid PE32+ signature') + + sections = [] + section_table = pe_offset + 24 + x + for i in range(num_sections): + x = section_table + i * 40 + # virtual_size = extract_int(b, x + 8, 4) + virtual_addr = extract_int(b, x + 12, 4) + # size_of_raw_data = extract_int(b, x + 16, 4) + # ptr_to_raw_data = extract_int(b, x + 20, 4) + flags = extract_int(b, x + 36, 4) + + discardable = bool(0x02000000 & flags) + sections.append({ 'virtual_addr': virtual_addr, 'discardable': discardable }) + + return sections + +def parse_map_file(map_path: str, module_name: str, sections: list[dict]) -> dict: + print(f'* Parsing and cleaning the MAP file: "{map_path}"') + + with open(map_path, 'rt', encoding='ascii') as f: + map_content = f.read() + + pattern = re.compile(r'^\[ *\d+\]\(sec +(\d+)\)\(fl 0x00\)\(ty +(\d+)\)\(scl +(\d+)\) \(nx 0\) 0x([0-9a-f]+) (.+)$', re.MULTILINE) + matches = pattern.findall(map_content) + + symbols = [] + with open(map_path, 'wt', encoding='ascii', newline='\n') as f: + for m in matches: + section_id = int(m[0]) + symbol_type = int(m[1]) + storage_class = int(m[2]) + relative_addr = int(m[3], 16) + symbol_name = m[4] + + # Target section is not discardable + if sections[section_id - 1]['discardable']: continue + + # Debug symbol, accepted type + # - IMAGE_SYM_DTYPE_NULL ("0") | scalar + # - IMAGE_SYM_DTYPE_FUNCTION ("20") | subroutine + if symbol_type not in (0, 20): continue + + # Debug symbol, accepted class + # - C_EXT = 2 | external, global + # - C_STAT = 3 | static + if storage_class not in (2, 3): continue + + # Debug symbol, accepted name + if symbol_name.startswith('.') and not symbol_name.startswith('.refptr.'): continue + + # Calculate Relative Virtual Address + section_start = sections[section_id - 1]['virtual_addr'] + rva = hex(section_start + relative_addr) + + # Save back only the function labels, loaded on the crash screen + if (section_id == 1) and (symbol_type == 20): + f.write(f'(sec{section_id})(fl0x00)(ty{symbol_type})(scl{storage_class})(nx 0)0x{relative_addr:016x} {symbol_name}\n') + + symbols.append({ 'module': module_name, 'address': rva, 'text': symbol_name }) + + return { 'labels': symbols } + +def main(): + if len(sys.argv) != 3: + print(f'Usage: "{sys.argv[0]}" "') + sys.exit(1) + + exe_path = sys.argv[1] + map_path = sys.argv[2] + + module_name = os.path.basename(exe_path) + sections = parse_exe_sections(exe_path) + symbols = parse_map_file(map_path, module_name, sections) + + output_path = exe_path + '.dd64' + print(f'* Saving symbol database for x64dbg to "{output_path}"') + with open(output_path, 'wt', newline='\n') as f: + json.dump(symbols, f, indent=0, separators=(',', ':')) + +if __name__ == "__main__": + main() diff --git a/windows.ld b/windows.ld new file mode 100644 index 000000000..bab8aa903 --- /dev/null +++ b/windows.ld @@ -0,0 +1,10 @@ +/** + * Removes the spam of ".ident" strings (around 50 KB), such as: + * "GCC: (Rev3, Built by MSYS2 project) 14.1.0", + * found in every compiled object file (and some static libraries). + */ +SECTIONS +{ + /DISCARD/ : { *(.rdata$zzz) } +} +INSERT BEFORE .rdata;