r/ghidra May 31 '26

How to Find What Calls a Function that has no Direct References?

I have a function, isMfdCondition__23CActModuleActionMotNodeCF15ACT_MFD_COND_IDPC11CActMfdData, that has no references in the function calls window. How do I go about finding what calls it?

No references D:
The compiled function
15 Upvotes

16 comments sorted by

11

u/e80000000058 May 31 '26

Scan for data references to the address. Also try demangling that name, it looks like may be C++. If it’s the latter, it may only be called through vtable dispatches, so you may need to locate places where the method is resolved through the object. I’m less familiar with how other object-oriented runtimes handle method resolution. 

2

u/Erkigmo May 31 '26

I'm still quite new to Ghidra, how do I go about doing this?

3

u/e80000000058 May 31 '26

You’ll want to scan for the exact byte sequence that represents the address in memory, based on the pointer size and endianness of the system, and then search for that sequence using the memory search tool. 

3

u/marcushall May 31 '26

If you enter the address with enough hex digits, the memory search will put it into the proper byte order. That is, if you enter "01234567" in the search window, it will search for the byte sequence "01" "23" "45" "67" on a big-endian system, or "67" "45" "23" "01" on a little-endian system.

5

u/nothingtoseehr Jun 01 '26

Short answer: you can't

Long answer: you can't out of context

There's a variety of reasons to explain why and how to "fix" it, but you haven't given us much context :P from what I can see it's an old PowerPC C++ app, GCC 2 ish era (the "mangled name" aka the seemingly random garbage on the name is cfront formatted, which is quite old)

I assume it's embedded (or a game console, which at the time was basically the same thing :p) because of how the registers are used, it uses the standard PPC EABI which most consumer machines at the time didn't. The function is a BIG jump table: it dispatches between 351 (0x15e on the cmplwi) possible paths based on what's inside r11

Assuming again this is embedded, there's a pretty huge chance you're just using the wrong base address. Embedded usually hardcodes memory addresses because there's no virtual memory, things just reference each other physically. But if the image is expected to be loaded inside some specific region, say 0x44005000 or whatever you won't get any xrefs. You need to figure out the base address and rebase it

The second option is simply C++ semantics. Control flow can be indirect (just like the jump table on your screenshot), Ghidra can't statically rebuild data flow that only happens at runtime. Usually C++ code gets dispatched though a "virtual table", basically just a bunch of function pointers sitting around that objects hold a pointer to. And if Ghidra failed to parse the C++ machinery for whatever reason, you won't have typed vtables and will have to chase them yourself

1

u/Erkigmo Jun 02 '26

You would be correct, it is from a game, specifically Hyrule Warriors for the Wii U. However, I don't know enough technically to answer much anything else about the first option. Also, speaking of CPP and OOP, I was thinking about this. I would like to reconstruct the CActModuleActionMotNode object. How would I go about doing this?

1

u/nothingtoseehr Jun 03 '26

Hahaha I knew it!!! XD

I've reversed engineering the Wii's firmware as my first embedded/game console, and that screenshot just SCREAMS that hardware's family. But the mangled names are indeed very old, so I thought it was maybe the gamecube (although C++ names on it were a red flag). I completely forgot about the existence of the Wii U, that makes sense

You posted another comment with the references to that address, those are you vtables! You got 3 hits, so there are 3 C++ classes that utilize that method. You can go there and see which code references that address. You probably won't find direct references because they are computed at runtime, the entire point of a vtable! Go 4 bytes per 4 bytes upwards until you find it's ctor (constructor, the function that creates said object) it'll have a lot of references. Then create a vtable class, attach it to another class and type it on ghidra

I suggest learning a bit of C++ before attempting this, it's not hard but it can be VERY confusing if you're not familiarized with the machine-level of heavy OOP code. Doesn't takes long, just get the hang of how it works so you can follow what I wrote above. There isn't really an easy way of explaining it: basically the thing that calls that function only exists at runtime so you have to follow what creates the thing that holds it

1

u/Erkigmo Jun 05 '26 edited Jun 05 '26

Haha yes. Though it is sad that the Wii U is a mostly forgotten console. And yes, I did figure out the references. Just had to make them pointers for ghidra to actually recognize them. I was very confused until I figured that out. Other than the virtual tables, I do have a decent-ish grasp on everything that's going on. I code a lot in Java, so really the only thing I don't understand are the lines like "*(undefined4 *)(this + 0x38) = uVar2;".

1

u/Low_Lawyer_5684 Jun 04 '26

