From f1987ecefc609a4185ce4d9c1b208897a6254571 Mon Sep 17 00:00:00 2001 From: Aetias Date: Sat, 27 Apr 2024 12:24:17 +0200 Subject: [PATCH] Add `elfkill` --- Makefile | 2 + include/global.h | 6 + tools/.gitignore | 1 + tools/elf/.gitignore | 2 + tools/elf/Makefile | 22 ++++ tools/elf/elfkill.cpp | 284 ++++++++++++++++++++++++++++++++++++++++++ tools/setup.py | 12 ++ 7 files changed, 329 insertions(+) create mode 100644 tools/elf/.gitignore create mode 100644 tools/elf/Makefile create mode 100644 tools/elf/elfkill.cpp diff --git a/Makefile b/Makefile index 6b9973e7..ac55bb2f 100644 --- a/Makefile +++ b/Makefile @@ -85,6 +85,7 @@ all: tools rom tools: cd $(TOOLS_DIR)/compress && $(MAKE) cd $(TOOLS_DIR)/rom && $(MAKE) + cd $(TOOLS_DIR)/elf && $(MAKE) .PHONY: rom rom: arm9 @@ -126,6 +127,7 @@ $(ASM_OBJS): $(TARGET_DIR)/%.o: % $(CXX_OBJS): $(TARGET_DIR)/%.o: % mkdir -p $(dir $@) LM_LICENSE_FILE=$(MW_LICENSE) $(WINE) $(MW_CC) $(CC_FLAGS) $(CXX_FLAGS) $< -o $@ + $(TOOLS_DIR)/elf/elfkill -s $< -e $@ $(C_OBJS): $(TARGET_DIR)/%.o: % mkdir -p $(dir $@) diff --git a/include/global.h b/include/global.h index e3d80975..10a927aa 100644 --- a/include/global.h +++ b/include/global.h @@ -15,6 +15,9 @@ #define NONMATCH(name) asm name #endif +// KILL(name) causes a function to be excluded from the output ROM, see elfkill.cpp +#define KILL(name) + // Prevent the IDE from reporting errors that the compiler/linker won't report #ifdef __INTELLISENSE__ #endif @@ -28,4 +31,7 @@ // Define .sbss variables by using #pragma section sbss begin|end #pragma define_section sbss ".data" ".sbss" +// Force variables to be in .data by using #pragma section force_data begin|end +#pragma define_section force_data ".data" ".data" + #endif diff --git a/tools/.gitignore b/tools/.gitignore index 04555228..a013c9f3 100644 --- a/tools/.gitignore +++ b/tools/.gitignore @@ -1,2 +1,3 @@ mwccarm/ temp/ +deps/ diff --git a/tools/elf/.gitignore b/tools/elf/.gitignore new file mode 100644 index 00000000..ea95eb29 --- /dev/null +++ b/tools/elf/.gitignore @@ -0,0 +1,2 @@ +elfkill +elfkill.exe diff --git a/tools/elf/Makefile b/tools/elf/Makefile new file mode 100644 index 00000000..16cfdfca --- /dev/null +++ b/tools/elf/Makefile @@ -0,0 +1,22 @@ +CXX := g++ +CFLAGS := -std=c++17 -g -Wall -I../include -I../deps/elfio + +ifneq ($(DEBUG),1) + CFLAGS += -O2 -DNDEBUG +endif + +ifeq ($(OS),Windows_NT) + ELFKILLFILE := elfkill.exe +else + ELFKILLFILE := elfkill +endif + +.PHONY: all clean + +all: $(ELFKILLFILE) + +clean: + rm -f $(ELFKILLFILE) + +$(ELFKILLFILE): elfkill.cpp + $(CXX) $(CFLAGS) -o $(ELFKILLFILE) elfkill.cpp diff --git a/tools/elf/elfkill.cpp b/tools/elf/elfkill.cpp new file mode 100644 index 00000000..4bae9aed --- /dev/null +++ b/tools/elf/elfkill.cpp @@ -0,0 +1,284 @@ +#include // cout, cerr, endl +#include // setw +#include // vector +#include // string +#include // unordered_set +#include // strcmp, strncpy +#include // ifstream +#include // remove + +#include +#include + +#define VERSION "1.0" + +using namespace ELFIO; + +struct SymbolSection { + Elf_Half index; + section *elfSection; + std::string name; + + bool Get(const elfio &elf, Elf_Half index) { + this->index = index; + + elfSection = elf.sections[index]; + if (elfSection == nullptr) { + std::cerr << "Failed to get section " << index << std::endl; + return false; + } + + name = elfSection->get_name(); + return true; + } + + bool SetName(const elfio &elf, const std::string &name) { + elfSection->set_name(name); + this->name = name; + + Elf_Word nameIndex = elfSection->get_name_string_offset(); + + section *shstrtab = elf.sections[".shstrtab"]; + if (shstrtab == nullptr) { + std::cerr << "Failed to get string section" << std::endl; + return false; + } + + string_section_accessor strAccessor(shstrtab); + const char *tabStr = strAccessor.get_string(nameIndex); + size_t len = strlen(tabStr); + + if (len < name.length()) { + std::cerr << "Cannot rename section " << tabStr << " because it is shorter than " << name << std::endl; + return false; + } + + // HACK: Strings shorter than `name` can't be renamed due to lack of space + strncpy((char*) tabStr, name.c_str(), len); + return true; + } +}; + +struct Symbol { + Elf_Xword index; + std::string name; + Elf64_Addr value; + Elf_Xword size; + unsigned char bind; + unsigned char type; + SymbolSection section; + unsigned char other; + + bool Get(const elfio &elf, const symbol_section_accessor &accessor, Elf_Xword index) { + this->index = index; + + if (!accessor.get_symbol(index, name, value, size, bind, type, section.index, other)) { + std::cerr << "Failed to get symbol at index " << index << std::endl; + return false; + } + + if (!section.Get(elf, section.index)) { + std::cerr << "...for symbol '" << name << "'" << std::endl; + return false; + } + + return true; + } + + static void PrintHeader() { + std::cout + << std::setw(75) << "name" + << std::setw(6) << "value" + << std::setw(6) << "size" + << std::setw(5) << "bind" + << std::setw(5) << "type" + << std::setw(8) << "section" + << std::setw(12) << "" + << std::setw(6) << "other" + << std::endl; + } + + void Print() const { + std::cout + << std::setw(75) << name + << std::setw(6) << value + << std::setw(6) << size + << std::setw(5) << (int) bind + << std::setw(5) << (int) type + << std::setw(5) << section.index << ' ' + << std::setw(14) << std::left << section.name << std::right + << std::setw(6) << (int) other + << std::endl; + } +}; + +bool GetFunctionSymbols(const elfio &elf, std::vector &outSymbols) { + section *symtab = elf.sections[".symtab"]; + if (symtab == nullptr) { + std::cerr << "No section called .symtab" << std::endl; + return false; + } + + symbol_section_accessor symAccessor(elf, symtab); + std::vector symbols; + for (Elf_Xword i = 0; i < symAccessor.get_symbols_num(); ++i) { + Symbol symbol; + if (!symbol.Get(elf, symAccessor, i)) return false; + + if (symbol.name.find("@", 0) == 0) continue; + if (symbol.name.find("$", 0) == 0) continue; + if (symbol.name.find(".", 0) == 0) continue; + if (symbol.section.name != ".text") continue; + + symbols.push_back(symbol); + } + + outSymbols = symbols; + return true; +} + +bool FindSymbolsToKill(const char *srcFile, std::unordered_set &outSymbolsToKill) { + std::ifstream file(srcFile); + + const std::string killMacro = "KILL("; + std::string line; + size_t row = 0; + std::unordered_set symbolsToKill; + while (std::getline(file, line)) { + row += 1; + size_t endOffset = 0; + while (true) { + size_t macroOffset = line.find(killMacro, endOffset); + if (macroOffset == std::string::npos) break; + + size_t symbolOffset = macroOffset + killMacro.length(); + symbolOffset = line.find_first_not_of(" \t", symbolOffset); + if (symbolOffset == std::string::npos) { + std::cerr + << srcFile << ':' << row << ':' << macroOffset + 1 + << ": Expected non-whitespace character after " << killMacro << std::endl; + return false; + } + + endOffset = line.find_first_of(" \t)", symbolOffset); + if (endOffset == std::string::npos) { + std::cerr + << srcFile << ':' << row << ':' << symbolOffset + 1 + << ": Expected whitespace character or ')' after kill symbol" << std::endl; + return false; + } + + std::string symbolToKill = line.substr(symbolOffset, endOffset - symbolOffset); + symbolsToKill.insert(symbolToKill); + } + } + + file.close(); + outSymbolsToKill = symbolsToKill; + return true; +} + +bool KillFunctionSymbols( + const elfio &elf, + std::vector &symbols, + const std::unordered_set &symbolsToKill +) { + for (Symbol &symbol : symbols) { + auto it = symbolsToKill.find(symbol.name); + if (it == symbolsToKill.end()) continue; + + if (!symbol.section.SetName(elf, ".dead")) return false; + } + + return true; +} + +bool DeleteElf(const char *elfFile) { + // Delete ELF file so the Makefile doesn't skip elfkill on next build + if (std::remove(elfFile) == 0) return true; + std::cerr << "Failed to delete ELF '" << elfFile << "' upon previous error" << std::endl; + return false; +} + +void PrintUsage(const char *program) { + std::cout + << "elfkill " VERSION "\n" + << "\n" + << "Usage: " << program << " -s SRCFILE -e ELFFILE\n" + << " -s SRCFILE\tSource C/C++ file\n" + << " -e ELFFILE\tELF file corresponding to SRCFILE\n" + << std::endl; +} + +int main(int argc, const char **argv) { + const char *program = argv[0]; + if (argc == 1) { + PrintUsage(program); + return 0; + } + const char *srcFile = nullptr; + const char *elfFile = nullptr; + for (int i = 1; i < argc; ++i) { + if (strcmp(argv[i], "-s") == 0) { + if (++i >= argc) { + std::cerr << "Expected filename after -s" << std::endl; + return 1; + } + srcFile = argv[i]; + } else if (strcmp(argv[i], "-e") == 0) { + if (++i >= argc) { + std::cerr << "Expected filename after -e" << std::endl; + return 1; + } + elfFile = argv[i]; + } else { + std::cerr << "Unknown option '" << argv[i] << "'" << std::endl; + return 1; + } + } + if (srcFile == nullptr) { + PrintUsage(program); + std::cerr << "Please provide a source file, see usage above" << std::endl; + return 1; + } + if (elfFile == nullptr) { + PrintUsage(program); + std::cerr << "Please provide an ELF file, see usage above" << std::endl; + return 1; + } + + elfio elf; + if (!elf.load(elfFile)) { + std::cerr << "Failed to load ELF file '" << elfFile << "'" << std::endl; + return 1; + } + + std::vector symbols; + if (!GetFunctionSymbols(elf, symbols)) { + DeleteElf(elfFile); + return 1; + } + + // Symbol::PrintHeader(); + // for (const Symbol &symbol : symbols) { + // symbol.Print(); + // } + // return 0; + + std::unordered_set symbolsToKill; + if (!FindSymbolsToKill(srcFile, symbolsToKill)) { + DeleteElf(elfFile); + return 1; + } + if (!KillFunctionSymbols(elf, symbols, symbolsToKill)) { + DeleteElf(elfFile); + return 1; + } + + // Symbol::PrintHeader(); + // for (const Symbol &symbol : symbols) { + // symbol.Print(); + // } + + elf.save(elfFile); +} diff --git a/tools/setup.py b/tools/setup.py index d3f420bf..e4433ca8 100644 --- a/tools/setup.py +++ b/tools/setup.py @@ -4,8 +4,11 @@ import io from pathlib import Path import subprocess import sys +import shutil tools_path = Path(__file__).parent +deps_path = tools_path / 'deps' +if not deps_path.exists(): deps_path.mkdir() print('\nInstalling toolchain...') response = requests.get('http://decomp.aetias.com/mwccarm.zip') @@ -14,3 +17,12 @@ zip_file.extractall(tools_path) print('\nPatching...') subprocess.run([sys.executable, 'patch_mwcc.py', 'mwccarm/2.0/sp1p5/mwasmarm.exe'], cwd=tools_path) + +print('\nInstalling ELFIO...') +response = requests.get('https://github.com/serge1/ELFIO/releases/download/Release_3.12/elfio-3.12.zip') +zip_file = zipfile.ZipFile(io.BytesIO(response.content)) +zip_file.extractall(deps_path) +elfio_path = deps_path / 'elfio-3.12' +elfio_new_path = deps_path / 'elfio' +if elfio_new_path.exists(): shutil.rmtree(elfio_new_path) +elfio_path.rename(elfio_new_path)