Ghidra: cross-references
A cross-reference is an edge the reference analyzer could prove: this instruction calls that function, or this instruction reads that address. The edges are how you move from a string to its user, and from a function to its callers.
To, from, and the kind of edge
Every direct reference has a direction. References from an instruction answer “what does this instruction name?” References to an address answer “who names this?” The second list is the one you live in. Callers of a function are references to its entry. Users of a string are references to the string’s address. The Listing prints a compact xref comment above a label. Right-click > References > Show References to Address opens the full dialog, with the from-address, the kind, and the operand.
Learn the kinds as words in that dialog. A call is a transfer that is expected to return. A jump is a transfer that is not a call. A read uses the value at the address. A write stores to it. The Listing’s short tags are a compression of those words. If a tag is ambiguous, open the dialog. Guessing from a one-letter suffix is how a read becomes a call in somebody’s notes.
Follow the edge you meant
Double-click the xref comment to jump to that from-address. If there are several, the tool asks which one, or the dialog lets you pick. This is the reliable path from data to code: define or find the string, ask for references to it, land on the instruction, then ask which function contains that instruction. It is also the path from a callee back to its callers, which is how you place a function in the program once you understand the body.
In the decompiler, a call printed as a name is the same edge. Right-click the name and ask for references to that function when you want the caller list without leaving the mental model of the C-like view. Then click one caller and confirm the Listing agrees about the call instruction. The confirmation takes a few seconds and saves a wrong paragraph later.
An empty list is a narrow fact
The analyzer records an edge when an operand contains the address. A call through a register, a jump through a table it failed to mark, or a pointer built by arithmetic can be absent from the list and still be real. The honest sentence is “no direct reference,” not “nothing uses this.” When a string you know is important has an empty list, look for the instructions that compute an address near it, and look for whether the string was ever defined as data. Undefined bytes do not collect the references a defined string would.
The other direction fails too. A reference can be stale if you cleared the instruction that used to be there and did not rerun reference analysis. One Shot of the reference analyzer rebuilds the edges from the instructions as they stand now. Do that after a boundary repair, before you trust a caller count.
Lab: every direct user of the default path
Select the check.conf string and open references to that address. Record each from-address and the kind. On the unstripped, unoptimized lab program you should see a single read, inside the function that chooses the path. If you see none, the string may not be defined as data yet, or this build computed the address in a way the pass missed. Define the string if needed, rerun the reference analyzer, and look again.
From that same function’s entry, open references to the entry and record the callers. Startup code, or main if you already have it, should appear once you have followed the chain far enough. Write the two lists under separate headings, “readers of the string” and “callers of the function.” Those headings stay useful on every program you will ever open.
How a string reference should read Data: s_/etc/harbor/check.conf_00102048 Xref: from 00101253 READ in the function that picks the path One reader, one instruction, kind read. A second reader would be a second line. Zero readers means you go look for a computed address.
Diagram
An edge the analyzer can see, and one it cannot
- From
References from an instruction are the addresses its operands name.
- To
References to an address are the instructions that name it. Callers of a function live here.
- Kind
The References dialog names the kind: call, jump, read, write, and a few related data kinds.
- Limit
Indirect calls and pointer arithmetic can hide an edge. An empty list means “no direct operand,” which is a smaller claim.
Why a reference list is sometimes empty
Listing. The instruction names the target address in an operand.
Xref. The reference analyzer records a call, jump, read, or write.
Follow. Double-click the xref comment, or open Show References.
Listing. The target is a value in a register, computed earlier.
Xref. The list can be empty even though the call is real.
Follow. Read the instructions that filled the register. The list will not invent the edge.
Data: s_/etc/harbor/check.conf_00102048 Xref: from 00101253 READ in the function that picks the path One reader, one instruction, kind read. A second reader would be a second line. Zero readers means you go look for a computed address.
Check yourself
1. You want every direct caller of the function that loads the config. Which list do you open?
2. The Listing shows a cross-reference on a string, and the References dialog calls the type a read. What happened?
3. A function pointer is loaded from a table and then called. The target function’s reference list does not include that call. What does the empty list mean?