Merge remote-tracking branch 'origin/patch'

Conflicts:
	Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramBuilder.java
This commit is contained in:
Ryan Kurtz
2023-07-05 07:07:49 -04:00
21 changed files with 515 additions and 174 deletions

View File

@@ -16,6 +16,8 @@
package ghidra.app.plugin.core.debug.gui.stack.vars;
import java.awt.Window;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.concurrent.CompletableFuture;
@@ -34,6 +36,7 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.stack.vars.VariableValueRow.*;
import ghidra.app.plugin.core.debug.gui.stack.vars.VariableValueUtils.VariableEvaluator;
import ghidra.app.plugin.core.debug.stack.*;
import ghidra.app.plugin.core.debug.stack.StackUnwindWarning.CustomStackUnwindWarning;
import ghidra.app.plugin.core.debug.utils.BackgroundUtils.PluginToolExecutorService;
import ghidra.app.plugin.core.debug.utils.BackgroundUtils.PluginToolExecutorService.TaskOpt;
import ghidra.app.plugin.core.hover.AbstractConfigurableHover;
@@ -147,13 +150,13 @@ public class VariableValueHoverService extends AbstractConfigurableHover
private final VariableValueTable table;
private final PluginTool tool;
private final DebuggerCoordinates current;
private final List<String> warnings;
private final StackUnwindWarningSet warnings;
private final DebuggerStaticMappingService mappingService;
private final VariableEvaluator eval;
public TableFiller(VariableValueTable table, PluginTool tool, DebuggerCoordinates current,
VariableEvaluator eval, List<String> warnings) {
VariableEvaluator eval, StackUnwindWarningSet warnings) {
this.table = table;
this.tool = tool;
this.current = current;
@@ -205,7 +208,8 @@ public class VariableValueHoverService extends AbstractConfigurableHover
if (stUnit instanceof Instruction stIns) {
fillDefinedData(dynData);
table.add(new InstructionRow(stIns));
table.add(new WarningsRow("Instruction taken from static listing"));
table.add(new WarningsRow(
StackUnwindWarningSet.custom("Instruction taken from static listing")));
return table;
}
throw new AssertionError();
@@ -304,10 +308,8 @@ public class VariableValueHoverService extends AbstractConfigurableHover
}
Variable variable = VariableValueUtils.findStackVariable(function, stackAddress);
return executeBackground(monitor -> {
UnwoundFrame<WatchValue> frame = eval.getStackFrame(function, warnings, monitor);
if (frame == null) {
throw new UnwindException("Cannot find frame for " + function);
}
UnwoundFrame<WatchValue> frame =
eval.getStackFrame(function, warnings, monitor, true);
if (variable != null) {
return fillFrameStorage(frame, variable.getName(), variable.getDataType(),
variable.getProgram(), variable.getVariableStorage());
@@ -363,15 +365,14 @@ public class VariableValueHoverService extends AbstractConfigurableHover
return executeBackground(monitor -> {
UnwoundFrame<WatchValue> frame;
if (function == null) {
warnings.add("Instruction is not in a function. Using innermost frame.");
warnings.add(new CustomStackUnwindWarning(
"Instruction is not in a function. Using innermost frame."));
frame = VariableValueUtils.locateInnermost(tool, current);
}
else {
frame = eval.getStackFrame(function, warnings, monitor);
frame = eval.getStackFrame(function, warnings, monitor, false);
}
if (frame == null) {
warnings.add(
"Could not locate " + function + " in stack. Using innermost frame.");
return fillRegisterNoFrame(register);
}
@@ -423,11 +424,8 @@ public class VariableValueHoverService extends AbstractConfigurableHover
return executeBackground(monitor -> {
UnwoundFrame<WatchValue> frame =
VariableValueUtils.requiresFrame(program, storage, symbolStorage)
? eval.getStackFrame(function, warnings, monitor)
? eval.getStackFrame(function, warnings, monitor, true)
: eval.getGlobalsFakeFrame();
if (frame == null) {
throw new UnwindException("Cannot find frame for " + function);
}
return fillFrameStorage(frame, name, type, program, storage);
});
}
@@ -436,11 +434,8 @@ public class VariableValueHoverService extends AbstractConfigurableHover
DataType type, PcodeOp op, AddressSetView symbolStorage) {
return executeBackground(monitor -> {
UnwoundFrame<WatchValue> frame = VariableValueUtils.requiresFrame(op, symbolStorage)
? eval.getStackFrame(function, warnings, monitor)
? eval.getStackFrame(function, warnings, monitor, true)
: eval.getGlobalsFakeFrame();
if (frame == null) {
throw new UnwindException("Cannot find frame for " + function);
}
return fillFrameOp(frame, function.getProgram(), name, type, op, symbolStorage);
});
}
@@ -578,10 +573,8 @@ public class VariableValueHoverService extends AbstractConfigurableHover
public CompletableFuture<VariableValueTable> fillVariable(Variable variable) {
Function function = variable.getFunction();
return executeBackground(monitor -> {
UnwoundFrame<WatchValue> frame = eval.getStackFrame(function, warnings, monitor);
if (frame == null) {
throw new UnwindException("Cannot find frame for " + function);
}
UnwoundFrame<WatchValue> frame =
eval.getStackFrame(function, warnings, monitor, true);
return fillFrameStorage(frame, variable.getName(), variable.getDataType(),
variable.getProgram(), variable.getVariableStorage());
});
@@ -590,7 +583,7 @@ public class VariableValueHoverService extends AbstractConfigurableHover
public CompletableFuture<VariableValueTable> fillVariableValueTable(VariableValueTable table,
ProgramLocation programLocation, DebuggerCoordinates current,
FieldLocation fieldLocation, Field field, List<String> warnings) {
FieldLocation fieldLocation, Field field, StackUnwindWarningSet warnings) {
if (traceManager == null || mappingService == null || current.getPlatform() == null) {
return null;
}
@@ -630,7 +623,7 @@ public class VariableValueHoverService extends AbstractConfigurableHover
return null;
}
VariableValueTable table = new VariableValueTable();
List<String> warnings = new ArrayList<>();
StackUnwindWarningSet warnings = new StackUnwindWarningSet();
CompletableFuture<VariableValueTable> future;
try {
future = fillVariableValueTable(table, programLocation,
@@ -638,7 +631,9 @@ public class VariableValueHoverService extends AbstractConfigurableHover
}
catch (Exception e) {
table.add(new ErrorRow(e));
return createTooltipComponent("<html>" + table.toHtml());
JComponent component = createTooltipComponent("<html>" + table.toHtml());
addErrorDetailsListener(component, table);
return component;
}
if (future == null) {
return null;
@@ -650,6 +645,7 @@ public class VariableValueHoverService extends AbstractConfigurableHover
if (!(component instanceof JToolTip tooltip)) {
throw new AssertionError("Expected a JToolTip");
}
addErrorDetailsListener(component, table);
future.handleAsync((__, ex) -> {
table.remove(RowKey.STATUS);
if (ex != null) {
@@ -668,6 +664,27 @@ public class VariableValueHoverService extends AbstractConfigurableHover
return tooltip;
}
protected void addErrorDetailsListener(JComponent component, VariableValueTable table) {
component.addMouseListener(new MouseAdapter() {
private boolean isShiftDoubleClick(MouseEvent evt) {
if (evt.getClickCount() != 2) {
return false;
}
if ((evt.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) == 0) {
return false;
}
return true;
}
@Override
public void mouseClicked(MouseEvent evt) {
if (isShiftDoubleClick(evt)) {
table.reportDetails();
}
}
});
}
public void traceClosed(Trace trace) {
synchronized (cachedEvaluators) {
cachedEvaluators.keySet().removeIf(coords -> coords.getTrace() == trace);

View File

@@ -18,13 +18,11 @@ package ghidra.app.plugin.core.debug.gui.stack.vars;
import static ghidra.app.plugin.core.debug.gui.stack.vars.VariableValueRow.*;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import generic.theme.GColor;
import generic.theme.GThemeDefaults.Colors;
import ghidra.app.plugin.core.debug.stack.UnwoundFrame;
import ghidra.app.plugin.core.debug.stack.*;
import ghidra.pcode.exec.DebuggerPcodeUtils.PrettyBytes;
import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue;
import ghidra.pcode.exec.ValueLocation;
@@ -40,6 +38,7 @@ import ghidra.trace.model.listing.TraceCodeUnit;
import ghidra.trace.model.memory.*;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.util.HTMLUtilities;
import ghidra.util.Msg;
import ghidra.util.exception.InvalidInputException;
/**
@@ -61,7 +60,8 @@ public interface VariableValueRow {
* @return the HTML-styled string
*/
static String styleSimple(Object obj) {
return obj == null ? htmlFg(COLOR_ERROR, "None") : HTMLUtilities.escapeHTML(obj.toString());
return obj == null ? htmlFg(COLOR_ERROR, "None")
: HTMLUtilities.escapeHTML(obj.toString()).replace("\n", "<br>");
}
/**
@@ -566,19 +566,7 @@ public interface VariableValueRow {
* A row to display the warnings encountered while unwinding the frame used to evaluate the
* variable
*/
record WarningsRow(String warnings) implements VariableValueRow {
/**
* Create a row from the given list of warnings
*
* @param warnings the warnings
*/
public WarningsRow(List<String> warnings) {
this(warnings.stream()
.map(String::trim)
.filter(w -> !w.isBlank())
.collect(Collectors.joining("\n")));
}
record WarningsRow(StackUnwindWarningSet warnings) implements VariableValueRow {
@Override
public RowKey key() {
return RowKey.WARNINGS;
@@ -591,8 +579,8 @@ public interface VariableValueRow {
@Override
public String valueToHtml() {
String[] split = warnings.split("\n");
String formatted = Stream.of(split)
String formatted = warnings.summarize()
.stream()
.map(w -> String.format("<li>%s</li>", HTMLUtilities.escapeHTML(w)))
.collect(Collectors.joining("\n "));
return String.format("""
@@ -604,17 +592,22 @@ public interface VariableValueRow {
@Override
public String valueToSimpleString() {
return warnings;
return warnings.summarize().stream().collect(Collectors.joining("\n"));
}
@Override
public String toHtml() {
if (warnings.isBlank()) {
if (warnings.isEmpty()) {
return "";
}
return String.format("<tr><td valign='top'><b>%s</b></td><td>%s</td></tr>",
keyToHtml(), valueToHtml());
}
@Override
public void reportDetails() {
warnings.reportDetails();
}
}
/**
@@ -633,11 +626,17 @@ public interface VariableValueRow {
@Override
public String valueToHtml() {
if (error instanceof EvaluationException || error instanceof UnwindException) {
return styleSimple(error.getMessage());
}
return styleSimple(error);
}
@Override
public String valueToSimpleString() {
if (error instanceof EvaluationException || error instanceof UnwindException) {
return error.getMessage();
}
return error.toString();
}
@@ -646,5 +645,13 @@ public interface VariableValueRow {
return String.format("<tr><td valign='top'><b>%s</b></td><td>%s</td></tr>",
keyToHtml(), valueToHtml());
}
@Override
public void reportDetails() {
Msg.showError(this, null, "Details", error.getMessage(), error);
}
}
default void reportDetails() {
}
}

View File

@@ -112,4 +112,12 @@ public class VariableValueTable {
rows.remove(key);
}
}
public void reportDetails() {
synchronized (rows) {
for (VariableValueRow row : rows.values()) {
row.reportDetails();
}
}
}
}

View File

@@ -21,6 +21,7 @@ import ghidra.app.decompiler.ClangLine;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.stack.*;
import ghidra.app.plugin.core.debug.stack.StackUnwindWarning.CustomStackUnwindWarning;
import ghidra.docking.settings.Settings;
import ghidra.docking.settings.SettingsDefinition;
import ghidra.framework.plugintool.PluginTool;
@@ -71,6 +72,9 @@ public enum VariableValueUtils {
@Override
protected boolean isLeaf(Varnode vn) {
if (vn.getDef() == null && (vn.isRegister() || vn.isAddress())) {
return true;
}
return vn.isConstant() ||
symbolStorage.contains(vn.getAddress(), vn.getAddress().add(vn.getSize() - 1));
}
@@ -516,7 +520,7 @@ public enum VariableValueUtils {
* Collect the addresses used for storage by any symbol in the given line of decompiled C code
*
* <p>
* It's not the greatest, but an variable to be evaluated should only be expressed in terms of
* It's not the greatest, but any variable to be evaluated should only be expressed in terms of
* symbols on the same line (at least by the decompiler's definition, wrapping shouldn't count
* against us). This can be used to determine where evaluation should cease descending into
* defining p-code ops. See {@link #requiresFrame(PcodeOp, AddressSetView)}, and
@@ -528,16 +532,24 @@ public enum VariableValueUtils {
public static AddressSet collectSymbolStorage(ClangLine line) {
AddressSet storage = new AddressSet();
for (ClangToken tok : line.getAllTokens()) {
Varnode vn = tok.getVarnode();
if (vn != null) {
storage.add(rangeFromVarnode(vn));
}
HighVariable hVar = tok.getHighVariable();
if (hVar == null) {
continue;
}
Varnode rep = hVar.getRepresentative();
if (rep != null) {
storage.add(rangeFromVarnode(rep));
}
HighSymbol hSym = hVar.getSymbol();
if (hSym == null) {
continue;
}
for (Varnode vn : hSym.getStorage().getVarnodes()) {
storage.add(rangeFromVarnode(vn));
for (Varnode stVn : hSym.getStorage().getVarnodes()) {
storage.add(rangeFromVarnode(stVn));
}
}
return storage;
@@ -725,25 +737,54 @@ public enum VariableValueUtils {
* @param function the desired function
* @param warnings a place to emit warnings
* @param monitor a monitor for cancellation
* @param required whether to throw an exception or register a warning
* @return the frame if found, or null
*/
public UnwoundFrame<WatchValue> getStackFrame(Function function, List<String> warnings,
TaskMonitor monitor) {
public UnwoundFrame<WatchValue> getStackFrame(Function function,
StackUnwindWarningSet warnings, TaskMonitor monitor, boolean required) {
synchronized (lock) {
if (unwound == null) {
doUnwind(monitor);
try {
doUnwind(monitor);
}
catch (Exception e) {
/**
* Most exceptions should be caught and wrapped by the unwind analysis. If
* one gets here, something bad has happened, and for debugging purposes, we
* should invalidate, so that the error will repeat next time the frame is
* requested.
*/
unwound = null;
throw e;
}
}
for (UnwoundFrame<WatchValue> frame : unwound.subList(coordinates.getFrame(),
unwound.size())) {
if (frame.getFunction() == function) {
String unwindWarnings = frame.getWarnings();
if (unwindWarnings != null && !unwindWarnings.isBlank()) {
warnings.add(unwindWarnings);
StackUnwindWarningSet unwindWarnings = frame.getWarnings();
if (unwindWarnings != null) {
warnings.addAll(unwindWarnings);
}
return frame;
}
}
String message;
if (unwound.isEmpty()) {
message = "Could not recover the innermost frame!";
}
else {
message = "There is no frame for %s among the %d frames unwound."
.formatted(function, unwound.size());
Exception error = unwound.get(unwound.size() - 1).getError();
if (error != null) {
message += "\nTerminating error: %s".formatted(error.getMessage());
}
}
if (required) {
throw new UnwindException(message);
}
warnings.add(new CustomStackUnwindWarning(message));
return null;
}
}

View File

@@ -78,7 +78,10 @@ public abstract class AbstractUnwoundFrame<T> implements UnwoundFrame<T> {
protected Address translateMemory(Program program, Address address) {
TraceLocation location = mappingService.getOpenMappedLocation(trace,
new ProgramLocation(program, address), snap);
return location == null ? null : location.getAddress();
if (location == null) {
throw new DynamicMappingException(program, address);
}
return location.getAddress();
}
}
@@ -97,7 +100,10 @@ public abstract class AbstractUnwoundFrame<T> implements UnwoundFrame<T> {
protected Address translateMemory(Program program, Address address) {
TraceLocation location = mappingService.getOpenMappedLocation(trace,
new ProgramLocation(program, address), snap);
return location == null ? null : location.getAddress();
if (location == null) {
throw new DynamicMappingException(program, address);
}
return location.getAddress();
}
}
@@ -131,6 +137,9 @@ public abstract class AbstractUnwoundFrame<T> implements UnwoundFrame<T> {
@Override
protected boolean isLeaf(Varnode vn) {
if (vn.getDef() == null && (vn.isRegister() || vn.isAddress())) {
return true;
}
return vn.isConstant() ||
symbolStorage.contains(vn.getAddress(), vn.getAddress().add(vn.getSize() - 1));
}

View File

@@ -18,6 +18,7 @@ package ghidra.app.plugin.core.debug.stack;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import ghidra.app.plugin.core.bookmark.BookmarkNavigator;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
@@ -61,8 +62,8 @@ public class AnalysisUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
private final int level;
private final Address pcVal;
private final Address spVal;
private final Address staticPcVal;
private final UnwindInfo info;
private final UnwindException infoErr;
private final SavedRegisterMap registerMap;
private final Address base;
@@ -83,35 +84,29 @@ public class AnalysisUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
* is the caller's (i.e., subclass') responsibility to ensure the given state
* corresponds to the given coordinates.
* @param level the level of this frame
* @param pcVal the address of the next instruction when this frame becomes the current frame
* @param pcVal the (dynamic) address of the next instruction when this frame becomes the
* current frame
* @param spVal the address of the top of the stack when this frame becomes the current frame
* @param staticPcVal the (static) address of the next instruction
* @param info the information used to unwind this frame
* @param infoErr if applicable, an error describing why the unwind info is missing or
* incomplete
* @param registerMap a map from registers to the offsets of their saved values on the stack
*/
AnalysisUnwoundFrame(PluginTool tool, DebuggerCoordinates coordinates, StackUnwinder unwinder,
PcodeExecutorState<T> state, int level, Address pcVal, Address spVal, UnwindInfo info,
UnwindException infoErr, SavedRegisterMap registerMap) {
PcodeExecutorState<T> state, int level, Address pcVal, Address spVal,
Address staticPcVal, UnwindInfo info, SavedRegisterMap registerMap) {
super(tool, coordinates, state);
if ((info == null) == (infoErr == null)) {
throw new AssertionError();
}
this.unwinder = unwinder;
this.level = level;
this.pcVal = pcVal;
this.spVal = spVal;
this.staticPcVal = staticPcVal;
this.info = info;
this.infoErr = infoErr;
this.registerMap = registerMap;
if (info != null) {
this.base = info.computeBase(spVal);
}
else {
this.base = null;
}
this.base = info.computeBase(spVal);
}
@Override
@@ -156,8 +151,10 @@ public class AnalysisUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
@Override
protected Address applyBase(long offset) {
if (base == null) {
throw new UnwindException("Cannot compute stack address for offset " + offset,
infoErr);
throw new UnwindException(
"Cannot compute stack address for offset %d.\nFrame error: %s".formatted(offset,
info.error().getMessage()),
info.error());
}
return base.add(offset);
}
@@ -194,8 +191,11 @@ public class AnalysisUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
@Override
public String getDescription() {
return String.format("%s %s pc=%s sp=%s base=%s", level, info.function(),
pcVal.toString(false), spVal.toString(false), base.toString(false));
return String.format("%s %s pc=%s sp=%s base=%s",
level, info.function(),
pcVal == null ? null : pcVal.toString(false),
spVal == null ? null : spVal.toString(false),
base == null ? null : base.toString(false));
}
@Override
@@ -225,7 +225,7 @@ public class AnalysisUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
*/
protected Structure generateStructure(int prevParamSize) {
FrameStructureBuilder builder =
new FrameStructureBuilder(language, pcVal, info, prevParamSize);
new FrameStructureBuilder(language, staticPcVal, info, prevParamSize);
return builder.build(StackUnwinder.FRAMES_PATH, "frame_" + pcVal.toString(false),
trace.getDataTypeManager());
}
@@ -324,7 +324,7 @@ public class AnalysisUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
TraceBookmarkManager bm = trace.getBookmarkManager();
TraceBookmarkType btWarn = getWarningBookmarkType();
Lifespan span = Lifespan.nowOnMaybeScratch(viewSnap);
String warnings = info.warnings().summarize();
String warnings = info.warnings().summarize().stream().collect(Collectors.joining("\n"));
Structure structure = resolveStructure(prevParamSize);
if (structure == null || structure.isZeroLength()) {
for (TraceBookmark existing : bm.getBookmarksAt(viewSnap, spPlusParams)) {
@@ -374,27 +374,13 @@ public class AnalysisUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
return info;
}
/**
* If the unwind information is absent or incomplete, get the error explaining why.
*
* <p>
* When analysis is incomplete, the frame may still be partially unwound, meaning only certain
* variables can be evaluated, and the return address may not be available. Typically, a
* partially unwound frame is the last frame that can be recovered in the stack. If the base
* pointer could not be recovered, then only register variables and static variables can be
* evaluated.
*
* @return the error
*/
public UnwindException getError() {
return infoErr;
@Override
public StackUnwindWarningSet getWarnings() {
return info.warnings();
}
@Override
public String getWarnings() {
if (info == null) {
return "";
}
return info.warnings().summarize();
public Exception getError() {
return info.error();
}
}

View File

@@ -0,0 +1,38 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.stack;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
public class DynamicMappingException extends EvaluationException {
private final Program program;
private final Address address;
public DynamicMappingException(Program program, Address address) {
super("Cannot map %s:%s to dynamic adress".formatted(program.getName(), address));
this.program = program;
this.address = address;
}
public Program getProgram() {
return program;
}
public Address getAddress() {
return address;
}
}

View File

@@ -0,0 +1,22 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.stack;
public class EvaluationException extends RuntimeException {
public EvaluationException(String message) {
super(message);
}
}

View File

@@ -87,8 +87,13 @@ public class FakeUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
}
@Override
public String getWarnings() {
return "";
public StackUnwindWarningSet getWarnings() {
return new StackUnwindWarningSet();
}
@Override
public Exception getError() {
return null;
}
@Override

View File

@@ -16,6 +16,7 @@
package ghidra.app.plugin.core.debug.stack;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.stack.StackUnwindWarning.CustomStackUnwindWarning;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.DebuggerPcodeUtils;
import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue;
@@ -263,14 +264,28 @@ public class ListingUnwoundFrame extends AbstractUnwoundFrame<WatchValue> {
}
@Override
public String getWarnings() {
public StackUnwindWarningSet getWarnings() {
StackUnwindWarningSet warnings = new StackUnwindWarningSet();
for (TraceBookmark bookmark : frame.getTrace()
.getBookmarkManager()
.getBookmarksAt(frame.getStartSnap(), frame.getMinAddress())) {
if (bookmark.getTypeString().equals(BookmarkType.WARNING)) {
return bookmark.getComment();
if (!bookmark.getTypeString().equals(BookmarkType.WARNING)) {
continue;
}
String comment = bookmark.getComment();
if (comment == null) {
continue;
}
for (String line : comment.split("\n")) {
warnings.add(new CustomStackUnwindWarning(line));
}
}
return warnings;
}
@Override
public Exception getError() {
// TODO: Can this be deserialized from a bookmark?
return null;
}

View File

@@ -23,9 +23,8 @@ import java.util.stream.Stream;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.Function;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.PcodeOpAST;
import ghidra.program.model.pcode.VarnodeAST;
import ghidra.program.model.pcode.*;
import ghidra.util.Msg;
/**
* A warning issued while unwinding a stack
@@ -67,6 +66,12 @@ public interface StackUnwindWarning {
return false;
}
/**
* For diagnostics, report any error details indicated by this warning, usually via {@link Msg}.
*/
default void reportDetails() {
}
/**
* The unwind analyzer could not find an exit path from the frame's program counter.
*/
@@ -85,10 +90,17 @@ public interface StackUnwindWarning {
/**
* The unwind analyzer discovered at last one exit path, but none could be analyzed.
*/
public record OpaqueReturnPathStackUnwindWarning(Address pc) implements StackUnwindWarning {
public record OpaqueReturnPathStackUnwindWarning(Address pc, Exception last)
implements StackUnwindWarning {
@Override
public String getMessage() {
return "Could not analyze any path from " + pc + " to a return";
return "Could not analyze any path from " + pc + " to a return.\nLast error: " +
last.getMessage();
}
@Override
public void reportDetails() {
Msg.showError(this, null, "Details", getMessage(), last);
}
}
@@ -188,7 +200,8 @@ public interface StackUnwindWarning {
}
/**
* While analyzing an indirect call, couldn't get the function signature because its input doesn't have a high variable.
* While analyzing an indirect call, couldn't get the function signature because its input
* doesn't have a high variable.
*/
public record NoHighVariableFromTargetPointerTypeUnwindWarning(VarnodeAST vn)
implements StackUnwindWarning {
@@ -208,4 +221,15 @@ public interface StackUnwindWarning {
return "Could not recover signature of indirect call: " + op;
}
}
/**
* A custom warning, either because a specific type is too onerous, or because the message was
* deserialized and the specific type and info cannot be recovered.
*/
public record CustomStackUnwindWarning(String message) implements StackUnwindWarning {
@Override
public String getMessage() {
return message;
}
}
}

View File

@@ -19,6 +19,7 @@ import java.util.*;
import java.util.stream.Collectors;
import ghidra.app.plugin.core.debug.stack.StackUnwindWarning.Combinable;
import ghidra.app.plugin.core.debug.stack.StackUnwindWarning.CustomStackUnwindWarning;
/**
* A bucket of warnings
@@ -29,6 +30,12 @@ import ghidra.app.plugin.core.debug.stack.StackUnwindWarning.Combinable;
public class StackUnwindWarningSet implements Collection<StackUnwindWarning> {
private final Collection<StackUnwindWarning> warnings = new LinkedHashSet<>();
public static StackUnwindWarningSet custom(String message) {
StackUnwindWarningSet set = new StackUnwindWarningSet();
set.add(new CustomStackUnwindWarning(message));
return set;
}
/**
* Create a new empty set
*/
@@ -130,7 +137,7 @@ public class StackUnwindWarningSet implements Collection<StackUnwindWarning> {
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public String summarize() {
public List<String> summarize() {
Set<Class<? extends StackUnwindWarning>> combined = new LinkedHashSet<>();
List<String> lines = new ArrayList<>();
for (StackUnwindWarning w : warnings) {
@@ -155,6 +162,12 @@ public class StackUnwindWarningSet implements Collection<StackUnwindWarning> {
lines.add(w.getMessage());
}
}
return lines.stream().collect(Collectors.joining("\n"));
return lines;
}
public void reportDetails() {
for (StackUnwindWarning w : warnings) {
w.reportDetails();
}
}
}

View File

@@ -203,6 +203,9 @@ public class StackUnwinder {
return unwind(coordinates, level, pcVal, spVal, state, new SavedRegisterMap(), monitor);
}
record StaticAndUnwind(Address staticPc, UnwindInfo info) {
}
/**
* Compute the unwind information for the given program counter and context
*
@@ -217,8 +220,8 @@ public class StackUnwinder {
* @return the unwind info, possibly incomplete
* @throws CancelledException if the monitor is cancelled
*/
public UnwindInfo computeUnwindInfo(long snap, int level, Address pcVal, TaskMonitor monitor)
throws CancelledException {
public StaticAndUnwind computeUnwindInfo(long snap, int level, Address pcVal,
TaskMonitor monitor) throws CancelledException {
// TODO: Try markup in trace first?
ProgramLocation staticPcLoc = mappings == null ? null
: mappings.getOpenMappedLocation(
@@ -229,23 +232,27 @@ public class StackUnwinder {
}
Program program = staticPcLoc.getProgram();
Address staticPc = staticPcLoc.getAddress();
// TODO: Cache these?
UnwindAnalysis ua = new UnwindAnalysis(program);
return ua.computeUnwindInfo(staticPc, monitor);
try {
// TODO: Cache these?
UnwindAnalysis ua = new UnwindAnalysis(program);
return new StaticAndUnwind(staticPc, ua.computeUnwindInfo(staticPc, monitor));
}
catch (Exception e) {
return new StaticAndUnwind(staticPc, UnwindInfo.errorOnly(e));
}
}
<T> AnalysisUnwoundFrame<T> unwind(DebuggerCoordinates coordinates, int level, Address pcVal,
Address spVal, PcodeExecutorState<T> state, SavedRegisterMap registerMap,
TaskMonitor monitor)
throws CancelledException {
TaskMonitor monitor) throws CancelledException {
try {
UnwindInfo info = computeUnwindInfo(coordinates.getSnap(), level, pcVal, monitor);
StaticAndUnwind sau = computeUnwindInfo(coordinates.getSnap(), level, pcVal, monitor);
return new AnalysisUnwoundFrame<>(tool, coordinates, this, state, level, pcVal, spVal,
info, null, registerMap);
sau.staticPc, sau.info, registerMap);
}
catch (UnwindException e) {
catch (Exception e) {
return new AnalysisUnwoundFrame<>(tool, coordinates, this, state, level, pcVal, spVal,
null, e, registerMap);
null, UnwindInfo.errorOnly(e), registerMap);
}
}

View File

@@ -67,17 +67,16 @@ class SymPcodeExecutor extends PcodeExecutor<Sym> {
* @return the executor
*/
public static SymPcodeExecutor forProgram(Program program, SymPcodeExecutorState state,
Reason reason, Set<StackUnwindWarning> warnings, TaskMonitor monitor) {
Reason reason, TaskMonitor monitor) {
CompilerSpec cSpec = program.getCompilerSpec();
SleighLanguage language = (SleighLanguage) cSpec.getLanguage();
SymPcodeArithmetic arithmetic = new SymPcodeArithmetic(cSpec);
return new SymPcodeExecutor(program, cSpec, language, arithmetic, state, reason, warnings,
monitor);
return new SymPcodeExecutor(program, cSpec, language, arithmetic, state, reason, monitor);
}
private final Program program;
private final Register sp;
private final Set<StackUnwindWarning> warnings;
final Set<StackUnwindWarning> warnings;
private final TaskMonitor monitor;
private final DecompInterface decomp = new DecompInterface();
@@ -87,11 +86,11 @@ class SymPcodeExecutor extends PcodeExecutor<Sym> {
public SymPcodeExecutor(Program program, CompilerSpec cSpec, SleighLanguage language,
SymPcodeArithmetic arithmetic, SymPcodeExecutorState state, Reason reason,
Set<StackUnwindWarning> warnings, TaskMonitor monitor) {
TaskMonitor monitor) {
super(language, arithmetic, state, reason);
this.program = program;
this.sp = cSpec.getStackPointer();
this.warnings = warnings;
this.warnings = state.warnings;
this.monitor = monitor;
}
@@ -217,7 +216,7 @@ class SymPcodeExecutor extends PcodeExecutor<Sym> {
protected FunctionSignature getSignatureFromTargetPointerType(PcodeOpAST op) {
VarnodeAST target = (VarnodeAST) op.getInput(0);
HighVariable high = target.getHigh();
if (high == null) {
warnings.add(new NoHighVariableFromTargetPointerTypeUnwindWarning(target));
return null;
@@ -243,7 +242,13 @@ class SymPcodeExecutor extends PcodeExecutor<Sym> {
*/
protected FunctionSignature getSignatureFromContextAtCallSite(PcodeOpAST op) {
FunctionDefinitionDataType sig = new FunctionDefinitionDataType("__indirect");
sig.setReturnType(op.getOutput().getHigh().getDataType());
Varnode output = op.getOutput();
if (output == null) {
sig.setReturnType(VoidDataType.dataType);
}
else {
sig.setReturnType(output.getHigh().getDataType());
}
// input 0 is the target, so drop it.
int numInputs = op.getNumInputs();
Parameter[] params = new Parameter[numInputs - 1];
@@ -251,19 +256,19 @@ class SymPcodeExecutor extends PcodeExecutor<Sym> {
for (int i = 1; i < numInputs; i++) {
Varnode input = op.getInput(i);
HighVariable highVar = input.getHigh();
DataType dataType = highVar.getDataType();
try {
/**
* NOTE: Not specifying storage, since: 1) It's not germane to the function
* signature, and 2) It may require chasing use-def chains through uniques.
*/
params[i - 1] = new ParameterImpl("param_" + i, highVar.getDataType(),
params[i - 1] = new ParameterImpl("param_" + i, dataType,
/*new VariableStorage(program, input),*/ program);
}
catch (InvalidInputException e) {
throw new AssertionError(e);
}
arguments[i - 1] = new ParameterDefinitionImpl("param_" + i,
input.getHigh().getDataType(), "generated");
arguments[i - 1] = new ParameterDefinitionImpl("param_" + i, dataType, "generated");
}
sig.setArguments(arguments);
sig.setComment("generated");

View File

@@ -15,8 +15,7 @@
*/
package ghidra.app.plugin.core.debug.stack;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import ghidra.app.plugin.core.debug.stack.Sym.*;
import ghidra.app.plugin.core.debug.stack.SymStateSpace.SymEntry;
@@ -50,6 +49,8 @@ public class SymPcodeExecutorState implements PcodeExecutorState<Sym> {
private final SymStateSpace registerSpace;
private final SymStateSpace uniqueSpace;
final Set<StackUnwindWarning> warnings = new LinkedHashSet<>();
/**
* Construct a new state for the given program
*/

View File

@@ -142,7 +142,7 @@ public class UnwindAnalysis {
private final BlockGraph graph;
private final BlockVertex pcBlock;
private final DijkstraShortestPathsAlgorithm<BlockVertex, BlockEdge> pathFinder;
private final Set<StackUnwindWarning> warnings = new HashSet<>();
private final Set<StackUnwindWarning> warnings = new LinkedHashSet<>();
/**
* Begin analysis for unwinding a frame, knowing only the program counter for that frame
@@ -353,7 +353,7 @@ public class UnwindAnalysis {
public SymPcodeExecutorState executeToPc(Deque<BlockEdge> to) throws CancelledException {
SymPcodeExecutorState state = new SymPcodeExecutorState(program);
SymPcodeExecutor exec =
SymPcodeExecutor.forProgram(program, state, Reason.EXECUTE_READ, warnings, monitor);
SymPcodeExecutor.forProgram(program, state, Reason.EXECUTE_READ, monitor);
executePathTo(exec, to);
executeBlockTo(exec, pcBlock.block, pc);
return state;
@@ -375,7 +375,7 @@ public class UnwindAnalysis {
public SymPcodeExecutorState executeFromPc(SymPcodeExecutorState state,
Deque<BlockEdge> from) throws CancelledException {
SymPcodeExecutor exec =
SymPcodeExecutor.forProgram(program, state, Reason.EXECUTE_READ, warnings, monitor);
SymPcodeExecutor.forProgram(program, state, Reason.EXECUTE_READ, monitor);
executeBlockFrom(exec, pcBlock.block, pc);
executePathFrom(exec, from);
return state;
@@ -454,37 +454,76 @@ public class UnwindAnalysis {
"Could not find a path from " + function + " entry to " + pc);
}
Collection<Deque<BlockEdge>> exitsPaths = getExitsPaths();
if (exitsPaths.isEmpty()) {
warnings.add(new NoReturnPathStackUnwindWarning(pc));
}
SymPcodeExecutorState lastSuccessfulEntryState = null;
Exception lastError = null;
// TODO: Proper exceptions for useless results
for (Deque<BlockEdge> entryPath : entryPaths) {
SymPcodeExecutorState entryState = executeToPc(entryPath);
Long depth = entryState.computeStackDepth();
if (depth == null) {
SymPcodeExecutorState entryState;
try {
entryState = executeToPc(entryPath);
}
catch (Exception e) {
lastError = e;
continue;
}
if (exitsPaths.isEmpty()) {
warnings.add(new NoReturnPathStackUnwindWarning(pc));
Long depth = entryState.computeStackDepth();
if (depth == null) {
lastError = new UnwindException("Cannot determine stack depth");
continue;
}
lastSuccessfulEntryState = entryState;
Map<Register, Address> mapByEntry = entryState.computeMapUsingStack();
for (Deque<BlockEdge> exitPath : exitsPaths) {
SymPcodeExecutorState exitState =
executeFromPc(entryState.forkRegs(), exitPath);
Address addressOfReturn = exitState.computeAddressOfReturn();
Long adjust = exitState.computeStackDepth();
if (addressOfReturn == null || adjust == null) {
SymPcodeExecutorState exitState;
try {
exitState = executeFromPc(entryState.forkRegs(), exitPath);
}
catch (Exception e) {
lastError = e;
continue;
}
Address addressOfReturn = exitState.computeAddressOfReturn();
if (addressOfReturn == null) {
lastError =
new UnwindException("Cannot determine address of return pointer");
continue;
}
Long adjust = exitState.computeStackDepth();
if (adjust == null) {
lastError = new UnwindException("Cannot determine stack adjustment");
continue;
}
warnings.addAll(entryState.warnings);
warnings.addAll(exitState.warnings);
Map<Register, Address> mapByExit = exitState.computeMapUsingRegisters();
mapByExit.entrySet().retainAll(mapByEntry.entrySet());
return new UnwindInfo(function, depth, adjust, addressOfReturn, mapByExit,
new StackUnwindWarningSet(warnings));
new StackUnwindWarningSet(warnings), null);
}
warnings.add(new OpaqueReturnPathStackUnwindWarning(pc));
long adjust = SymPcodeExecutor.computeStackChange(function, warnings);
return new UnwindInfo(function, depth, adjust, null, mapByEntry,
new StackUnwindWarningSet(warnings));
}
throw new UnwindException(
"Could not analyze any path from " + function + " entry to " + pc);
if (lastSuccessfulEntryState != null) {
warnings.add(new OpaqueReturnPathStackUnwindWarning(pc, lastError));
try {
long adjust = SymPcodeExecutor.computeStackChange(function, warnings);
return new UnwindInfo(function, lastSuccessfulEntryState.computeStackDepth(),
adjust, null, lastSuccessfulEntryState.computeMapUsingStack(),
new StackUnwindWarningSet(warnings), lastError);
}
catch (Exception e) {
return new UnwindInfo(function, lastSuccessfulEntryState.computeStackDepth(),
null, null, lastSuccessfulEntryState.computeMapUsingStack(),
new StackUnwindWarningSet(warnings), e);
}
}
return new UnwindInfo(function, null, null, null, null,
new StackUnwindWarningSet(warnings), new UnwindException(
"Could not analyze any path from %s entry to %s.\n%s".formatted(function, pc,
lastError.getMessage()),
lastError));
}
}

View File

@@ -23,7 +23,7 @@ public class UnwindException extends RuntimeException {
super(message);
}
public UnwindException(String message, UnwindException cause) {
public UnwindException(String message, Exception cause) {
super(message, cause);
}
}

View File

@@ -32,8 +32,12 @@ import ghidra.util.task.TaskMonitor;
/**
* Information for interpreting the current stack frame and unwinding to the next
*/
public record UnwindInfo(Function function, long depth, long adjust, Address ofReturn,
Map<Register, Address> saved, StackUnwindWarningSet warnings) {
public record UnwindInfo(Function function, Long depth, Long adjust, Address ofReturn,
Map<Register, Address> saved, StackUnwindWarningSet warnings, Exception error) {
public static UnwindInfo errorOnly(Exception error) {
return new UnwindInfo(null, null, null, null, null, new StackUnwindWarningSet(), error);
}
/**
* The function that was analyzed
@@ -55,7 +59,7 @@ public record UnwindInfo(Function function, long depth, long adjust, Address ofR
*
* @return the depth
*/
public long depth() {
public Long depth() {
return depth;
}
@@ -67,7 +71,7 @@ public record UnwindInfo(Function function, long depth, long adjust, Address ofR
*
* @return the adjustment
*/
public long adjust() {
public Long adjust() {
return adjust;
}
@@ -139,7 +143,7 @@ public record UnwindInfo(Function function, long depth, long adjust, Address ofR
* @return the base address
*/
public Address computeBase(Address spVal) {
return spVal.subtract(depth);
return depth == null ? null : spVal.subtract(depth);
}
/**

View File

@@ -124,12 +124,23 @@ public interface UnwoundFrame<T> {
/**
* Get the warnings generated during analysis
*
* <p>
* Several warnings may be returned, each on its own line.
*
* @return the warnings
*/
String getWarnings();
StackUnwindWarningSet getWarnings();
/**
* If the unwind is in error or incomplete, get the error explaining why.
*
* <p>
* When analysis is incomplete, the frame may still be partially unwound, meaning only certain
* variables can be evaluated, and the return address may not be available. Typically, a
* partially unwound frame is the last frame that can be recovered in the stack. If the base
* pointer could not be recovered, then only register variables and static variables can be
* evaluated.
*
* @return the error
*/
Exception getError();
/**
* Get the value of the storage from the frame

View File

@@ -602,14 +602,14 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerGUITest {
UnwindInfo infoAtEntry = ua.computeUnwindInfo(entry, monitor);
assertEquals(
new UnwindInfo(function, 0, 4, stack(0), Map.of(), new StackUnwindWarningSet()),
new UnwindInfo(function, 0L, 4L, stack(0), Map.of(), new StackUnwindWarningSet(), null),
infoAtEntry);
UnwindInfo infoAtBody = ua.computeUnwindInfo(bodyInstr, monitor);
assertEquals(new UnwindInfo(function, -20, 4, stack(0),
assertEquals(new UnwindInfo(function, -20L, 4L, stack(0),
Map.of(
register("EBP"), stack(-4)),
new StackUnwindWarningSet()),
new StackUnwindWarningSet(), null),
infoAtBody);
}
@@ -629,16 +629,17 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerGUITest {
UnwindInfo infoAtEntry = ua.computeUnwindInfo(entry, monitor);
assertEquals(
new UnwindInfo(function, 0, 4, stack(0), Map.of(), new StackUnwindWarningSet()),
new UnwindInfo(function, 0L, 4L, stack(0), Map.of(), new StackUnwindWarningSet(), null),
infoAtEntry);
UnwindInfo infoAtBody = ua.computeUnwindInfo(bodyInstr, monitor);
assertEquals(new UnwindInfo(function, -20, 4, stack(0),
assertEquals(new UnwindInfo(function, -20L, 4L, stack(0),
Map.of(
register("EBP"), stack(-4)),
new StackUnwindWarningSet(
new UnspecifiedConventionStackUnwindWarning(myExtern),
new UnknownPurgeStackUnwindWarning(myExtern))),
new UnknownPurgeStackUnwindWarning(myExtern)),
null),
infoAtBody);
}
@@ -656,16 +657,17 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerGUITest {
UnwindInfo infoAtEntry = ua.computeUnwindInfo(entry, monitor);
assertEquals(
new UnwindInfo(function, 0, 4, stack(0), Map.of(), new StackUnwindWarningSet()),
new UnwindInfo(function, 0L, 4L, stack(0), Map.of(), new StackUnwindWarningSet(), null),
infoAtEntry);
UnwindInfo infoAtBody = ua.computeUnwindInfo(bodyInstr, monitor);
DataType ptr2Undef = new PointerDataType(DataType.DEFAULT, program.getDataTypeManager());
assertEquals(new UnwindInfo(function, -20, 4, stack(0),
assertEquals(new UnwindInfo(function, -20L, 4L, stack(0),
Map.of(
register("EBP"), stack(-4)),
new StackUnwindWarningSet(
new UnexpectedTargetTypeStackUnwindWarning(ptr2Undef))),
new UnexpectedTargetTypeStackUnwindWarning(ptr2Undef)),
null),
infoAtBody);
}
@@ -1163,7 +1165,7 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerGUITest {
ProgramLocation programLocation, DebuggerCoordinates current,
FieldLocation fieldLocation, Field field) throws Throwable {
VariableValueTable table = new VariableValueTable();
List<String> warnings = new ArrayList<>();
StackUnwindWarningSet warnings = new StackUnwindWarningSet();
waitOn(valuesService.fillVariableValueTable(table, programLocation, current,
fieldLocation, field, warnings));
table.add(new WarningsRow(warnings));

View File

@@ -0,0 +1,87 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.bin.format.macho.dyld;
import java.io.IOException;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.app.util.bin.format.macho.MachConstants;
import ghidra.app.util.bin.format.macho.dyld.DyldChainedPtr.DyldChainType;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
/**
* Represents a dyld_chained_starts_offsets structure.
*
* @see <a href="https://github.com/apple-oss-distributions/dyld/blob/main/include/mach-o/fixup-chains.h">mach-o/fixup-chains.h</a>
*/
public class DyldChainedStartsOffsets implements StructConverter {
private int pointerFormat;
private int startsCount;
private int[] chainStarts;
/**
* Creates a new {@link DyldChainedStartsOffsets}
*
* @param reader A {@link BinaryReader} positioned at the start of the structure
* @throws IOException if there was an IO-related problem creating the structure
*/
public DyldChainedStartsOffsets(BinaryReader reader) throws IOException {
pointerFormat = reader.readNextInt();
startsCount = reader.readNextInt();
chainStarts = reader.readNextIntArray(startsCount);
}
/**
* Gets the pointer format
*
* @return The pointer format
*/
public DyldChainType getPointerFormat() {
return DyldChainType.lookupChainPtr(pointerFormat);
}
/**
* Gets the starts count
*
* @return The starts count
*/
public int getStartsCount() {
return startsCount;
}
/**
* Gets the chain start offsets
*
* @return The chain start offsets
*/
public int[] getChainStartOffsets() {
return chainStarts;
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
StructureDataType struct = new StructureDataType("dyld_chained_starts_offset", 0);
struct.add(DWORD, "pointer_format", "DYLD_CHAINED_PTR_*");
struct.add(DWORD, "starts_count", "number of starts in array");
struct.add(new ArrayDataType(IBO32, startsCount, 1), "chain_starts",
"array chain start offsets");
struct.setCategoryPath(new CategoryPath(MachConstants.DATA_TYPE_CATEGORY));
return struct;
}
}