mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
GP-1539: Polish the DebuggerGoToDialog. Allow labels and plain addresses.
This commit is contained in:
parent
738e662e82
commit
b51d423d4b
27 changed files with 759 additions and 103 deletions
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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)
|
||||
*
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue