Summary
All range-filtered tables (funcs, names, instructions, strings, xrefs, …) return 0 rows for any program that has an EXTERNAL block (i.e. unresolved external symbols), while non-range tables such as segments work and db_info is correct.
Environment
- Ghidra 12.0.4, JDK 21
- Linux aarch64 (root cause is platform-independent — it depends on signed
uint64 handling on the JVM host plus the program's address-space layout)
Repro
Analyze any dynamically linked binary with unresolved imports (e.g. int f(int x){return x*2;} int main(void){return f(21);} linked against libc):
ghidrasql --binary /tmp/tinybin --project /tmp/p --project-name t --analyze -q "SELECT COUNT(*) FROM funcs"
# -> 0
ghidrasql --binary /tmp/tinybin --project /tmp/p --project-name t -q "SELECT COUNT(*) FROM segments"
# -> non-zero (e.g. 32)
(Server log shows the program imported, analyzed, and bound; db_info returns the correct language_id/image_base.)
Root cause
src/lib/src/source_libghidra.cpp:
static constexpr std::uint64_t kAllAddressesMax = std::numeric_limits<std::uint64_t>::max();
is passed as the range upper bound to every List* RPC (client_.ListFunctions(kAllAddressesMin, kAllAddressesMax, …), etc.). Over the protobuf uint64 field, the Java host reads 0xFFFFFFFFFFFFFFFF as a signed long = -1. In FunctionsRuntime.listFunctions (and the equivalent runtimes) this hits:
long endOffset = request.rangeEnd(); // -1
if (endOffset <= 0) { endOffset = program.getMaxAddress().getOffset(); }
When the program has an EXTERNAL block placed in an address space that sorts after the main RAM space, program.getMaxAddress() lands in that block, whose getOffset() is a tiny value. The resulting [startOffset, endOffset] window then excludes every real function, so the iterator yields nothing. Programs without externals (or where the EXTERNAL space sorts first) happen to work, which is why this isn't seen on every binary.
Suggested fix (either)
- Client (one-liner): use
INT64_MAX (0x7FFFFFFFFFFFFFFF) as the sentinel so it remains a large positive value on the JVM host and the <= 0 fallback never triggers:
static constexpr std::uint64_t kAllAddressesMax =
static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max());
- Host (more correct): don't derive the upper bound from
getMaxAddress().getOffset() across address spaces. Treat an out-of-range/negative endOffset as "unbounded," or bound by the program's actual code address range rather than the overall max address.
The client-side change alone fixes all range-filtered tables in our testing.
Summary
All range-filtered tables (
funcs,names,instructions,strings,xrefs, …) return 0 rows for any program that has an EXTERNAL block (i.e. unresolved external symbols), while non-range tables such assegmentswork anddb_infois correct.Environment
uint64handling on the JVM host plus the program's address-space layout)Repro
Analyze any dynamically linked binary with unresolved imports (e.g.
int f(int x){return x*2;} int main(void){return f(21);}linked against libc):(Server log shows the program imported, analyzed, and bound;
db_inforeturns the correctlanguage_id/image_base.)Root cause
src/lib/src/source_libghidra.cpp:is passed as the range upper bound to every
List*RPC (client_.ListFunctions(kAllAddressesMin, kAllAddressesMax, …), etc.). Over the protobufuint64field, the Java host reads0xFFFFFFFFFFFFFFFFas a signedlong= -1. InFunctionsRuntime.listFunctions(and the equivalent runtimes) this hits:When the program has an EXTERNAL block placed in an address space that sorts after the main RAM space,
program.getMaxAddress()lands in that block, whosegetOffset()is a tiny value. The resulting[startOffset, endOffset]window then excludes every real function, so the iterator yields nothing. Programs without externals (or where the EXTERNAL space sorts first) happen to work, which is why this isn't seen on every binary.Suggested fix (either)
INT64_MAX(0x7FFFFFFFFFFFFFFF) as the sentinel so it remains a large positive value on the JVM host and the<= 0fallback never triggers:getMaxAddress().getOffset()across address spaces. Treat an out-of-range/negativeendOffsetas "unbounded," or bound by the program's actual code address range rather than the overall max address.The client-side change alone fixes all range-filtered tables in our testing.