I'm working on ghidra-delinker-extension [1], which is a relocatable object file exporter for Ghidra.
The algorithms needed to slice up a Ghidra database into relocatable sections, and especially to recover relocations through analysis are really tricky to get right. My MIPS analyzer in particular is an eldritch horror due to several factors combining into a huge mess (branch delay slots, split HI16/LO16 relocations, code flow analysis, register graph dependency...).
The entire endeavor requires an unusual level of exacting precision to work and will produce some really exotic undefined behavior when it fails, but when it works you feel like a mechanic in a Mad Max universe, stripping programs for parts and building unholy chimeras from them, some examples I've linked in the readme. It has also led to a poster presentation to the SURE workshop at ACM CCS 2025 in Taiwan as a hobbyist, an absolutely insane story.
Mad respect. I tried extracting a clean .o file out of a statically linked ELF once, and it's an absolute nightmare. How are you handling switch tables and indirect jumps? Without dynamic analysis, it's sometimes physically impossible to figure out what a register is actually pointing to
I have analyzers that resynthesize relocations from the contents of the Ghidra database, no custom annotations required. They evaluate relocation candidate spots through primary references and pointers/instructions and emit warnings if the math doesn't check out.
It does require a reasonably accurate Ghidra database to work properly, but I've had users delink megabytes of code and data from a program successfully (as in, relinking it at a different address results in a functionally identical executable) once they've cleaned it up. The accuracy warning in the readme is mostly because it's really complicated to describe exactly what inaccuracies you can get away with, there's a fair amount of wiggle room in reality as long as you know what you're doing.
I'm curious how your analyzers handle those tricky MIPS edge cases like split address loads via lui and addiu where you have interleaved instructions in between. If Ghidra fails to collapse those into a single reference, does your math check just flag it as an error and bail, or does it actually try to reconstruct the instruction chain itself?
I wrote an analyzer to do that, you can find it here: https://github.com/boricj/ghidra-delinker-extension/blob/mas...
It uses register dependency graph traversal and code block flow analysis to try and find which two instructions are the likely targets of a HI16/LO16 for a given reference. It also uses an unhealthy amount of recursion and has been rewritten multiple times in order to deal with all these edge cases.
Unfortunately, my extension is only set up for one HI16 relocation having one or many LO16 child relocations. There's an undocumented GNU extension that allows multiple HI16 relocations to be shared by a single LO16 relocation, and I have a really ugly hack that shifts branch targets to "normalize" this into the standard pattern. I don't know why this hack works (I didn't even know about the GNU extension, I thought it was an assembler peephole optimization), but with it my extension can delink the whole of Tenchu: Stealth Assassins without any manual annotations, just the regular references from the cleaned-up Ghidra database.