Skip to content

[AUTOMATED] Recover MIPS import call arguments, stub names and lazy-stub tail calls - #1498

Open
phix33 wants to merge 1 commit into
Noelo-Lab:mainfrom
phix33:octeon/mips-imports
Open

phix33 wants to merge 1 commit into
Noelo-Lab:mainfrom
phix33:octeon/mips-imports

Conversation

@phix33

@phix33 phix33 commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

In MIPS PIC shared objects, an import call drops the arguments its declaration states, the first import is called by its section label, and the last lazy-binding stub cannot be decompiled:

cat > f.c <<'EOF'
typedef __SIZE_TYPE__ size_t;
size_t strlen(const char *);
int transform(char *);

size_t len_plus_one(const char *s) { return strlen(s) + 1; }
int then_store(char *p) { int n = transform(p); p[0] = 'Z'; return n + 7; }
EOF
clang --target=mips-linux-gnu -O2 -fPIC -c f.c
mips-linux-gnu-ld -shared f.o -o f.so
decompiler/target/release/kuna decompile f.so len_plus_one
decompiler/target/release/kuna decompile f.so then_store
decompiler/target/release/kuna decompile f.so strlen

Current output, excerpted:

  return strlen() + 1;
  v1 = _MIPS_STUBS_();
error: Unable to load 512 bytes at r0x000101f0

Fix

  • With gotimportproto, a call that the loader proves goes through an import's GOT entry takes that import's declared prototype. len_plus_one now returns strlen(a0) + 1. Other indirect calls still need a declaration with explicit storage.
  • The first stub is named after its import rather than _MIPS_STUBS_, the section label at the same address, so then_store calls transform(). The label stays an alias.
  • The loader checks each .MIPS.stubs entry's instructions, GOT slot and dynamic-symbol index, and records its resolver call. With stubtailcall, flow ends the stub with that call as a tail call, and function discovery stops there instead of running into the next stub or past the section. The strlen stub now decompiles as return (*v1)();.
  • Both options are on by default; off restores the current flow and prototypes.

Tests

  • Original GCC 7.3 O32 big- and little-endian shared objects, with build provenance, vary local or exported helpers, symbol stripping and stack protection, and an Octeon III n32 object covers the stubs with an explicit target. CI needs no MIPS toolchain.
  • Stage tests run each option at its default and off. CLI and console tests check that every resolver call stays inside its stub, in the decompiler, the Listing and kuna xrefs, and that declared imports keep their arguments. x86-64 and PE controls show other indirect calls are unchanged.
  • No datatest expectation changed. The stage baseline only gains the new tests and option-count assertions. Across 976 decompile requests and 861 public images, output changes only in 37 MIPS images: stub bodies, last-stub errors, import names and declared arguments. A function calling through the stubs costs about 3% more instructions, since the stubs now decode instead of failing early; other timing and peak memory are unchanged within noise.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant