mirror of
https://github.com/coop-deluxe/sm64coopdx.git
synced 2025-01-22 07:32:07 -05:00
[Experimental] Reduce the size of Windows builds by cleaning the MinGW debug info (#585)
* Reduce the size of Windows builds by cleaning the debug info * Update IS_DEV_OR_DEBUG in Makefile
This commit is contained in:
parent
f695f988a2
commit
0627d6ab48
3 changed files with 141 additions and 2 deletions
12
Makefile
12
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
|
||||
|
||||
|
|
121
tools/clean_mapfile.py
Normal file
121
tools/clean_mapfile.py
Normal file
|
@ -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]}" <exe_path> <map_path>"')
|
||||
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()
|
10
windows.ld
Normal file
10
windows.ld
Normal file
|
@ -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;
|
Loading…
Reference in a new issue