Skip to main content
Glama
DisassemblyService.java81.4 kB
package com.ghidramcp.services; import ghidra.app.decompiler.DecompInterface; import ghidra.app.decompiler.DecompileOptions; import ghidra.app.decompiler.DecompileResults; import ghidra.app.util.template.TemplateSimplifier; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressIterator; import ghidra.program.model.data.*; import ghidra.program.model.listing.*; import ghidra.program.model.listing.CodeUnitFormat; import ghidra.program.model.listing.CodeUnitFormatOptions; import ghidra.program.model.listing.CodeUnitFormatOptions.ShowBlockName; import ghidra.program.model.listing.CodeUnitFormatOptions.ShowNamespace; import ghidra.program.model.listing.OperandRepresentationList; import ghidra.program.model.pcode.*; import ghidra.program.model.symbol.*; import ghidra.program.model.lang.Register; import ghidra.program.model.lang.RegisterValue; import ghidra.util.Msg; import ghidra.util.task.ConsoleTaskMonitor; import java.util.*; import java.util.stream.Collectors; /** * Service for disassembly operations */ public class DisassemblyService { private final FunctionNavigator navigator; private final int decompileTimeout; private final CodeUnitFormat codeUnitFormatter; private final DataLookupService dataLookupService; public DisassemblyService(FunctionNavigator navigator, int decompileTimeout) { this.navigator = navigator; this.decompileTimeout = decompileTimeout; this.dataLookupService = new DataLookupService(); // Initialize CodeUnitFormat with comprehensive formatting options // Using full constructor since fields are protected CodeUnitFormatOptions formatOptions = new CodeUnitFormatOptions( ShowBlockName.NEVER, // showBlockName ShowNamespace.NON_LOCAL, // showNamespace - Show namespace for non-local references null, // localPrefixOverride true, // doRegVariableMarkup - Enable register variable names true, // doStackVariableMarkup - Enable stack variable names true, // includeInferredVariableMarkup - Infer variables when possible true, // alwaysShowPrimaryReference - Enable "=>" notation true, // includeScalarReferenceAdjustment - Show offset adjustments true, // showLibraryInNamespace true, // followReferencedPointers - Enable "->" for pointers new TemplateSimplifier() // templateSimplifier - Handles C++ template simplification ); this.codeUnitFormatter = new CodeUnitFormat(formatOptions); } /** * Disassemble a function by address * @param addressStr Address as string * @param includeBytes Include raw instruction bytes in output (default: false) * @return Disassembly or error message */ public String disassembleFunction(String addressStr, boolean includeBytes) { Program program = navigator.getCurrentProgram(); if (program == null) return "No program loaded"; if (addressStr == null || addressStr.isEmpty()) return "Address is required"; try { Address addr = program.getAddressFactory().getAddress(addressStr); Function func = navigator.getFunctionForAddress(program, addr); if (func == null) return "No function found at or containing address " + addressStr; return disassembleFunctionInProgram(func, program, includeBytes); } catch (Exception e) { return "Error disassembling function: " + e.getMessage(); } } /** * Disassemble a function within a program with comprehensive Ghidra-style information * @param func Function to disassemble * @param program Program containing the function * @param includeBytes Include raw instruction bytes in output * @return Enhanced disassembly or null */ public String disassembleFunctionInProgram(Function func, Program program, boolean includeBytes) { try { StringBuilder result = new StringBuilder(); Listing listing = program.getListing(); Address entryPoint = func.getEntryPoint(); Address end = func.getBody().getMaxAddress(); // Check for non-contiguous function body and add warning if needed String contiguityWarning = checkFunctionContiguity(func, program); if (contiguityWarning != null) { result.append(contiguityWarning).append("\n"); } // 1. Add PLATE comment (function documentation box) appendPlateComment(result, listing, entryPoint); // 2. Add function signature with calling convention and parameters appendFunctionSignature(result, func, program); // 3. Add register assumptions appendRegisterAssumptions(result, func, program); // 4. Add local variables table with XREFs appendLocalVariables(result, func, program); // 5. Add function label with XREFs showing callers appendFunctionLabel(result, func, program); // 6. Add disassembly with enhanced annotations appendEnhancedDisassembly(result, func, program, listing, entryPoint, end, includeBytes); return result.toString(); } catch (Exception e) { Msg.error(this, "Error disassembling function in external program", e); } return null; } /** * Check if a function's body is contiguous (no gaps in the function) * @param func Function to check * @param program Program containing the function * @return Warning string if non-contiguous, null otherwise */ private String checkFunctionContiguity(Function func, Program program) { try { FunctionManager funcMgr = program.getFunctionManager(); Address funcEnd = func.getBody().getMaxAddress(); // Find the next function in memory after this one // Start searching from one byte after the function's entry point FunctionIterator funcIter = funcMgr.getFunctions(func.getEntryPoint(), true); Function nextFunc = null; while (funcIter.hasNext()) { Function candidate = funcIter.next(); // Skip the current function and find the first function that starts after our function ends if (candidate.getEntryPoint().compareTo(func.getEntryPoint()) > 0) { nextFunc = candidate; break; } } if (nextFunc != null) { Address nextFuncStart = nextFunc.getEntryPoint(); // Calculate the gap between this function's end and the next function's start // funcEnd + 1 should equal nextFuncStart for contiguous functions long gap = nextFuncStart.subtract(funcEnd) - 1; if (gap > 0) { // There's a gap between this function and the next StringBuilder warning = new StringBuilder(); warning.append("WARNING: Function body is not contiguous\n"); warning.append(" Function: ").append(func.getName()); warning.append(" @ ").append(func.getEntryPoint()).append("\n"); warning.append(" Declared range: ").append(func.getEntryPoint()); warning.append(" - ").append(funcEnd); // Calculate function size long funcSize = funcEnd.subtract(func.getEntryPoint()) + 1; warning.append(" (").append(funcSize).append(" bytes)\n"); warning.append(" Gap detected: ").append(gap).append(" byte"); if (gap != 1) warning.append("s"); warning.append(" between end (").append(funcEnd); warning.append(") and next function start (").append(nextFuncStart).append(")\n"); // Add note about gap size if (gap >= 100) { warning.append(" Note: Large gap (>= 100 bytes) - may indicate legitimate spacing\n"); } warning.append(" Next function: ").append(nextFunc.getName()); warning.append(" @ ").append(nextFuncStart).append("\n"); warning.append(" Possible issue: Incorrect function boundary detection\n"); warning.append(" Action: Verify function ends correctly or merge with adjacent function\n"); return warning.toString(); } } } catch (Exception e) { // Silently fail - don't block disassembly if contiguity check fails Msg.debug(this, "Could not check function contiguity: " + e.getMessage()); } return null; } /** * Append PLATE comment (function documentation box) */ private void appendPlateComment(StringBuilder result, Listing listing, Address entryPoint) { String plateComment = listing.getComment(CommentType.PLATE, entryPoint); if (plateComment != null && !plateComment.isEmpty()) { String[] lines = plateComment.split("\n"); int maxLength = Arrays.stream(lines).mapToInt(String::length).max().orElse(60); maxLength = Math.max(maxLength, 60); result.append(" "); result.append("*".repeat(maxLength + 4)).append("\n"); for (String line : lines) { result.append(" * "); result.append(String.format("%-" + maxLength + "s", line)); result.append(" *\n"); } result.append(" "); result.append("*".repeat(maxLength + 4)).append("\n"); } } /** * Append function signature with return type, calling convention, and parameters */ private void appendFunctionSignature(StringBuilder result, Function func, Program program) { result.append(" "); result.append(func.getReturnType().getName()); result.append(" "); String callingConvention = func.getCallingConventionName(); if (callingConvention != null && !callingConvention.equals("default")) { result.append("__").append(callingConvention).append(" "); } // Add namespace if not global Namespace namespace = func.getParentNamespace(); if (namespace != null && !namespace.isGlobal()) { result.append(namespace.getName()).append("::"); } result.append(func.getName()).append("("); Parameter[] params = func.getParameters(); for (int i = 0; i < params.length; i++) { if (i > 0) result.append(", "); result.append(params[i].getDataType().getName()).append(" ").append(params[i].getName()); } result.append(")\n"); } /** * Append register assumptions (e.g., assume CS = 0x2a0a) */ private void appendRegisterAssumptions(StringBuilder result, Function func, Program program) { try { ghidra.program.model.listing.ProgramContext programContext = program.getProgramContext(); Address entryPoint = func.getEntryPoint(); // Get all registers from the language List<Register> registers = programContext.getRegisters(); List<String> assumptions = new ArrayList<>(); for (Register register : registers) { // Skip sub-registers and only check base registers if (register.getBaseRegister() != register) { continue; } // Get the register value at the function entry point RegisterValue regValue = programContext.getRegisterValue(register, entryPoint); if (regValue != null && regValue.hasValue()) { // Format: assume <register> = <hex_value> java.math.BigInteger value = regValue.getUnsignedValue(); if (value != null) { String hexValue = "0x" + value.toString(16); assumptions.add("assume " + register.getName() + " = " + hexValue); } } } // Output assumptions with proper indentation for (String assumption : assumptions) { result.append(" "); result.append(assumption); result.append("\n"); } } catch (Exception e) { // Silently continue if getting register assumptions fails Msg.debug(this, "Could not get register assumptions: " + e.getMessage()); } } /** * Append local variables table with types and XREFs */ private void appendLocalVariables(StringBuilder result, Function func, Program program) { try { // Get ALL variables from the function (parameters + locals) Variable[] allVars = func.getAllVariables(); // Sort by storage location for better organization Arrays.sort(allVars, (v1, v2) -> { String s1 = v1.getVariableStorage().toString(); String s2 = v2.getVariableStorage().toString(); return s1.compareTo(s2); }); // Decompile to get XREF information DecompInterface decomp = new DecompInterface(); decomp.openProgram(program); DecompileResults decompResults = decomp.decompileFunction(func, this.decompileTimeout, new ConsoleTaskMonitor()); decomp.flushCache(); Map<String, List<XRefInfo>> xrefMap = new HashMap<>(); if (decompResults != null && decompResults.decompileCompleted()) { HighFunction highFunc = decompResults.getHighFunction(); if (highFunc != null) { // Build XREF map from high-level variables LocalSymbolMap symbolMap = highFunc.getLocalSymbolMap(); Iterator<HighSymbol> symbols = symbolMap.getSymbols(); while (symbols.hasNext()) { HighSymbol symbol = symbols.next(); HighVariable highVar = symbol.getHighVariable(); if (highVar != null) { String varName = symbol.getName(); List<XRefInfo> xrefs = new ArrayList<>(); Varnode[] instances = highVar.getInstances(); for (Varnode vn : instances) { // Check for definition (write) PcodeOp def = vn.getDef(); if (def != null) { Address addr = def.getSeqnum().getTarget(); if (addr != null) { xrefs.add(new XRefInfo(addr.toString(), "W")); } } // Check for uses (read) Iterator<PcodeOp> descendants = vn.getDescendants(); while (descendants.hasNext()) { PcodeOp use = descendants.next(); Address addr = use.getSeqnum().getTarget(); if (addr != null) { // Determine if it's a pointer dereference or read String type = isPointerDeref(use) ? "*" : "R"; xrefs.add(new XRefInfo(addr.toString(), type)); } } } xrefMap.put(varName, xrefs); } } } } // Output all variables in Ghidra UI format for (Variable var : allVars) { String dataType = var.getDataType().getDisplayName(); String storage = formatVariableStorage(var); String varName = var.getName(); // Format: dataType (18 chars) storage (15 chars) name (40 chars) XREF result.append(" "); result.append(String.format("%-18s", dataType)); result.append(String.format("%-15s", storage)); result.append(String.format("%-40s", varName)); // Add XREFs if available List<XRefInfo> xrefs = xrefMap.get(varName); if (xrefs != null && !xrefs.isEmpty()) { // Remove duplicates and sort Map<String, String> uniqueXrefs = new LinkedHashMap<>(); for (XRefInfo xref : xrefs) { uniqueXrefs.put(xref.address, xref.type); } result.append("XREF[").append(uniqueXrefs.size()).append("]: "); int count = 0; for (Map.Entry<String, String> entry : uniqueXrefs.entrySet()) { if (count > 0) result.append(", "); result.append(entry.getKey()).append("(").append(entry.getValue()).append(")"); count++; if (count >= 11) break; // Limit to 11 XREFs like Ghidra } } result.append("\n"); } } catch (Exception e) { // Silently continue if getting variable info fails Msg.debug(this, "Could not get variable info: " + e.getMessage()); } } /** * Format variable storage location in Ghidra UI format */ private String formatVariableStorage(Variable var) { String storage = var.getVariableStorage().toString(); // Check if it's a stack variable if (var.isStackVariable()) { int stackOffset = var.getStackOffset(); int size = var.getLength(); // Format as Stack[offset]:size with hex offset return String.format("Stack[%s]:%d", stackOffset < 0 ? "-0x" + Integer.toHexString(-stackOffset) : "0x" + Integer.toHexString(stackOffset), size); } else if (var.isRegisterVariable()) { return "Register"; } else if (var.isMemoryVariable()) { return var.getMinAddress().toString(); } return storage; } /** * Check if a PcodeOp represents a pointer dereference */ private boolean isPointerDeref(PcodeOp op) { int opcode = op.getOpcode(); return opcode == PcodeOp.LOAD || opcode == PcodeOp.STORE || opcode == PcodeOp.INDIRECT || opcode == PcodeOp.PTRSUB; } /** * Helper class to store XREF information with address and type */ private static class XRefInfo { String address; String type; // R=read, W=write, *=pointer deref, j=jump XRefInfo(String address, String type) { this.address = address; this.type = type; } } /** * Append function label with XREFs showing callers */ private void appendFunctionLabel(StringBuilder result, Function func, Program program) { result.append(" "); // Add namespace if not global Namespace namespace = func.getParentNamespace(); if (namespace != null && !namespace.isGlobal()) { result.append(namespace.getName()).append("::"); } result.append(func.getName()); // Add XREFs to function (callers) ReferenceManager refManager = program.getReferenceManager(); ReferenceIterator refIter = refManager.getReferencesTo(func.getEntryPoint()); List<String> callers = new ArrayList<>(); while (refIter.hasNext()) { Reference ref = refIter.next(); Address fromAddr = ref.getFromAddress(); Function fromFunc = program.getFunctionManager().getFunctionContaining(fromAddr); if (fromFunc != null && !fromFunc.equals(func)) { String caller = fromFunc.getName() + ":" + fromAddr.toString(); if (!callers.contains(caller)) { callers.add(caller); } } } if (!callers.isEmpty()) { result.append(" XREF[").append(callers.size()).append("]: "); result.append(callers.stream().limit(3).collect(Collectors.joining(", "))); if (callers.size() > 3) { result.append("..."); } } result.append("\n"); } /** * Append enhanced disassembly with annotations */ private void appendEnhancedDisassembly(StringBuilder result, Function func, Program program, Listing listing, Address start, Address end, boolean includeBytes) { ReferenceManager refManager = program.getReferenceManager(); SymbolTable symbolTable = program.getSymbolTable(); InstructionIterator instructions = listing.getInstructions(start, true); while (instructions.hasNext()) { Instruction instr = instructions.next(); Address addr = instr.getAddress(); if (addr.compareTo(end) > 0) { break; } // Check for error messages (disassembly errors) at this address List<String> errorMessages = getErrorMessages(program, addr); if (!errorMessages.isEmpty()) { for (String errorMsg : errorMessages) { result.append("Error (Bad Instruction): ").append(errorMsg).append("\n"); } result.append("Changes Unsaved\n"); } // Show label at this address if any (with XREFs for jump targets) Symbol primarySymbol = symbolTable.getPrimarySymbol(addr); if (primarySymbol != null && !primarySymbol.getName().equals(func.getName())) { result.append(" "); result.append(primarySymbol.getName()); // Add XREFs for labels using reference type display strings ReferenceIterator labelXrefs = refManager.getReferencesTo(addr); List<String> jumpRefs = new ArrayList<>(); while (labelXrefs.hasNext()) { Reference ref = labelXrefs.next(); if (ref.getReferenceType().isJump() || ref.getReferenceType().isConditional()) { String refTypeStr = getRefTypeDisplayString(ref); jumpRefs.add(ref.getFromAddress().toString() + refTypeStr); } } if (!jumpRefs.isEmpty()) { result.append(" XREF[").append(jumpRefs.size()).append("]: "); result.append(String.join(", ", jumpRefs.stream().limit(5).collect(Collectors.toList()))); } result.append("\n"); } // Get instruction bytes (only if requested) String bytesField = ""; if (includeBytes) { byte[] bytes = null; try { bytes = instr.getBytes(); } catch (ghidra.program.model.mem.MemoryAccessException e) { // Memory access failed - we'll show "??" placeholders like Ghidra UI does } StringBuilder bytesStr = new StringBuilder(); if (bytes != null) { for (byte b : bytes) { bytesStr.append(String.format("%02x ", b & 0xFF)); } } else { // Show "??" to indicate bytes couldn't be read (matches Ghidra UI behavior) bytesStr.append("??"); } // Format the bytes field (limit to ~12 chars worth of bytes to match Ghidra) bytesField = String.format("%-12s", bytesStr.toString().trim()); } // Build instruction mnemonic and operands with enhanced references String mnemonicStr = instr.getMnemonicString(); String enhancedOperands = buildEnhancedOperands(instr, program, func); // Calculate proper column widths result.append(" "); // 7 spaces result.append(String.format("%-10s", addr.toString())); // address (10 chars) if (includeBytes) { result.append(bytesField); // instruction bytes (12 chars) - only if requested } result.append(String.format("%-10s", mnemonicStr)); // mnemonic (10 chars) // Add operands with references and symbols result.append(enhancedOperands); // Add function names for CALL instructions with prototypes if (instr.getFlowType().isCall()) { appendCallDetails(result, instr, program, refManager); } // Add all comment types appendAllComments(result, listing, addr); result.append("\n"); // Show XREFs TO this address (who references this instruction) - limit to 5 ReferenceIterator xrefsTo = refManager.getReferencesTo(addr); List<String> xrefList = new ArrayList<>(); while (xrefsTo.hasNext()) { Reference ref = xrefsTo.next(); Address fromAddr = ref.getFromAddress(); // Don't show sequential flow as XREF if (!ref.getReferenceType().isFlow()) { Function fromFunc = program.getFunctionManager().getFunctionContaining(fromAddr); String xrefStr = fromAddr.toString(); if (fromFunc != null && !fromFunc.equals(func)) { xrefStr = fromFunc.getName() + ":" + xrefStr; } xrefStr += " (" + ref.getReferenceType().getName() + ")"; xrefList.add(xrefStr); } } if (!xrefList.isEmpty() && xrefList.size() <= 5) { for (String xref : xrefList) { result.append(" XREF from: ").append(xref).append("\n"); } } } } /** * Build enhanced operand string using Ghidra's CodeUnitFormat * This automatically handles variable names, symbols, and reference markup */ private String buildEnhancedOperands(Instruction instr, Program program, Function func) { StringBuilder result = new StringBuilder(); int numOperands = instr.getNumOperands(); for (int i = 0; i < numOperands; i++) { if (i > 0) { // Use instruction's separator if available, otherwise use comma String separator = instr.getSeparator(i); result.append(separator != null ? separator : ","); } // Get formatted operand representation using CodeUnitFormat // This handles: // - Variable name replacement (VariableOffset objects) // - Symbol/label resolution (LabelString objects) // - Arrow notation (=> and ->) // - Data value display OperandRepresentationList repList = codeUnitFormatter.getOperandRepresentationList(instr, i); if (repList != null) { result.append(repList.toString()); } else { // Fallback for unsupported languages result.append(instr.getDefaultOperandRepresentation(i)); } } return result.toString(); } /** * Append call details including function prototypes */ private void appendCallDetails(StringBuilder result, Instruction instr, Program program, ReferenceManager refManager) { Reference[] refs = refManager.getReferencesFrom(instr.getAddress()); for (Reference ref : refs) { if (ref.getReferenceType().isCall()) { Function calledFunc = program.getFunctionManager().getFunctionAt(ref.getToAddress()); if (calledFunc != null) { // Add function name result.append(" ").append(calledFunc.getName()); // Add function signature/prototype on next line String signature = calledFunc.getSignature().toString(); if (signature != null && !signature.isEmpty()) { result.append("\n"); result.append(" "); result.append(signature); } // Add call destination override if thunk or indirect if (calledFunc.isThunk()) { Function thunkedFunc = calledFunc.getThunkedFunction(true); if (thunkedFunc != null) { result.append("\n"); result.append(" -- Call Destination Override: "); result.append(thunkedFunc.getName()); result.append(" (").append(thunkedFunc.getEntryPoint()).append(")"); } } } } } } /** * Append all comment types for an address */ private void appendAllComments(StringBuilder result, Listing listing, Address addr) { List<String> comments = new ArrayList<>(); String preComment = listing.getComment(CommentType.PRE, addr); if (preComment != null && !preComment.isEmpty()) { comments.add("[PRE: " + preComment.replace("\n", " ") + "]"); } String eolComment = listing.getComment(CommentType.EOL, addr); if (eolComment != null && !eolComment.isEmpty()) { comments.add("; " + eolComment); } String postComment = listing.getComment(CommentType.POST, addr); if (postComment != null && !postComment.isEmpty()) { comments.add("[POST: " + postComment.replace("\n", " ") + "]"); } String repeatableComment = listing.getComment(CommentType.REPEATABLE, addr); if (repeatableComment != null && !repeatableComment.isEmpty()) { comments.add("[REP: " + repeatableComment.replace("\n", " ") + "]"); } if (!comments.isEmpty()) { result.append(" ").append(String.join(" ", comments)); } } /** * Get reference type display string (adapted from XRefFieldFactory.java:680-703) * Returns type indicators: (j)=jump, (c)=call, (R)=read, (W)=write, (RW)=read-write, * (T)=thunk, (*)=data */ private String getRefTypeDisplayString(Reference reference) { RefType refType = reference.getReferenceType(); if (reference instanceof ThunkReference) { return "(T)"; } if (refType.isCall()) { return "(c)"; } else if (refType.isJump()) { return "(j)"; } else if (refType.isRead() && refType.isWrite()) { return "(RW)"; } else if (refType.isRead() || refType.isIndirect()) { return "(R)"; } else if (refType.isWrite()) { return "(W)"; } else if (refType.isData()) { return "(*)"; } return ""; } /** * Collect XREFs with function names included */ private List<String> collectXRefsWithFunctionNames(Address targetAddr, ReferenceManager refManager, Program program) { List<String> xrefList = new ArrayList<>(); ReferenceIterator xrefsTo = refManager.getReferencesTo(targetAddr); while (xrefsTo.hasNext()) { Reference ref = xrefsTo.next(); Address fromAddr = ref.getFromAddress(); StringBuilder xrefStr = new StringBuilder(); // Find function containing this reference Function fromFunc = program.getFunctionManager().getFunctionContaining(fromAddr); if (fromFunc != null) { xrefStr.append(fromFunc.getName()).append(":"); } xrefStr.append(fromAddr.toString()); xrefStr.append(getRefTypeDisplayString(ref)); xrefList.add(xrefStr.toString()); } return xrefList; } /** * Get detailed disassembly context around a specific address. * Displays code units (instructions AND data) in memory order, matching Ghidra UI listing view. * * @param addressStr Address as string * @param before Number of code units before the address (default: 5) * @param after Number of code units after the address (default: 5) * @param includeBytes Include raw instruction/data bytes in output (default: false) * @return Detailed disassembly with context showing both instructions and data */ public String getAddressContext(String addressStr, int before, int after, boolean includeBytes) { Program program = navigator.getCurrentProgram(); if (program == null) return "No program loaded"; if (addressStr == null || addressStr.isEmpty()) return "Address is required"; try { Address targetAddr = program.getAddressFactory().getAddress(addressStr); if (targetAddr == null) { return "Error: Invalid address format: " + addressStr; } Listing listing = program.getListing(); ReferenceManager refManager = program.getReferenceManager(); SymbolTable symbolTable = program.getSymbolTable(); StringBuilder result = new StringBuilder(); result.append("Disassembly context for address: ").append(targetAddr).append("\n"); result.append("Context window: -").append(before).append(" to +").append(after).append(" code units\n\n"); // Find the function containing this address (if any) Function containingFunc = program.getFunctionManager().getFunctionContaining(targetAddr); if (containingFunc != null) { result.append("Function: ").append(containingFunc.getName()); result.append(" @ ").append(containingFunc.getEntryPoint()).append("\n\n"); } // Get the CODE UNIT at the target address (could be instruction OR data) CodeUnit targetUnit = listing.getCodeUnitContaining(targetAddr); if (targetUnit == null) { return "Error: No code unit found at address " + targetAddr; } // Collect code units BEFORE the target (in memory order) List<CodeUnit> beforeUnits = new ArrayList<>(); CodeUnit unit = targetUnit; for (int i = 0; i < before && unit != null; i++) { unit = listing.getCodeUnitBefore(unit.getMinAddress()); if (unit != null) { beforeUnits.add(0, unit); // Add to beginning to maintain order } } // Collect code units AFTER the target (including target itself) // IMPORTANT: Start from targetUnit's min address, not targetAddr! // This ensures we include the parent struct/array even if targetAddr is in the middle List<CodeUnit> afterUnits = new ArrayList<>(); CodeUnitIterator unitIter = listing.getCodeUnits(targetUnit.getMinAddress(), true); int count = 0; while (unitIter.hasNext() && count <= after) { CodeUnit cu = unitIter.next(); if (cu != null) { afterUnits.add(cu); count++; } } // Combine all units List<CodeUnit> allUnits = new ArrayList<>(beforeUnits); allUnits.addAll(afterUnits); // Display all code units (instructions AND data) for (CodeUnit cu : allUnits) { Address addr = cu.getMinAddress(); boolean isTarget = addr.equals(targetAddr) || (addr.compareTo(targetAddr) <= 0 && cu.getMaxAddress().compareTo(targetAddr) >= 0); // Check if this is an instruction or data if (cu instanceof Instruction) { displayInstruction((Instruction)cu, isTarget, result, program, listing, refManager, symbolTable, containingFunc, includeBytes); } else if (cu instanceof Data) { displayData((Data)cu, isTarget, targetAddr, result, program, listing, refManager, symbolTable, includeBytes, before, after); } } return result.toString(); } catch (Exception e) { return "Error getting address context: " + e.getMessage(); } } /** * Get error messages from bookmarks at a specific address */ private List<String> getErrorMessages(Program program, Address addr) { List<String> errorMessages = new ArrayList<>(); try { ghidra.program.model.listing.BookmarkManager bookmarkMgr = program.getBookmarkManager(); if (bookmarkMgr != null) { // Get all "Error" type bookmarks at this address ghidra.program.model.listing.Bookmark[] bookmarks = bookmarkMgr.getBookmarks(addr, "Error"); if (bookmarks != null) { for (ghidra.program.model.listing.Bookmark bookmark : bookmarks) { String comment = bookmark.getComment(); if (comment != null && !comment.isEmpty()) { errorMessages.add(comment); } } } } } catch (Exception e) { Msg.debug(this, "Could not retrieve error bookmarks: " + e.getMessage()); } return errorMessages; } /** * Display an instruction in Ghidra UI format */ private void displayInstruction(Instruction instruction, boolean isTarget, StringBuilder result, Program program, Listing listing, ReferenceManager refManager, SymbolTable symbolTable, Function containingFunc, boolean includeBytes) { Address addr = instruction.getAddress(); // Check for error messages (disassembly errors) at this address List<String> errorMessages = getErrorMessages(program, addr); if (!errorMessages.isEmpty()) { for (String errorMsg : errorMessages) { result.append("Error (Bad Instruction): ").append(errorMsg).append("\n"); } result.append("Changes Unsaved\n"); } // Check if this address is a function entry point and show function start marker Function funcAtAddr = program.getFunctionManager().getFunctionAt(addr); if (funcAtAddr != null) { // This is a function entry point - add start marker result.append(" ┌─ FUNCTION: "); result.append(funcAtAddr.getName()); // Add function attributes (ENTRY POINT, THUNK, etc.) List<String> attributes = new ArrayList<>(); if (funcAtAddr.isThunk()) { attributes.add("THUNK"); } if (program.getSymbolTable().getPrimarySymbol(addr) != null) { Symbol sym = program.getSymbolTable().getPrimarySymbol(addr); if (sym.isExternal()) { attributes.add("EXTERNAL"); } else if (sym.isExternalEntryPoint()) { attributes.add("ENTRY POINT"); } } if (!attributes.isEmpty()) { result.append(" (").append(String.join(", ", attributes)).append(")"); } result.append("\n"); } // Show label at this address if any (skip function labels since we show them above) Symbol primarySymbol = symbolTable.getPrimarySymbol(addr); if (primarySymbol != null && (containingFunc == null || !primarySymbol.getName().equals(containingFunc.getName())) && (funcAtAddr == null || !primarySymbol.getName().equals(funcAtAddr.getName()))) { result.append(" "); result.append(primarySymbol.getName()); // Add XREFs for labels with function names List<String> jumpRefs = collectXRefsWithFunctionNames(addr, refManager, program); // Filter only jump/conditional references jumpRefs = jumpRefs.stream() .filter(xref -> xref.contains("(j)") || xref.contains("(c)")) .collect(Collectors.toList()); if (!jumpRefs.isEmpty()) { result.append(" XREF[").append(jumpRefs.size()).append("]: "); result.append(String.join(", ", jumpRefs.stream().limit(5).collect(java.util.stream.Collectors.toList()))); } result.append("\n"); } // Mark target instruction with arrow if (isTarget) { result.append(" --> "); } else { result.append(" "); } // Get instruction bytes (only if requested) String bytesField = ""; if (includeBytes) { byte[] bytes = null; try { bytes = instruction.getBytes(); } catch (ghidra.program.model.mem.MemoryAccessException e) { // Memory access failed } StringBuilder bytesStr = new StringBuilder(); if (bytes != null) { for (byte b : bytes) { bytesStr.append(String.format("%02x ", b & 0xFF)); } } else { bytesStr.append("??"); } bytesField = String.format("%-12s", bytesStr.toString().trim()); } // Build instruction mnemonic and operands String mnemonicStr = instruction.getMnemonicString(); String enhancedOperands = buildEnhancedOperands(instruction, program, containingFunc); // Format the instruction result.append(String.format("%-10s", addr.toString())); // address if (includeBytes) { result.append(bytesField); // bytes - only if requested } result.append(String.format("%-10s", mnemonicStr)); // mnemonic result.append(enhancedOperands); // operands // Add function names for CALL instructions if (instruction.getFlowType().isCall()) { Reference[] refs = refManager.getReferencesFrom(addr); for (Reference ref : refs) { if (ref.getReferenceType().isCall()) { Function calledFunc = program.getFunctionManager().getFunctionAt(ref.getToAddress()); if (calledFunc != null) { result.append(" ").append(calledFunc.getName()); String signature = calledFunc.getSignature().toString(); if (signature != null && !signature.isEmpty()) { result.append("\n"); result.append(" "); result.append(signature); } } } } } // Add comments appendAllComments(result, listing, addr); result.append("\n"); // Show XREFs TO this address (limit to 3 for context view) if (isTarget) { ReferenceIterator xrefsTo = refManager.getReferencesTo(addr); List<String> xrefList = new ArrayList<>(); while (xrefsTo.hasNext()) { Reference ref = xrefsTo.next(); Address fromAddr = ref.getFromAddress(); if (!ref.getReferenceType().isFlow()) { Function fromFunc = program.getFunctionManager().getFunctionContaining(fromAddr); String xrefStr = fromAddr.toString(); if (fromFunc != null) { xrefStr = fromFunc.getName() + ":" + xrefStr; } xrefStr += " (" + ref.getReferenceType().getName() + ")"; xrefList.add(xrefStr); } } if (!xrefList.isEmpty() && xrefList.size() <= 3) { for (String xref : xrefList) { result.append(" XREF from: ").append(xref).append("\n"); } } else if (xrefList.size() > 3) { result.append(" XREF from: [").append(xrefList.size()).append(" references]\n"); } } // Check if this instruction marks the end of a function if (containingFunc != null) { Address funcMaxAddr = containingFunc.getBody().getMaxAddress(); // Function ends if this is the last instruction OR if this is a terminal instruction (RET, JMP, etc.) boolean isLastInstr = addr.equals(funcMaxAddr) || (instruction.getNext() != null && instruction.getNext().getAddress().compareTo(funcMaxAddr) > 0); boolean isTerminalInstr = instruction.getFlowType().isTerminal(); if (isLastInstr || (isTerminalInstr && addr.compareTo(funcMaxAddr) >= 0)) { result.append(" └─ END FUNCTION: "); result.append(containingFunc.getName()); result.append("\n"); } } } /** * Display a data item in Ghidra UI format * Handles composite types (structs/arrays) by showing components */ private void displayData(Data data, boolean isTarget, Address targetAddr, StringBuilder result, Program program, Listing listing, ReferenceManager refManager, SymbolTable symbolTable, boolean includeBytes, int before, int after) { Address addr = data.getMinAddress(); // Check for error messages (disassembly/data errors) at this address List<String> errorMessages = getErrorMessages(program, addr); if (!errorMessages.isEmpty()) { for (String errorMsg : errorMessages) { result.append("Error (Bad Instruction): ").append(errorMsg).append("\n"); } result.append("Changes Unsaved\n"); } // Check if this is a composite type (needed to decide symbol display) int numComponents = data.getNumComponents(); boolean hasComponents = numComponents > 0; // 1. Show PLATE COMMENT if present (bordered comment box) String plateComment = listing.getComment(CommentType.PLATE, addr); if (plateComment != null && !plateComment.isEmpty()) { String[] lines = plateComment.split("\n"); int maxLength = Arrays.stream(lines).mapToInt(String::length).max().orElse(60); maxLength = Math.max(maxLength, 60); result.append(" "); result.append("*".repeat(maxLength + 4)).append("\n"); for (String line : lines) { result.append(" * "); result.append(String.format("%-" + maxLength + "s", line)); result.append(" *\n"); } result.append(" "); result.append("*".repeat(maxLength + 4)).append("\n"); } // 2. Show LABEL/SYMBOL with XREFs // Only show symbols here if we're NOT expanding the composite // (When expanding, parent symbol shown above struct, field symbols shown on components) if (!hasComponents) { Symbol[] symbols = symbolTable.getSymbols(addr); if (symbols != null && symbols.length > 0) { for (Symbol symbol : symbols) { // Skip function symbols and other non-label types SymbolType symType = symbol.getSymbolType(); if (symType != SymbolType.LABEL && symType != SymbolType.GLOBAL && symType != SymbolType.LOCAL_VAR) { continue; } result.append(" "); // Add namespace if present Namespace namespace = symbol.getParentNamespace(); if (namespace != null && !namespace.isGlobal()) { result.append(namespace.getName()).append("::"); } result.append(symbol.getName()); // Show XREFs to this symbol with function names (limit to first 3) List<String> xrefList = collectXRefsWithFunctionNames(addr, refManager, program); if (!xrefList.isEmpty()) { result.append(" XREF[").append(xrefList.size()).append("]: "); // Show first 3 XREFs result.append(String.join(", ", xrefList.stream().limit(3).collect(Collectors.toList()))); if (xrefList.size() > 3) { result.append(", [more]"); } } result.append("\n"); } } } // 3. Display composite or simple data if (hasComponents) { // This is a composite type - show parent type line then components displayCompositeData(data, isTarget, targetAddr, result, program, listing, refManager, symbolTable, includeBytes, before, after); } else { // Simple data type - show single line displaySimpleData(data, isTarget, result, listing, includeBytes, refManager, program); } } /** * Display a composite data type (struct or array) with all its components * For large composites, only show a subset around the target address */ private void displayCompositeData(Data data, boolean isTarget, Address targetAddr, StringBuilder result, Program program, Listing listing, ReferenceManager refManager, SymbolTable symbolTable, boolean includeBytes, int before, int after) { Address addr = data.getMinAddress(); // Check if this is a union type DataType dataType = data.getDataType(); if (dataType instanceof Union) { displayUnionData(data, isTarget, targetAddr, result, program, listing, refManager, symbolTable, includeBytes, before, after); return; } // Show parent-level symbols with XREFs (only struct/array level, not field level) Symbol[] symbols = symbolTable.getSymbols(addr); if (symbols != null && symbols.length > 0) { for (Symbol symbol : symbols) { // Skip function symbols and non-label types SymbolType symType = symbol.getSymbolType(); if (symType != SymbolType.LABEL && symType != SymbolType.GLOBAL && symType != SymbolType.LOCAL_VAR) { continue; } // Skip field-level symbols (with . in name like "parent.field") String symbolName = symbol.getName(); if (symbolName.contains(".")) { continue; // Field symbol - will be shown on component line } result.append(" "); // Add namespace if present Namespace namespace = symbol.getParentNamespace(); if (namespace != null && !namespace.isGlobal()) { result.append(namespace.getName()).append("::"); } result.append(symbolName); // Show XREFs with function names List<String> xrefList = collectXRefsWithFunctionNames(addr, refManager, program); if (!xrefList.isEmpty()) { result.append(" XREF[").append(xrefList.size()).append("]: "); result.append(String.join(", ", xrefList.stream().limit(3).collect(Collectors.toList()))); if (xrefList.size() > 3) { result.append(", [more]"); } } result.append("\n"); } } // Show parent type line (e.g., "IntuiText", "addr[160]") if (isTarget) { result.append(" --> "); } else { result.append(" "); } // Get data type string String dataTypeStr = codeUnitFormatter.getMnemonicRepresentation(data); result.append(String.format("%-10s", addr.toString())); if (includeBytes) { result.append(String.format("%-12s", "")); // Empty bytes column for parent } result.append(dataTypeStr); // Add EOL comment if present String eolComment = listing.getComment(CommentType.EOL, addr); if (eolComment != null && !eolComment.isEmpty()) { result.append(" ; ").append(eolComment); } result.append("\n"); // Add blank line for visual separation when expanding struct/array result.append("\n"); // Display components int numComponents = data.getNumComponents(); // Determine which components to display // If targeting a specific component within the composite, show a window around it // Otherwise, show first N components int startIdx = 0; int endIdx = numComponents; final int MAX_COMPONENTS_TO_SHOW = 20; // Limit display to avoid overwhelming output if (isTarget && !addr.equals(targetAddr)) { // Target is within this composite but not at its start // Use shared lookup service to find which component contains the target DataLookupResult componentLookup = dataLookupService.lookupDataAtAddress(program, targetAddr); int targetComponentIdx = (componentLookup != null) ? componentLookup.getComponentIndex() : -1; if (targetComponentIdx >= 0) { // Show window around target component (e.g., 5 before and 5 after) startIdx = Math.max(0, targetComponentIdx - 5); endIdx = Math.min(numComponents, targetComponentIdx + 6); } else { // Couldn't find exact component, show first few endIdx = Math.min(numComponents, MAX_COMPONENTS_TO_SHOW); } } else { // Not targeting a specific component, show first N endIdx = Math.min(numComponents, MAX_COMPONENTS_TO_SHOW); } // Show indicator if we're not showing components from the beginning if (startIdx > 0) { result.append(" ... (").append(startIdx).append(" components omitted)\n"); } // Display selected components (indented) for (int i = startIdx; i < endIdx; i++) { Data component = data.getComponent(i); if (component == null) continue; // Check if this component contains the target address boolean isTargetComponent = (component.getMinAddress().compareTo(targetAddr) <= 0 && component.getMaxAddress().compareTo(targetAddr) >= 0); displayComponent(component, i, data.isArray(), isTargetComponent, result, listing, refManager, symbolTable, includeBytes, program); } // Show indicator if we're not showing components to the end if (endIdx < numComponents) { result.append(" ... (").append(numComponents - endIdx).append(" more components)\n"); } // Add blank line at end for visual separation result.append("\n"); // Show POST comment if present String postComment = listing.getComment(CommentType.POST, addr); if (postComment != null && !postComment.isEmpty()) { result.append(" ; [POST] "); result.append(postComment.replace("\n", "\n ; ")); result.append("\n"); } } /** * Display a union data type with all its member views * Each union member represents an alternative interpretation of the same memory */ private void displayUnionData(Data data, boolean isTarget, Address targetAddr, StringBuilder result, Program program, Listing listing, ReferenceManager refManager, SymbolTable symbolTable, boolean includeBytes, int before, int after) { Address addr = data.getMinAddress(); Union union = (Union) data.getDataType(); int unionSize = data.getLength(); // Calculate offset of target within union int targetOffset = (int)(targetAddr.getOffset() - addr.getOffset()); // Show parent-level symbols with XREFs Symbol[] symbols = symbolTable.getSymbols(addr); if (symbols != null && symbols.length > 0) { for (Symbol symbol : symbols) { SymbolType symType = symbol.getSymbolType(); if (symType != SymbolType.LABEL && symType != SymbolType.GLOBAL && symType != SymbolType.LOCAL_VAR) { continue; } String symbolName = symbol.getName(); if (symbolName.contains(".")) { continue; } result.append(" "); Namespace namespace = symbol.getParentNamespace(); if (namespace != null && !namespace.isGlobal()) { result.append(namespace.getName()).append("::"); } result.append(symbolName).append("\n"); // Show XREFs with function names List<String> xrefList = collectXRefsWithFunctionNames(addr, refManager, program); if (!xrefList.isEmpty()) { result.append(" XREF[").append(xrefList.size()).append("]: "); result.append(String.join(", ", xrefList.stream().limit(3).collect(Collectors.toList()))); if (xrefList.size() > 3) { result.append(", ..."); } result.append("\n"); } } } // Show union type header line result.append(" "); result.append(String.format("%-10s", addr.toString())); result.append(union.getDisplayName()); result.append(" (union, ").append(unionSize).append(" bytes)\n\n"); // Get union components (each is a different view) DataTypeComponent[] components = union.getComponents(); // Display each union member as a separate view int viewNumber = 1; for (DataTypeComponent component : components) { String memberName = component.getFieldName(); if (memberName == null || memberName.isEmpty()) { memberName = component.getDataType().getDisplayName(); } result.append(" === View ").append(viewNumber).append(": "); result.append(memberName).append(" ===\n"); DataType memberType = component.getDataType(); // Handle different member types if (memberType instanceof Structure) { // Expand struct fields displayUnionStructView(data, (Structure) memberType, addr, targetAddr, targetOffset, result, program, listing, refManager, symbolTable, includeBytes, before, after); } else if (memberType instanceof Array) { // Show array with offset notation displayUnionArrayView(data, (Array) memberType, memberName, addr, targetAddr, targetOffset, result, program, refManager, includeBytes); } else if (memberType instanceof Union) { // Nested union - show recursively (simplified) displayUnionNestedView(data, (Union) memberType, memberName, addr, targetAddr, targetOffset, result, program, refManager, includeBytes); } else { // Simple type - show single line displayUnionSimpleView(data, memberType, memberName, addr, targetAddr, targetOffset, result, program, refManager, includeBytes); } result.append("\n"); viewNumber++; } } /** * Display a struct member view within a union * Applies context window to show only fields around the target offset */ private void displayUnionStructView(Data unionData, Structure struct, Address baseAddr, Address targetAddr, int targetOffset, StringBuilder result, Program program, Listing listing, ReferenceManager refManager, SymbolTable symbolTable, boolean includeBytes, int before, int after) { DataTypeComponent[] fields = struct.getComponents(); int numFields = fields.length; // Find the target field index int targetFieldIndex = -1; for (int i = 0; i < numFields; i++) { DataTypeComponent field = fields[i]; int fieldOffset = field.getOffset(); int fieldLength = field.getLength(); if (fieldOffset <= targetOffset && targetOffset < fieldOffset + fieldLength) { targetFieldIndex = i; break; } } // If target field not found, default to first field if (targetFieldIndex < 0) { targetFieldIndex = 0; } // Calculate window around target field int startIdx = Math.max(0, targetFieldIndex - before); int endIdx = Math.min(numFields, targetFieldIndex + after + 1); // Show truncation indicator for fields before the window if (startIdx > 0) { result.append(" ... (").append(startIdx).append(" fields before)\n"); } // Display fields within the window for (int i = startIdx; i < endIdx; i++) { DataTypeComponent field = fields[i]; int fieldOffset = field.getOffset(); int fieldLength = field.getLength(); Address fieldAddr = baseAddr.add(fieldOffset); // Check if this field contains the target address boolean isTargetField = (fieldOffset <= targetOffset && targetOffset < fieldOffset + fieldLength); // Mark target field with arrow if (isTargetField) { result.append(" --> "); } else { result.append(" "); } // Show address result.append(String.format("%-10s", fieldAddr.toString())); // Show field type String fieldTypeName = field.getDataType().getDisplayName(); result.append(String.format("%-12s", fieldTypeName)); // Get field value from memory String valueStr = getFieldValue(unionData, fieldOffset, field.getDataType(), program); result.append(String.format("%-24s", valueStr)); // Show field name or offset notation String fieldName = field.getFieldName(); if (fieldName != null && !fieldName.isEmpty()) { result.append(fieldName); } else { result.append("(offset +").append(fieldOffset).append(")"); } // Show XREFs for this field address List<String> xrefList = collectXRefsWithFunctionNames(fieldAddr, refManager, program); if (!xrefList.isEmpty()) { result.append("\n XREF[").append(xrefList.size()).append("]: "); result.append(String.join(", ", xrefList.stream().limit(3).collect(Collectors.toList()))); if (xrefList.size() > 3) { result.append(", ..."); } } result.append("\n"); } // Show truncation indicator for fields after the window if (endIdx < numFields) { result.append(" ... (").append(numFields - endIdx).append(" fields after)\n"); } } /** * Display an array member view within a union */ private void displayUnionArrayView(Data unionData, Array arrayType, String memberName, Address baseAddr, Address targetAddr, int targetOffset, StringBuilder result, Program program, ReferenceManager refManager, boolean includeBytes) { int elementSize = arrayType.getElementLength(); int numElements = arrayType.getNumElements(); String elementTypeName = arrayType.getDataType().getDisplayName(); // Calculate which element contains the target int targetElement = (elementSize > 0) ? targetOffset / elementSize : 0; int offsetInElement = (elementSize > 0) ? targetOffset % elementSize : targetOffset; // For array views, show the target offset notation boolean isTarget = (targetOffset >= 0 && targetOffset < arrayType.getLength()); if (isTarget) { result.append(" --> "); } else { result.append(" "); } result.append(String.format("%-10s", targetAddr.toString())); result.append(String.format("%-12s", elementTypeName + "[" + numElements + "]")); result.append(String.format("%-24s", " (offset +" + targetOffset + ")")); // Show XREFs inherited from union base List<String> xrefList = collectXRefsWithFunctionNames(baseAddr, refManager, program); if (!xrefList.isEmpty()) { result.append("\n XREF[").append(xrefList.size()).append("]: "); result.append("(inherits from union base)"); } result.append("\n"); } /** * Display a nested union member view */ private void displayUnionNestedView(Data unionData, Union nestedUnion, String memberName, Address baseAddr, Address targetAddr, int targetOffset, StringBuilder result, Program program, ReferenceManager refManager, boolean includeBytes) { // Simplified display for nested unions boolean isTarget = (targetOffset >= 0 && targetOffset < nestedUnion.getLength()); if (isTarget) { result.append(" --> "); } else { result.append(" "); } result.append(String.format("%-10s", baseAddr.toString())); result.append(String.format("%-12s", nestedUnion.getDisplayName())); result.append("(nested union, ").append(nestedUnion.getLength()).append(" bytes)"); // Show components count result.append(" [").append(nestedUnion.getNumComponents()).append(" members]"); result.append("\n"); } /** * Display a simple type member view within a union */ private void displayUnionSimpleView(Data unionData, DataType memberType, String memberName, Address baseAddr, Address targetAddr, int targetOffset, StringBuilder result, Program program, ReferenceManager refManager, boolean includeBytes) { boolean isTarget = (targetOffset >= 0 && targetOffset < memberType.getLength()); if (isTarget) { result.append(" --> "); } else { result.append(" "); } result.append(String.format("%-10s", baseAddr.toString())); result.append(String.format("%-12s", memberType.getDisplayName())); // Get value String valueStr = getFieldValue(unionData, 0, memberType, program); result.append(String.format("%-24s", valueStr)); if (memberName != null && !memberName.isEmpty()) { result.append(memberName); } // Show XREFs List<String> xrefList = collectXRefsWithFunctionNames(baseAddr, refManager, program); if (!xrefList.isEmpty()) { result.append("\n XREF[").append(xrefList.size()).append("]: "); result.append(String.join(", ", xrefList.stream().limit(3).collect(Collectors.toList()))); if (xrefList.size() > 3) { result.append(", ..."); } } result.append("\n"); } /** * Get the value of a field from memory */ private String getFieldValue(Data data, int offset, DataType fieldType, Program program) { try { Address fieldAddr = data.getMinAddress().add(offset); int length = fieldType.getLength(); if (length <= 0 || length > 8) { return ""; } // Read bytes from memory byte[] bytes = new byte[length]; program.getMemory().getBytes(fieldAddr, bytes); // Format based on type if (length == 1) { return String.format("%02xh", bytes[0] & 0xFF); } else if (length == 2) { int value = (bytes[0] & 0xFF) | ((bytes[1] & 0xFF) << 8); return String.format("%04xh", value); } else if (length == 4) { int value = (bytes[0] & 0xFF) | ((bytes[1] & 0xFF) << 8) | ((bytes[2] & 0xFF) << 16) | ((bytes[3] & 0xFF) << 24); return String.format("%08xh", value); } else { // Just show first byte for now return String.format("%02xh", bytes[0] & 0xFF); } } catch (Exception e) { return "??"; } } /** * Display a component of a composite data type (array element or struct field) */ private void displayComponent(Data component, int index, boolean isArray, boolean isTargetComponent, StringBuilder result, Listing listing, ReferenceManager refManager, SymbolTable symbolTable, boolean includeBytes, Program program) { Address componentAddr = component.getMinAddress(); // Mark target component with arrow, otherwise indent if (isTargetComponent) { result.append(" --> "); } else { result.append(" "); } // Show address result.append(String.format("%-10s", componentAddr.toString())); // Show bytes if requested if (includeBytes) { byte[] bytes = null; try { bytes = component.getBytes(); } catch (Exception e) { // Memory access failed } StringBuilder bytesStr = new StringBuilder(); if (bytes != null) { int bytesToShow = Math.min(bytes.length, 4); for (int i = 0; i < bytesToShow; i++) { bytesStr.append(String.format("%02x ", bytes[i] & 0xFF)); } if (bytes.length > bytesToShow) { bytesStr.append("..."); } } else { bytesStr.append("??"); } result.append(String.format("%-12s", bytesStr.toString().trim())); } // Show data type String componentType = codeUnitFormatter.getMnemonicRepresentation(component); result.append(String.format("%-12s", componentType)); // Show value - get symbol at referenced address if it's a pointer/reference String valueStr = ""; // Check if this component has a reference (pointer to another address) Reference[] refs = refManager.getReferencesFrom(componentAddr); if (refs != null && refs.length > 0) { // Find primary data reference for (Reference ref : refs) { if (ref.isPrimary() && !ref.getReferenceType().isCall() && !ref.getReferenceType().isJump()) { Address toAddr = ref.getToAddress(); // Get symbol at the referenced address Symbol symbol = symbolTable.getPrimarySymbol(toAddr); if (symbol != null) { valueStr = symbol.getName(); } else { // No symbol, just show the address valueStr = component.getDefaultValueRepresentation(); } break; } } } // If no reference found, use default representation if (valueStr.isEmpty()) { try { valueStr = component.getDefaultValueRepresentation(); if (valueStr == null || valueStr.isEmpty()) { Object value = component.getValue(); if (value != null) { valueStr = value.toString(); } } } catch (Exception e) { valueStr = ""; } } // Format value with proper width (increased to accommodate symbol names) result.append(String.format("%-24s", valueStr)); // Show field name (for structs) or index (for arrays) if (isArray) { result.append("[").append(index).append("]"); } else { // Struct field - show field name separated from value String fieldName = component.getFieldName(); if (fieldName != null && !fieldName.isEmpty()) { result.append(fieldName); } } // Show XREFs for this component (with function names) List<String> xrefList = collectXRefsWithFunctionNames(componentAddr, refManager, program); if (!xrefList.isEmpty()) { result.append(" XREF[").append(xrefList.size()).append("]: "); result.append(String.join(", ", xrefList.stream().limit(3).collect(Collectors.toList()))); if (xrefList.size() > 3) { result.append(", [more]"); } } // Show pointer references (e.g., "? -> 0023051e") Reference[] referencesFrom = refManager.getReferencesFrom(componentAddr); if (referencesFrom != null && referencesFrom.length > 0) { for (Reference ref : referencesFrom) { if (ref.isPrimary() && !ref.getReferenceType().isCall() && !ref.getReferenceType().isJump()) { result.append(" ? -> ").append(ref.getToAddress().toString()); break; // Only show first primary reference } } } // Add EOL comment if present String eolComment = listing.getComment(CommentType.EOL, componentAddr); if (eolComment != null && !eolComment.isEmpty()) { result.append(" ; ").append(eolComment); } result.append("\n"); } /** * Display a simple (non-composite) data item */ private void displaySimpleData(Data data, boolean isTarget, StringBuilder result, Listing listing, boolean includeBytes, ReferenceManager refManager, Program program) { Address addr = data.getMinAddress(); // Mark target with arrow if (isTarget) { result.append(" --> "); } else { result.append(" "); } // Get data bytes (only if requested) String bytesField = ""; if (includeBytes) { byte[] bytes = null; try { bytes = data.getBytes(); } catch (Exception e) { // Memory access failed } // Format bytes (show first few bytes like Ghidra UI) StringBuilder bytesStr = new StringBuilder(); if (bytes != null) { int bytesToShow = Math.min(bytes.length, 4); // Show max 4 bytes like UI for (int i = 0; i < bytesToShow; i++) { bytesStr.append(String.format("%02x ", bytes[i] & 0xFF)); } if (bytes.length > bytesToShow) { bytesStr.append("..."); // Indicate there are more bytes } } else { bytesStr.append("??"); } bytesField = String.format("%-12s", bytesStr.toString().trim()); } // Get data type using CodeUnitFormat String dataTypeStr = codeUnitFormatter.getMnemonicRepresentation(data); // Get data value using CodeUnitFormat String valueStr = ""; try { valueStr = codeUnitFormatter.getDataValueRepresentationString(data); } catch (Exception e) { // Fallback to default representation Object value = data.getValue(); if (value != null) { valueStr = value.toString(); } } // Format the data line result.append(String.format("%-10s", addr.toString())); // address (10 chars) if (includeBytes) { result.append(bytesField); // bytes (12 chars) - only if requested } result.append(String.format("%-10s", dataTypeStr)); // data type (10 chars) result.append(" "); result.append(valueStr); // value // Show pointer references (e.g., "? -> 0023051e") Reference[] referencesFrom = refManager.getReferencesFrom(addr); if (referencesFrom != null && referencesFrom.length > 0) { for (Reference ref : referencesFrom) { if (ref.isPrimary() && !ref.getReferenceType().isCall() && !ref.getReferenceType().isJump()) { result.append(" ? -> ").append(ref.getToAddress().toString()); break; // Only show first primary reference } } } // Add comments (EOL, POST, PRE if on same line) String eolComment = listing.getComment(CommentType.EOL, addr); if (eolComment != null && !eolComment.isEmpty()) { result.append(" ; ").append(eolComment); } result.append("\n"); // Show POST COMMENT if present (on separate line below) String postComment = listing.getComment(CommentType.POST, addr); if (postComment != null && !postComment.isEmpty()) { result.append(" ; [POST] "); result.append(postComment.replace("\n", "\n ; ")); result.append("\n"); } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/HK47196/GhidraMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server