Skip to content

kAllAddressesMax (UINT64_MAX) read as -1 by Java host → range-filtered tables empty for programs with EXTERNAL blocks #2

Description

@ogeidr

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions