Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -27,13 +29,27 @@ public static void export(ExportContext ctx, Quokka.Builder builder) {
SymbolTable symTable = program.getSymbolTable();

List<DataRecord> records = new ArrayList<>();
Map<Long, List<Integer>> refsFrom = new HashMap<>();
Map<Long, List<Integer>> 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()) {
if (ctx.getMonitor().isCancelled()) break;

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
Expand All @@ -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)
Expand All @@ -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<Integer> xrefsTo, List<Integer> xrefsFrom) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -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<Long, InstructionSlot> 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) {}
}
27 changes: 27 additions & 0 deletions tests/python/tests/ghidra/test_ghidra_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading