mirror of https://github.com/zeldaret/tp.git
610 lines
18 KiB
Python
610 lines
18 KiB
Python
"""
|
|
|
|
makerel.py - Generate .rel files from .plf files and a static binary
|
|
|
|
"""
|
|
|
|
import sys
|
|
import glob
|
|
import os
|
|
import traceback
|
|
|
|
from pathlib import Path
|
|
from dataclasses import dataclass, field
|
|
from typing import List, Set, Tuple, Dict
|
|
|
|
try:
|
|
import libelf
|
|
import librel
|
|
import click
|
|
import logging
|
|
|
|
from rich.logging import RichHandler
|
|
from rich.console import Console
|
|
except ImportError as e:
|
|
MISSING_PREREQUISITES = (
|
|
f"Missing prerequisite python module {e}.\n"
|
|
f"Run `python3 -m pip install --user -r tools/requirements.txt` to install prerequisites."
|
|
)
|
|
|
|
print(MISSING_PREREQUISITES, file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
VERSION = "1.0"
|
|
CONSOLE = Console()
|
|
|
|
logging.basicConfig(
|
|
level="NOTSET",
|
|
format="%(message)s",
|
|
datefmt="[%X]",
|
|
handlers=[RichHandler(console=CONSOLE, rich_tracebacks=True)],
|
|
)
|
|
|
|
LOG = logging.getLogger("rich")
|
|
LOG.setLevel(logging.INFO)
|
|
|
|
SECTION_MASK = {".init", ".text", ".ctors", ".dtors", ".rodata", ".data", ".bss"}
|
|
|
|
REL_SECTION_MASK = {
|
|
".rela.init",
|
|
".rela.text",
|
|
".rela.ctors",
|
|
".rela.dtors",
|
|
".rela.rodata",
|
|
".rela.data",
|
|
".rela.bss",
|
|
}
|
|
|
|
|
|
@click.group()
|
|
@click.version_option(VERSION)
|
|
def makerel():
|
|
pass
|
|
|
|
|
|
@makerel.command(name="unresolved")
|
|
@click.option("--debug/--no-debug")
|
|
@click.option("--output", "-o", default="forceactive.txt", required=True)
|
|
@click.argument("str_paths", metavar="<ELFs>", nargs=-1)
|
|
def unresolved(debug, output, str_paths):
|
|
"""Generate a list of symbols which must be in the static executable (and other RELs)."""
|
|
|
|
if debug:
|
|
LOG.setLevel(logging.DEBUG)
|
|
|
|
static, plfs = load_elfs(str_paths)
|
|
|
|
if static:
|
|
LOG.error(f"unresolved does not handle executable files '{static.path}'")
|
|
sys.exit(1)
|
|
|
|
undef_symbols = set()
|
|
for plf in plfs:
|
|
for relocation in plf.relocations:
|
|
symbol = relocation.symbol
|
|
if isinstance(symbol, libelf.UndefSymbol):
|
|
assert symbol.name
|
|
undef_symbols.add(symbol.name)
|
|
|
|
LOG.debug(f"found {len(undef_symbols)} symbols")
|
|
|
|
names = list(undef_symbols)
|
|
names.sort()
|
|
with open(output, "w") as file:
|
|
for name in names:
|
|
file.write(f"{name}\n")
|
|
|
|
CONSOLE.LOG(f"Unresolved Symbols: '{output}'")
|
|
|
|
|
|
@makerel.command(name="build")
|
|
@click.option("--debug/--no-debug")
|
|
@click.option("--yaz0", "-y", "compress_yaz0")
|
|
@click.option("--id-offset", "-i", "rel_id_offset", default=1)
|
|
@click.option(
|
|
"--spoof-path", "-q", "spoof_path", default="D:\\zeldaGC_USA\\dolzel2\\bin\\Final\\"
|
|
)
|
|
@click.option("--string-table", "-s", "string_path", required=True)
|
|
@click.option(
|
|
"--symbols", default="ELF", type=click.Choice(["ELF", "DEFS"], case_sensitive=False)
|
|
)
|
|
@click.argument("str_paths", metavar="<ELFs>", nargs=-1)
|
|
def build(
|
|
debug, symbols, str_paths, rel_id_offset, compress_yaz0, spoof_path, string_path
|
|
):
|
|
"""Build RELs files from a list of plfs files."""
|
|
|
|
if debug:
|
|
LOG.setLevel(logging.DEBUG)
|
|
|
|
static, plfs = load_elfs(str_paths)
|
|
if not static:
|
|
LOG.error(f"static executable ('main.elf') expected")
|
|
sys.exit(1)
|
|
|
|
#
|
|
id = rel_id_offset
|
|
elfs = []
|
|
for plf in plfs:
|
|
if static == plf:
|
|
rel = IndexedElf(0, plf)
|
|
else:
|
|
rel = IndexedElf(id, plf)
|
|
id += 1
|
|
elfs.append(rel)
|
|
|
|
# sort relocations
|
|
for elf in elfs:
|
|
if "_unresolved" in elf.plf.symbol_map:
|
|
elf._unresolved = elf.plf.symbol_map["_unresolved"][0]
|
|
|
|
for _, relocations in elf.plf.section_relocations:
|
|
relocations.sort(key=lambda r: r.offset)
|
|
|
|
# symbol table
|
|
symbol_table = dict()
|
|
for elf in elfs:
|
|
for symbol in elf.plf.symbols:
|
|
if isinstance(symbol, libelf.UndefSymbol):
|
|
continue
|
|
if not symbol.isBindGlobal():
|
|
continue
|
|
symbol_table[symbol.name] = (elf, symbol)
|
|
|
|
for elf in elfs:
|
|
if elf.id != 0:
|
|
elf.resolve_relocations(symbol_table)
|
|
|
|
string_list = StringList(string_path, spoof_path)
|
|
for elf in elfs:
|
|
if elf.id != 0:
|
|
write_rel_from_elf(elf, string_list, compress_yaz0)
|
|
|
|
string_list.write()
|
|
|
|
|
|
def apply_rel24_relocation(relocation, section, symbol):
|
|
if not symbol or not isinstance(symbol, libelf.OffsetSymbol):
|
|
return False
|
|
|
|
try:
|
|
if librel.apply_relocation(
|
|
relocation.type,
|
|
0,
|
|
section.data,
|
|
0,
|
|
relocation.offset,
|
|
symbol.offset,
|
|
relocation.addend,
|
|
):
|
|
return True
|
|
except librel.RELRelocationException as e:
|
|
LOG.error(f"applying relocation failed!")
|
|
LOG.error(
|
|
f"relocation: {librel.RELOCATION_NAMES[relocation.type]} {relocation.offset:04X}"
|
|
)
|
|
LOG.error(
|
|
f"section: {section.header.sh_addr:08X} {section.header.sh_size:04X} {section.name}"
|
|
)
|
|
LOG.error(f"symbol: {symbol.offset:08X} {symbol.name}+0x{relocation.addend:X}")
|
|
LOG.error(e)
|
|
CONSOLE.print_exception()
|
|
traceback.print_tb()
|
|
traceback.print_exc()
|
|
sys.exit(1)
|
|
|
|
return False
|
|
|
|
|
|
@dataclass
|
|
class StringList:
|
|
output_path: str
|
|
spoof_path: str
|
|
data: str = ""
|
|
|
|
def add(self, path):
|
|
offset = len(self.data)
|
|
name = str(path)
|
|
if self.spoof_path:
|
|
name = f"{self.spoof_path}{path.name} "
|
|
self.data += name
|
|
return offset, len(name)
|
|
|
|
def write(self):
|
|
if len(self.data) > 0:
|
|
with open(self.output_path, "w") as file:
|
|
file.write(self.data.replace(' ','\0'))
|
|
|
|
|
|
@dataclass
|
|
class ImpTable:
|
|
id: int
|
|
last_section: int
|
|
section_offset: int = 0
|
|
relocations: List[Tuple[int, int, int, int]] = field(default_factory=list)
|
|
rel_offset: int = 0
|
|
|
|
def section(self, section_id):
|
|
self.last_section = section_id
|
|
self.section_offset = 0
|
|
self.relocations.append((0, librel.R_DOLPHIN_SECTION, section_id, 0))
|
|
|
|
def end(self):
|
|
self.relocations.append((0, librel.R_DOLPHIN_END, 0, 0))
|
|
|
|
def nop(self, offset):
|
|
self.relocations.append((offset, librel.R_DOLPHIN_NOP, 0, 0))
|
|
|
|
def relocation(self, offset, type, section, addend):
|
|
self.relocations.append((offset, type, section, addend))
|
|
|
|
|
|
@dataclass
|
|
class IndexedElf:
|
|
id: int
|
|
plf: libelf.Object
|
|
|
|
_unresolved: libelf.Symbol = None
|
|
imp_tables: Dict[int, ImpTable] = field(default_factory=dict)
|
|
imp_table_order: List[int] = field(default_factory=list)
|
|
complete_relocations: Set[libelf.Relocation] = field(default_factory=set)
|
|
|
|
def symbol_by_name(self, name):
|
|
if name in self.plf.symbol_map:
|
|
symbols = self.plf.symbol_map[name]
|
|
if len(symbols) > 1:
|
|
LOG.warning(symbols)
|
|
return symbols[0]
|
|
return None
|
|
|
|
def resolve_relocation(self, section, relocation, symbol_table):
|
|
# relocations of function calls should all point to _unresolved if it is defined.
|
|
if self._unresolved:
|
|
if relocation.type == librel.R_PPC_REL24:
|
|
apply_rel24_relocation(relocation, relocation.section, self._unresolved)
|
|
|
|
ext_symbol = None
|
|
my_symbol = relocation.symbol
|
|
|
|
if my_symbol.name and my_symbol.name.endswith("stringBase0"):
|
|
replace_symbol = self.symbol_by_name("@stringBase0")
|
|
if replace_symbol:
|
|
my_symbol = replace_symbol
|
|
|
|
# LOG.info(f"relocation for: {my_symbol.name} ({type(my_symbol).__name__})")
|
|
other = None
|
|
if isinstance(my_symbol, libelf.UndefSymbol):
|
|
if my_symbol.name in symbol_table:
|
|
other, ext_symbol = symbol_table[my_symbol.name]
|
|
else:
|
|
other = self
|
|
ext_symbol = my_symbol
|
|
if relocation.type == librel.R_PPC_REL24:
|
|
if apply_rel24_relocation(relocation, relocation.section, ext_symbol):
|
|
self.complete_relocations.add(relocation)
|
|
return True
|
|
|
|
if ext_symbol:
|
|
# different module id, create new imp table
|
|
if not other.id in self.imp_tables:
|
|
self.imp_table_order.append(other.id)
|
|
self.imp_tables[other.id] = ImpTable(other.id, -1)
|
|
|
|
table = self.imp_tables[other.id]
|
|
|
|
# new section
|
|
if table.last_section != section.header.sh_info:
|
|
assert section.header.sh_info <= 255
|
|
table.section(section.header.sh_info)
|
|
|
|
relative_offset = relocation.offset - table.section_offset
|
|
table.section_offset = relocation.offset
|
|
|
|
# too big offset, insert nop's until the offset is manageable
|
|
while relative_offset >= 0xFFFF:
|
|
table.nop(0xFFFF)
|
|
relative_offset -= 0xFFFF
|
|
|
|
if not ext_symbol.section:
|
|
assert other.id == 0
|
|
assert isinstance(ext_symbol, libelf.AbsoluteSymbol)
|
|
|
|
found_section = None
|
|
for section in other.plf.sections.values():
|
|
if (
|
|
ext_symbol.address >= section.header.sh_addr
|
|
and ext_symbol.address
|
|
< section.header.sh_addr + section.header.sh_size
|
|
):
|
|
found_section = section
|
|
break
|
|
|
|
if found_section:
|
|
ext_symbol.section = found_section
|
|
else:
|
|
LOG.error(
|
|
f"error no-section provided for relocation of: {my_symbol.name} ({type(my_symbol).__name__}) ({self.plf.name} <- {other.plf.name})"
|
|
)
|
|
LOG.error(vars(relocation))
|
|
LOG.error(ext_symbol)
|
|
k = vars(ext_symbol)
|
|
k.pop("object", None)
|
|
k.pop("section", None)
|
|
LOG.error(k)
|
|
sys.exit(1)
|
|
assert ext_symbol.section.header.sh_info <= 255
|
|
|
|
addend = relocation.addend
|
|
if isinstance(ext_symbol, libelf.OffsetSymbol):
|
|
addend += ext_symbol.offset
|
|
elif isinstance(ext_symbol, libelf.AbsoluteSymbol):
|
|
addend += ext_symbol.address
|
|
|
|
section_id = ext_symbol.section.header.sh_info
|
|
if section_id == 0:
|
|
section_id = ext_symbol.section.header.id
|
|
|
|
table.relocation(relative_offset, relocation.type, section_id, addend)
|
|
self.complete_relocations.add(relocation)
|
|
return True
|
|
return False
|
|
|
|
def resolve_relocations(self, symbol_table):
|
|
for name, relocations in self.plf.section_relocations:
|
|
if not name in REL_SECTION_MASK:
|
|
continue
|
|
section = self.plf.sections[name]
|
|
for relocation in relocations:
|
|
if not self.resolve_relocation(section, relocation, symbol_table):
|
|
LOG.error(f"relocation failed: {name:<14} {relocation.symbol.name}")
|
|
sys.exit(1)
|
|
|
|
|
|
def align_next(offset, alignment):
|
|
return (offset - 1 + alignment) & ~(alignment - 1)
|
|
|
|
|
|
def write_rel(
|
|
path: Path,
|
|
id: int,
|
|
align: int,
|
|
bss_align: int,
|
|
bss_size: int,
|
|
name_offset: int,
|
|
name_size: int,
|
|
prolog: libelf.Symbol,
|
|
epilog: libelf.Symbol,
|
|
unresolved: libelf.Symbol,
|
|
sections: List[librel.Section],
|
|
imp_tables: List[ImpTable],
|
|
):
|
|
|
|
output = librel.REL()
|
|
output.index = id
|
|
output.numSections = len(sections)
|
|
output.sectionInfoOffset = 0x4C # for version 3
|
|
output.nameOffset = name_offset
|
|
output.nameSize = name_size
|
|
output.version = 3
|
|
output.bssSize = bss_size
|
|
output.relOffset = 0
|
|
output.impOffset = 0
|
|
output.impSize = 0
|
|
|
|
if prolog:
|
|
assert isinstance(prolog, libelf.OffsetSymbol)
|
|
output.prolog = prolog.offset
|
|
output.prologSection = prolog.section.header.id
|
|
|
|
if epilog:
|
|
assert isinstance(epilog, libelf.OffsetSymbol)
|
|
output.epilog = epilog.offset
|
|
output.epilogSection = epilog.section.header.id
|
|
|
|
if unresolved:
|
|
assert isinstance(unresolved, libelf.OffsetSymbol)
|
|
output.unresolved = unresolved.offset
|
|
output.unresolvedSection = unresolved.section.header.id
|
|
|
|
assert output.version >= 2
|
|
output.align = align
|
|
output.bssAlign = bss_align
|
|
|
|
assert output.version >= 3
|
|
|
|
with path.open("wb") as file:
|
|
librel.write_header(file, output)
|
|
|
|
sections_offset = file.tell()
|
|
assert sections_offset == output.sectionInfoOffset
|
|
|
|
for section in sections:
|
|
librel.write_section_header(file, section)
|
|
|
|
section_data_offset = file.tell()
|
|
for section in sections:
|
|
if section.data:
|
|
padding = section.offset - file.tell()
|
|
if padding > 0:
|
|
file.write(b"\x00" * padding)
|
|
|
|
assert section.offset == file.tell()
|
|
librel.write_section_data(file, section)
|
|
|
|
output.impOffset = file.tell()
|
|
output.impSize = len(imp_tables) * 0x8
|
|
|
|
file.write(b"\xFF" * output.impSize)
|
|
|
|
output.fixSize = file.tell()
|
|
output.relOffset = file.tell()
|
|
rel_offset = output.relOffset
|
|
for table in imp_tables:
|
|
table.rel_offset = rel_offset
|
|
if table.id == id:
|
|
output.fixSize = rel_offset
|
|
for relocation in table.relocations:
|
|
librel.write_relocation(file, *relocation)
|
|
rel_offset += len(table.relocations) * 8
|
|
|
|
file.seek(output.impOffset, os.SEEK_SET)
|
|
for table in imp_tables:
|
|
librel.write_imp(file, table.id, table.rel_offset)
|
|
|
|
file.seek(0, os.SEEK_SET)
|
|
librel.write_header(file, output)
|
|
|
|
|
|
def write_rel_from_elf(elf: IndexedElf, string_list: StringList, compress_yaz0: bool):
|
|
assert elf.id != 0
|
|
|
|
path = Path(str(elf.plf.path).replace(".plf", ".rel"))
|
|
yaz0_path = Path(str(elf.plf.path).replace(".plf", ".rel.yaz0"))
|
|
LOG.debug(f"output: '{path}'")
|
|
|
|
# add the plf path to the string list
|
|
name_offset, name_size = string_list.add(elf.plf.path)
|
|
|
|
# insert end relocation value for each imp-table
|
|
for table in elf.imp_tables.values():
|
|
if len(table.relocations) > 0:
|
|
table.end()
|
|
|
|
# count sections
|
|
section_count = 1 # null section
|
|
for elf_section in elf.plf.sections.values():
|
|
if elf_section.name == ".dead" or elf_section.name == ".rela.dead":
|
|
continue
|
|
section_count += 1
|
|
|
|
bss_size = 0
|
|
bss_align = 1
|
|
align = 1
|
|
offset = 0x4C + 0x8 * section_count
|
|
|
|
# build section list based on elf sections
|
|
sections = list()
|
|
sections.append(librel.Section(0, 0, False, False, 0))
|
|
for elf_section in elf.plf.sections.values():
|
|
if not elf_section.name in SECTION_MASK:
|
|
continue
|
|
|
|
section = librel.Section(
|
|
elf_section.header.id, 0, False, False, elf_section.header.sh_size
|
|
)
|
|
if elf_section.header.sh_type == libelf.SHT_NOBITS:
|
|
if elf_section.header.sh_addralign >= 1:
|
|
if elf_section.header.sh_addralign >= bss_align:
|
|
bss_align = elf_section.header.sh_addralign
|
|
|
|
bss_size += section.length
|
|
else:
|
|
if elf_section.header.sh_addralign >= 1:
|
|
offset = align_next(offset, elf_section.header.sh_addralign)
|
|
assert offset % elf_section.header.sh_addralign == 0
|
|
|
|
if elf_section.header.sh_addralign >= align:
|
|
align = elf_section.header.sh_addralign
|
|
|
|
section.offset = offset
|
|
section.data = elf_section.data
|
|
if (elf_section.header.sh_flags & libelf.SHF_EXECINSTR) != 0:
|
|
section.executable_flag = True
|
|
|
|
offset += section.length
|
|
|
|
sections.append(section)
|
|
|
|
while len(sections) < section_count:
|
|
sections.append(librel.Section(len(sections), 0, False, False, 0))
|
|
|
|
# find prolog, epilog, and unresolved symbols
|
|
prolog = elf.symbol_by_name("_prolog")
|
|
epilog = elf.symbol_by_name("_epilog")
|
|
unresolved = elf.symbol_by_name("_unresolved")
|
|
|
|
# the order of the imp-tables are:
|
|
# sorted rels (by module id) except the current one and module 0
|
|
# imp-table for the current rel
|
|
# imp-table for module 0
|
|
tables = []
|
|
for table in elf.imp_tables.values():
|
|
if table.id == 0 or table.id == elf.id:
|
|
continue
|
|
tables.append(table)
|
|
tables.sort(key=lambda x: x.id)
|
|
|
|
if elf.id in elf.imp_tables:
|
|
tables.append(elf.imp_tables[elf.id])
|
|
|
|
if 0 in elf.imp_tables:
|
|
tables.append(elf.imp_tables[0])
|
|
|
|
# write the rel files
|
|
write_rel(
|
|
path,
|
|
elf.id,
|
|
align,
|
|
bss_align,
|
|
bss_size,
|
|
name_offset,
|
|
name_size,
|
|
prolog,
|
|
epilog,
|
|
unresolved,
|
|
sections,
|
|
tables,
|
|
)
|
|
|
|
|
|
def load_elfs(str_paths):
|
|
static = None
|
|
plfs = []
|
|
for plfs_path in str_paths:
|
|
files = glob.glob(plfs_path, recursive=True)
|
|
if len(files) <= 0:
|
|
LOG.error(f"found zero files for path: '{plfs_path}'")
|
|
|
|
for plf_path in files:
|
|
path = Path(plf_path)
|
|
LOG.debug(f"load: '{path}'")
|
|
try:
|
|
plf = libelf.load_object_from_path(path)
|
|
if plf.executable:
|
|
if static:
|
|
LOG.error(f"multiple executable files not supported")
|
|
sys.exit(1)
|
|
static = plf
|
|
plfs.append(plf)
|
|
except libelf.ElfException as e:
|
|
LOG.error(f"error: '{path}'")
|
|
LOG.error(e)
|
|
|
|
return static, plfs
|
|
|
|
|
|
def convert_arg_line_to_args(arg_line: str):
|
|
return arg_line.split(' ')
|
|
|
|
|
|
def _read_args_from_files(args: List[str]):
|
|
new_args: List[str] = []
|
|
for arg in args:
|
|
if not arg or arg[0] != '@':
|
|
new_args.append(arg)
|
|
else:
|
|
with open(arg[1:], 'r') as file:
|
|
file_args: List[str] = []
|
|
for line in file:
|
|
for file_arg in convert_arg_line_to_args(line.strip()):
|
|
file_args.append(file_arg)
|
|
file_args = _read_args_from_files(file_args)
|
|
new_args.extend(file_args)
|
|
|
|
return new_args
|
|
|
|
|
|
if __name__ == "__main__":
|
|
args = _read_args_from_files(sys.argv[1:])
|
|
makerel(args)
|