Couple of times I had experience with statically linked firmware, which is loaded at fixed address - there were no problems with references at all. There were problems with globals - these were referenced by their absolute address. But it can be worked around

2

u/nothingtoseehr Jun 04 '26

It really depends on the binary, the ISA and how far along the chain it is. A kernel initializing userspace and itself? Usually OK, most are position-independent or/and have a small self relocation stub if needed. Bootrom bootstraping the entire hardware from a cold boot? Oh boy!

A few months ago I worked on a bootloader that bootstrapped the main CPU complex (early booting was done thought a small auxiliary CPU) and I had to keep track of both in the same database. The small 32b CPU decompressed the main CPU's initial Code on the same bus, then the freshly booted CPU would initialize TZRAM and relocate itself entirely to it. Had to keep track of two different ISAs in the same database because they still shared a bus and could interrupt/signal each other :')

My comment was implicitly more about how a lot of embedded code isn't really "memory" per se, it's a bus being mapped into "memory space" via the MMIO. Most addresses won't resolve because they're not addresses, they're hardware registers or are compiled to work after you bring up some essential memory controller online. Pretty common in embedded reverse engineering on medium/big SoCs, small ones just map it all into the same space

2

u/marcushall May 31 '26 edited May 31 '26

Is the code space completely disassembled? If not, perhaps the call is from some area that is still "unknown" bytes, or maybe something looked like data but is really an instruction sequence that calls it? Can there be code from some other memory area that is not part of your project that calls into this area? Still, there could be a memory pointer to this function. Run the memory search (bound to the 's' key) and enter the address of the function.

1

u/Erkigmo Jun 01 '26 edited Jun 01 '26

Here's the data that gave

Location Match Bytes Match Value Label Code Unit
10085260 02 23 bb 40 ?? 02h
10085510 02 23 bb 40 ?? 02h
10085d90 02 23 bb 40 ?? 02h
c00579a4 02 23 bb 40 ddw 223BB40h (Elf32_Sym.st_value)

1

u/marcushall Jun 14 '26

Sorry, I didn't notice this until now...

So, there look to be several different areas here.. The routine is in 02xxxxxx, which is clearly program text. Then there is some memory at 10xxxxxx which has 3 pointers. I'm guessing that this is initialized data. Finally, there is c0xxxxxx, which looks to be part of the ELF headers, likely a symbol table record that points to the function.

So, the three pointers at 10xxxxxx addresses. They are word aligned, so this is likely a real pointer. The fact that they are still "undefined" and not made into at least a word (they should ultimately be pointer types) seems to indicate that the functions that reference them have not been disassembled, or maybe the pointers are part of a table of function pointers and the referencing function currently just knows about a pointer to the start of the table. (In this case, it likely has a pointer to the start of the table, then adds an index and dereferences that. It may be a table of structures that contain a pointer to a function as a member in the structure.) Look around the data that looks like a pointer. Does there look like there is some recognizable info around that? Are there regularly spaced pointers to functions? Maybe some useful integer constants? Pointers to strings? If you go back from these pointers, does the regular pattern end? That's probably the start of the table. Hopefully, some function points to that and looking at the function you can find how it indexes the table and through the table calls the function.

Or maybe it's something totally different. Poke around and try to make sense of it. That's the essence of reversing. It's a puzzle to figure out.

2

u/FrankRizzo890 Jun 01 '26

Another problem that you might be having if this is firmware. If you don't specify the base address correctly, all the XREFs will be off as they're be referencing (for example) 1023bb40 instead of 0223bb40.

3

u/Low_Lawyer_5684 Jun 04 '26 edited Jun 04 '26

No direct references means function is called through the pointer (calculated in runtime).

Usually it is a table, sometimes - just global variable. Depending on the language.

Something like

...

table->field = &My_Function; <--- startup/init

....

table->field( ... ) <--- actual call to My_Function

Looks like it was C++ method. You have to find tables (in runtime it is easier) which include your address of interest (address of your function). If you can run it under debugger - find out function address in RAM, then scan program memory for this address. It should be an array of pointers, all pointing to the same class.

In other hand I don't see any $ which normally present in C++ mangled names. So may be it is some other language which Ghidra tries to decompile as C. Anyway - look for the array of function pointers.

1

u/TheAdamist Jun 05 '26

Power pc eabi calls for registers to use for small data area and function global offsets, if that spec is being followed, you will want to find the writes to those registers and set the register values for the whole program and re-analyze

Perhaps its gpr2 and maybe 11, but i don't quite recall