mirror of
https://github.com/NationalSecurityAgency/ghidra
synced 2024-10-04 01:15:18 +00:00
GP-4368: Fix breakpoint command list parsing. (closes #6257)
This commit is contained in:
parent
75beb82103
commit
1b22bac110
|
@ -22,7 +22,7 @@ import java.util.regex.Pattern;
|
|||
|
||||
import org.apache.commons.collections4.MultiMapUtils;
|
||||
import org.apache.commons.collections4.MultiValuedMap;
|
||||
import org.apache.commons.collections4.multimap.HashSetValuedHashMap;
|
||||
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
|
||||
|
||||
import agent.gdb.manager.parsing.GdbParsingUtils.AbstractGdbParser;
|
||||
import agent.gdb.manager.parsing.GdbParsingUtils.GdbParseError;
|
||||
|
@ -30,11 +30,13 @@ import agent.gdb.manager.parsing.GdbParsingUtils.GdbParseError;
|
|||
/**
|
||||
* A parser for GDB/MI records
|
||||
*
|
||||
* <p>
|
||||
* While this is a much more machine-friendly format, it has some interesting idiosyncrasies that
|
||||
* make it annoying even within a machine. This class attempts to impose a nice abstraction of these
|
||||
* records while dealing with nuances particular to certain records, but in general. Examine GDB's
|
||||
* documentation for some example records.
|
||||
*
|
||||
* <p>
|
||||
* There seem to be one primitive type and two (and a half?) aggregate types in these records. The
|
||||
* one primitive type is a string. The aggregates are lists and maps, and maybe "field lists" which
|
||||
* behave like multi-valued maps. Maps introduce IDs, which comprise the map keys or field names.
|
||||
|
@ -88,7 +90,7 @@ public class GdbMiParser extends AbstractGdbParser {
|
|||
/**
|
||||
* Build the field list
|
||||
*
|
||||
* @return
|
||||
* @return the field list
|
||||
*/
|
||||
public GdbMiFieldList build() {
|
||||
return list;
|
||||
|
@ -97,41 +99,14 @@ public class GdbMiParser extends AbstractGdbParser {
|
|||
|
||||
/**
|
||||
* A key-value entry in the field list
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
*/
|
||||
public static class Entry {
|
||||
private final String key;
|
||||
private final Object value;
|
||||
|
||||
private Entry(String key, Object value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key
|
||||
*
|
||||
* @return the key
|
||||
*/
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value
|
||||
*
|
||||
* @return the value
|
||||
*/
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
public record Entry(String key, Object value) {
|
||||
}
|
||||
|
||||
private MultiValuedMap<String, Object> map = new HashSetValuedHashMap<String, Object>() {
|
||||
@Override
|
||||
protected HashSet<Object> createCollection() {
|
||||
return new LinkedHashSet<>();
|
||||
}
|
||||
};
|
||||
private MultiValuedMap<String, Object> map = new ArrayListValuedHashMap<String, Object>();
|
||||
private MultiValuedMap<String, Object> unmodifiableMap =
|
||||
MultiMapUtils.unmodifiableMultiValuedMap(map);
|
||||
private final List<Entry> entryList = new ArrayList<>();
|
||||
|
@ -198,6 +173,7 @@ public class GdbMiParser extends AbstractGdbParser {
|
|||
/**
|
||||
* Assume only a single list is associated with the key, and get that list
|
||||
*
|
||||
* <p>
|
||||
* For convenience, the list is cast to a list of elements of a given type. This cast is
|
||||
* unchecked.
|
||||
*
|
||||
|
@ -220,10 +196,8 @@ public class GdbMiParser extends AbstractGdbParser {
|
|||
*/
|
||||
public GdbMiFieldList getFieldList(String key) {
|
||||
Object obj = getSingleton(key);
|
||||
if (obj instanceof List) {
|
||||
if (((List<?>) obj).isEmpty()) {
|
||||
return GdbMiFieldList.builder().build();
|
||||
}
|
||||
if (obj instanceof List<?> list && list.isEmpty()) {
|
||||
return GdbMiFieldList.builder().build();
|
||||
}
|
||||
return (GdbMiFieldList) obj;
|
||||
}
|
||||
|
@ -334,7 +308,7 @@ public class GdbMiParser extends AbstractGdbParser {
|
|||
*
|
||||
* @see #parseObject(CharSequence)
|
||||
* @return the object
|
||||
* @throws GdbParseError
|
||||
* @throws GdbParseError if no text matches
|
||||
*/
|
||||
public Object parseObject() throws GdbParseError {
|
||||
switch (peek(true)) {
|
||||
|
@ -369,9 +343,11 @@ public class GdbMiParser extends AbstractGdbParser {
|
|||
char ch = buf.get();
|
||||
if (ch > 0xff) {
|
||||
throw new GdbParseError("byte", "U+" + String.format("%04X", ch));
|
||||
} else if (ch == '"') {
|
||||
}
|
||||
else if (ch == '"') {
|
||||
break;
|
||||
} else if (ch != '\\') {
|
||||
}
|
||||
else if (ch != '\\') {
|
||||
baos.write(ch);
|
||||
continue;
|
||||
}
|
||||
|
@ -495,6 +471,11 @@ public class GdbMiParser extends AbstractGdbParser {
|
|||
result.add(UNNAMED, fieldVal);
|
||||
continue;
|
||||
}
|
||||
if (c == '"') {
|
||||
String bareString = parseString();
|
||||
result.add(null, bareString);
|
||||
continue;
|
||||
}
|
||||
String fieldId = match(FIELD_ID, true);
|
||||
match(EQUALS, true);
|
||||
Object fieldVal = parseObject();
|
||||
|
|
|
@ -18,12 +18,12 @@ package agent.gdb.manager.parsing;
|
|||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import agent.gdb.manager.parsing.GdbMiParser;
|
||||
import agent.gdb.manager.parsing.GdbMiParser.GdbMiFieldList;
|
||||
import agent.gdb.manager.parsing.GdbParsingUtils.GdbParseError;
|
||||
|
||||
|
@ -36,41 +36,80 @@ public class GdbMiParserTest {
|
|||
|
||||
@Test
|
||||
public void testMatch() throws GdbParseError {
|
||||
GdbMiParser parser = new GdbMiParser("Hello, World!");
|
||||
GdbMiParser parser = new GdbMiParser("""
|
||||
Hello, World""");
|
||||
assertEquals("Hello", parser.match(Pattern.compile("\\w+"), true));
|
||||
assertEquals(",", parser.match(GdbMiParser.COMMA, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseString() throws GdbParseError {
|
||||
GdbMiParser parser = new GdbMiParser("\"Hello, World!\\n\"");
|
||||
GdbMiParser parser = new GdbMiParser("""
|
||||
"Hello, World!\\n"\
|
||||
""");
|
||||
assertEquals("Hello, World!\n", parser.parseString());
|
||||
parser.checkEmpty(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseList() throws GdbParseError {
|
||||
GdbMiParser parser = new GdbMiParser("[\"Hello\",\"World\"]");
|
||||
GdbMiParser parser = new GdbMiParser("""
|
||||
["Hello","World"]""");
|
||||
assertEquals(Arrays.asList(new String[] { "Hello", "World" }), parser.parseList());
|
||||
parser.checkEmpty(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseMap() throws GdbParseError {
|
||||
GdbMiParser parser = new GdbMiParser("{h=\"Hello\",w=\"World\"}");
|
||||
GdbMiParser parser = new GdbMiParser("""
|
||||
{h="Hello",w="World"}""");
|
||||
assertEquals(buildFieldList((exp) -> {
|
||||
exp.add("h", "Hello");
|
||||
exp.add("w", "World");
|
||||
}), parser.parseMap());
|
||||
parser.checkEmpty(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseStringEscapes() throws GdbParseError {
|
||||
GdbMiParser parser = new GdbMiParser("\"basic=\\n\\b\\t\\f\\r c=\\e[0m\\a delim=\\\\\\\" octal=\\000\\177\"");
|
||||
assertEquals("basic=\n\b\t\f\r c=\033[0m\007 delim=\\\" octal=\000\177", parser.parseString());
|
||||
GdbMiParser parser = new GdbMiParser("""
|
||||
"basic=\\n\\b\\t\\f\\r c=\\e[0m\\a delim=\\\\\\" octal=\\000\\177"\
|
||||
""");
|
||||
assertEquals("basic=\n\b\t\f\r c=\033[0m\007 delim=\\\" octal=\000\177",
|
||||
parser.parseString());
|
||||
parser.checkEmpty(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseStringUTF8() throws GdbParseError {
|
||||
GdbMiParser parser = new GdbMiParser("\"\\302\\244 \\342\\204\\212 \\343\\201\\251 \\351\\276\\231 \\360\\237\\230\\200\"");
|
||||
GdbMiParser parser = new GdbMiParser("""
|
||||
"\\302\\244 \\342\\204\\212 \\343\\201\\251 \\351\\276\\231 \\360\\237\\230\\200"\
|
||||
""");
|
||||
assertEquals("\u00a4 \u210a \u3069 \u9f99 \ud83d\ude00", parser.parseString());
|
||||
parser.checkEmpty(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseBreakpointCommandList() throws GdbParseError {
|
||||
GdbMiParser parser = new GdbMiParser("""
|
||||
BreakpointTable={nr_rows="1",nr_cols="6",hdr=[{width="7",alignment="-1",\
|
||||
col_name="number",colhdr="Num"},{width="14",alignment="-1",col_name="type",\
|
||||
colhdr="Type"},{width="4",alignment="-1",col_name="disp",colhdr="Disp"},\
|
||||
{width="3",alignment="-1",col_name="enabled",colhdr="Enb"},{width="18",\
|
||||
alignment="-1",col_name="addr",colhdr="Address"},{width="40",alignment="2",\
|
||||
col_name="what",colhdr="What"}],body=[bkpt={number="1",type="breakpoint",\
|
||||
disp="keep",enabled="y",addr="0x00007ffff779c96f",at="<poll+31>",\
|
||||
thread-groups=["i1"],times="0",script={"echo asdf","echo ghjk","echo asdf"},\
|
||||
original-location="*0x7ffff779c96f"}]}""");
|
||||
GdbMiFieldList result = parser.parseFields(false);
|
||||
GdbMiFieldList table = result.getFieldList("BreakpointTable");
|
||||
GdbMiFieldList body = table.getFieldList("body");
|
||||
List<Object> bkpts = List.copyOf(body.get("bkpt"));
|
||||
assertEquals(1, bkpts.size());
|
||||
GdbMiFieldList bkpt0 = (GdbMiFieldList) bkpts.get(0);
|
||||
GdbMiFieldList script = bkpt0.getFieldList("script");
|
||||
List<Object> lines = List.copyOf(script.get(null));
|
||||
assertEquals(List.of("echo asdf", "echo ghjk", "echo asdf"), lines);
|
||||
parser.checkEmpty(false);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue