GP-1539: Polish the DebuggerGoToDialog. Allow labels and plain addresses.

This commit is contained in:
Dan 2023-03-24 14:41:12 -04:00
parent 738e662e82
commit b51d423d4b
27 changed files with 759 additions and 103 deletions

View file

@ -16,11 +16,12 @@
package ghidra.pcode.exec;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
import ghidra.app.plugin.processors.sleigh.*;
import ghidra.app.plugin.processors.sleigh.template.ConstructTpl;
import ghidra.pcode.utils.MessageFormattingUtils;
import ghidra.pcodeCPort.pcoderaw.VarnodeData;
import ghidra.pcodeCPort.sleighbase.SleighBase;
import ghidra.pcodeCPort.slghsymbol.*;
@ -47,6 +48,94 @@ public enum SleighProgramCompiler {
private static final String EXPRESSION_SOURCE_NAME = "expression";
public static final String NIL_SYMBOL_NAME = "__nil";
public interface PcodeLogEntry {
public static String formatList(List<PcodeLogEntry> list) {
return list.stream().map(e -> e.format()).collect(Collectors.joining("\n"));
}
Location loc();
String msg();
String type();
default String format() {
return "%s: %s".formatted(type(), MessageFormattingUtils.format(loc(), msg()));
}
}
record PcodeError(Location loc, String msg) implements PcodeLogEntry {
@Override
public String type() {
return "ERROR";
}
}
record PcodeWarning(Location loc, String msg) implements PcodeLogEntry {
@Override
public String type() {
return "WARNING";
}
}
public static class DetailedSleighException extends SleighException {
private final List<PcodeLogEntry> details;
public DetailedSleighException(List<PcodeLogEntry> details) {
super(PcodeLogEntry.formatList(details));
this.details = List.copyOf(details);
}
public List<PcodeLogEntry> getDetails() {
return details;
}
}
/**
* A p-code parser that provides programmatic access to error diagnostics.
*/
public static class ErrorCollectingPcodeParser extends PcodeParser {
private final List<PcodeLogEntry> entries = new ArrayList<>();
public ErrorCollectingPcodeParser(SleighLanguage language) {
super(language, UniqueLayout.INJECT.getOffset(language));
}
@Override
public void reportError(Location location, String msg) {
entries.add(new PcodeError(location, msg));
super.reportError(location, msg);
}
@Override
public void reportWarning(Location location, String msg) {
entries.add(new PcodeWarning(location, msg));
super.reportWarning(location, msg);
}
@Override
public ConstructTpl compilePcode(String pcodeStatements, String srcFile, int srcLine)
throws SleighException {
try {
return super.compilePcode(pcodeStatements, srcFile, srcLine);
}
finally {
if (getErrors() != 0) {
throw new DetailedSleighException(entries);
}
}
}
@Override
public SleighSymbol findSymbol(String nm) {
SleighSymbol symbol = super.findSymbol(nm);
if (symbol == null) {
throw new SleighException("Unknown register: '" + nm + "'");
}
return symbol;
}
}
/**
* Create a p-code parser for the given language
*
@ -54,7 +143,7 @@ public enum SleighProgramCompiler {
* @return a parser
*/
public static PcodeParser createParser(SleighLanguage language) {
return new PcodeParser(language, UniqueLayout.INJECT.getOffset(language));
return new ErrorCollectingPcodeParser(language);
}
/**
@ -69,7 +158,7 @@ public enum SleighProgramCompiler {
*/
public static ConstructTpl compileTemplate(Language language, PcodeParser parser,
String sourceName, String source) {
return parser.compilePcode(source, EXPRESSION_SOURCE_NAME, 1);
return parser.compilePcode(source, sourceName, 1);
}
/**
@ -162,22 +251,22 @@ public enum SleighProgramCompiler {
}
/**
* Compile the given Sleigh source into a simple p-code program
* Compile the given Sleigh source into a simple p-code program with the given parser
*
* <p>
* This is suitable for modifying program state using Sleigh statements. Most likely, in
* scripting, or perhaps in a Sleigh repl. The library given during compilation must match the
* library given for execution, at least in its binding of userop IDs to symbols.
*
* @param the parser to use
* @param language the language of the target p-code machine
* @param sourceName a diagnostic name for the Sleigh source
* @param source the Sleigh source
* @param library the userop library or stub library for binding userop symbols
* @return the compiled p-code program
*/
public static PcodeProgram compileProgram(SleighLanguage language, String sourceName,
String source, PcodeUseropLibrary<?> library) {
PcodeParser parser = createParser(language);
public static PcodeProgram compileProgram(PcodeParser parser, SleighLanguage language,
String sourceName, String source, PcodeUseropLibrary<?> library) {
Map<Integer, UserOpSymbol> symbols = library.getSymbols(language);
addParserSymbols(parser, symbols);
@ -186,7 +275,18 @@ public enum SleighProgramCompiler {
}
/**
* Compile the given Sleigh expression into a p-code program that can evaluate it
* Compile the given Sleigh source into a simple p-code program
*
* @see #compileProgram(PcodeParser, SleighLanguage, String, String, PcodeUseropLibrary)
*/
public static PcodeProgram compileProgram(SleighLanguage language, String sourceName,
String source, PcodeUseropLibrary<?> library) {
return compileProgram(createParser(language), language, sourceName, source, library);
}
/**
* Compile the given Sleigh expression into a p-code program that can evaluate it, using the
* given parser
*
* <p>
* TODO: Currently, expressions cannot be compiled for a user-supplied userop library. The
@ -198,8 +298,8 @@ public enum SleighProgramCompiler {
* @return a p-code program whose {@link PcodeExpression#evaluate(PcodeExecutor)} method will
* evaluate the expression on the given executor and its state.
*/
public static PcodeExpression compileExpression(SleighLanguage language, String expression) {
PcodeParser parser = createParser(language);
public static PcodeExpression compileExpression(PcodeParser parser, SleighLanguage language,
String expression) {
Map<Integer, UserOpSymbol> symbols = PcodeExpression.CAPTURING.getSymbols(language);
addParserSymbols(parser, symbols);
@ -208,6 +308,15 @@ public enum SleighProgramCompiler {
return constructProgram(PcodeExpression::new, language, template, symbols);
}
/**
* Compile the given Sleigh expression into a p-code program that can evaluate it
*
* @see #compileExpression(PcodeParser, SleighLanguage, String)
*/
public static PcodeExpression compileExpression(SleighLanguage language, String expression) {
return compileExpression(createParser(language), language, expression);
}
/**
* Generate a Sleigh symbol for context when compiling a userop definition
*

View file

@ -290,6 +290,39 @@ public enum SleighUtils {
});
}
public static void matchDereference(Tree tree, Consumer<Tree> onSpace, Consumer<Tree> onSize,
Consumer<Tree> onOffset) {
switch (tree.getChildCount()) {
case 3:
match(tree, SleighParser.OP_DEREFERENCE, onSpace, onSize, onOffset);
return;
case 2:
Tree child0 = tree.getChild(0);
switch (child0.getType()) {
case SleighParser.OP_IDENTIFIER:
match(tree, SleighParser.OP_DEREFERENCE, onSpace, onOffset);
return;
case SleighParser.OP_BIN_CONSTANT:
case SleighParser.OP_DEC_CONSTANT:
case SleighParser.OP_HEX_CONSTANT:
match(tree, SleighParser.OP_DEREFERENCE, onSize, onOffset);
return;
default:
throw new AssertionError(
"OP_DEREFERENCE with 2 children where child[0] is " +
SleighParser.tokenNames[child0.getType()]);
}
case 1:
match(tree, SleighParser.OP_DEREFERENCE, onOffset);
return;
default:
// Likely, the op is mismatched. Ensure the error message says so.
match(tree, SleighParser.OP_DEREFERENCE);
throw new AssertionError(
"OP_DEREFERENCE with " + tree.getChildCount() + " children");
}
}
/**
* Check if the given tree represents an unconditional breakpoint in the emulator
*
@ -390,6 +423,36 @@ public enum SleighUtils {
}
}
public record AddressOf(String space, Tree offset) {
}
public static AddressOf recoverAddressOf(String defaultSpace, Tree tree) {
var l = new Object() {
String space = defaultSpace;
Tree offset;
};
matchDereference(tree, wantSpaceId -> {
match(wantSpaceId, SleighParser.OP_IDENTIFIER, id -> {
l.space = getIdentifier(id);
});
}, wantSize -> {
// I don't care about size
}, wantOffset -> {
l.offset = wantOffset;
});
return new AddressOf(l.space, removeParenthesisTree(Objects.requireNonNull(l.offset)));
}
public static AddressOf recoverAddressOf(String defaultSpace, String expression) {
try {
Tree tree = parseSleighExpression(expression);
return recoverAddressOf(defaultSpace, tree);
}
catch (SleighParseError | MismatchException e) {
return null;
}
}
/**
* Synthesize a tree (node)
*

View file

@ -0,0 +1,104 @@
/* ###
* 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.pcode.exec;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
import org.junit.Before;
import org.junit.Test;
import generic.Unique;
import generic.test.AbstractGTest;
import ghidra.GhidraTestApplicationLayout;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.plugin.processors.sleigh.SleighLanguageHelper;
import ghidra.framework.Application;
import ghidra.framework.ApplicationConfiguration;
import ghidra.pcode.exec.SleighProgramCompiler.DetailedSleighException;
import ghidra.pcode.exec.SleighProgramCompiler.PcodeLogEntry;
import ghidra.sleigh.grammar.Location;
import utility.function.ExceptionalCallback;
public class SleighProgramCompilerTest extends AbstractGTest {
protected <T> T rfail(String message) {
fail(message);
throw new AssertionError();
}
protected <E extends Exception> E expect(Class<E> cls, ExceptionalCallback<E> cb) {
try {
cb.call();
}
catch (Throwable e) {
if (!cls.isInstance(e)) {
e.printStackTrace();
return rfail("Expected " + cls + ". Got " + e.getClass());
}
return cls.cast(e);
}
return rfail("Expected " + cls + ". Got success");
}
@Before
public void setUp() throws IOException {
if (!Application.isInitialized()) {
Application.initializeApplication(
new GhidraTestApplicationLayout(new File(getTestDirectoryPath())),
new ApplicationConfiguration());
}
}
@Test
public void testCompileProgramErrLocations() throws Throwable {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
DetailedSleighException exc = expect(DetailedSleighException.class, () -> {
PcodeProgram program =
SleighProgramCompiler.compileProgram(language, "test", "noreg = noreg;",
PcodeUseropLibrary.NIL);
// Shouldn't get here, but if we do, I'd like to see the program:
System.err.println(program);
});
PcodeLogEntry entry = Unique.assertOne(exc.getDetails());
Location loc = entry.loc();
assertEquals("test", loc.filename);
assertEquals(1, loc.lineno);
assertEquals(
"unknown start, end, next2, operand, epsilon, or varnode 'noreg' in varnode reference",
entry.msg());
}
@Test
public void testCompileExpressionErrLocations() throws Throwable {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
DetailedSleighException exc = expect(DetailedSleighException.class, () -> {
PcodeProgram program = SleighProgramCompiler.compileExpression(language, "noreg");
// Shouldn't get here, but if we do, I'd like to see the program:
System.err.println(program);
});
PcodeLogEntry entry = Unique.assertOne(exc.getDetails());
// TODO: It'd be nice if loc included a column number and token length
Location loc = entry.loc();
assertEquals("expression", loc.filename);
assertEquals(1, loc.lineno);
assertEquals(
"unknown start, end, next2, operand, epsilon, or varnode 'noreg' in varnode reference",
entry.msg());
}
}

View file

@ -15,13 +15,13 @@
*/
package ghidra.pcode.exec;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.junit.Assert.*;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.tree.Tree;
import org.junit.Test;
import ghidra.pcode.exec.SleighUtils.AddressOf;
import ghidra.pcode.exec.SleighUtils.SleighParseError;
public class SleighUtilsTest {
@ -63,6 +63,40 @@ public class SleighUtilsTest {
}
}
@Test
public void testRecoverAddressOfMismatchErr() {
AddressOf addrOf = SleighUtils.recoverAddressOf(null, "ptr + 8");
assertNull(addrOf);
}
@Test
public void testRecoverAddressOfForm1() {
AddressOf addrOf = SleighUtils.recoverAddressOf(null, "*ptr");
assertEquals(null, addrOf.space());
assertEquals("ptr", SleighUtils.generateSleighExpression(addrOf.offset()));
}
@Test
public void testRecoverAddressOfForm2a() {
AddressOf addrOf = SleighUtils.recoverAddressOf(null, "*:8 ptr");
assertEquals(null, addrOf.space());
assertEquals("ptr", SleighUtils.generateSleighExpression(addrOf.offset()));
}
@Test
public void testRecoverAddressOfForm2b() {
AddressOf addrOf = SleighUtils.recoverAddressOf(null, "*[ram] ptr");
assertEquals("ram", addrOf.space());
assertEquals("ptr", SleighUtils.generateSleighExpression(addrOf.offset()));
}
@Test
public void testRecoverAddressOfForm3() {
AddressOf addrOf = SleighUtils.recoverAddressOf(null, "*[ram]:8 ptr");
assertEquals("ram", addrOf.space());
assertEquals("ptr", SleighUtils.generateSleighExpression(addrOf.offset()));
}
@Test
public void testRecoverConditionEqDec() {
assertEquals("RAX == 0",