Merge remote-tracking branch 'origin/GP-6904_d-millar_unwinder_tests--SQUASHED'

This commit is contained in:
Ryan Kurtz
2026-06-16 05:13:00 -04:00
5 changed files with 1346 additions and 29 deletions

View File

@@ -85,9 +85,6 @@ public class DebuggerTrackLocationTrait {
if (!value.getEntryKey().equals(TraceStackFrame.KEY_PC)) {
return;
}
if (!value.getEntryKey().equals(TraceStackFrame.KEY_SP)) {
return;
}
TraceStackFrame frame = value.getParent().queryInterface(TraceStackFrame.class);
if (frame == null) {
return;
@@ -107,9 +104,6 @@ public class DebuggerTrackLocationTrait {
if (!value.getEntryKey().equals(TraceStackFrame.KEY_PC)) {
return;
}
if (!value.getEntryKey().equals(TraceStackFrame.KEY_SP)) {
return;
}
TraceStackFrame frame = value.getParent().queryInterface(TraceStackFrame.class);
if (frame == null) {
return;

View File

@@ -819,12 +819,23 @@ public enum VariableValueUtils {
public UnwoundFrame<WatchValue> getStackFrame(Function function,
StackUnwindWarningSet warnings, TaskMonitor monitor, boolean required) {
synchronized (lock) {
// We unwind first from the current frame, because we want the closest function
// match above the current frame.
// NB: Unwinding from 0 may provide better info
AnalysisUnwoundFrame<WatchValue> currentFrame =
unwinder.findMatchForFunction(function, coordinates, warnings, monitor);
if (currentFrame != null) {
return currentFrame;
}
// Unwind from 0 for functions below the current frame
currentFrame = unwinder.findMatchForFunction(function, coordinates.frame(0),
warnings, monitor);
if (currentFrame != null) {
warnings.add(new CustomStackUnwindWarning("Unwinding from frame 0"));
return currentFrame;
}
warnings.add(
new CustomStackUnwindWarning("Failed to find match for %s among the %d frames."
.formatted(function, unwinder.getRecoveredFrameCount())));

View File

@@ -195,26 +195,19 @@ public class StackUnwinder {
continue;
}
Address pcVal = pcOrSp(frame, coord, state, true);
Address pcVal = pcOrSp(frame, coord, warnings, state, true);
if (pcVal == null) {
// True if not frame 0 and no access to regs or stack
break;
}
ProgramLocation loc = getProgramLocation(coord.getSnap(), pcVal);
if (loc != null && service != null) {
UnwindInfo info =
service.getUnwindInfo(loc.getProgram(), loc.getAddress(), monitor);
if (info == null) {
// Continue here to generate a frame and prevent recalculating info
try {
StaticAndUnwind sau = computeUnwindInfo(coord.getSnap(), pcVal, monitor);
info = sau.info();
}
catch (CancelledException e) {
warnings.add(
new CustomStackUnwindWarning("Unwind cancelled for frame " + level));
}
}
// Created UnwindInfo if it doesn't exist
service.getUnwindInfo(loc.getProgram(), loc.getAddress(), monitor);
}
Address spVal = pcOrSp(frame, coord, state, false);
Address spVal = pcOrSp(frame, coord, warnings, state, false);
SavedRegisterMap nextRegisterMap = updateMap(frame, registerMap);
frame = unwind(coord, pcVal, spVal, state, nextRegisterMap, monitor);
@@ -243,7 +236,7 @@ public class StackUnwinder {
}
private Address pcOrSp(AnalysisUnwoundFrame<WatchValue> frame,
DebuggerCoordinates coordinates,
DebuggerCoordinates coordinates, StackUnwindWarningSet warnings,
PcodeExecutorState<?> state, boolean getPc) {
TraceThread thread = coordinates.getThread();
int level = coordinates.getFrame();
@@ -284,20 +277,30 @@ public class StackUnwinder {
UnwindInfo prevInfo = frame.getUnwindInfo();
Address base = frame.getBasePointer();
try {
regVal = getPc ? prevInfo.computeNextPc(base, state, codeSpace, pc)
: prevInfo.computeNextSp(base);
if (regVal != null) {
return regVal;
if (prevInfo.ofReturn() == null) {
warnings.add(new CustomStackUnwindWarning(
"Indeterminate return from frame preceding " + level));
}
else {
regVal = getPc ? prevInfo.computeNextPc(base, state, codeSpace, pc)
: prevInfo.computeNextSp(base);
if (regVal != null) {
return regVal;
}
}
}
catch (Exception e) {
// Use the fall-back case below
warnings.add(new CustomStackUnwindWarning(e.getMessage()));
}
}
// Fall-back to current frame
RegisterValue rval = state.inspectRegisterValue(getPc ? pc : sp);
return codeSpace.getAddress(rval.getUnsignedValue().longValue());
if (coordinates.getFrame() == 0) {
RegisterValue rval = state.inspectRegisterValue(getPc ? pc : sp);
return codeSpace.getAddress(rval.getUnsignedValue().longValue());
}
return null;
}
record StaticAndUnwind(Address staticPc, UnwindInfo info) {}

View File

@@ -0,0 +1,648 @@
/* ###
* 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 agent.dbgeng.rmi;
import static org.junit.Assert.*;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.net.*;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import db.Transaction;
import docking.widgets.fieldpanel.Layout;
import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.listener.IndexMapper;
import docking.widgets.fieldpanel.listener.LayoutModelListener;
import docking.widgets.fieldpanel.support.FieldLocation;
import generic.test.category.NightlyCategory;
import ghidra.app.decompiler.*;
import ghidra.app.decompiler.component.*;
import ghidra.app.decompiler.location.DefaultDecompilerLocation;
import ghidra.app.plugin.assembler.AssemblySelector;
import ghidra.app.plugin.assembler.AssemblySemanticException;
import ghidra.app.plugin.assembler.sleigh.sem.*;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.stack.vars.*;
import ghidra.app.plugin.core.debug.gui.stack.vars.VariableValueRow.*;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
import ghidra.app.plugin.core.debug.stack.StackUnwindWarningSet;
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
import ghidra.app.plugin.core.decompile.DecompilePlugin;
import ghidra.app.plugin.core.decompile.DecompilerProvider;
import ghidra.app.plugin.core.disassembler.DisassemblerPlugin;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.app.util.viewer.field.FieldFactory;
import ghidra.app.util.viewer.field.ListingField;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainFolder;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.program.util.*;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.database.module.DBTraceStaticMappingManager;
import ghidra.trace.model.Trace;
import ghidra.trace.model.modules.TraceStaticMapping;
import ghidra.util.InvalidNameException;
import ghidra.util.NumericUtilities;
import ghidra.util.exception.CancelledException;
import junit.framework.AssertionFailedError;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class DbgEngStackUnwindTest extends AbstractDbgEngTraceRmiTest {
Address stackRefInstr;
protected List<Program> programs;
CodeBrowserPlugin codeBrowserPlugin;
ListingPanel staticListing;
DebuggerListingPlugin listingPlugin;
VariableValueHoverPlugin valuesPlugin;
ListingPanel dynamicListing;
VariableValueHoverService valuesService;
DebuggerStaticMappingService mappingService;
DecompilerProvider decompilerProvider;
DecompilerPanel decompilerPanel;
record PythonAndTrace(PythonAndConnection conn, ManagedDomainObject mdo)
implements AutoCloseable {
public void execute(String cmd) {
conn.execute(cmd);
}
public String executeCapture(String cmd) {
return conn.executeCapture(cmd);
}
@Override
public void close() throws Exception {
Exception toThrow = null;
try {
conn.close();
}
catch (Exception e) {
toThrow = e;
}
try {
mdo.close();
}
catch (Exception e) {
toThrow = e;
}
if (toThrow != null) {
throw toThrow;
}
}
}
@SuppressWarnings("resource")
protected PythonAndTrace startAndSyncPython(ManagedDomainObject mdo) throws Exception {
PythonAndConnection conn = startAndConnectPython();
try {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
return new PythonAndTrace(conn, mdo);
}
catch (Exception e) {
conn.close();
throw e;
}
}
protected long lastSnap(PythonAndTrace conn) {
return conn.conn.connection().getLastSnapshot(tb.trace);
}
@Test
public void testBasicUnwind() throws Throwable {
List<String> files = Arrays.asList("kernel32.dll", "expSpin.exe", "ntdll.dll");
addPlugins();
DomainFile[] dfs = getDomainFiles(files);
tool.acceptDomainFiles(dfs);
DomainFile traceDF = getDomainFile("expSpin_dbgeng");
ManagedDomainObject mdo = new ManagedDomainObject(traceDF, false, false, monitor);
resyncMappings((Trace) mdo.get());
tool.acceptDomainFiles(new DomainFile[] { traceDF });
programs = openPrograms(dfs);
try (PythonAndTrace conn = startAndSyncPython(mdo)) {
traceManager.activateSnap(2);
waitForPass(() -> assertEquals(Boolean.TRUE,
traceManager.getCurrent().getSnap() == 2));
checkRtlUserThreadStartUnwindFrom0(2);
checkMainUnwindFrom0(1);
checkSleepUnwindFrom0(0);
}
}
@Test
public void testUnwindActivateMidStack() throws Throwable {
List<String> files = Arrays.asList("kernel32.dll", "expSpin.exe", "ntdll.dll");
addPlugins();
DomainFile[] dfs = getDomainFiles(files);
tool.acceptDomainFiles(dfs);
DomainFile traceDF = getDomainFile("expSpin_dbgeng");
ManagedDomainObject mdo = new ManagedDomainObject(traceDF, false, false, monitor);
resyncMappings((Trace) mdo.get());
tool.acceptDomainFiles(new DomainFile[] { traceDF });
programs = openPrograms(dfs);
try (PythonAndTrace conn = startAndSyncPython(mdo)) {
traceManager.activateSnap(2);
waitForPass(() -> assertEquals(Boolean.TRUE,
traceManager.getCurrent().getSnap() == 2));
traceManager.activateFrame(1);
checkRtlUserThreadStartUnwindAlt(2);
checkMainUnwindFrom0(1);
checkSleepUnwindFrom0(0);
}
}
@Test
public void testUnwindActivateHighFrame() throws Throwable {
List<String> files = Arrays.asList("kernel32.dll", "expSpin.exe", "ntdll.dll");
addPlugins();
DomainFile[] dfs = getDomainFiles(files);
tool.acceptDomainFiles(dfs);
DomainFile traceDF = getDomainFile("expSpin_dbgeng");
ManagedDomainObject mdo = new ManagedDomainObject(traceDF, false, false, monitor);
resyncMappings((Trace) mdo.get());
tool.acceptDomainFiles(new DomainFile[] { traceDF });
programs = openPrograms(dfs);
try (PythonAndTrace conn = startAndSyncPython(mdo)) {
traceManager.activateSnap(2);
waitForPass(() -> assertEquals(Boolean.TRUE,
traceManager.getCurrent().getSnap() == 2));
traceManager.activateFrame(4);
checkRtlUserThreadStartUnwindAlt(2);
checkMainUnwindFrom0(1);
checkSleepUnwindFrom0(0);
}
}
@Test
public void testBasicUnwindMissingLowFrame() throws Throwable {
List<String> files = Arrays.asList("expSpin.exe", "ntdll.dll");
addPlugins();
DomainFile[] dfs = getDomainFiles(files);
tool.acceptDomainFiles(dfs);
DomainFile traceDF = getDomainFile("expSpin_dbgeng");
ManagedDomainObject mdo = new ManagedDomainObject(traceDF, false, false, monitor);
resyncMappings((Trace) mdo.get());
tool.acceptDomainFiles(new DomainFile[] { traceDF });
programs = openPrograms(dfs);
try (PythonAndTrace conn = startAndSyncPython(mdo)) {
traceManager.activateSnap(2);
waitForPass(() -> assertEquals(Boolean.TRUE,
traceManager.getCurrent().getSnap() == 2));
checkRtlUserThreadStartUnwindAlt(1);
checkMainUnwindFrom0(0);
}
}
@Test
public void testBasicUnwindMissingMedFrame() throws Throwable {
List<String> files = Arrays.asList("kernel32.dll", "ntdll.dll");
addPlugins();
DomainFile[] dfs = getDomainFiles(files);
tool.acceptDomainFiles(dfs);
DomainFile traceDF = getDomainFile("expSpin_dbgeng");
ManagedDomainObject mdo = new ManagedDomainObject(traceDF, false, false, monitor);
resyncMappings((Trace) mdo.get());
tool.acceptDomainFiles(new DomainFile[] { traceDF });
programs = openPrograms(dfs);
try (PythonAndTrace conn = startAndSyncPython(mdo)) {
traceManager.activateSnap(2);
waitForPass(() -> assertEquals(Boolean.TRUE,
traceManager.getCurrent().getSnap() == 2));
checkRtlUserThreadStartUnwindAlt(1);
checkSleepUnwindFrom0(0);
}
}
@Test
public void testBasicUnwindMissingHighFrame() throws Throwable {
List<String> files = Arrays.asList("kernel32.dll", "expSpin.exe");
addPlugins();
DomainFile[] dfs = getDomainFiles(files);
tool.acceptDomainFiles(dfs);
DomainFile traceDF = getDomainFile("expSpin_dbgeng");
ManagedDomainObject mdo = new ManagedDomainObject(traceDF, false, false, monitor);
resyncMappings((Trace) mdo.get());
tool.acceptDomainFiles(new DomainFile[] { traceDF });
programs = openPrograms(dfs);
try (PythonAndTrace conn = startAndSyncPython(mdo)) {
traceManager.activateSnap(2);
waitForPass(() -> assertEquals(Boolean.TRUE,
traceManager.getCurrent().getSnap() == 2));
checkMainUnwindFrom0(1);
checkSleepUnwindFrom0(0);
}
}
private void checkSleepUnwindFrom0(int at) throws Throwable {
programManager.openProgram(programs.get(at));
VariableValueTable table =
getTable(programs.get(at), 0x18001b100L, "dwMilliseconds", "Sleep(dwMilliseconds);");
assertTable(Map.of(
RowKey.NAME, "Name: dwMilliseconds",
RowKey.FRAME,
"Frame: 0 Sleep pc=7ff909fab100 sp=be6c2ff798 base=be6c2ff798",
RowKey.STORAGE, "Storage: ECX:4",
RowKey.TYPE, "Type: DWORD",
RowKey.LOCATION, "Location: ECX:4",
RowKey.INTEGER, "Integer: (UNKNOWN) 1000, 0x3e8",
RowKey.VALUE, "Value: (UNKNOWN) 3E8h",
RowKey.WARNINGS, "IGNORED"), table);
}
private void checkMainUnwindFrom0(int at) throws Throwable {
programManager.openProgram(programs.get(at));
VariableValueTable table =
getTable(programs.get(at), 0x140001000L, "local_58",
"for (local_58 = 0; local_58 < 10; local_58 = local_58 + 1) {");
assertTable(Map.of(
RowKey.NAME, "Name: local_58",
RowKey.FRAME,
"Frame: 1 FUN_140001000 pc=7ff7f73c1040 sp=be6c2ff7a0 base=be6c2ff818",
RowKey.STORAGE, "Storage: Stack[-0x58]:4",
RowKey.TYPE, "Type: undefined4",
RowKey.LOCATION, "Location: be6c2ff7c0:4",
RowKey.BYTES, "Bytes: (UNKNOWN) 00 00 00 00",
RowKey.INTEGER, "Integer: (UNKNOWN) 0",
RowKey.VALUE, "Value: (UNKNOWN) 00000000h",
RowKey.WARNINGS, "IGNORED"), table);
}
private void checkRtlUserThreadStartUnwindFrom0(int at) throws Throwable {
programManager.openProgram(programs.get(at));
VariableValueTable table =
getTable(programs.get(at), 0x18007edd0L, "uVar1", "uVar1 = (*param_1)(param_2);");
assertTable(Map.of(
RowKey.NAME, "Name: uVar1",
RowKey.FRAME,
"Frame: 4 RtlUserThreadStart pc=7ff90adfedfb sp=be6c2ff890 base=be6c2ff908",
RowKey.STORAGE, "Storage: EAX:4",
RowKey.TYPE, "Type: undefined4",
RowKey.LOCATION, "Location: EAX:4",
RowKey.INTEGER, "Integer: (UNKNOWN) 3543813394, 0xd33a4d12\n-751153902, -0x2cc5b2ee",
RowKey.VALUE, "Value: (UNKNOWN) D33A4D12h",
RowKey.WARNINGS, "IGNORED"), table);
}
private void checkRtlUserThreadStartUnwindAlt(int at) throws Throwable {
programManager.openProgram(programs.get(at));
VariableValueTable table =
getTable(programs.get(at), 0x18007edd0L, "uVar1", "uVar1 = (*param_1)(param_2);");
assertTable(Map.of(
RowKey.NAME, "Name: uVar1",
RowKey.FRAME,
"Frame: 4 RtlUserThreadStart pc=7ff90adfedfb sp=be6c2ff890 base=be6c2ff908",
RowKey.STORAGE, "Storage: EAX:4",
RowKey.TYPE, "Type: undefined4",
RowKey.LOCATION, "Location: EAX:4",
RowKey.INTEGER, "Integer: (UNKNOWN) 0",
RowKey.VALUE, "Value: (UNKNOWN) 00000000h",
RowKey.WARNINGS, "IGNORED"), table);
}
private VariableValueTable getTable(Program p, long addr, String tokText, String fieldText)
throws Throwable {
Address entry = addr(p, addr);
ProgramLocation pl = new ProgramLocation(p, entry);
goTo(staticListing, pl);
Function f = p.getFunctionManager().getFunctionAt(entry);
HoverLocation loc = findTokenLocation(decompilerPanel, f, tokText, fieldText);
return getVariableValueTable(loc.pLoc, traceManager.getCurrent(), loc.fLoc, loc.field);
}
private DomainFile[] getDomainFiles(List<String> files)
throws InvalidNameException, CancelledException, IOException {
DomainFile[] dfs = new DomainFile[files.size()];
int i = 0;
for (String fname : files) {
dfs[i++] = getDomainFile(fname);
}
return dfs;
}
private DomainFile getDomainFile(String fname)
throws InvalidNameException, CancelledException, IOException {
DomainFolder rootFolder = tool.getProject()
.getProjectData()
.getRootFolder();
File f = getTestDataFile(fname + ".gzf");
return rootFolder.createFile(fname, f, monitor);
}
private List<Program> openPrograms(DomainFile[] files) {
List<Program> progs = new ArrayList<>();
for (int i = 0; i < files.length; i++) {
progs.add(programManager.openProgram(files[i]));
}
return progs;
}
private void resyncMappings(Trace trace)
throws MalformedURLException, URISyntaxException {
DomainFolder rootFolder = tool.getProject()
.getProjectData()
.getRootFolder();
DBTraceStaticMappingManager staticMappingManager =
(DBTraceStaticMappingManager) trace.getStaticMappingManager();
Collection<? extends TraceStaticMapping> allEntries =
staticMappingManager.getAllEntries();
Collection<TraceStaticMapping> newEntries = new HashSet<TraceStaticMapping>();
for (TraceStaticMapping mapping : allEntries) {
newEntries.add(mapping);
}
try (Transaction tx = trace.openTransaction("Remove .text mapping")) {
for (TraceStaticMapping mapping : newEntries) {
mapping.delete();
}
}
for (TraceStaticMapping mapping : newEntries) {
String previousRoot = mapping.getStaticProgramURL().toString();
String fileName = previousRoot.substring(previousRoot.lastIndexOf("?/") + 2);
URL localProjectURL = rootFolder.getLocalProjectURL();
URL url = new URI(localProjectURL + fileName).toURL();
try (Transaction tx = trace.openTransaction("Remove .text mapping")) {
staticMappingManager.add(mapping.getTraceAddressRange(), mapping.getLifespan(),
url, mapping.getStaticAddress());
}
}
}
protected void addPlugins() throws Throwable {
codeBrowserPlugin = addPlugin(tool, CodeBrowserPlugin.class);
staticListing = codeBrowserPlugin.getProvider().getListingPanel();
listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class);
mappingService = addPlugin(tool, DebuggerStaticMappingServicePlugin.class);
dynamicListing = listingPlugin.getProvider().getListingPanel();
//addPlugin(tool, DebuggerControlPlugin.class);
//addPlugin(tool, DebuggerStaticMappingPlugin.class);
//addPlugin(tool, DebuggerModelPlugin.class);
addPlugin(tool, DisassemblerPlugin.class);
addPlugin(tool, DecompilePlugin.class);
valuesService = valuesPlugin.getHoverService();
decompilerProvider = waitForComponentProvider(DecompilerProvider.class);
decompilerPanel = decompilerProvider.getDecompilerPanel();
tool.showComponentProvider(decompilerProvider, true);
}
public record HoverLocation(ProgramLocation pLoc, FieldLocation fLoc, Field field,
ClangToken token) {}
public static <T extends ProgramLocation> HoverLocation findLocation(ListingPanel panel,
Address address, Class<T> locType, Predicate<T> predicate) {
Layout layout = panel.getLayout(address);
int numFields = layout.getNumFields();
for (int i = 0; i < numFields; i++) {
Field field = layout.getField(i);
if (!(field instanceof ListingField listingField)) {
continue;
}
FieldFactory factory = listingField.getFieldFactory();
int numRows = field.getNumRows();
for (int r = 0; r < numRows; r++) {
int numCols = field.getNumCols(r);
for (int c = 0; c < numCols; c++) {
ProgramLocation loc = factory.getProgramLocation(r, c, listingField);
if (!locType.isInstance(loc)) {
continue;
}
if (!predicate.test(locType.cast(loc))) {
continue;
}
return new HoverLocation(loc, new FieldLocation(0, i, r, c), field, null);
}
}
}
return null;
}
public static HoverLocation findVariableLocation(ListingPanel panel, Function function,
String name) {
return findLocation(panel, function.getEntryPoint(), VariableLocation.class,
varLoc -> name.equals(varLoc.getVariable().getName()));
}
public static HoverLocation findOperandLocation(ListingPanel panel, Instruction ins,
Object operand) {
return findLocation(panel, ins.getAddress(), OperandFieldLocation.class, opLoc -> {
int subIdx = opLoc.getSubOperandIndex();
if (subIdx == -1) {
return false;
}
return operand.equals(
ins.getDefaultOperandRepresentationList(opLoc.getOperandIndex()).get(subIdx));
});
}
public static HoverLocation findTokenLocation(DecompilerPanel decompilerPanel,
Function function, String tokText, String fieldText) {
DecompileResults results = waitForValue(() -> {
ProgramLocation pLoc;
try {
pLoc = decompilerPanel.getCurrentLocation();
}
catch (NullPointerException e) {
/**
* HACK: There's an unlikely race condition where the layout controller has created
* the array of layouts but not fully populated it by the time we ask for the
* current location. This may cause a line we inspect to still have null in it and
* throw an NPE. Whatever. Just catch the thing and return null so that we try
* again. As far as I can tell, this is not indicative of a problem in production,
* because the controller won't issue an updated event until that array is fully
* populated.
*/
return null;
}
if (!(pLoc instanceof DecompilerLocation dLoc)) {
return null;
}
DecompileResults dr = dLoc.getDecompile();
if (dr == null || dr.getFunction() != function) {
return null;
}
return dr;
});
return runSwing(() -> {
Program program = function.getProgram();
ClangLayoutController layoutController = decompilerPanel.getLayoutController();
BigInteger numIndexes = layoutController.getNumIndexes();
for (BigInteger i = BigInteger.ZERO; i.compareTo(numIndexes) < 0; i =
i.add(BigInteger.ONE)) {
Layout layout = layoutController.getLayout(i);
int numFields = layout.getNumFields();
for (int j = 0; j < numFields; j++) {
Field field = layout.getField(j);
if (!(field instanceof ClangTextField clangField)) {
continue;
}
if (!fieldText.equals(field.getText().trim())) {
continue;
}
int numRows = field.getNumRows();
for (int r = 0; r < numRows; r++) {
int numCols = field.getNumCols(r);
for (int c = 0; c < numCols; c++) {
FieldLocation fLoc = new FieldLocation(i, j, r, c);
ClangToken token = clangField.getToken(fLoc);
if (token != null && tokText.equals(token.getText())) {
Address entryPoint = function.getEntryPoint();
DecompilerLocationInfo info =
new DecompilerLocationInfo(entryPoint, results, token,
i.intValue(), 0);
DefaultDecompilerLocation loc = token.getMinAddress() == null ? null
: new DefaultDecompilerLocation(program,
token.getMinAddress(), info);
return new HoverLocation(loc, fLoc, field, token);
}
}
}
}
}
return null;
});
}
protected HoverLocation findTokenLocation(Function function, String tokText, String fieldText)
throws Throwable {
CompletableFuture<Void> ready = new CompletableFuture<>();
decompilerPanel.getLayoutController().addLayoutModelListener(new LayoutModelListener() {
@Override
public void modelSizeChanged(IndexMapper indexMapper) {
if (decompilerPanel.getCurrentLocation() != null) {
ready.complete(null);
}
}
@Override
public void dataChanged(BigInteger start, BigInteger end) {
}
});
tool.showComponentProvider(decompilerProvider, true);
ready.get(5, TimeUnit.SECONDS);
try {
return findTokenLocation(decompilerPanel, function, tokText, fieldText);
}
catch (AssertionFailedError e) {
throw e;
}
}
protected static void assertTable(Map<RowKey, String> texts, VariableValueTable table) {
ErrorRow error = (ErrorRow) table.get(RowKey.ERROR);
if (error != null && !texts.containsKey(RowKey.ERROR)) {
throw new AssertionError("ErrorRow present", error.error());
}
for (Map.Entry<RowKey, String> ent : texts.entrySet()) {
RowKey key = ent.getKey();
VariableValueRow row = table.get(key);
assertNotNull("Missing " + key, row);
if (key != RowKey.WARNINGS) {
assertEquals(ent.getValue(), row.toSimpleString());
}
}
assertEquals(texts.size(), table.getNumRows());
}
protected VariableValueTable getVariableValueTable(ProgramLocation programLocation,
DebuggerCoordinates current, FieldLocation fieldLocation, Field field)
throws Throwable {
VariableValueTable table = new VariableValueTable();
StackUnwindWarningSet warnings = new StackUnwindWarningSet();
waitOn(valuesService.fillVariableValueTable(table, programLocation, current, fieldLocation,
field, warnings));
table.add(new WarningsRow(warnings));
return table;
}
public static final AssemblySelector NO_16BIT_CALLS = new AssemblySelector() {
@Override
public Selection select(AssemblyResolutionResults rr, AssemblyPatternBlock ctx)
throws AssemblySemanticException {
for (AssemblyResolvedPatterns res : filterCompatibleAndSort(rr, ctx)) {
byte[] ins = res.getInstruction().getVals();
// HACK to avoid 16-bit CALL.... TODO: Why does this happen?
if (ins.length >= 2 && ins[0] == (byte) 0x66 && ins[1] == (byte) 0xe8) {
System.err.println(
"Filtered 16-bit call " + NumericUtilities.convertBytesToString(ins));
continue;
}
return new Selection(res.getInstruction().fillMask(), res.getContext());
}
throw new AssemblySemanticException(semanticErrors);
}
};
}

View File

@@ -0,0 +1,661 @@
/* ###
* 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 agent.lldb.rmi;
import static org.junit.Assert.*;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.net.*;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import db.Transaction;
import docking.widgets.fieldpanel.Layout;
import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.listener.IndexMapper;
import docking.widgets.fieldpanel.listener.LayoutModelListener;
import docking.widgets.fieldpanel.support.FieldLocation;
import generic.test.category.NightlyCategory;
import ghidra.app.decompiler.*;
import ghidra.app.decompiler.component.*;
import ghidra.app.decompiler.location.DefaultDecompilerLocation;
import ghidra.app.plugin.assembler.AssemblySelector;
import ghidra.app.plugin.assembler.AssemblySemanticException;
import ghidra.app.plugin.assembler.sleigh.sem.*;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.stack.vars.*;
import ghidra.app.plugin.core.debug.gui.stack.vars.VariableValueRow.*;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
import ghidra.app.plugin.core.debug.stack.StackUnwindWarningSet;
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
import ghidra.app.plugin.core.decompile.DecompilePlugin;
import ghidra.app.plugin.core.decompile.DecompilerProvider;
import ghidra.app.plugin.core.disassembler.DisassemblerPlugin;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.app.util.viewer.field.FieldFactory;
import ghidra.app.util.viewer.field.ListingField;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainFolder;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.program.util.*;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.database.module.DBTraceStaticMappingManager;
import ghidra.trace.model.Trace;
import ghidra.trace.model.modules.TraceStaticMapping;
import ghidra.util.InvalidNameException;
import ghidra.util.NumericUtilities;
import ghidra.util.exception.CancelledException;
import junit.framework.AssertionFailedError;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class LldbStackUnwindTest extends AbstractLldbTraceRmiTest {
Address stackRefInstr;
protected List<Program> programs;
CodeBrowserPlugin codeBrowserPlugin;
ListingPanel staticListing;
DebuggerListingPlugin listingPlugin;
VariableValueHoverPlugin valuesPlugin;
ListingPanel dynamicListing;
VariableValueHoverService valuesService;
DebuggerStaticMappingService mappingService;
DecompilerProvider decompilerProvider;
DecompilerPanel decompilerPanel;
record LldbAndTrace(LldbAndConnection conn, ManagedDomainObject mdo) implements AutoCloseable {
public void execute(String cmd) {
conn.execute(cmd);
}
public String executeCapture(String cmd) {
return conn.executeCapture(cmd);
}
public void success() {
conn.success();
}
@Override
public void close() throws Exception {
Exception toThrow = null;
try {
conn.close();
}
catch (Exception e) {
toThrow = e;
}
try {
mdo.close();
}
catch (Exception e) {
toThrow = e;
}
if (toThrow != null) {
throw toThrow;
}
}
}
@SuppressWarnings("resource")
protected LldbAndTrace startAndSyncLldb(ManagedDomainObject mdo) throws Exception {
LldbAndConnection conn = startAndConnectLldb();
try {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
return new LldbAndTrace(conn, mdo);
}
catch (Exception e) {
conn.close();
throw e;
}
}
protected long lastSnap(LldbAndTrace conn) {
return conn.conn.connection().getLastSnapshot(tb.trace);
}
@Test
public void testBasicUnwind() throws Throwable {
List<String> files = Arrays.asList("kernel32.dll", "expSpin.exe", "ntdll.dll");
addPlugins();
DomainFile[] dfs = getDomainFiles(files);
tool.acceptDomainFiles(dfs);
DomainFile traceDF = getDomainFile("expSpin_lldb");
ManagedDomainObject mdo = new ManagedDomainObject(traceDF, false, false, monitor);
resyncMappings((Trace) mdo.get());
tool.acceptDomainFiles(new DomainFile[] { traceDF });
programs = openPrograms(dfs);
try (LldbAndTrace conn = startAndSyncLldb(mdo)) {
traceManager.activateSnap(2);
waitForPass(() -> assertEquals(Boolean.TRUE,
traceManager.getCurrent().getSnap() == 2));
checkRtlUserThreadStartUnwindFrom0(2);
checkMainUnwindFrom0(1);
checkSleepUnwindFrom0(0);
conn.success();
}
}
@Test
public void testUnwindActivateMidStack() throws Throwable {
List<String> files = Arrays.asList("kernel32.dll", "expSpin.exe", "ntdll.dll");
addPlugins();
DomainFile[] dfs = getDomainFiles(files);
tool.acceptDomainFiles(dfs);
DomainFile traceDF = getDomainFile("expSpin_lldb");
ManagedDomainObject mdo = new ManagedDomainObject(traceDF, false, false, monitor);
resyncMappings((Trace) mdo.get());
tool.acceptDomainFiles(new DomainFile[] { traceDF });
programs = openPrograms(dfs);
try (LldbAndTrace conn = startAndSyncLldb(mdo)) {
traceManager.activateSnap(2);
waitForPass(() -> assertEquals(Boolean.TRUE,
traceManager.getCurrent().getSnap() == 2));
traceManager.activateFrame(1);
checkRtlUserThreadStartUnwindAlt(2);
checkMainUnwindFrom0(1);
checkSleepUnwindFrom0(0);
conn.success();
}
}
@Test
public void testUnwindActivateHighFrame() throws Throwable {
List<String> files = Arrays.asList("kernel32.dll", "expSpin.exe", "ntdll.dll");
addPlugins();
DomainFile[] dfs = getDomainFiles(files);
tool.acceptDomainFiles(dfs);
DomainFile traceDF = getDomainFile("expSpin_lldb");
ManagedDomainObject mdo = new ManagedDomainObject(traceDF, false, false, monitor);
resyncMappings((Trace) mdo.get());
tool.acceptDomainFiles(new DomainFile[] { traceDF });
programs = openPrograms(dfs);
try (LldbAndTrace conn = startAndSyncLldb(mdo)) {
traceManager.activateSnap(2);
waitForPass(() -> assertEquals(Boolean.TRUE,
traceManager.getCurrent().getSnap() == 2));
traceManager.activateFrame(4);
checkRtlUserThreadStartUnwindAlt(2);
checkMainUnwindFrom0(1);
checkSleepUnwindFrom0(0);
conn.success();
}
}
@Test
public void testBasicUnwindMissingLowFrame() throws Throwable {
List<String> files = Arrays.asList("expSpin.exe", "ntdll.dll");
addPlugins();
DomainFile[] dfs = getDomainFiles(files);
tool.acceptDomainFiles(dfs);
DomainFile traceDF = getDomainFile("expSpin_lldb");
ManagedDomainObject mdo = new ManagedDomainObject(traceDF, false, false, monitor);
resyncMappings((Trace) mdo.get());
tool.acceptDomainFiles(new DomainFile[] { traceDF });
programs = openPrograms(dfs);
try (LldbAndTrace conn = startAndSyncLldb(mdo)) {
traceManager.activateSnap(2);
waitForPass(() -> assertEquals(Boolean.TRUE,
traceManager.getCurrent().getSnap() == 2));
checkRtlUserThreadStartUnwindAlt(1);
checkMainUnwindFrom0(0);
conn.success();
}
}
@Test
public void testBasicUnwindMissingMedFrame() throws Throwable {
List<String> files = Arrays.asList("kernel32.dll", "ntdll.dll");
addPlugins();
DomainFile[] dfs = getDomainFiles(files);
tool.acceptDomainFiles(dfs);
DomainFile traceDF = getDomainFile("expSpin_lldb");
ManagedDomainObject mdo = new ManagedDomainObject(traceDF, false, false, monitor);
resyncMappings((Trace) mdo.get());
tool.acceptDomainFiles(new DomainFile[] { traceDF });
programs = openPrograms(dfs);
try (LldbAndTrace conn = startAndSyncLldb(mdo)) {
traceManager.activateSnap(2);
waitForPass(() -> assertEquals(Boolean.TRUE,
traceManager.getCurrent().getSnap() == 2));
checkRtlUserThreadStartUnwindAlt(1);
checkSleepUnwindFrom0(0);
conn.success();
}
}
@Test
public void testBasicUnwindMissingHighFrame() throws Throwable {
List<String> files = Arrays.asList("kernel32.dll", "expSpin.exe");
addPlugins();
DomainFile[] dfs = getDomainFiles(files);
tool.acceptDomainFiles(dfs);
DomainFile traceDF = getDomainFile("expSpin_lldb");
ManagedDomainObject mdo = new ManagedDomainObject(traceDF, false, false, monitor);
resyncMappings((Trace) mdo.get());
tool.acceptDomainFiles(new DomainFile[] { traceDF });
programs = openPrograms(dfs);
try (LldbAndTrace conn = startAndSyncLldb(mdo)) {
traceManager.activateSnap(2);
waitForPass(() -> assertEquals(Boolean.TRUE,
traceManager.getCurrent().getSnap() == 2));
checkMainUnwindFrom0(1);
checkSleepUnwindFrom0(0);
conn.success();
}
}
private void checkSleepUnwindFrom0(int at) throws Throwable {
programManager.openProgram(programs.get(at));
VariableValueTable table =
getTable(programs.get(at), 0x18001b100L, "dwMilliseconds", "Sleep(dwMilliseconds);");
assertTable(Map.of(
RowKey.NAME, "Name: dwMilliseconds",
RowKey.FRAME,
"Frame: 0 Sleep pc=7fffb7c1b100 sp=f9a118fd78 base=f9a118fd78",
RowKey.STORAGE, "Storage: ECX:4",
RowKey.TYPE, "Type: DWORD",
RowKey.LOCATION, "Location: ECX:4",
RowKey.INTEGER, "Integer: (KNOWN) 1000, 0x3e8",
RowKey.VALUE, "Value: (KNOWN) 3E8h",
RowKey.WARNINGS, "IGNORED"), table);
}
private void checkMainUnwindFrom0(int at) throws Throwable {
programManager.openProgram(programs.get(at));
VariableValueTable table =
getTable(programs.get(at), 0x140001000L, "local_58",
"for (local_58 = 0; local_58 < 10; local_58 = local_58 + 1) {");
assertTable(Map.of(
RowKey.NAME, "Name: local_58",
RowKey.FRAME,
"Frame: 1 FUN_140001000 pc=7ff6bd611040 sp=f900000000 base=f900000078",
RowKey.STORAGE, "Storage: Stack[-0x58]:4",
RowKey.TYPE, "Type: undefined4",
RowKey.LOCATION, "Location: f900000020:4",
RowKey.BYTES, "Bytes: (UNKNOWN) 00 00 00 00",
RowKey.INTEGER, "Integer: (UNKNOWN) 0",
RowKey.VALUE, "Value: (UNKNOWN) 00000000h",
RowKey.WARNINGS, "IGNORED"), table);
}
private void checkRtlUserThreadStartUnwindFrom0(int at) throws Throwable {
programManager.openProgram(programs.get(at));
VariableValueTable table =
getTable(programs.get(at), 0x18007edd0L, "uVar1", "uVar1 = (*param_1)(param_2);");
assertTable(Map.of(
RowKey.NAME, "Name: uVar1",
RowKey.FRAME,
"Frame: 4 RtlUserThreadStart pc=7fffb7e3edfb sp=f900000000 base=f900000078",
RowKey.STORAGE, "Storage: EAX:4",
RowKey.TYPE, "Type: undefined4",
RowKey.LOCATION, "Location: EAX:4",
RowKey.INTEGER, "Integer: (KNOWN) 1193036728, 0x471c4bb8",
RowKey.VALUE, "Value: (KNOWN) 471C4BB8h",
RowKey.WARNINGS, "IGNORED"), table);
}
private void checkRtlUserThreadStartUnwindAlt(int at) throws Throwable {
programManager.openProgram(programs.get(at));
VariableValueTable table =
getTable(programs.get(at), 0x18007edd0L, "uVar1", "uVar1 = (*param_1)(param_2);");
assertTable(Map.of(
RowKey.NAME, "Name: uVar1",
RowKey.FRAME,
"Frame: 4 RtlUserThreadStart pc=7fffb7e3edfb sp=f900000000 base=f900000078",
RowKey.STORAGE, "Storage: EAX:4",
RowKey.TYPE, "Type: undefined4",
RowKey.LOCATION, "Location: EAX:4",
RowKey.INTEGER, "Integer: (KNOWN) 2702704640, 0xa1180000\n-1592262656, -0x5ee80000",
RowKey.VALUE, "Value: (KNOWN) A1180000h",
RowKey.WARNINGS, "IGNORED"), table);
}
private VariableValueTable getTable(Program p, long addr, String tokText, String fieldText)
throws Throwable {
Address entry = addr(p, addr);
ProgramLocation pl = new ProgramLocation(p, entry);
goTo(staticListing, pl);
Function f = p.getFunctionManager().getFunctionAt(entry);
HoverLocation loc = findTokenLocation(decompilerPanel, f, tokText, fieldText);
return getVariableValueTable(loc.pLoc, traceManager.getCurrent(), loc.fLoc, loc.field);
}
private DomainFile[] getDomainFiles(List<String> files)
throws InvalidNameException, CancelledException, IOException {
DomainFile[] dfs = new DomainFile[files.size()];
int i = 0;
for (String fname : files) {
dfs[i++] = getDomainFile(fname);
}
return dfs;
}
private DomainFile getDomainFile(String fname)
throws InvalidNameException, CancelledException, IOException {
DomainFolder rootFolder = tool.getProject()
.getProjectData()
.getRootFolder();
File f = getTestDataFile(fname + ".gzf");
return rootFolder.createFile(fname, f, monitor);
}
private List<Program> openPrograms(DomainFile[] files) {
List<Program> progs = new ArrayList<>();
for (int i = 0; i < files.length; i++) {
progs.add(programManager.openProgram(files[i]));
}
return progs;
}
private void resyncMappings(Trace trace)
throws MalformedURLException, URISyntaxException {
DomainFolder rootFolder = tool.getProject()
.getProjectData()
.getRootFolder();
DBTraceStaticMappingManager staticMappingManager =
(DBTraceStaticMappingManager) trace.getStaticMappingManager();
Collection<? extends TraceStaticMapping> allEntries =
staticMappingManager.getAllEntries();
Collection<TraceStaticMapping> newEntries = new HashSet<TraceStaticMapping>();
for (TraceStaticMapping mapping : allEntries) {
newEntries.add(mapping);
}
try (Transaction tx = trace.openTransaction("Remove .text mapping")) {
for (TraceStaticMapping mapping : newEntries) {
mapping.delete();
}
for (TraceStaticMapping mapping : newEntries) {
String previousRoot = mapping.getStaticProgramURL().toString();
String fileName = previousRoot.substring(previousRoot.lastIndexOf("?/") + 2);
URL localProjectURL = rootFolder.getLocalProjectURL();
URL url = new URI(localProjectURL + fileName).toURL();
staticMappingManager.add(mapping.getTraceAddressRange(), mapping.getLifespan(),
url, mapping.getStaticAddress());
}
}
}
protected void addPlugins() throws Throwable {
codeBrowserPlugin = addPlugin(tool, CodeBrowserPlugin.class);
staticListing = codeBrowserPlugin.getProvider().getListingPanel();
listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class);
mappingService = addPlugin(tool, DebuggerStaticMappingServicePlugin.class);
dynamicListing = listingPlugin.getProvider().getListingPanel();
//addPlugin(tool, DebuggerControlPlugin.class);
//addPlugin(tool, DebuggerStaticMappingPlugin.class);
//addPlugin(tool, DebuggerModelPlugin.class);
addPlugin(tool, DisassemblerPlugin.class);
addPlugin(tool, DecompilePlugin.class);
valuesService = valuesPlugin.getHoverService();
decompilerProvider = waitForComponentProvider(DecompilerProvider.class);
decompilerPanel = decompilerProvider.getDecompilerPanel();
tool.showComponentProvider(decompilerProvider, true);
}
public record HoverLocation(ProgramLocation pLoc, FieldLocation fLoc, Field field,
ClangToken token) {}
public static <T extends ProgramLocation> HoverLocation findLocation(ListingPanel panel,
Address address, Class<T> locType, Predicate<T> predicate) {
Layout layout = panel.getLayout(address);
int numFields = layout.getNumFields();
for (int i = 0; i < numFields; i++) {
Field field = layout.getField(i);
if (!(field instanceof ListingField listingField)) {
continue;
}
FieldFactory factory = listingField.getFieldFactory();
int numRows = field.getNumRows();
for (int r = 0; r < numRows; r++) {
int numCols = field.getNumCols(r);
for (int c = 0; c < numCols; c++) {
ProgramLocation loc = factory.getProgramLocation(r, c, listingField);
if (!locType.isInstance(loc)) {
continue;
}
if (!predicate.test(locType.cast(loc))) {
continue;
}
return new HoverLocation(loc, new FieldLocation(0, i, r, c), field, null);
}
}
}
return null;
}
public static HoverLocation findVariableLocation(ListingPanel panel, Function function,
String name) {
return findLocation(panel, function.getEntryPoint(), VariableLocation.class,
varLoc -> name.equals(varLoc.getVariable().getName()));
}
public static HoverLocation findOperandLocation(ListingPanel panel, Instruction ins,
Object operand) {
return findLocation(panel, ins.getAddress(), OperandFieldLocation.class, opLoc -> {
int subIdx = opLoc.getSubOperandIndex();
if (subIdx == -1) {
return false;
}
return operand.equals(
ins.getDefaultOperandRepresentationList(opLoc.getOperandIndex()).get(subIdx));
});
}
public static HoverLocation findTokenLocation(DecompilerPanel decompilerPanel,
Function function, String tokText, String fieldText) {
DecompileResults results = waitForValue(() -> {
ProgramLocation pLoc;
try {
pLoc = decompilerPanel.getCurrentLocation();
}
catch (NullPointerException e) {
/**
* HACK: There's an unlikely race condition where the layout controller has created
* the array of layouts but not fully populated it by the time we ask for the
* current location. This may cause a line we inspect to still have null in it and
* throw an NPE. Whatever. Just catch the thing and return null so that we try
* again. As far as I can tell, this is not indicative of a problem in production,
* because the controller won't issue an updated event until that array is fully
* populated.
*/
return null;
}
if (!(pLoc instanceof DecompilerLocation dLoc)) {
return null;
}
DecompileResults dr = dLoc.getDecompile();
if (dr == null || dr.getFunction() != function) {
return null;
}
return dr;
});
return runSwing(() -> {
Program program = function.getProgram();
ClangLayoutController layoutController = decompilerPanel.getLayoutController();
BigInteger numIndexes = layoutController.getNumIndexes();
for (BigInteger i = BigInteger.ZERO; i.compareTo(numIndexes) < 0; i =
i.add(BigInteger.ONE)) {
Layout layout = layoutController.getLayout(i);
int numFields = layout.getNumFields();
for (int j = 0; j < numFields; j++) {
Field field = layout.getField(j);
if (!(field instanceof ClangTextField clangField)) {
continue;
}
if (!fieldText.equals(field.getText().trim())) {
continue;
}
int numRows = field.getNumRows();
for (int r = 0; r < numRows; r++) {
int numCols = field.getNumCols(r);
for (int c = 0; c < numCols; c++) {
FieldLocation fLoc = new FieldLocation(i, j, r, c);
ClangToken token = clangField.getToken(fLoc);
if (token != null && tokText.equals(token.getText())) {
Address entryPoint = function.getEntryPoint();
DecompilerLocationInfo info =
new DecompilerLocationInfo(entryPoint, results, token,
i.intValue(), 0);
DefaultDecompilerLocation loc = token.getMinAddress() == null ? null
: new DefaultDecompilerLocation(program,
token.getMinAddress(), info);
return new HoverLocation(loc, fLoc, field, token);
}
}
}
}
}
return null;
});
}
protected HoverLocation findTokenLocation(Function function, String tokText, String fieldText)
throws Throwable {
CompletableFuture<Void> ready = new CompletableFuture<>();
decompilerPanel.getLayoutController().addLayoutModelListener(new LayoutModelListener() {
@Override
public void modelSizeChanged(IndexMapper indexMapper) {
if (decompilerPanel.getCurrentLocation() != null) {
ready.complete(null);
}
}
@Override
public void dataChanged(BigInteger start, BigInteger end) {
}
});
tool.showComponentProvider(decompilerProvider, true);
ready.get(5, TimeUnit.SECONDS);
try {
return findTokenLocation(decompilerPanel, function, tokText, fieldText);
}
catch (AssertionFailedError e) {
throw e;
}
}
protected static void assertTable(Map<RowKey, String> texts, VariableValueTable table) {
ErrorRow error = (ErrorRow) table.get(RowKey.ERROR);
if (error != null && !texts.containsKey(RowKey.ERROR)) {
throw new AssertionError("ErrorRow present", error.error());
}
for (Map.Entry<RowKey, String> ent : texts.entrySet()) {
RowKey key = ent.getKey();
VariableValueRow row = table.get(key);
assertNotNull("Missing " + key, row);
if (key != RowKey.WARNINGS) {
assertEquals(ent.getValue(), row.toSimpleString());
}
}
assertEquals(texts.size(), table.getNumRows());
}
protected VariableValueTable getVariableValueTable(ProgramLocation programLocation,
DebuggerCoordinates current, FieldLocation fieldLocation, Field field)
throws Throwable {
VariableValueTable table = new VariableValueTable();
StackUnwindWarningSet warnings = new StackUnwindWarningSet();
waitOn(valuesService.fillVariableValueTable(table, programLocation, current, fieldLocation,
field, warnings));
table.add(new WarningsRow(warnings));
return table;
}
public static final AssemblySelector NO_16BIT_CALLS = new AssemblySelector() {
@Override
public Selection select(AssemblyResolutionResults rr, AssemblyPatternBlock ctx)
throws AssemblySemanticException {
for (AssemblyResolvedPatterns res : filterCompatibleAndSort(rr, ctx)) {
byte[] ins = res.getInstruction().getVals();
// HACK to avoid 16-bit CALL.... TODO: Why does this happen?
if (ins.length >= 2 && ins[0] == (byte) 0x66 && ins[1] == (byte) 0xe8) {
System.err.println(
"Filtered 16-bit call " + NumericUtilities.convertBytesToString(ins));
continue;
}
return new Selection(res.getInstruction().fillMask(), res.getContext());
}
throw new AssemblySemanticException(semanticErrors);
}
};
}