feat: Implement proper cross-references (xrefs) functionality
- Java plugin now uses Ghidra ReferenceManager to find real cross-references - Added detailed information about xrefs, including related functions and instructions - Bridge script now provides simplified and human-readable text for xrefs - Support bi-directional search for references to/from addresses - Added filtering by reference type - Properly implement getCurrentAddress using Ghidra service APIs
This commit is contained in:
parent
4f3042f6ee
commit
96788f35fc
@ -898,9 +898,18 @@ def list_xrefs(port: int = DEFAULT_GHIDRA_PORT,
|
||||
"result": list of xref objects with from_addr, to_addr, type, from_function, to_function fields,
|
||||
"size": total number of xrefs matching the filter,
|
||||
"offset": current offset for pagination,
|
||||
"limit": current limit for pagination
|
||||
"limit": current limit for pagination,
|
||||
"xrefs": simplified array of cross-references for AI consumption
|
||||
}
|
||||
"""
|
||||
# At least one of the address parameters must be provided
|
||||
if not to_addr and not from_addr:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Either to_addr or from_addr parameter is required",
|
||||
"timestamp": int(time.time() * 1000)
|
||||
}
|
||||
|
||||
params = {
|
||||
"offset": offset,
|
||||
"limit": limit
|
||||
@ -921,9 +930,55 @@ def list_xrefs(port: int = DEFAULT_GHIDRA_PORT,
|
||||
simplified.setdefault("offset", offset)
|
||||
simplified.setdefault("limit", limit)
|
||||
|
||||
# For AI consumption, make the references more directly accessible
|
||||
# Create a simplified, flattened view of references for AI consumption
|
||||
if "result" in simplified and isinstance(simplified["result"], dict) and "references" in simplified["result"]:
|
||||
simplified["xrefs"] = simplified["result"]["references"]
|
||||
references = simplified["result"]["references"]
|
||||
flat_refs = []
|
||||
|
||||
for ref in references:
|
||||
flat_ref = {
|
||||
"from_addr": ref.get("from_addr"),
|
||||
"to_addr": ref.get("to_addr"),
|
||||
"type": ref.get("refType")
|
||||
}
|
||||
|
||||
# Add source function info if available
|
||||
if "from_function" in ref and isinstance(ref["from_function"], dict):
|
||||
flat_ref["from_function"] = ref["from_function"].get("name")
|
||||
flat_ref["from_function_addr"] = ref["from_function"].get("address")
|
||||
|
||||
# Add target function info if available
|
||||
if "to_function" in ref and isinstance(ref["to_function"], dict):
|
||||
flat_ref["to_function"] = ref["to_function"].get("name")
|
||||
flat_ref["to_function_addr"] = ref["to_function"].get("address")
|
||||
|
||||
# Add symbol info if available
|
||||
if "from_symbol" in ref:
|
||||
flat_ref["from_symbol"] = ref["from_symbol"]
|
||||
if "to_symbol" in ref:
|
||||
flat_ref["to_symbol"] = ref["to_symbol"]
|
||||
|
||||
# Add instruction text if available
|
||||
if "from_instruction" in ref:
|
||||
flat_ref["from_instruction"] = ref["from_instruction"]
|
||||
if "to_instruction" in ref:
|
||||
flat_ref["to_instruction"] = ref["to_instruction"]
|
||||
|
||||
flat_refs.append(flat_ref)
|
||||
|
||||
# Add the simplified references
|
||||
simplified["xrefs"] = flat_refs
|
||||
|
||||
# Create a text representation for easier consumption
|
||||
text_refs = []
|
||||
for ref in flat_refs:
|
||||
from_func = f"[{ref.get('from_function', '??')}]" if "from_function" in ref else ""
|
||||
to_func = f"[{ref.get('to_function', '??')}]" if "to_function" in ref else ""
|
||||
|
||||
line = f"{ref.get('from_addr')} {from_func} -> {ref.get('to_addr')} {to_func} ({ref.get('type', '??')})"
|
||||
text_refs.append(line)
|
||||
|
||||
simplified["xrefs_text"] = "\n".join(text_refs)
|
||||
|
||||
return simplified
|
||||
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
package eu.starsong.ghidra.endpoints;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
import eu.starsong.ghidra.api.ResponseBuilder;
|
||||
@ -10,7 +8,12 @@ import ghidra.program.model.address.AddressFactory;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.symbol.Reference;
|
||||
import ghidra.program.model.symbol.ReferenceIterator;
|
||||
import ghidra.program.model.symbol.ReferenceManager;
|
||||
import ghidra.program.model.symbol.RefType;
|
||||
import ghidra.program.model.listing.CodeUnit;
|
||||
import ghidra.program.model.symbol.Symbol;
|
||||
import ghidra.program.model.symbol.SymbolTable;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
@ -44,8 +47,9 @@ public class XrefsEndpoints extends AbstractEndpoint {
|
||||
try {
|
||||
if ("GET".equals(exchange.getRequestMethod())) {
|
||||
Map<String, String> qparams = parseQueryParams(exchange);
|
||||
String addressStr = qparams.get("address");
|
||||
String type = qparams.get("type"); // "to" or "from"
|
||||
String toAddrStr = qparams.get("to_addr");
|
||||
String fromAddrStr = qparams.get("from_addr");
|
||||
String refTypeStr = qparams.get("type");
|
||||
int offset = parseIntOrDefault(qparams.get("offset"), 0);
|
||||
int limit = parseIntOrDefault(qparams.get("limit"), 50);
|
||||
|
||||
@ -64,114 +68,107 @@ public class XrefsEndpoints extends AbstractEndpoint {
|
||||
// Add common links
|
||||
builder.addLink("program", "/program");
|
||||
|
||||
// If no address is provided, show current address (if any)
|
||||
if (addressStr == null || addressStr.isEmpty()) {
|
||||
Address currentAddress = getCurrentAddress(program);
|
||||
if (currentAddress == null) {
|
||||
sendErrorResponse(exchange, 400, "Address parameter is required", "MISSING_PARAMETER");
|
||||
// At least one of to_addr or from_addr must be provided
|
||||
if ((toAddrStr == null || toAddrStr.isEmpty()) &&
|
||||
(fromAddrStr == null || fromAddrStr.isEmpty())) {
|
||||
sendErrorResponse(exchange, 400, "Either to_addr or from_addr parameter is required", "MISSING_PARAMETER");
|
||||
return;
|
||||
}
|
||||
addressStr = currentAddress.toString();
|
||||
}
|
||||
|
||||
// Parse address
|
||||
// Parse addresses
|
||||
AddressFactory addressFactory = program.getAddressFactory();
|
||||
Address address;
|
||||
Address toAddr = null;
|
||||
Address fromAddr = null;
|
||||
|
||||
if (toAddrStr != null && !toAddrStr.isEmpty()) {
|
||||
try {
|
||||
address = addressFactory.getAddress(addressStr);
|
||||
toAddr = addressFactory.getAddress(toAddrStr);
|
||||
} catch (Exception e) {
|
||||
sendErrorResponse(exchange, 400, "Invalid address format", "INVALID_PARAMETER");
|
||||
sendErrorResponse(exchange, 400, "Invalid to_addr format: " + toAddrStr, "INVALID_PARAMETER");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Simplified cross-reference implementation due to API limitations
|
||||
if (fromAddrStr != null && !fromAddrStr.isEmpty()) {
|
||||
try {
|
||||
fromAddr = addressFactory.getAddress(fromAddrStr);
|
||||
} catch (Exception e) {
|
||||
sendErrorResponse(exchange, 400, "Invalid from_addr format: " + fromAddrStr, "INVALID_PARAMETER");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get reference manager
|
||||
ReferenceManager refManager = program.getReferenceManager();
|
||||
List<Map<String, Object>> referencesList = new ArrayList<>();
|
||||
|
||||
// Get function at address if any
|
||||
Function function = program.getFunctionManager().getFunctionAt(address);
|
||||
if (function != null) {
|
||||
Map<String, Object> funcRef = new HashMap<>();
|
||||
funcRef.put("direction", "from");
|
||||
funcRef.put("name", function.getName());
|
||||
funcRef.put("address", function.getEntryPoint().toString());
|
||||
funcRef.put("signature", function.getSignature().toString());
|
||||
funcRef.put("type", "function");
|
||||
|
||||
referencesList.add(funcRef);
|
||||
// Get references to this address
|
||||
if (toAddr != null) {
|
||||
ReferenceIterator refsTo = refManager.getReferencesTo(toAddr);
|
||||
while (refsTo.hasNext()) {
|
||||
Reference ref = refsTo.next();
|
||||
if (refTypeStr != null && !ref.getReferenceType().getName().equalsIgnoreCase(refTypeStr)) {
|
||||
continue; // Skip if type filter doesn't match
|
||||
}
|
||||
|
||||
// Get related addresses as placeholders for xrefs
|
||||
// Need to be careful with address arithmetic
|
||||
Address prevAddr = null;
|
||||
Address nextAddr = null;
|
||||
|
||||
try {
|
||||
// Try to get addresses safely
|
||||
if (address.getOffset() > 0) {
|
||||
prevAddr = address.subtract(1);
|
||||
Map<String, Object> refMap = createReferenceMap(program, ref, "to");
|
||||
referencesList.add(refMap);
|
||||
}
|
||||
nextAddr = address.add(1);
|
||||
} catch (Exception e) {
|
||||
Msg.error(this, "Error with address arithmetic: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
// Only add previous reference if we have a valid previous address
|
||||
if (prevAddr != null) {
|
||||
Map<String, Object> prevRef = new HashMap<>();
|
||||
prevRef.put("direction", "to");
|
||||
prevRef.put("address", prevAddr.toString());
|
||||
prevRef.put("target", address.toString());
|
||||
prevRef.put("refType", "data");
|
||||
prevRef.put("isPrimary", true);
|
||||
referencesList.add(prevRef);
|
||||
// Get references from this address
|
||||
if (fromAddr != null) {
|
||||
ReferenceIterator refsFrom = refManager.getReferencesFrom(fromAddr);
|
||||
while (refsFrom.hasNext()) {
|
||||
Reference ref = refsFrom.next();
|
||||
if (refTypeStr != null && !ref.getReferenceType().getName().equalsIgnoreCase(refTypeStr)) {
|
||||
continue; // Skip if type filter doesn't match
|
||||
}
|
||||
|
||||
// Only add next reference if we have a valid next address
|
||||
if (nextAddr != null) {
|
||||
Map<String, Object> nextRef = new HashMap<>();
|
||||
nextRef.put("direction", "from");
|
||||
nextRef.put("address", address.toString());
|
||||
nextRef.put("target", nextAddr.toString());
|
||||
nextRef.put("refType", "flow");
|
||||
nextRef.put("isPrimary", true);
|
||||
referencesList.add(nextRef);
|
||||
Map<String, Object> refMap = createReferenceMap(program, ref, "from");
|
||||
referencesList.add(refMap);
|
||||
}
|
||||
|
||||
// Add a self reference if nothing else is available
|
||||
if (referencesList.isEmpty()) {
|
||||
Map<String, Object> selfRef = new HashMap<>();
|
||||
selfRef.put("direction", "self");
|
||||
selfRef.put("address", address.toString());
|
||||
selfRef.put("target", address.toString());
|
||||
selfRef.put("refType", "self");
|
||||
selfRef.put("isPrimary", true);
|
||||
referencesList.add(selfRef);
|
||||
}
|
||||
|
||||
// Sort by type and address
|
||||
Collections.sort(referencesList, (a, b) -> {
|
||||
int typeCompare = ((String)a.get("direction")).compareTo((String)b.get("direction"));
|
||||
// First sort by direction
|
||||
int directionCompare = ((String)a.get("direction")).compareTo((String)b.get("direction"));
|
||||
if (directionCompare != 0) return directionCompare;
|
||||
|
||||
// Then by reference type
|
||||
int typeCompare = ((String)a.get("refType")).compareTo((String)b.get("refType"));
|
||||
if (typeCompare != 0) return typeCompare;
|
||||
return ((String)a.get("address")).compareTo((String)b.get("address"));
|
||||
|
||||
// Finally by from_address
|
||||
return ((String)a.get("from_addr")).compareTo((String)b.get("from_addr"));
|
||||
});
|
||||
|
||||
// Apply pagination
|
||||
List<Map<String, Object>> paginatedRefs =
|
||||
applyPagination(referencesList, offset, limit, builder, "/xrefs",
|
||||
"address=" + addressStr + (type != null ? "&type=" + type : ""));
|
||||
buildQueryString(toAddrStr, fromAddrStr, refTypeStr));
|
||||
|
||||
// Create result object
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("address", address.toString());
|
||||
if (toAddr != null) {
|
||||
result.put("to_addr", toAddrStr);
|
||||
}
|
||||
if (fromAddr != null) {
|
||||
result.put("from_addr", fromAddrStr);
|
||||
}
|
||||
result.put("references", paginatedRefs);
|
||||
result.put("note", "This is a simplified cross-reference implementation due to API limitations");
|
||||
|
||||
// Add the result to the builder
|
||||
builder.result(result);
|
||||
|
||||
// Add specific links
|
||||
builder.addLink("refsFrom", "/xrefs?address=" + addressStr + "&type=from");
|
||||
builder.addLink("refsTo", "/xrefs?address=" + addressStr + "&type=to");
|
||||
if (toAddr != null) {
|
||||
builder.addLink("to_function", "/functions/" + toAddrStr);
|
||||
}
|
||||
if (fromAddr != null) {
|
||||
builder.addLink("from_function", "/functions/" + fromAddrStr);
|
||||
}
|
||||
|
||||
// Send the HATEOAS-compliant response
|
||||
sendJsonResponse(exchange, builder.build(), 200);
|
||||
@ -185,6 +182,92 @@ public class XrefsEndpoints extends AbstractEndpoint {
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> createReferenceMap(Program program, Reference ref, String direction) {
|
||||
Map<String, Object> refMap = new HashMap<>();
|
||||
|
||||
// Basic reference information
|
||||
refMap.put("direction", direction);
|
||||
refMap.put("from_addr", ref.getFromAddress().toString());
|
||||
refMap.put("to_addr", ref.getToAddress().toString());
|
||||
refMap.put("refType", ref.getReferenceType().getName());
|
||||
refMap.put("isPrimary", ref.isPrimary());
|
||||
|
||||
// Get source function (if any)
|
||||
Function fromFunc = program.getFunctionManager().getFunctionContaining(ref.getFromAddress());
|
||||
if (fromFunc != null) {
|
||||
Map<String, Object> fromFuncMap = new HashMap<>();
|
||||
fromFuncMap.put("name", fromFunc.getName());
|
||||
fromFuncMap.put("address", fromFunc.getEntryPoint().toString());
|
||||
fromFuncMap.put("offset", ref.getFromAddress().subtract(fromFunc.getEntryPoint()));
|
||||
refMap.put("from_function", fromFuncMap);
|
||||
}
|
||||
|
||||
// Get target function (if any)
|
||||
Function toFunc = program.getFunctionManager().getFunctionContaining(ref.getToAddress());
|
||||
if (toFunc != null) {
|
||||
Map<String, Object> toFuncMap = new HashMap<>();
|
||||
toFuncMap.put("name", toFunc.getName());
|
||||
toFuncMap.put("address", toFunc.getEntryPoint().toString());
|
||||
toFuncMap.put("offset", ref.getToAddress().subtract(toFunc.getEntryPoint()));
|
||||
refMap.put("to_function", toFuncMap);
|
||||
}
|
||||
|
||||
// Get source symbol (if any)
|
||||
SymbolTable symbolTable = program.getSymbolTable();
|
||||
Symbol[] fromSymbols = symbolTable.getSymbols(ref.getFromAddress());
|
||||
if (fromSymbols != null && fromSymbols.length > 0) {
|
||||
refMap.put("from_symbol", fromSymbols[0].getName());
|
||||
}
|
||||
|
||||
// Get target symbol (if any)
|
||||
Symbol[] toSymbols = symbolTable.getSymbols(ref.getToAddress());
|
||||
if (toSymbols != null && toSymbols.length > 0) {
|
||||
refMap.put("to_symbol", toSymbols[0].getName());
|
||||
}
|
||||
|
||||
// Get the instruction/data at the from address (if applicable)
|
||||
try {
|
||||
CodeUnit codeUnit = program.getListing().getCodeUnitAt(ref.getFromAddress());
|
||||
if (codeUnit != null) {
|
||||
refMap.put("from_instruction", codeUnit.toString());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Ignore exceptions when getting code units
|
||||
}
|
||||
|
||||
// Get the instruction/data at the to address (if applicable)
|
||||
try {
|
||||
CodeUnit codeUnit = program.getListing().getCodeUnitAt(ref.getToAddress());
|
||||
if (codeUnit != null) {
|
||||
refMap.put("to_instruction", codeUnit.toString());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Ignore exceptions when getting code units
|
||||
}
|
||||
|
||||
return refMap;
|
||||
}
|
||||
|
||||
private String buildQueryString(String toAddr, String fromAddr, String refType) {
|
||||
StringBuilder query = new StringBuilder();
|
||||
|
||||
if (toAddr != null && !toAddr.isEmpty()) {
|
||||
query.append("to_addr=").append(toAddr);
|
||||
}
|
||||
|
||||
if (fromAddr != null && !fromAddr.isEmpty()) {
|
||||
if (query.length() > 0) query.append("&");
|
||||
query.append("from_addr=").append(fromAddr);
|
||||
}
|
||||
|
||||
if (refType != null && !refType.isEmpty()) {
|
||||
if (query.length() > 0) query.append("&");
|
||||
query.append("type=").append(refType);
|
||||
}
|
||||
|
||||
return query.toString();
|
||||
}
|
||||
|
||||
private Address getCurrentAddress(Program program) {
|
||||
if (program == null) return null;
|
||||
|
||||
@ -192,8 +275,34 @@ public class XrefsEndpoints extends AbstractEndpoint {
|
||||
PluginTool tool = getTool();
|
||||
if (tool != null) {
|
||||
try {
|
||||
// Fallback to program's min address
|
||||
return program.getAddressFactory().getDefaultAddressSpace().getMinAddress();
|
||||
// Get the current location service from the tool
|
||||
ghidra.app.services.GoToService goToService = tool.getService(ghidra.app.services.GoToService.class);
|
||||
if (goToService != null) {
|
||||
return goToService.getDefaultAddress(program);
|
||||
}
|
||||
|
||||
// Try to get the address from the code browser service
|
||||
ghidra.app.services.CodeViewerService codeViewerService =
|
||||
tool.getService(ghidra.app.services.CodeViewerService.class);
|
||||
if (codeViewerService != null) {
|
||||
ghidra.app.nav.Navigatable navigatable = codeViewerService.getNavigatable();
|
||||
if (navigatable != null && navigatable.getProgram() == program) {
|
||||
Address addr = navigatable.getLocation().getAddress();
|
||||
if (addr != null) {
|
||||
return addr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to get the address from the listing service
|
||||
ghidra.app.services.ProgramManager programManager =
|
||||
tool.getService(ghidra.app.services.ProgramManager.class);
|
||||
if (programManager != null) {
|
||||
ghidra.program.util.ProgramLocation location = programManager.getCurrentLocation();
|
||||
if (location != null && location.getProgram() == program) {
|
||||
return location.getAddress();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Msg.error(this, "Error getting current address from tool", e);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user