diff --git a/ghidra_extension/src/main/java/com/quarkslab/quokka/ExportPipeline.java b/ghidra_extension/src/main/java/com/quarkslab/quokka/ExportPipeline.java index c4beaf22..b510c78a 100644 --- a/ghidra_extension/src/main/java/com/quarkslab/quokka/ExportPipeline.java +++ b/ghidra_extension/src/main/java/com/quarkslab/quokka/ExportPipeline.java @@ -83,6 +83,7 @@ public static void export(Program program, File outputFile, // Phase 5: References monitor.setMessage("Quokka: exporting references..."); ReferenceExporter.export(ctx, builder); + ReferenceExporter.attachInstructionXrefs(ctx, builder); Msg.info(ExportPipeline.class, "Phase 5 (References) complete: " + builder.getReferencesCount() + " references"); diff --git a/ghidra_extension/src/main/java/com/quarkslab/quokka/export/DataExporter.java b/ghidra_extension/src/main/java/com/quarkslab/quokka/export/DataExporter.java index 252d1f0e..8f97a35e 100644 --- a/ghidra_extension/src/main/java/com/quarkslab/quokka/export/DataExporter.java +++ b/ghidra_extension/src/main/java/com/quarkslab/quokka/export/DataExporter.java @@ -12,7 +12,9 @@ import java.util.ArrayList; import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Phase 7: Export Data[] -- defined data symbols from the Listing. @@ -27,6 +29,19 @@ public static void export(ExportContext ctx, Quokka.Builder builder) { SymbolTable symTable = program.getSymbolTable(); List records = new ArrayList<>(); + Map> refsFrom = new HashMap<>(); + Map> refsTo = new HashMap<>(); + for (int refIdx = 0; refIdx < builder.getReferencesCount(); refIdx++) { + Quokka.Reference ref = builder.getReferences(refIdx); + if (ref.getSource().hasAddress()) { + refsFrom.computeIfAbsent(ref.getSource().getAddress(), + ignored -> new ArrayList<>()).add(refIdx); + } + if (ref.getDestination().hasAddress()) { + refsTo.computeIfAbsent(ref.getDestination().getAddress(), + ignored -> new ArrayList<>()).add(refIdx); + } + } DataIterator dataIter = program.getListing().getDefinedData(true); while (dataIter.hasNext()) { @@ -34,6 +49,7 @@ public static void export(ExportContext ctx, Quokka.Builder builder) { Data data = dataIter.next(); Address addr = data.getMinAddress(); + long addrOffset = addr.getOffset(); int segIdx = ctx.resolveSegmentIndex(addr); if (segIdx < 0) continue; // Skip data outside known segments @@ -55,7 +71,9 @@ public static void export(ExportContext ctx, Quokka.Builder builder) { boolean notInitialized = block != null && !block.isInitialized(); records.add(new DataRecord(segIdx, segOff, fileOff, - typeIdx, size, name, notInitialized)); + typeIdx, size, name, notInitialized, + refsTo.getOrDefault(addrOffset, List.of()), + refsFrom.getOrDefault(addrOffset, List.of()))); } // Sort by (segment_index, segment_offset) @@ -74,11 +92,18 @@ public static void export(ExportContext ctx, Quokka.Builder builder) { if (rec.name != null && !rec.name.isEmpty()) { dataBuilder.setName(rec.name); } + for (Integer refIdx : rec.xrefsTo) { + dataBuilder.addXrefTo(refIdx); + } + for (Integer refIdx : rec.xrefsFrom) { + dataBuilder.addXrefFrom(refIdx); + } builder.addData(dataBuilder); } } private record DataRecord(int segIdx, long segOff, long fileOff, - int typeIdx, int size, String name, boolean notInitialized) {} + int typeIdx, int size, String name, boolean notInitialized, + List xrefsTo, List xrefsFrom) {} } diff --git a/ghidra_extension/src/main/java/com/quarkslab/quokka/export/ReferenceExporter.java b/ghidra_extension/src/main/java/com/quarkslab/quokka/export/ReferenceExporter.java index 3db22fb0..91a9eff9 100644 --- a/ghidra_extension/src/main/java/com/quarkslab/quokka/export/ReferenceExporter.java +++ b/ghidra_extension/src/main/java/com/quarkslab/quokka/export/ReferenceExporter.java @@ -4,6 +4,9 @@ import com.quarkslab.quokka.util.RefTypeMapper; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressIterator; +import ghidra.program.model.listing.Instruction; +import ghidra.program.model.listing.InstructionIterator; +import ghidra.program.model.listing.Listing; import ghidra.program.model.listing.Program; import ghidra.program.model.symbol.Reference; import ghidra.program.model.symbol.ReferenceManager; @@ -12,7 +15,9 @@ import java.util.ArrayList; import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Phase 5: Export Reference[] from Ghidra ReferenceManager. @@ -71,6 +76,77 @@ public static void export(ExportContext ctx, Quokka.Builder builder) { } } + public static void attachInstructionXrefs(ExportContext ctx, + Quokka.Builder builder) { + Program program = ctx.getProgram(); + Listing listing = program.getListing(); + Map instructionSlots = new HashMap<>(); + + for (int funcIdx = 0; funcIdx < builder.getFunctionsCount(); funcIdx++) { + Quokka.Function.Builder funcBuilder = + builder.getFunctionsBuilder(funcIdx); + for (int blockIdx = 0; blockIdx < funcBuilder.getBlocksCount(); + blockIdx++) { + Quokka.Block.Builder blockBuilder = + funcBuilder.getBlocksBuilder(blockIdx); + long start = builder.getSegments(blockBuilder.getSegmentIndex()) + .getVirtualAddr() + blockBuilder.getSegmentOffset(); + long end = start + blockBuilder.getSize(); + Address startAddr = program.getAddressFactory() + .getDefaultAddressSpace() + .getAddress(start); + + InstructionIterator instrIter = + listing.getInstructions(startAddr, true); + int instrIdx = 0; + while (instrIter.hasNext() && instrIdx < blockBuilder.getNInstr()) { + Instruction instruction = instrIter.next(); + long addr = instruction.getAddress().getOffset(); + if (addr < start) { + continue; + } + if (addr >= end) { + break; + } + instructionSlots.put(addr, + new InstructionSlot(funcIdx, blockIdx, instrIdx)); + instrIdx++; + } + } + } + + for (int refIdx = 0; refIdx < builder.getReferencesCount(); refIdx++) { + Quokka.Reference ref = builder.getReferences(refIdx); + if (ref.getSource().hasAddress()) { + InstructionSlot slot = + instructionSlots.get(ref.getSource().getAddress()); + if (slot != null) { + builder.getFunctionsBuilder(slot.functionIndex) + .getBlocksBuilder(slot.blockIndex) + .addInstructionsXrefFrom( + Quokka.Block.InstructionXref.newBuilder() + .setInstrBbIdx(slot.instructionIndex) + .setXrefIndex(refIdx)); + } + } + if (ref.getDestination().hasAddress()) { + InstructionSlot slot = + instructionSlots.get(ref.getDestination().getAddress()); + if (slot != null) { + builder.getFunctionsBuilder(slot.functionIndex) + .getBlocksBuilder(slot.blockIndex) + .addInstructionsXrefTo( + Quokka.Block.InstructionXref.newBuilder() + .setInstrBbIdx(slot.instructionIndex) + .setXrefIndex(refIdx)); + } + } + } + } + private record RefRecord(long source, long destination, Quokka.EdgeType type) {} + + private record InstructionSlot(int functionIndex, int blockIndex, + int instructionIndex) {} } diff --git a/tests/python/tests/ghidra/test_ghidra_export.py b/tests/python/tests/ghidra/test_ghidra_export.py index 412333bd..3da85796 100644 --- a/tests/python/tests/ghidra/test_ghidra_export.py +++ b/tests/python/tests/ghidra/test_ghidra_export.py @@ -120,6 +120,33 @@ def test_has_normal_function_with_blocks(self): return pytest.fail("No NORMAL function with >1 block found") + def test_xrefs_attached_to_python_objects(self): + """Ghidra references should be reachable through high-level xref APIs.""" + block_xrefs_from = sum( + len(block.instructions_xref_from) + for func in self.prog.proto.functions + for block in func.blocks + ) + block_xrefs_to = sum( + len(block.instructions_xref_to) + for func in self.prog.proto.functions + for block in func.blocks + ) + data_xrefs_to = sum(len(data.xref_to) for data in self.prog.proto.data) + + assert len(self.prog.proto.references) > 0 + assert block_xrefs_from > 0 + assert block_xrefs_to > 0 + assert data_xrefs_to > 0 + + assert any(func.callees for func in self.prog.values()) + assert any( + inst.code_refs_from or inst.data_refs_from + for func in self.prog.values() + if func.has_body + for inst in func.instructions + ) + # --------------------------------------------------------------------------- # many_types_cpp: comprehensive type system validation