Ghidra: imports, exports, and the Symbol Tree
The Symbol Tree is the index of names: functions you found, data you defined, imports the file expects from other libraries, and exports it offers to other programs. Imports are a capability list. They say what the file is built to ask for, not what it will do on a given run.
The tree is an index of names
The Symbol Tree, usually docked beside the Listing, is every name in the database that has a category. Functions are the function objects. Labels include data and code addresses that are not function entries. Imports are symbols the file expects a loader to resolve in some other module. Exports are symbols this file offers. Classes and namespaces appear when the binary, or your types, created them. The filter box at the top is Go To for people who remember a fragment of a name and not the address.
An imported symbol and a user-defined name can sit in the tree together. Their sources differ. The import came from the file’s tables. The user name came from you. When you write a report, those are different kinds of evidence, and the tree is where you check which one you are looking at.
Imports are a capability list
Read the import branch by library. A dynamic harbor-check depends on the C library for fopen, fgets, sscanf, fclose, and printf, or on whatever subset your compiler actually emitted. That set supports a sentence: the program is built to open a stream, read a line, parse it, and print. The sentence is useful because it is checkable against the functions you have already read, and because it does not claim which path is opened or whether a given run succeeds. The path is a string reference. The success is a branch. The import is only the capability.
On Windows the same reading uses DLL names. A program that imports a networking DLL is built to use that DLL’s functions. A program that imports registry APIs is built to open and write registry keys. Group the names by what they are for, and then confirm the interesting ones by reading the function that calls them. A name in the import table can be unused. Unused imports are still worth a glance and a poor thing to hang a conclusion on.
Calls often arrive through a stub
ELF calls typically land on a procedure-linkage stub, a thunk of one jump, which transfers to the external. PE calls often go through an import thunk as well. The decompiler collapses the stub and prints the external’s name. The Listing shows the stub. Follow it once, so you believe the collapse, and then quote the external. If the import is by ordinal and the name is missing, say so. An ordinal without a name is an unresolved capability, and guessing the name from memory is how a note becomes fiction. Look the ordinal up in a source you trust for that DLL, or leave it as an ordinal.
Exports deserve the same honesty. A main executable may export nothing interesting and still have a perfectly real main as a local symbol. A shared library’s exports are the interface you document. If the lab ever hands you a .so, start at Exports, open each one, and write the capability of that function. Do not go hunting for a main that the file does not have.
Lab: the sentence, then the check
From the Symbol Tree alone, without opening a function body, write the capability sentence for your dynamic harbor-check. Then open the file-opening function and underline the words in your sentence that the body supports. If the sentence says “writes formatted text,” you should be able to point at printf or an equivalent. If you wrote something the imports do not contain, strike it.
Repeat the import list on the -O2 build if you imported it. The names should largely agree even when the function boundaries do not. That agreement is the point of reading imports before you read code: you learn the ceiling of what the file is built to do, and then the bodies tell you which part of the ceiling they actually reach.
Diagram
From an import to a sentence about capability
- Tree
The Symbol Tree groups Functions, Labels, Imports, and Exports. Filter it when you know part of a name.
- Library
Imports are grouped by the library that provides them. fopen lives with the C library. A Windows networking API lives with the DLL that exports it.
- Thunk
A call in the Listing may hit a tiny stub first. Follow the stub to the external symbol, and name the external in your note.
- Sentence
Write the capability the set implies. “Reads a file and writes formatted text” is a sentence. A dump of every import is a transcript.
fopen, fgets, sscanf, fclose, printf The file is built to open a FILE*, read a line, parse it, close the stream, and write formatted text. That is the capability list. It matches the source you compiled. It does not, by itself, say which path is opened.
fopen, fgets, sscanf, fclose, printf The file is built to open a FILE*, read a line, parse it, close the stream, and write formatted text. That is the capability list. It matches the source you compiled. It does not, by itself, say which path is opened.
Check yourself
1. The Symbol Tree shows fopen under the C library’s imports, and a thunk in .text jumps to it. What do you write in the notebook?
2. You switch from the harbor-check executable to a shared library you built, and you want the functions other programs can call. Which branch of the tree?
3. The import list of a stripped -O2 build is the same set of libc names as the -O0 build, but load_config no longer exists as its own function. What changed?