diff --git a/Ghidra/Features/Base/src/main/help/help/topics/AutoAnalysisPlugin/AutoAnalysis.htm b/Ghidra/Features/Base/src/main/help/help/topics/AutoAnalysisPlugin/AutoAnalysis.htm index d9799704d8..98aa03fe84 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/AutoAnalysisPlugin/AutoAnalysis.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/AutoAnalysisPlugin/AutoAnalysis.htm @@ -450,6 +450,18 @@

Started By: Importing or adding to a program, Auto Analyze command

+

Format String Analyzer

+ +
+

This analyzer detects variadic function calls in the bodies of each function that intersect + the current selection. It then parses their format string arguments to infer the correct function + call signatures. Currently, this analyzer only supports printf, scanf, and their variants (e.g., snprintf, fscanf). + If the current selection is emtpy, it searches through every function within the binary. Once + the signatures are inferred, they are overridden.

+ +

Started By: Importing or adding to a program, Auto Analyze command

+ +

Image Analyzer

diff --git a/Ghidra/Features/Base/src/main/help/help/topics/ImporterPlugin/importer.htm b/Ghidra/Features/Base/src/main/help/help/topics/ImporterPlugin/importer.htm index feb8f0a400..f06baf5dc6 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/ImporterPlugin/importer.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/ImporterPlugin/importer.htm @@ -364,7 +364,7 @@

Intel Hex Options

-

Bass Address

+

Base Address

This field is used to specify the start address in memory for where to load the @@ -395,7 +395,7 @@

Motorola Hex Options

-

Bass Address

+

Base Address

This field is used to specify the start address in memory for where to load the diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/ApplyDataArchiveAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/ApplyDataArchiveAnalyzer.java index 8e7ddb64c5..eff1f5a496 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/ApplyDataArchiveAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/ApplyDataArchiveAnalyzer.java @@ -27,7 +27,7 @@ import ghidra.program.model.address.AddressSetView; import ghidra.program.model.data.DataTypeManager; import ghidra.program.model.listing.Program; import ghidra.program.model.symbol.SourceType; -import ghidra.util.Msg; +import ghidra.util.exception.VersionException; import ghidra.util.task.TaskMonitor; public class ApplyDataArchiveAnalyzer extends AbstractAnalyzer { @@ -67,7 +67,7 @@ public class ApplyDataArchiveAnalyzer extends AbstractAnalyzer { try { dtm = service.openDataTypeArchive(archiveName); if (dtm == null) { - Msg.showError(this, null, "Failed to Apply Data Types", + log.appendMsg("Apply Data Archives", "Failed to locate data type archive: " + archiveName); } else { @@ -75,8 +75,18 @@ public class ApplyDataArchiveAnalyzer extends AbstractAnalyzer { } } catch (Exception e) { - if (mgr.debugOn) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); + Throwable cause = e.getCause(); + if (cause instanceof VersionException) { + log.appendMsg("Apply Data Archives", + "Unable to open archive " + archiveName + ": " + cause.toString()); + } + else { + String msg = e.getMessage(); + if (msg == null) { + msg = e.toString(); + } + log.appendMsg("Apply Data Archives", + "Unexpected Error opening archive " + archiveName + ": " + msg); } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeManagerHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeManagerHandler.java index 7bb8ea5c2f..e54d1d0499 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeManagerHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeManagerHandler.java @@ -438,9 +438,7 @@ public class DataTypeManagerHandler { addArchive(archive); } if (isUserAction && (archive instanceof FileArchive)) { - if (file != null) { - userOpenedFileArchiveNames.add(getSaveableArchive(file.getAbsolutePath())); - } + userOpenedFileArchiveNames.add(getSaveableArchive(file.getAbsolutePath())); } return archive; } @@ -1251,8 +1249,8 @@ public class DataTypeManagerHandler { public Set getPossibleEquateNames(long value) { Set equateNames = new HashSet<>(); - for (int i = 0; i < openArchives.size(); i++) { - DataTypeManager dtMgr = openArchives.get(i).getDataTypeManager(); + for (Archive element : openArchives) { + DataTypeManager dtMgr = element.getDataTypeManager(); dtMgr.findEnumValueNames(value, equateNames); } return equateNames; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/BinaryReader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/BinaryReader.java index 9dca6ebcdc..c07c127c00 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/BinaryReader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/BinaryReader.java @@ -74,11 +74,20 @@ public class BinaryReader { */ public BinaryReader clone(long newIndex) { BinaryReader clone = new BinaryReader(provider, isLittleEndian()); - clone.converter = converter; clone.currentIndex = newIndex; return clone; } + /** + * Returns an independent clone of this reader positioned at the same index. + * + * @return a independent clone of this reader positioned at the same index + */ + @Override + public BinaryReader clone() { + return clone(currentIndex); + } + /** * Returns true if this reader will extract values in little endian, * otherwise in big endian. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/FileEntry.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/FileEntry.java index 9a73c6127c..90f7c49b12 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/FileEntry.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/FileEntry.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,33 +18,34 @@ package ghidra.app.util.bin.format.dwarf.line; import java.io.IOException; import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.format.dwarf4.LEB128; public class FileEntry { private String fileName; - private LEB128 directoryIndex; - private LEB128 lastModifiedTime; - private LEB128 fileLengthInBytes; + private long directoryIndex; + private long lastModifiedTime; + private long fileLengthInBytes; FileEntry(BinaryReader reader) throws IOException { fileName = reader.readNextAsciiString(); if (fileName.length() == 0) { return; } - directoryIndex = new LEB128(reader, false); - lastModifiedTime = new LEB128(reader, false); - fileLengthInBytes = new LEB128(reader, false); + directoryIndex = LEB128.readAsLong(reader, false); + lastModifiedTime = LEB128.readAsLong(reader, false); + fileLengthInBytes = LEB128.readAsLong(reader, false); } public String getFileName() { return fileName; } - public LEB128 getDirectoryIndex() { + public long getDirectoryIndex() { return directoryIndex; } - public LEB128 getLastModifiedTime() { + public long getLastModifiedTime() { return lastModifiedTime; } - public LEB128 getFileLengthInBytes() { + public long getFileLengthInBytes() { return fileLengthInBytes; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/LEB128.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/LEB128.java deleted file mode 100644 index 90c2ba9d44..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/LEB128.java +++ /dev/null @@ -1,48 +0,0 @@ -/* ### - * 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.dwarf.line; - -import java.io.IOException; - -import ghidra.app.util.bin.BinaryReader; - -class LEB128 { - private long value; - - LEB128(BinaryReader reader, boolean isSigned) throws IOException { - if (isSigned) { - throw new UnsupportedOperationException(); - } - int shift = 0; - while (true) { - int nextByte = reader.readNextByte() & 0xff; - value |= ((nextByte & 0x7f) << shift); - if ((nextByte & 0x80) == 0) { - break; - } - shift += 7; - } - } - - long getValue() { - return value; - } - - @Override - public String toString() { - return "0x" + Long.toHexString(value); - } -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/StatementProgramInstructions.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/StatementProgramInstructions.java index 55db62c520..c38ceab821 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/StatementProgramInstructions.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/StatementProgramInstructions.java @@ -16,6 +16,7 @@ package ghidra.app.util.bin.format.dwarf.line; import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.format.dwarf4.LEB128; import java.io.IOException; @@ -92,7 +93,7 @@ public final class StatementProgramInstructions { } private void executeExtended(int opcode) throws IOException { - LEB128 length = new LEB128(reader, false); + long length = LEB128.readAsLong(reader, false); long oldIndex = reader.getPointerIndex(); int extendedOpcode = reader.readNextByte(); @@ -110,7 +111,7 @@ public final class StatementProgramInstructions { throw new UnsupportedOperationException(); } - if (oldIndex + length.getValue() != reader.getPointerIndex()) { + if (oldIndex + length != reader.getPointerIndex()) { throw new IllegalStateException("Index values do not match!"); } } @@ -122,23 +123,23 @@ public final class StatementProgramInstructions { break; } case DW_LNS_advance_pc: { - LEB128 value = new LEB128(reader, false); - machine.address += (value.getValue() * prologue.getMinimumInstructionLength()); + long value = LEB128.readAsLong(reader, false); + machine.address += (value * prologue.getMinimumInstructionLength()); break; } case DW_LNS_advance_line: { - LEB128 value = new LEB128(reader, false); - machine.line += value.getValue(); + long value = LEB128.readAsLong(reader, false); + machine.line += value; break; } case DW_LNS_set_file: { - LEB128 value = new LEB128(reader, false); - machine.file = (int) value.getValue(); + long value = LEB128.readAsLong(reader, false); + machine.file = (int) value; break; } case DW_LNS_set_column: { - LEB128 value = new LEB128(reader, false); - machine.column = (int) value.getValue(); + long value = LEB128.readAsLong(reader, false); + machine.column = (int) value; break; } case DW_LNS_negate_statement: { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/StatementProgramPrologue.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/StatementProgramPrologue.java index ffba47f952..fa353c695c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/StatementProgramPrologue.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/StatementProgramPrologue.java @@ -168,10 +168,10 @@ public class StatementProgramPrologue { * @param directoryIndex the directory index * @return the directory or current directory */ - public String getDirectoryByIndex(LEB128 directoryIndex) { - if (directoryIndex.getValue() == 0) { + public String getDirectoryByIndex(long directoryIndex) { + if (directoryIndex == 0) { return "."; } - return includeDirectories.get((int)directoryIndex.getValue() - 1); + return includeDirectories.get((int)directoryIndex - 1); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFAbbreviation.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFAbbreviation.java index c484ed7116..0c5738c9d2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFAbbreviation.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFAbbreviation.java @@ -43,11 +43,11 @@ public class DWARFAbbreviation TaskMonitor monitor) throws IOException, CancelledException { - int ac = LEB128.decode32u(reader); + int ac = LEB128.readAsUInt32(reader); if (ac == 0) { return null; } - int tag = LEB128.decode32u(reader); + int tag = LEB128.readAsUInt32(reader); DWARFChildren hasChildren = DWARFChildren.find((int) reader.readNextByte()); // Read each attribute specification until attribute and its value is 0 diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFAttributeSpecification.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFAttributeSpecification.java index 8f10672f53..b870e27544 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFAttributeSpecification.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFAttributeSpecification.java @@ -38,8 +38,8 @@ public class DWARFAttributeSpecification { * @throws IOException */ public static DWARFAttributeSpecification read(BinaryReader reader) throws IOException { - int attribute = LEB128.decode32u(reader); - DWARFForm attributeForm = DWARFForm.find(LEB128.decode32u(reader)); + int attribute = LEB128.readAsUInt32(reader); + DWARFForm attributeForm = DWARFForm.find(LEB128.readAsUInt32(reader)); return attribute != 0 && attributeForm != DWARFForm.NULL ? new DWARFAttributeSpecification(attribute, attributeForm) : null; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFLine.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFLine.java index fb7215fed6..1bed4feea9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFLine.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFLine.java @@ -201,9 +201,9 @@ public class DWARFLine { // This entry exists only if the length of the string is more than 0 if (this.name.length() > 0) { - this.directory_index = LEB128.decode(reader, false); - this.modification_time = LEB128.decode(reader, false); - this.length = LEB128.decode(reader, false); + this.directory_index = LEB128.readAsLong(reader, false); + this.modification_time = LEB128.readAsLong(reader, false); + this.length = LEB128.readAsLong(reader, false); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DebugInfoEntry.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DebugInfoEntry.java index 9c5e885253..ebfcc9c6ba 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DebugInfoEntry.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DebugInfoEntry.java @@ -53,7 +53,7 @@ public class DebugInfoEntry { public static DebugInfoEntry read(BinaryReader reader, DWARFCompilationUnit unit, DWARFAttributeFactory attributeFactory) throws IOException { long offset = reader.getPointerIndex(); - int abbreviationCode = LEB128.decode32u(reader); + int abbreviationCode = LEB128.readAsUInt32(reader); // Check for terminator DIE if (abbreviationCode == 0) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/LEB128.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/LEB128.java index 10277d6fa3..e9b3e19e52 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/LEB128.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/LEB128.java @@ -18,122 +18,195 @@ package ghidra.app.util.bin.format.dwarf4; import java.io.IOException; import ghidra.app.util.bin.BinaryReader; -import ghidra.util.NumberUtil; -public final class LEB128 { +import ghidra.app.util.bin.ByteArrayProvider; - /** - * Decode a LEB128 signed number and return it as a java 32 bit int. - *

- * If the value of the number can not fit in the int type, an {@link IOException} will - * be thrown. - * - * @param reader - * @return - * @throws IOException - */ - public static int decode32s(BinaryReader reader) throws IOException { - long tmp = decode(reader, true); - if (tmp < Integer.MIN_VALUE || tmp > Integer.MAX_VALUE) { - throw new IOException( - "LEB128 value out of range for java 32 bit signed int: " + Long.toString(tmp)); - } +/** + * Class to hold result of reading a LEB128 value, along with size and position metadata. + *

+ * Note: If a LEB128 value that would result in a native value longer than 64bits is attempted to + * be read, an {@link IOException} will be thrown, and the stream's position will be left at the last read byte. + *

+ * If this was a valid (but overly large) LEB128, the caller's stream will be left still pointing to LEB data. + *

+ */ +public class LEB128 { - return (int) tmp; + private final long offset; + private final long value; + private final int byteLength; + + private LEB128(long offset, long value, int byteLength) { + this.offset = offset; + this.value = value; + this.byteLength = byteLength; } /** - * Decode a LEB128 unsigned number and return it as a java 32 bit int. - *

- * If the value of the number can not fit in the positive range of the int type, - * an {@link IOException} will be thrown. - * - * @param reader - * @return - * @throws IOException + * Returns the value as an unsigned int32. If the actual value + * is outside the positive range of a java int (ie. 0.. {@link Integer#MAX_VALUE}), + * an exception is thrown. + * + * @return int in the range of 0 to {@link Integer#MAX_VALUE} + * @throws IOException if value is outside range */ - public static int decode32u(BinaryReader reader) throws IOException { - long tmp = decode(reader, false); - - // NOTE: will only be lt 0 if tmp's value was larger than what fits in signed long and it wrapped. - if (tmp < 0 || tmp > Integer.MAX_VALUE) { - throw new IOException("LEB128 value out of range for java 32 bit unsigned int: " + - Long.toUnsignedString(tmp)); - } - - return (int) tmp; + public int asUInt32() throws IOException { + ensureInt32u(value); + return (int) value; } /** - * Decodes a LEB128 number using a binary reader and stores it in a long. - *

- * Large unsigned integers that use 64 bits will be returned in java's native - * 'long' type, which is signed. It is up to the caller to treat the value as unsigned. - *

- * Large integers that use more than 64 bits will cause an IOException to be thrown. - *

- * @param reader the binary reader - * @param isSigned true if the value is signed - * @throws IOException if an I/O error occurs + * Returns the value as an signed int32. If the actual value + * is outside the range of a java int (ie. {@link Integer#MIN_VALUE}.. {@link Integer#MAX_VALUE}), + * an exception is thrown. + * + * @return int in the range of {@link Integer#MIN_VALUE} to {@link Integer#MAX_VALUE} + * @throws IOException if value is outside range */ - public static long decode(BinaryReader reader, boolean isSigned) throws IOException { - int nextByte = 0; - int shift = 0; - long value = 0; - boolean overflow = false; - while (true) { - nextByte = reader.readNextUnsignedByte(); - if (shift == 70 || (isSigned == false && shift == 63 && nextByte > 1)) { - // if the value being read is more than 64 bits long mark it as overflow. - // keep reading the rest of the number so the caller is not left in the - // middle of the LEB128 number's guts. - overflow = true; - } - - // must cast to long before shifting otherwise shift values greater than 32 cause problems - value |= ((long) (nextByte & 0x7F)) << shift; - shift += 7; - - if ((nextByte & 0x80) == 0) { - break; - } - } - if (overflow) { - throw new IOException( - "Unsupported LEB128 value, too large to fit in 64bit java long variable"); - } - if ((isSigned) && (shift < Long.SIZE) && ((nextByte & 0x40) != 0)) { - value |= -(1 << shift); - } - + public int asInt32() throws IOException { + ensureInt32s(value); + return (int) value; + } + + /** + * Returns the value as a 64bit primitive long. Interpreting the signed-ness of the + * value will depend on the way the value was read (ie. if {@link #readSignedValue(BinaryReader)} + * vs. {@link #readUnsignedValue(BinaryReader)} was used). + * + * @return long value. + */ + public long asLong() { return value; } /** - * Decodes a LEB128 number using a byte array and stores it in a long. - * This function cannot read numbers larger than Long.MAX_VALUE. - * @param bytes the bytes representing the LEB128 number - * @param isSigned true if the value is signed - * @throws IOException + * Returns the offset of the LEB128 value in the stream it was read from. + * + * @return stream offset of the LEB128 value */ - public static long decode(byte[] bytes, boolean isSigned) throws IOException { - return decode(bytes, 0, isSigned); + public long getOffset() { + return offset; } /** - * Decodes a LEB128 number using an offset into a byte array and stores it in a long. - * This function cannot read numbers larger than Long.MAX_VALUE. - * @param bytes the bytes representing the LEB128 number - * @param offset offset into the byte array - * @param isSigned true if the value is signed - * @throws IOException + * Returns the number of bytes that were used to store the LEB128 value in the stream + * it was read from. + * + * @return number of bytes used to store the read LEB128 value */ - public static long decode(byte[] bytes, int offset, boolean isSigned) throws IOException { + public int getLength() { + return byteLength; + } + + @Override + public String toString() { + return String.format("LEB128: value: %d, offset: %d, byteLength: %d", value, offset, + byteLength); + } + + /** + * Reads a LEB128 value from the BinaryReader and returns a {@link LEB128} instance + * that contains the value along with size and position metadata. + *

+ * See {@link #readAsLong(BinaryReader, boolean)}. + * + * @param reader {@link BinaryReader} to read bytes from + * @param isSigned true if the value is signed + * @return a {@link LEB128} instance with the read LEB128 value with metadata + * @throws IOException if an I/O error occurs or value is outside the range of a java + * 64 bit int + */ + public static LEB128 readValue(BinaryReader reader, boolean isSigned) throws IOException { + long offset = reader.getPointerIndex(); + long value = LEB128.readAsLong(reader, isSigned); + int size = (int) (reader.getPointerIndex() - offset); + return new LEB128(offset, value, size); + } + + /** + * Reads an unsigned LEB128 value from the BinaryReader and returns a {@link LEB128} instance + * that contains the value along with size and position metadata. + *

+ * See {@link #readAsLong(BinaryReader, boolean)}. + * + * @param reader {@link BinaryReader} to read bytes from + * @return a {@link LEB128} instance with the read LEB128 value with metadata + * @throws IOException if an I/O error occurs or value is outside the range of a java + * 64 bit int + */ + public static LEB128 readUnsignedValue(BinaryReader reader) throws IOException { + return readValue(reader, false); + } + + /** + * Reads an signed LEB128 value from the BinaryReader and returns a {@link LEB128} instance + * that contains the value along with size and position metadata. + *

+ * See {@link #readAsLong(BinaryReader, boolean)}. + * + * @param reader {@link BinaryReader} to read bytes from + * @return a {@link LEB128} instance with the read LEB128 value with metadata + * @throws IOException if an I/O error occurs or value is outside the range of a java + * 64 bit int + */ + public static LEB128 readSignedValue(BinaryReader reader) throws IOException { + return readValue(reader, true); + } + + /** + * Reads a LEB128 signed number from the BinaryReader and returns it as a java 32 bit int. + *

+ * If the value of the number can not fit in the int type, an {@link IOException} will + * be thrown. + * + * @param reader {@link BinaryReader} to read bytes from + * @return signed int32 value + * @throws IOException if error reading bytes or value is outside the + * range of a signed int32 + */ + public static int readAsInt32(BinaryReader reader) throws IOException { + long tmp = readAsLong(reader, true); + ensureInt32s(tmp); + return (int) tmp; + } + + /** + * Reads a LEB128 unsigned number from the BinaryReader and returns it as a java 32 bit int. + *

+ * If the value of the number can not fit in the positive range of the int type, + * an {@link IOException} will be thrown. + * + * @param reader {@link BinaryReader} to read bytes from + * @return unsigned int32 value 0..Integer.MAX_VALUE + * @throws IOException if error reading bytes or value is outside the + * positive range of a java 32 bit int (ie. 0..Integer.MAX_VALUE) + */ + public static int readAsUInt32(BinaryReader reader) throws IOException { + long tmp = readAsLong(reader, false); + ensureInt32u(tmp); + return (int) tmp; + } + + /** + * Reads a LEB128 number from the BinaryReader and returns it as a java 64 bit long int. + *

+ * Large unsigned integers that use all 64 bits are be returned in a java native + * 'long' type, which is signed. It is up to the caller to treat the value as unsigned. + *

+ * Large integers that use more than 64 bits will cause an IOException to be thrown. + *

+ * @param reader {@link BinaryReader} to read bytes from + * @param isSigned true if the value is signed + * @return long integer value. Caller must treat it as unsigned if isSigned parameter was + * set to false + * @throws IOException if an I/O error occurs or value is outside the range of a java + * 64 bit int + */ + public static long readAsLong(BinaryReader reader, boolean isSigned) throws IOException { int nextByte = 0; int shift = 0; long value = 0; - for (int i = offset; i < bytes.length; i++) { - nextByte = bytes[i] & NumberUtil.UNSIGNED_BYTE_MASK; - + while (true) { + nextByte = reader.readNextUnsignedByte(); if (shift == 70 || (isSigned == false && shift == 63 && nextByte > 1)) { throw new IOException( "Unsupported LEB128 value, too large to fit in 64bit java long variable"); @@ -141,7 +214,6 @@ public final class LEB128 { // must cast to long before shifting otherwise shift values greater than 32 cause problems value |= ((long) (nextByte & 0x7F)) << shift; - shift += 7; if ((nextByte & 0x80) == 0) { @@ -149,11 +221,62 @@ public final class LEB128 { } } if ((isSigned) && (shift < Long.SIZE) && ((nextByte & 0x40) != 0)) { - long tmp1 = (1L << shift); - long tmp2 = -tmp1; - value |= tmp2; + // 0x40 is the new 'high' sign bit since 0x80 is the continuation flag + value |= (-1L << shift); } return value; } + + /** + * Decodes a LEB128 number from a byte array and returns it as a long. + *

+ * See {@link #readAsLong(BinaryReader, boolean)}. + * + * @param bytes the bytes representing the LEB128 number + * @param isSigned true if the value is signed + * @return long integer value. Caller must treat it as unsigned if isSigned parameter was + * set to false + * @throws IOException if error reading bytes or value is outside the + * range of a java 64 bit int + */ + public static long decode(byte[] bytes, boolean isSigned) throws IOException { + return decode(bytes, 0, isSigned); + } + + /** + * Decodes a LEB128 number from a byte array and returns it as a long. + *

+ * See {@link #readAsLong(BinaryReader, boolean)}. + * + * @param bytes the bytes representing the LEB128 number + * @param offset offset in byte array of where to start reading bytes + * @param isSigned true if the value is signed + * @return long integer value. Caller must treat it as unsigned if isSigned parameter was + * set to false + * @throws IOException if error reading bytes or value is outside the + * range of a java 64 bit int + */ + public static long decode(byte[] bytes, int offset, boolean isSigned) throws IOException { + ByteArrayProvider bap = new ByteArrayProvider(bytes); + BinaryReader br = new BinaryReader(bap, true); + br.setPointerIndex(offset); + return readAsLong(br, isSigned); + } + + private static void ensureInt32u(long value) throws IOException { + if (value < 0 || value > Integer.MAX_VALUE) { + throw new IOException("LEB128 value out of range for java 32 bit unsigned int: " + + Long.toUnsignedString(value)); + } + } + + private static void ensureInt32s(long value) throws IOException { + if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) { + throw new IOException( + "LEB128 value out of range for java 32 bit signed int: " + + Long.toString(value)); + } + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/attribs/DWARFAttributeFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/attribs/DWARFAttributeFactory.java index 331f3d0502..c1b18fc653 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/attribs/DWARFAttributeFactory.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/attribs/DWARFAttributeFactory.java @@ -73,7 +73,7 @@ public class DWARFAttributeFactory { return new DWARFNumericAttribute(uoffset + unit.getStartOffset()); } case DW_FORM_ref_udata: { - long uoffset = LEB128.decode(reader, false); + long uoffset = LEB128.readAsLong(reader, false); return new DWARFNumericAttribute(uoffset + unit.getStartOffset()); } @@ -103,7 +103,7 @@ public class DWARFAttributeFactory { return new DWARFBlobAttribute(reader.readNextByteArray(length)); } case DW_FORM_block: { - int length = LEB128.decode32u(reader); + int length = LEB128.readAsUInt32(reader); if (length < 0 || length > MAX_BLOCK4_SIZE) { throw new IOException("Invalid/bad dw_form_block size: " + length); } @@ -121,12 +121,12 @@ public class DWARFAttributeFactory { case DW_FORM_data8: return new DWARFNumericAttribute(reader.readNextLong()); case DW_FORM_sdata: - return new DWARFNumericAttribute(LEB128.decode(reader, true)); + return new DWARFNumericAttribute(LEB128.readAsLong(reader, true)); case DW_FORM_udata: - return new DWARFNumericAttribute(LEB128.decode(reader, false)); + return new DWARFNumericAttribute(LEB128.readAsLong(reader, false)); case DW_FORM_exprloc: { - int length = LEB128.decode32u(reader); + int length = LEB128.readAsUInt32(reader); if (length < 0 || length > MAX_BLOCK4_SIZE) { throw new IOException("Invalid/bad dw_form_exprloc size: " + length); } @@ -157,7 +157,7 @@ public class DWARFAttributeFactory { // Indirect Form case DW_FORM_indirect: - DWARFForm formValue = DWARFForm.find(LEB128.decode32u(reader)); + DWARFForm formValue = DWARFForm.find(LEB128.readAsUInt32(reader)); DWARFAttributeValue value = read(reader, unit, formValue); return new DWARFIndirectAttribute(value, formValue); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/expression/DWARFExpression.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/expression/DWARFExpression.java index c5b8f66e45..3743f31fbe 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/expression/DWARFExpression.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/expression/DWARFExpression.java @@ -137,9 +137,9 @@ public class DWARFExpression { case U_LONG: return reader.readNextLong(); /* & there is no mask for ulong */ case S_LEB128: - return LEB128.decode(reader, true); + return LEB128.readAsLong(reader, true); case U_LEB128: - return LEB128.decode(reader, false); + return LEB128.readAsLong(reader, false); case SIZED_BLOB: throw new IOException("Can't read SIZED_BLOB as a Long value"); case DWARF_INT: diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/AndroidElfRelocationTableDataType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/AndroidElfRelocationTableDataType.java index 1b4b37bc19..4840bb148d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/AndroidElfRelocationTableDataType.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/AndroidElfRelocationTableDataType.java @@ -92,7 +92,7 @@ public class AndroidElfRelocationTableDataType extends DynamicDataType { static LEB128Info parse(BinaryReader reader, boolean signed) throws IOException { long nextPos = reader.getPointerIndex(); - long value = LEB128.decode(reader, signed); + long value = LEB128.readAsLong(reader, signed); long pos = reader.getPointerIndex(); int size = (int) (pos - nextPos); return new LEB128Info((int) nextPos, value, size); @@ -132,7 +132,7 @@ public class AndroidElfRelocationTableDataType extends DynamicDataType { // NOTE: assumes 2-GByte MemBuffer limit int offset = (int) reader.getPointerIndex(); - long groupSize = LEB128.decode(reader, true); + long groupSize = LEB128.readAsLong(reader, true); if (groupSize > remainingRelocations) { Msg.debug(this, "Group relocation count " + groupSize + " exceeded total count " + remainingRelocations); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfRelocationTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfRelocationTable.java index 5f80da427b..c4a8bcbaa5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfRelocationTable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfRelocationTable.java @@ -205,20 +205,20 @@ public class ElfRelocationTable implements ElfFileSection, ByteArrayConverter { try { int relocationIndex = 0; - long remainingRelocations = LEB128.decode(reader, true); - long offset = LEB128.decode(reader, true); + long remainingRelocations = LEB128.readAsLong(reader, true); + long offset = LEB128.readAsLong(reader, true); long addend = 0; while (remainingRelocations > 0) { - long groupSize = LEB128.decode(reader, true); + long groupSize = LEB128.readAsLong(reader, true); if (groupSize > remainingRelocations) { Msg.warn(this, "Group relocation count " + groupSize + " exceeded total count " + remainingRelocations); break; } - long groupFlags = LEB128.decode(reader, true); + long groupFlags = LEB128.readAsLong(reader, true); boolean groupedByInfo = (groupFlags & AndroidElfRelocationGroup.RELOCATION_GROUPED_BY_INFO_FLAG) != 0; boolean groupedByDelta = (groupFlags & @@ -228,22 +228,22 @@ public class ElfRelocationTable implements ElfFileSection, ByteArrayConverter { boolean groupHasAddend = (groupFlags & AndroidElfRelocationGroup.RELOCATION_GROUP_HAS_ADDEND_FLAG) != 0; - long groupOffsetDelta = groupedByDelta ? LEB128.decode(reader, true) : 0; - long groupRInfo = groupedByInfo ? LEB128.decode(reader, true) : 0; + long groupOffsetDelta = groupedByDelta ? LEB128.readAsLong(reader, true) : 0; + long groupRInfo = groupedByInfo ? LEB128.readAsLong(reader, true) : 0; if (groupedByAddend && groupHasAddend) { - addend += LEB128.decode(reader, true); + addend += LEB128.readAsLong(reader, true); } for (int i = 0; i < groupSize; i++) { - offset += groupedByDelta ? groupOffsetDelta : LEB128.decode(reader, true); + offset += groupedByDelta ? groupOffsetDelta : LEB128.readAsLong(reader, true); - long info = groupedByInfo ? groupRInfo : LEB128.decode(reader, true); + long info = groupedByInfo ? groupRInfo : LEB128.readAsLong(reader, true); long rAddend = 0; if (groupHasAddend) { if (!groupedByAddend) { - addend += LEB128.decode(reader, true); + addend += LEB128.readAsLong(reader, true); } rAddend = addend; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSectionHeaderType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSectionHeaderType.java index 8cfbd8b83e..a72d46bf06 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSectionHeaderType.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSectionHeaderType.java @@ -139,10 +139,6 @@ public class ElfSectionHeaderType { public final String description; public ElfSectionHeaderType(int value, String name, String description) { - if (value < 0) { - throw new IllegalArgumentException( - "ElfProgramHeaderType value out of range: 0x" + Long.toHexString(value)); - } this.value = value; this.name = name; this.description = description; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/GnuBuildIdSection.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/GnuBuildIdSection.java new file mode 100644 index 0000000000..a48e32f91c --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/GnuBuildIdSection.java @@ -0,0 +1,91 @@ +/* ### + * 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.elf; + +import static ghidra.app.util.bin.StructConverter.*; + +import ghidra.program.model.data.*; +import ghidra.program.model.mem.MemBuffer; +import ghidra.program.model.mem.MemoryAccessException; +import ghidra.util.exception.DuplicateNameException; + +/** + * Factory data type that marks up a Gnu Build-Id record from a + * ELF .note.gnu.build-id section + */ +public class GnuBuildIdSection extends FactoryStructureDataType { + private static final int MAX_SANE_STR_LENS = 1024; + private long sectionSize; + + /** + * Creates a new GnuBuildIdDataType instance. + * + * @param dtm the {@link DataTypeManager} for the program + * @param sectionSize the size of the section (for bounds checking, assumes this + * is the only record in the section) + */ + public GnuBuildIdSection(DataTypeManager dtm, long sectionSize) { + super("Gnu_BuildId", dtm); + this.sectionSize = sectionSize; + } + + @Override + public DataType clone(DataTypeManager dtm) { + if (dtm == dataMgr) { + return this; + } + return new GnuBuildIdSection(dtm, sectionSize); + } + + @Override + protected void populateDynamicStructure(MemBuffer buf, Structure es) { + try { + long nameLen = buf.getUnsignedInt(0); + long descLen = buf.getUnsignedInt(4); + if (nameLen > MAX_SANE_STR_LENS || descLen > MAX_SANE_STR_LENS || + nameLen + descLen + 12 /* sizeof int fields */ > sectionSize) { + return; + } + + es.add(DWORD, "namesz", "Length of name field"); + es.add(DWORD, "descsz", "Length of description field"); + es.add(DWORD, "type", "Vendor specific type"); + if (nameLen > 0) { + es.add(StringDataType.dataType, (int) nameLen, "name", "Build-id vendor name"); + } + if (descLen > 0) { + es.add(new ArrayDataType(BYTE, (int) descLen, BYTE.getLength(), dataMgr), + "description", "Build-id value"); + } + } + catch (MemoryAccessException e) { + // ignore and drop thru with partial defined structure type + } + + } + + @Override + protected Structure setCategoryPath(Structure struct, MemBuffer buf) { + try { + struct.setCategoryPath(new CategoryPath("/ELF")); + } + catch (DuplicateNameException e) { + // ignore - will not happen + } + return struct; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/GnuDebugLinkSection.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/GnuDebugLinkSection.java new file mode 100644 index 0000000000..a26cca5f49 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/GnuDebugLinkSection.java @@ -0,0 +1,75 @@ +/* ### + * 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.elf; + +import static ghidra.app.util.bin.StructConverter.*; + +import ghidra.docking.settings.SettingsImpl; +import ghidra.program.model.data.*; +import ghidra.program.model.mem.MemBuffer; +import ghidra.util.NumericUtilities; +import ghidra.util.exception.DuplicateNameException; + +/** + * Factory data type that marks up a ELF .gnu_debuglink section. + */ +public class GnuDebugLinkSection extends FactoryStructureDataType { + private long sectionSize; + + /** + * Creates a new GnuDebugLinkDataType instance. + * + * @param dtm the program's {@link DataTypeManager} + * @param sectionSize the size of the section (for bounds checking) + */ + public GnuDebugLinkSection(DataTypeManager dtm, long sectionSize) { + super("Gnu_DebugLink", dtm); + this.sectionSize = sectionSize; + } + + @Override + public DataType clone(DataTypeManager dtm) { + if (dtm == dataMgr) { + return this; + } + return new GnuDebugLinkSection(dtm, sectionSize); + } + + @Override + protected void populateDynamicStructure(MemBuffer buf, Structure es) { + StringDataInstance filenameStr = StringDataInstance.getStringDataInstance( + StringDataType.dataType, buf, SettingsImpl.NO_SETTINGS, -1); + int filenameLen = filenameStr.getStringLength(); + if (filenameLen <= 0 || filenameLen + 4 /* crc field */ > sectionSize) { + return; + } + filenameLen = (int) NumericUtilities.getUnsignedAlignedValue(filenameLen, 4); + es.add(StringDataType.dataType, filenameLen, "filename", "Debug file name"); + es.add(DWORD, "crc", null); + } + + @Override + protected Structure setCategoryPath(Structure struct, MemBuffer buf) { + try { + struct.setCategoryPath(new CategoryPath("/ELF")); + } + catch (DuplicateNameException e) { + // ignore - will not happen + } + return struct; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/html/FunctionDataTypeHTMLRepresentation.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/html/FunctionDataTypeHTMLRepresentation.java index 160f540c36..d2c780c871 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/html/FunctionDataTypeHTMLRepresentation.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/html/FunctionDataTypeHTMLRepresentation.java @@ -104,8 +104,8 @@ public class FunctionDataTypeHTMLRepresentation extends HTMLDataTypeRepresentati String name = var.getName(); DataType locatableType = getLocatableDataType(dataType); - lines.add(new VariableTextLine(HTMLUtilities.friendlyEncodeHTML(displayName), name, - locatableType)); + lines.add(new VariableTextLine(HTMLUtilities.friendlyEncodeHTML(displayName), + HTMLUtilities.friendlyEncodeHTML(name), locatableType)); } return lines; @@ -114,7 +114,7 @@ public class FunctionDataTypeHTMLRepresentation extends HTMLDataTypeRepresentati private static String buildHTMLText(TextLine returnType, TextLine functionName, List arguments, TextLine varArgs, TextLine voidArgs) { - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); String returnTypeText = returnType.getText(); returnTypeText = wrapStringInColor(returnTypeText, returnType.getTextColor()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java index 6198ce639d..4d273f4e6d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java @@ -167,6 +167,8 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper { markupHashTable(monitor); markupGnuHashTable(monitor); + markupGnuBuildId(monitor); + markupGnuDebugLink(monitor); processGNU(monitor); processGNU_readOnly(monitor); @@ -749,7 +751,7 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper { Address baseAddress = relocationSpace.getTruncatedAddress(baseWordOffset, true); // long relocationOffset = baseWordOffset + reloc.getOffset(); // r_offset is defined to be a byte offset (assume byte size is 1) - Address relocAddr = context != null ? context.getRelocationAddress(baseAddress, reloc.getOffset()) : baseAddress.addWrap(reloc.getOffset());; + Address relocAddr = context != null ? context.getRelocationAddress(baseAddress, reloc.getOffset()) : baseAddress.addWrap(reloc.getOffset()); // Address relocAddr = relocationSpace.getTruncatedAddress(relocationOffset, true); long[] values = new long[] { reloc.getSymbolIndex() }; @@ -867,7 +869,7 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper { } Structure phStructDt = (Structure) elf.getProgramHeaders()[0].toDataType(); - phStructDt = (Structure) phStructDt.clone(program.getDataTypeManager()); + phStructDt = phStructDt.clone(program.getDataTypeManager()); Array arrayDt = new ArrayDataType(phStructDt, headerCount, size); @@ -925,7 +927,7 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper { } Structure shStructDt = (Structure) elf.getSections()[0].toDataType(); - shStructDt = (Structure) shStructDt.clone(program.getDataTypeManager()); + shStructDt = shStructDt.clone(program.getDataTypeManager()); Array arrayDt = new ArrayDataType(shStructDt, elf.e_shnum(), elf.e_shentsize()); @@ -1899,6 +1901,38 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper { // return new AddressRangeImpl(alignedAddress, freeRange.getMaxAddress()); // } + private void markupGnuBuildId(TaskMonitor monitor) { + + ElfSectionHeader sh = elf.getSection(".note.gnu.build-id"); + Address addr = findLoadAddress(sh, 0); + if (addr == null) { + return; + } + try { + listing.createData(addr, + new GnuBuildIdSection(program.getDataTypeManager(), sh.getSize())); + } + catch (Exception e) { + log("Failed to properly markup Gnu Build-Id at " + addr + ": " + getMessage(e)); + } + } + + private void markupGnuDebugLink(TaskMonitor monitor) { + + ElfSectionHeader sh = elf.getSection(".gnu_debuglink"); + Address addr = findLoadAddress(sh, 0); + if (addr == null) { + return; + } + try { + listing.createData(addr, + new GnuDebugLinkSection(program.getDataTypeManager(), sh.getSize())); + } + catch (Exception e) { + log("Failed to properly markup Gnu DebugLink at " + addr + ": " + getMessage(e)); + } + } + private void markupHashTable(TaskMonitor monitor) { ElfDynamicTable dynamicTable = elf.getDynamicTable(); diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf4/LEB128Test.java b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf4/LEB128Test.java index 6983990862..de4c6b4a79 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf4/LEB128Test.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf4/LEB128Test.java @@ -15,154 +15,199 @@ */ package ghidra.app.util.bin.format.dwarf4; -import ghidra.app.util.bin.BinaryReader; -import ghidra.app.util.bin.ByteArrayProvider; +import static org.junit.Assert.*; import java.io.IOException; +import java.util.List; import org.junit.Assert; import org.junit.Test; +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.ByteArrayProvider; +import ghidra.util.NumericUtilities; + public class LEB128Test { private static final boolean BR_IS_LITTLE_ENDIAN = true; - private BinaryReader br(byte... bytes) { + private BinaryReader br(int... intBytes) { + byte[] bytes = new byte[intBytes.length]; + for (int i = 0; i < intBytes.length; i++) { + bytes[i] = (byte) intBytes[i]; + } return new BinaryReader(new ByteArrayProvider(bytes), BR_IS_LITTLE_ENDIAN); } - /** - * Test reading the largest unsigned LEB128 int that we can handle (64 used bits). - *

- * - * @throws IOException - */ - @Test - public void testMax64bitUnsigned() throws IOException { - byte[] bytes = new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, - (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01 }; + static class TestEntry { + long expectedValue; + byte[] bytes; - long value = LEB128.decode(bytes, false); - - // -1 signed long == MAX unsigned - Assert.assertEquals(-1, value); + TestEntry(long expectedValue, byte[] bytes) { + this.expectedValue = expectedValue; + this.bytes = bytes; + } } - /** - * Test reading a number that is larger than 32 bits to ensure that all the shifting - * done in the LEB128 reader doesn't have a 32bit fault. - * @throws IOException - */ - @Test - public void test36bitUnsigned() throws IOException { - byte[] bytes = new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, - (byte) 0x01 }; + private TestEntry te(long expectedValue, int... intBytes) { + byte[] bytes = new byte[intBytes.length]; + for (int i = 0; i < intBytes.length; i++) { + bytes[i] = (byte) intBytes[i]; + } + return new TestEntry(expectedValue, bytes); + } - long value = LEB128.decode(bytes, false); - Assert.assertEquals(0xfffffffffL, value); + private List unsignedTestEntries = List.of( + // misc + te(0L, 0x80, 0x80, 0x80, 0x80, 0x80, 0x0), // Tests reading a zero value that is encoded in non-optimal way. + te(-1L, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01), // -1 == MAX unsigned long + te(0xf_ffff_ffffL, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01), // more than 32 bits to test shifting > 32bits + + // 1 byte + te(1L, 0x01), + te(63L, 0x3f), + te(64L, 0x40), + + // 1 byte to 2 byte transition + te(125L, 0x7d), + te(126L, 0x7e), + te(127L, 0x7f), + te(128L, 0x80, 0x01), + te(129L, 0x81, 0x01), + te(130L, 0x82, 0x01), + te(131L, 0x83, 0x01), + + te(254L, 0xfe, 0x01), + te(255L, 0xff, 0x01), + te(256L, 0x80, 0x02), + te(257L, 0x81, 0x02), + + // 2 byte to 3 byte transition + te(16382L, 0xfe, 0x7f), + te(16383L, 0xff, 0x7f), + te(16384L, 0x80, 0x80, 0x01), + te(16385L, 0x81, 0x80, 0x01), + + // 3 byte to 4 byte transition + te(2097151L, 0xff, 0xff, 0x7f), + te(2097152L, 0x80, 0x80, 0x80, 0x01), + te(2097153L, 0x81, 0x80, 0x80, 0x01), + + // 4 byte to 5 byte transition + te(268435455L, 0xff, 0xff, 0xff, 0x7f), + te(268435456L, 0x80, 0x80, 0x80, 0x80, 0x01), + te(268435457L, 0x81, 0x80, 0x80, 0x80, 0x01), + + // 5 byte to 6 byte transition + te(34359738367L, 0xff, 0xff, 0xff, 0xff, 0x7f), + te(34359738368L, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01), + te(34359738369L, 0x81, 0x80, 0x80, 0x80, 0x80, 0x01) + // + ); + + private List signedTestEntries = List.of( + // misc + te(-2130303778817L, 0xff, 0xff, 0xff, 0xff, 0xff, 0x41), + + // 1 byte positive stuff + te(0L, 0x00), + te(1L, 0x01), + + // 1 byte to 2 byte transition (positive) + te(63L, 0x3f), + te(64L, 0xc0, 0x00), + te(65L, 0xc1, 0x00), + te(66L, 0xc2, 0x00), + + te(126L, 0xfe, 0x00), + te(127L, 0xff, 0x00), + te(128L, 0x80, 0x01), + te(129L, 0x81, 0x01), + + te(254L, 0xfe, 0x01), + te(255L, 0xff, 0x01), + te(256L, 0x80, 0x02), + te(257L, 0x81, 0x02), + + // 2 byte to 3 byte transition + te(8190L, 0xfe, 0x3f), + te(8191L, 0xff, 0x3f), + te(8192L, 0x80, 0xc0, 0x00), + te(8193L, 0x81, 0xc0, 0x00), + + // 1 byte negative stuff + te(-1L, 0x7f), + te(-2L, 0x7e), + te(-3L, 0x7d), + te(-4L, 0x7c), + te(-5L, 0x7b), + te(-6L, 0x7a), + + // 1 byte to 2 byte transition (negative) + te(-64L, 0x40), + te(-65L, 0xbf, 0x7f), + + te(-127, 0x81, 0x7f), + te(-128, 0x80, 0x7f), + te(-129, 0xff, 0x7e), + + // 2 byte to 3 byte transition (negative) + te(-8191L, 0x81, 0x40), + te(-8192L, 0x80, 0x40), + te(-8193L, 0xff, 0xbf, 0x7f), + te(-8194L, 0xfe, 0xbf, 0x7f) + + ); + + @Test + public void testUnsignedTestEntries() throws IOException { + testTestEntries(unsignedTestEntries, false, "Unsigned TestEntry"); } @Test - public void testSignedNeg2() throws IOException { - byte[] bytes = new byte[] { (byte) 0x7e }; - - long value = LEB128.decode(bytes, true); - Assert.assertEquals(-2, value); + public void testSignedTestEntries() throws IOException { + testTestEntries(signedTestEntries, true, "Signed TestEntry"); } - @Test - public void testSignedNeg127() throws IOException { - byte[] bytes = new byte[] { (byte) 0x81, (byte) 0x7f }; - - long value = LEB128.decode(bytes, true); - Assert.assertEquals(-127, value); + public void testTestEntries(List testEntries, boolean signed, String name) + throws IOException { + for (int i = 0; i < testEntries.size(); i++) { + TestEntry te = testEntries.get(i); + BinaryReader br = + new BinaryReader(new ByteArrayProvider(te.bytes), BR_IS_LITTLE_ENDIAN); + long actualValue = LEB128.readAsLong(br, signed); + assertEquals(String.format( + "%s[%d] failed: leb128(%s) != %d. Expected=%d / %x, actual=%d / %x", + name, i, NumericUtilities.convertBytesToString(te.bytes), te.expectedValue, + te.expectedValue, te.expectedValue, actualValue, actualValue), te.expectedValue, + actualValue); + assertEquals(String.format("%s[%d] failed: left-over bytes: %d", name, i, + te.bytes.length - br.getPointerIndex()), te.bytes.length, br.getPointerIndex()); + } } - @Test - public void testSignedNeg128() throws IOException { - byte[] bytes = new byte[] { (byte) 0x80, (byte) 0x7f }; - - long value = LEB128.decode(bytes, true); - Assert.assertEquals(-128, value); - } - - @Test - public void testSignedNeg129() throws IOException { - byte[] bytes = new byte[] { (byte) 0xff, (byte) 0x7e }; - - long value = LEB128.decode(bytes, true); - Assert.assertEquals(-129, value); - } - - @Test - public void testSigned129() throws IOException { - byte[] bytes = new byte[] { (byte) 0x81, (byte) 0x1 }; - - long value = LEB128.decode(bytes, true); - Assert.assertEquals(129, value); - } - - /** - * Tests reading an zero value that is encoded in non-optimal way. - * @throws IOException - */ - @Test - public void testAltZeroEncoded() throws IOException { - byte[] bytes = new byte[] { (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, - (byte) 0x0 }; - - long value = LEB128.decode(bytes, false); - Assert.assertEquals(0, value); - } - - /** - * Test a 36bit signed negative value. - * - * @throws IOException - */ - @Test - public void test36bitSignedNeg() throws IOException { - byte[] bytes = new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, - (byte) 0x41 }; - - long value = LEB128.decode(bytes, true); - Assert.assertEquals(-2130303778817L, value); - } - - /** - * Test reading a unsigned LEB128 that is just 1 bit too large for a java 64 bit long int. - * @throws IOException - */ - @Test + @Test(expected = IOException.class) public void testToolargeUnsigned() throws IOException { - byte[] bytes = new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, - (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x02 }; + // Test reading a unsigned LEB128 that is just 1 bit too large for a java 64 bit long int. - try { - long value = LEB128.decode(bytes, false); - Assert.fail( - "Should not be able to read a LEB128 that is larger than what can fit in java 64bit long int: " + - value); - } - catch (IOException ioe) { - // good - } + BinaryReader br = br(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02); + + long value = LEB128.readAsLong(br, false); + Assert.fail( + "Should not be able to read a LEB128 that is larger than what can fit in java 64bit long int: " + + value); } - /** - * Test that the BinaryReader stream is correctly positioned after the LEB128 bytes after - * reading a LEB128 that is too large for a java 64 bit long int. - * - * @throws IOException - */ @Test - public void testToolargeBinaryReaderStreamPosition() throws IOException { - BinaryReader br = br((byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, - (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, - (byte) 0x02, (byte) 0x1, (byte) 0x2); + public void testTooLargeValueBinaryReaderStreamPosition() { + // Test that the BinaryReader stream is 'correctly' positioned after the LEB128 bytes after + // reading a LEB128 that is too large for a java 64 bit long int. + + BinaryReader br = + br(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02, 0x1, 0x2); try { - long value = LEB128.decode(br, false); + long value = LEB128.readAsLong(br, false); Assert.fail( "Should not be able to read a LEB128 that is larger than what can fit in java 64bit long int: " + Long.toUnsignedString(value)); @@ -171,38 +216,23 @@ public class LEB128Test { // good } - Assert.assertEquals(1, br.readNextByte() & 0xFF); - Assert.assertEquals(2, br.readNextByte() & 0xFF); + Assert.assertEquals(10, br.getPointerIndex()); } - /** - * Test uint32 max - * - * @throws IOException - */ @Test public void testUint32Max() throws IOException { - int value = - LEB128.decode32u(br((byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07)); + int value = LEB128.readAsUInt32(br(0xff, 0xff, 0xff, 0xff, 0x07)); Assert.assertEquals(Integer.MAX_VALUE, value); } - /** - * Test uint32 max overflow with 0xff_ff_ff_ff - * - * @throws IOException - */ - @Test + @Test(expected = IOException.class) public void testUint32Overflow() throws IOException { - try { - int value = LEB128.decode32u( - br((byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f)); - Assert.fail( - "Should not be able to read a LEB128 that is larger than what can fit in java 32 bit int: " + - Integer.toUnsignedString(value)); - } - catch (IOException ioe) { - // good - } + + // Test uint32 max overflow with 0xff_ff_ff_ff + + int value = LEB128.readAsUInt32(br(0xff, 0xff, 0xff, 0xff, 0x0f)); + Assert.fail( + "Should not be able to read a LEB128 that is larger than what can fit in java 32 bit int: " + + Integer.toUnsignedString(value)); } } diff --git a/Ghidra/Features/Decompiler/build.gradle b/Ghidra/Features/Decompiler/build.gradle index f398c9414d..d0251eb6a9 100644 --- a/Ghidra/Features/Decompiler/build.gradle +++ b/Ghidra/Features/Decompiler/build.gradle @@ -546,6 +546,7 @@ model { } } else if (b.toolChain in Clang) { + b.cppCompiler.args "-std=c++11" b.cppCompiler.args "-Wall" b.cppCompiler.args "-O2" // for DEBUG, comment this line out // b.cppCompiler.args "-g" // for DEBUG, uncomment this line diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/types.h b/Ghidra/Features/Decompiler/src/decompile/cpp/types.h index d549d65959..d109066370 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/types.h +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/types.h @@ -181,6 +181,20 @@ typedef char int1; typedef uint8 uintp; #endif +#if defined (__APPLE_CC__) && defined (__aarch64__) +#define HOST_ENDIAN 0 +typedef unsigned int uintm; +typedef int intm; +typedef unsigned long uint8; +typedef long int8; +typedef unsigned int uint4; +typedef int int4; +typedef unsigned short uint2; +typedef short int2; +typedef unsigned char uint1; +typedef char int1; +typedef uint8 uintp; +#endif #if defined(_WINDOWS) #pragma warning (disable:4312) diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/string/variadic/FormatArgument.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/string/variadic/FormatArgument.java new file mode 100644 index 0000000000..f71909e425 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/string/variadic/FormatArgument.java @@ -0,0 +1,63 @@ +/* ### + * 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.string.variadic; +/** + * This class represents a single argument of a variadic function + */ +public class FormatArgument { + + private String lengthModifier; + private String conversionSpecifier; + + /** + * Constructor for a FormatArg + * + * @param lengthModifier length modifier of a format argument + * @param conversionSpec conversion specifier of a format argument + */ + public FormatArgument(String lengthModifier, String conversionSpec) { + this.lengthModifier = lengthModifier; + this.conversionSpecifier = conversionSpec; + } + + /** + * lenghtModifier getter + * + * @return lengthModifier + */ + public String getLengthModifier() { + return this.lengthModifier; + } + + /** + * convertionSpec getter + * + * @return conversionSpecifier + */ + public String getConversionSpecifier() { + return this.conversionSpecifier; + } + + /** + * Converts FormatArg to String + * + * @return FormatArgument as String + */ + public String toString() { + + return String.format("[%s, %s]", this.lengthModifier, this.conversionSpecifier); + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/string/variadic/FormatStringAnalyzer.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/string/variadic/FormatStringAnalyzer.java new file mode 100644 index 0000000000..9d9cd23198 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/string/variadic/FormatStringAnalyzer.java @@ -0,0 +1,392 @@ +/* ### + * 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.string.variadic; + +import java.util.*; + +import org.apache.commons.collections4.IteratorUtils; + +import ghidra.app.decompiler.*; +import ghidra.app.decompiler.parallel.*; +import ghidra.app.services.*; +import ghidra.app.util.importer.MessageLog; +import ghidra.framework.options.Options; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.data.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.pcode.HighFunctionDBUtil; +import ghidra.program.model.pcode.PcodeOpAST; +import ghidra.program.util.DefinedDataIterator; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.InvalidInputException; +import ghidra.util.task.TaskMonitor; + +public class FormatStringAnalyzer extends AbstractAnalyzer { + + // Array of substrings of variadic function names that are searched for + private static final String[] VARIADIC_SUBSTRINGS = { "printf", "scanf" }; + private static final String NAME = "Variadic Function Signature Override"; + private static final String DESCRIPTION = + "Detects variadic function calls in the bodies of each function that intersect the" + + "current selection and parses their format string arguments to infer the correct " + + "signatures. Currently, this analyzer only supports printf, scanf, and thier variants " + + "(e.g., snprintf, fscanf). If the current selection is empty, it searches through " + + "every function. Once the correct signatures are inferred, they are overridden."; + private final static boolean OPTION_DEFAULT_CREATE_BOOKMARKS_ENABLED = false; + private final static String OPTION_NAME_CREATE_BOOKMARKS = "Create Analysis Bookmarks"; + private static final String OPTION_DESCRIPTION_CREATE_BOOKMARKS = + "Select this check box if you want this analyzer to create analysis bookmarks " + + "when items of interest are created/identified by the analyzer."; + + private boolean createBookmarksEnabled = OPTION_DEFAULT_CREATE_BOOKMARKS_ENABLED; + + // Any function name containing this substring is determined to be an input type function + private static final String INPUT_FUNCTION_SUBSTRING = "scanf"; + private Program currentProgram = null; + private FormatStringParser parser; + + public FormatStringAnalyzer() { + super(NAME, DESCRIPTION, AnalyzerType.FUNCTION_SIGNATURES_ANALYZER); + setSupportsOneTimeAnalysis(); + setPriority(AnalysisPriority.LOW_PRIORITY); + setDefaultEnablement(false); + setPrototype(); + } + + @Override + public boolean canAnalyze(Program program) { + return true; + } + + private synchronized FormatStringParser getParser() { + if (parser == null) { + parser = new FormatStringParser(currentProgram); + } + return parser; + } + + private synchronized void disposeParser() { + parser = null; + } + + @Override + public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) { + this.currentProgram = program; + try { + run(set, monitor); + } + catch (CancelledException e) { + // User cancelled analysis + } + finally { + disposeParser(); + } + return true; + } + + private void run(AddressSetView selection, TaskMonitor monitor) + throws CancelledException { + + DefinedDataIterator dataIterator = DefinedDataIterator.definedStrings(currentProgram); + Map stringsByAddress = new HashMap<>(); + for (Data data : dataIterator) { + String s = data.getDefaultValueRepresentation(); + if (s.contains("%")) { + stringsByAddress.put(data.getAddress(), data); + } + monitor.checkCanceled(); + } + + FunctionIterator functionIterator = currentProgram.getListing().getFunctions(true); + FunctionIterator externalIterator = currentProgram.getListing().getExternalFunctions(); + Iterator programFunctionIterator = IteratorUtils.chainedIterator(functionIterator,externalIterator); + Map> namesToParameters = new HashMap<>(); + + Map namesToReturn = new HashMap<>(); + Set toDecompile = new HashSet<>(); + Set variadicFunctionNames = new HashSet<>(); + + // Find variadic function names and their parameter data types + for (Function function : IteratorUtils.asIterable(programFunctionIterator)) { + String name = function.getName().strip(); + if (usesVariadicFormatString(function)) { + for (String variadicSubstring : VARIADIC_SUBSTRINGS) { + if (name.contains(variadicSubstring)) { + variadicFunctionNames.add(name); + namesToParameters.put(name, getParameters(function)); + namesToReturn.put(name, function.getReturnType()); + break; + } + } + } + monitor.checkCanceled(); + } + + Iterator functionsToSearchIterator = selection != null + ? currentProgram.getFunctionManager() + .getFunctionsOverlapping(selection) + : currentProgram.getFunctionManager().getFunctionsNoStubs(true); + + // Find functions that call variadic functions + while (functionsToSearchIterator.hasNext()) { + Function function = functionsToSearchIterator.next(); + Set calledFunctions = function.getCalledFunctions(monitor); + for (Function calledFunction : calledFunctions) { + // If this function calls a variadic function, add it to functions to decompile + if (namesToParameters.containsKey(calledFunction.getName())) { + toDecompile.add(function); + break; + } + } + monitor.checkCanceled(); + } + + decompile(currentProgram, monitor, stringsByAddress, variadicFunctionNames, + namesToParameters, + namesToReturn, + toDecompile); + } + + private void decompile(Program program, TaskMonitor monitor, + Map stringsByAddress, + Set variadicFunctionNames, + Map> namesToParameters, Map namesToReturn, + Set toDecompile) { + + DecompilerCallback callback = initDecompilerCallback(program, stringsByAddress, + variadicFunctionNames, namesToParameters, namesToReturn); + if (toDecompile.isEmpty()) { + Msg.info(this, "No functions detected that make variadic function calls with " + + "format strings containing format specifiers"); + return; + } + try { + ParallelDecompiler.decompileFunctions(callback, toDecompile, monitor); + } + catch (Exception e) { + Msg.error(this, "Error: could not decompile functions with ParallelDecompiler", e); + } + finally { + callback.dispose(); + } + } + + private DecompilerCallback initDecompilerCallback(Program program, + Map stringsByAddress, + Set variadicFuncNames, Map> namesToParameters, + Map namesToReturn) { + return new DecompilerCallback<>(program, + new VariadicSignatureDecompileConfigurer()) { + @Override + public Void process(DecompileResults results, TaskMonitor tMonitor) throws Exception { + if (results == null) { + return null; + } + Function function = results.getFunction(); + PcodeFunctionParser pcodeParser = new PcodeFunctionParser(program); + if (results.getHighFunction() == null || + results.getHighFunction().getPcodeOps() == null) { + return null; + } + Iterator pcodeOpASTIterator = results.getHighFunction().getPcodeOps(); + List pcodeOpASTs = new ArrayList<>(); + if ((results.getHighFunction() != null) && pcodeOpASTIterator != null) { + while (pcodeOpASTIterator.hasNext()) { + PcodeOpAST pcodeAST = pcodeOpASTIterator.next(); + pcodeOpASTs.add(pcodeAST); + } + } + List functionCallDataList = pcodeParser.parseFunctionForCallData( + pcodeOpASTs, stringsByAddress, variadicFuncNames); + if (functionCallDataList != null && functionCallDataList.size() > 0) { + overrideCallList(program, function, functionCallDataList, namesToParameters, + namesToReturn); + } + tMonitor.checkCanceled(); + return null; + } + }; + } + + private List getParameters(Function function) { + // NOTE: Currently only considers variadic functions with format string + // arguments. + List dataTypes = new ArrayList<>(); + for (ParameterDefinition pd : function.getSignature().getArguments()) { + dataTypes.add(pd.getDataType()); + } + return dataTypes; + } + + private boolean usesVariadicFormatString(Function function) { + int paramCount = function.getParameterCount(); + return function.hasVarArgs() && paramCount > 0 && + isCharPointer(function.getParameters()[paramCount - 1].getDataType()); + } + + private boolean isCharPointer(DataType dataType) { + if (dataType instanceof TypeDef) { + dataType = ((TypeDef) dataType).getBaseDataType(); + } + if (!(dataType instanceof Pointer)) { + return false; + } + DataType dt = ((Pointer) dataType).getDataType(); + return dt instanceof CharDataType || dt instanceof WideCharDataType || + dt instanceof WideChar16DataType || dt instanceof WideChar32DataType; + } + + private class VariadicSignatureDecompileConfigurer implements DecompileConfigurer { + + // DecompInterface allows for control of decompilation processes + @Override + public void configure(DecompInterface decompiler) { + decompiler.toggleCCode(true); // Produce C code + decompiler.toggleSyntaxTree(true); // Produce syntax tree + decompiler.openProgram(currentProgram); + decompiler.setSimplificationStyle("normalize"); + DecompileOptions options = new DecompileOptions(); + options.grabFromProgram(currentProgram); + decompiler.setOptions(options); + } + } + + private ParameterDefinition[] parseParameters(Function function, + Address address, + String callFunctionName, String formatString, + Map> namesToParameters) { + + Program functionProgram = function.getProgram(); + + FormatStringParser parser = getParser(); + + // DataTypes of arguments are treated differently when the variadic function + // looks like scanf since it takes in inputs. We need this information + // so that the correct DataType arguments are generated + boolean isOutputType = !callFunctionName.contains(INPUT_FUNCTION_SUBSTRING); + List formatArguments = + parser.convertToFormatArgumentList(formatString, isOutputType); + + DataType[] dataTypes = isOutputType ? parser.convertToOutputDataTypes(formatArguments) + : parser.convertToInputDataTypes(formatArguments); + + if (dataTypes == null) { + + currentProgram.getBookmarkManager() + .setBookmark(address, BookmarkType.ANALYSIS, "Unrecognized format string", + "Format string could not be parsed: " + formatString); + return null; + } + ParameterDefinition[] paramDefs = + createParameters(callFunctionName, dataTypes, functionProgram, namesToParameters); + return paramDefs; + } + + private ParameterDefinition[] createParameters(String callFunctionName, DataType[] dataTypes, + Program program, Map> namesToParameters) { + List initialFunctionParameters = namesToParameters.get(callFunctionName); + int numberOfParameters = initialFunctionParameters.size() + dataTypes.length; + if (numberOfParameters == 0) { + return null; // Invalid function + } + ParameterDefinition[] parameterDefinitions = new ParameterDefinition[numberOfParameters]; + for (int i = 0; i < numberOfParameters; i++) { + if (i < initialFunctionParameters.size()) { + parameterDefinitions[i] = + new ParameterDefinitionImpl("param" + i, initialFunctionParameters.get(i), ""); + } + else { + parameterDefinitions[i] = new ParameterDefinitionImpl("param" + i, + dataTypes[i - initialFunctionParameters.size()], ""); + } + } + return parameterDefinitions; + } + + private FunctionSignature initSignature(Function function, Address address, + String callFunctionName, String formatString, + Map> namesToParameters, Map namesToReturn) { + ParameterDefinition[] parameterDefinitions = + parseParameters(function, address, callFunctionName, formatString, namesToParameters); + if (parameterDefinitions == null || parameterDefinitions.length == 0) { + return null; + } + + FunctionDefinitionDataType signature = new FunctionDefinitionDataType(callFunctionName); + signature.setArguments(parameterDefinitions); + signature.setReturnType(namesToReturn.get(callFunctionName)); + return signature; + } + + private void overrideCallList(Program program, Function function, + List functionCallDataList, + Map> namesToParameters, Map namesToReturn) { + if (function == null || functionCallDataList == null) { + return; + } + for (FunctionCallData data : functionCallDataList) { + overrideFunctionCall(program, function, data.getAddressOfCall(), data.getCallFuncName(), + data.getFormatString(), namesToParameters, namesToReturn); + } + } + + private void overrideFunctionCall(Program program, Function function, Address address, + String callFunctionName, String formatString, + Map> namesToParameters, + Map namesToReturn) { + if (formatString == null) { + return; + } + FunctionSignature functionSignature = initSignature(function, address, callFunctionName, + formatString, namesToParameters, namesToReturn); + if (functionSignature == null || function == null || address == null) { + return; + } + + try { + if (createBookmarksEnabled) { + BookmarkManager bookmark = program.getBookmarkManager(); + bookmark.setBookmark(address, BookmarkType.ANALYSIS, + "Function Signature Override", + "Override for call to function " + callFunctionName); + } + HighFunctionDBUtil.writeOverride(function, address, functionSignature); + } + catch (InvalidInputException e) { + Msg.error(this, "Error: invalid input given to writeOverride()", e); + } + } + + @Override + public boolean removed(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) + throws CancelledException { + return false; + } + + @Override + public void registerOptions(Options options, Program program) { + options.registerOption(OPTION_NAME_CREATE_BOOKMARKS, createBookmarksEnabled, null, + OPTION_DESCRIPTION_CREATE_BOOKMARKS); + } + + @Override + public void optionsChanged(Options options, Program program) { + createBookmarksEnabled = + options.getBoolean(OPTION_NAME_CREATE_BOOKMARKS, createBookmarksEnabled); + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/string/variadic/FormatStringParser.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/string/variadic/FormatStringParser.java new file mode 100644 index 0000000000..957d25b555 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/string/variadic/FormatStringParser.java @@ -0,0 +1,961 @@ +/* ### + * 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.string.variadic; + +import java.util.*; +import java.util.stream.Collectors; + +import ghidra.program.model.data.*; +import ghidra.program.model.listing.Program; +import ghidra.util.Msg; + +/** + * Class for parsing a variadic function's format String to determine the proper + * number of arguments and their DataTypes. It analyzes format strings from variadic functions. + * Parses format strings adhering to docs https://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html + * and https://en.cppreference.com/w/c/io/fscanf. If a format string doesn't adhere properly + * to what is specified in the docs, the string will not continue to be parsed since this is + * undefined behavior. + *
+ * The standard C formats may make optional use of the following extended precision types + * which may be defined as a {@link TypeDef} the appropriate Datatype implementation. + * If a format string is encountered which refers to one of these types which has not + * previously been defined, a TypeDef will be fabricated although it may not be correct. + *

    + *
  • intmax_t - maximum sized signed integer (default: long long)
  • + *
  • uintmax_t - maximum size unsigned integer (default: unsigned long long)
  • + *
  • size_t - unsigned integer type corresponding to sizeof (default: varies)
  • + *
  • ptrdiff_t - signed integer type (default: varies)
  • + *
+ */ +public class FormatStringParser { + + public static final String INTMAX_T_NAME = "intmax_t"; + public static final String UINTMAX_T_NAME = "uintmax_t"; + public static final String SIZE_T_NAME = "size_t"; + public static final String PTRDIFF_T_NAME = "ptrdiff_t"; + + private DataTypeManager dataTypeManager; + + private TypeDef intmax_t; + private TypeDef uintmax_t; + private TypeDef size_t; + private TypeDef ptrdiff_t; + + /** + * Constructor for FormatStringParser. + *
+ * NOTE: Warning messages will be logged once per instantiation when + * appropriate required TypeDef (intmax_t, uintmax_t, size_t, ptrdif_t) + * has not been predefined. + * + * @param program currentProgram + */ + public FormatStringParser(Program program) { + this.dataTypeManager = program.getDataTypeManager(); + } + + /** + * This function takes in a format string and returns List of Strings each holding + * format data. Each String is a substring of the given format string that corresponds to one + * or more DataTypes. These DataTypes determine which arguments need to be given to the variadic + * function. For instance, given the format String "%d %4.2s", this function will return + * the List ["d", "4.2s"] + * + * @param formatString format String + * @return List of substrings of formatStr + */ + private List parseFormatString(String formatString) { + + List formatArgumentList = new ArrayList<>(); + String current = ""; + for (int i = 0; i < formatString.length(); i++) { + char c = formatString.charAt(i); + if (c == '%') { + if (emitPercent(formatString, i)) { + ++i; + } + else { + ++i; + c = formatString.charAt(i); + while (!isConversionSpecifier(c)) { + current += c; + ++i; + if (i >= formatString.length()) { + return null; + } + c = formatString.charAt(i); + } + formatArgumentList.add(current + c); + current = ""; + } + } + } + return formatArgumentList; + } + + /** + * Takes in a single String from parseFormatString's output List and converts it to + * the corresponding FormatArgument(s) and populates the formatArgumentList List. + * isOutputType is true when using a format string for a function that "outputs" + * Strings (e.g., printf, fprintf, etc.). When it's false, it evaluates the + * String's data types as if the function "inputs" Strings (e.g., scanf) + * + * @param formatString Format String + * @param formatArgumentList List of FormatArgument that will be written to + * @param isOutputType Type of variadic function + * @return True if format string successfully parsed + */ + private boolean convertToFormatArguments(String formatString, + List formatArgumentList, boolean isOutputType) { + + FormatParsingData data = new FormatParsingData(); + for (int i = 0; i < formatString.length(); i++) { + char c = formatString.charAt(i); + i = preprocessChar(formatString, i, isOutputType); + if (i == -1) { + return false; + } + if (isFlag(c)) { + continue; + } + if (data.getLengthModifier() != null) { + return addArgumentWithModifier(c, data, formatArgumentList); + } + data.setLengthModifier(detectLengthModifier(c)); + if (data.getLengthModifier() == null) { + data.setConversionSpecifier(detectConversionSpecifier(c)); + if (data.getConversionSpecifier() != null) { + if (!verifyConversionPair(data.getLengthModifier(), + data.getConversionSpecifier())) { + return false; + } + formatArgumentList.add(new FormatArgument(data.getLengthModifier(), + data.getConversionSpecifier())); + return true; + } + // If length modifier and conversion specs aren't present + // and we get an unknown char, format string is invalid + if (data.isPrecisionComplete()) { + return false; + } + if (!Character.isDigit(c) && c != '.' && c != '*') { + return false; + } + if (isOutputType) { + // At this point c is either a number, '*', or '.' + i = handleOutputConversionArgument(formatString, i, data, formatArgumentList); + if (i == -1) { + return false; + } + } + else { + i = handleInputConversionArgument(formatString, i, data, formatArgumentList); + if (i == -1) { + return false; + } + } + } + else if (i + 1 < formatString.length()) { + i = initiateLengthModifierExtension(formatString, i, data); + } + } + return true; + } + + private int preprocessChar(String formatString, int i, boolean isOutputType) { + + char c = formatString.charAt(i); + if (c == '$') { + return -1; + } + if (isFlag(c)) { + if (!isOutputType) { + return -1; + } + i = skipFlags(formatString, i); + } + return i; + } + + private int initiateLengthModifierExtension(String formatString, int i, + FormatParsingData data) { + String tmpLengthModifier = + extendLengthModifier(data.getLengthModifier(), formatString.charAt(i + 1)); + if (tmpLengthModifier != null) { + ++i; + data.setLengthModifier(tmpLengthModifier); + } + return i; + } + + private boolean addArgumentWithModifier(char c, FormatParsingData data, + List formatArgumentList) { + data.setConversionSpecifier(detectConversionSpecifier(c)); + if ((data.getConversionSpecifier() == null) || + !(verifyConversionPair(data.getLengthModifier(), data.getConversionSpecifier()))) { + return false; // Problem with format string + } + formatArgumentList + .add(new FormatArgument(data.getLengthModifier(), data.getConversionSpecifier())); + return true; + + } + + private int handleOutputConversionArgument(String formatString, int i, FormatParsingData data, + List formatArgumentList) { + char c = formatString.charAt(i); + if (!data.isPrecisionComplete() && !data.isFieldWidthComplete() && c != '.') { + if (c == '*') { + formatArgumentList.add(new FormatArgument(null, "*")); + } + else { + i = skipIntegers(formatString, i); + } + if (i == -1) { + return i; + } + data.setFieldWidthComplete(true); + } + else if (data.isFieldWidthComplete() && c != '.') { + return -1; + } + else if (!data.isPrecisionComplete() && c == '.') { + if (i + 1 < formatString.length() && formatString.charAt(i + 1) == '*') { + ++i; + formatArgumentList.add(new FormatArgument(null, "*")); + } + else { + i = skipIntegers(formatString, i + 1); + } + if (i == -1) { + return i; + } + data.setPrecisionComplete(true); + } + else { + return -1; + } + return i; + } + + private int handleInputConversionArgument(String formatString, int i, FormatParsingData data, + List formatArgumentList) { + char c = formatString.charAt(i); + if (c == '*') { + formatArgumentList.add(new FormatArgument(null, "*")); + } + else if (Character.isDigit(c)) { + i = skipIntegers(formatString, i + 1); + if (i == -1) { + return i; + } + data.setPrecisionComplete(true); + } + else { + return -1; + } + return i; + } + + /** + * Takes in a String and converts it to a List of FormatArgument with each FormatArgument + * corresponding to an additional argument. isOutputType is true when using a + * format string for output data types (e.g. printf, fprintf, etc.). When it's + * false, it evaluates the String's data types as if they were input types (e.g. + * scanf) + * + * @param formatString format String + * @param isOutputType Type of variadic function + * @return List of FormatArgument + */ + + public List convertToFormatArgumentList(String formatString, + boolean isOutputType) { + + if (formatString == null) { + return null; + } + List formatStrArgumentList = parseFormatString(formatString); + if (formatStrArgumentList == null) { + return null; + } + List formatArgumentList = new ArrayList<>(); + for (String formatStrArgument : formatStrArgumentList) { + boolean status = + convertToFormatArguments(formatStrArgument, formatArgumentList, isOutputType); + if (!status) { + if (formatStrArgumentList.stream() + .filter(str -> str.contains("$")) + .findAny() + .isPresent()) { + return analyzeFormatStringWithParameters(formatString); + } + return null; + } + } + return formatArgumentList.contains(null) ? null : formatArgumentList; + } + + /** + * + * Handles format Strings with parameters. In this parser, we define a format + * String parameter to be an integer n provided in the form: "%n$" or "*n$", where n is + * the index of the referred argument. If a placeholder uses a format + * argument parameter, all other placeholders must also have a parameter. Also, + * all gaps between format argument indices are not supported. For instance, if + * the first and third arguments are used, there must also be a parameter for a + * second argument. Any parameter pattern beginning with % or * and ending with + * $ must have integer in between. Failing to adhere by the format string + * parameter requirements returns null. + * + * @param formatString format String + * @return List of FormatArgument + * + * + * TODO: What if multiple conversion specs refer to the same placeholder + * with different types? Ex: "%1$*1$x" (uses unsigned int and int) + * Currently just overwrites previous type + * + */ + public List analyzeFormatStringWithParameters(String formatString) { + + FormatParsingData data = new FormatParsingData(); + Map formatArgumentMap = new HashMap<>(); + for (int i = 0; i < formatString.length(); i++) { + char c = formatString.charAt(i); + if (c == ' ') { + continue; + } + if (c == '%') { + if (emitPercent(formatString, i)) { + ++i; + } + else { + data.setInConversion(true); + data.clearData(); + data.setParameterIndex(locateParameterIndex(formatString, i)); + if (data.getParameterIndex() == 0) { + return null; // $ operand number is required + } + i += Integer.toString(data.getParameterIndex()).length() + 1; // i should be at $ + if (isFlag(formatString.charAt(i + 1))) { + i = skipFlags(formatString, i + 1); + } + continue; + } + } + if (data.isInConversion()) { + if (data.getLengthModifier() != null) { + data.setConversionSpecifier(detectConversionSpecifier(c)); + if (data.getConversionSpecifier() == null) { + return null; // Problem with format string + } + formatArgumentMap.put(data.getParameterIndex(), new FormatArgument( + data.getLengthModifier(), data.getConversionSpecifier())); + data.setInConversion(false); + continue; + } + data.setLengthModifier(detectLengthModifier(c)); + if (data.getLengthModifier() == null) { + i = searchWithNullModifier(formatString, i, data, formatArgumentMap); + if (i == -1) { + return null; + } + } + } + } + return convertMapToList(formatArgumentMap); + } + + // Continue format String conversion parsing for when the length modifier is null + private int searchWithNullModifier(String formatString, int i, FormatParsingData data, + Map formatArgumentMap) { + char c = formatString.charAt(i); + data.setConversionSpecifier(detectConversionSpecifier(c)); + if (data.getConversionSpecifier() != null) { + formatArgumentMap.put(data.getParameterIndex(), + new FormatArgument(data.getLengthModifier(), data.getConversionSpecifier())); + data.setInConversion(false); + } + else { + if (data.isPrecisionComplete()) { + return -1; + } + if (!Character.isDigit(c) && c != '.' && c != '*') { + return -1; + } + // At this point c is either a number, '*', or '.' + if (!data.isPrecisionComplete() && !data.isFieldWidthComplete() && c != '.') { + i = handleOutputConversionForParameters(formatString, i, data, formatArgumentMap); + if (i == -1) { + return -1; + } + } + else if (data.isFieldWidthComplete() && c != '.') { + return -1; + } + else if (!data.isPrecisionComplete() && c == '.') { + i = handlePrecisionForParameters(formatString, i, data, formatArgumentMap); + if (i == -1) { + return -1; + } + } + else { + return -1; + } + } + return i; + } + + // Takes care of optional precision indicated by a period ('.') and followed by an + // asterick or series of integers + private int handlePrecisionForParameters(String formatString, int i, FormatParsingData data, + Map formatArgumentMap) { + + if (i + 1 < formatString.length() && formatString.charAt(i + 1) == '*') { + ++i; + int precisionIdx = locateParameterIndex(formatString, i); + if (precisionIdx == 0) { + return -1; + } + i += Integer.toString(precisionIdx).length() + 1; + // i should be at $ + formatArgumentMap.put(precisionIdx, new FormatArgument(null, "d")); + } + else { + i = skipIntegers(formatString, i + 1); // i should be at last number + if (i == -1) { + return -1; + } + } + data.setPrecisionComplete(true); + return i; + } + + private int handleOutputConversionForParameters(String formatString, int i, + FormatParsingData data, Map formatArgumentMap) { + char c = formatString.charAt(i); + if (c == '*') { + int fieldWidthIdx = locateParameterIndex(formatString, i); + if (fieldWidthIdx == 0) { + return i; + } + i += Integer.toString(fieldWidthIdx).length() + 1; + // i should be at $ + formatArgumentMap.put(fieldWidthIdx, new FormatArgument(null, "d")); + } + else { + i = skipIntegers(formatString, i); + if (i == -1) { + return i; + } + } + data.setFieldWidthComplete(true); + return i; + + } + + private List convertMapToList(Map formatArgumentMap) { + List formatArgumentList = new ArrayList<>(); + for (int i = 1; i <= formatArgumentMap.size(); i++) { + FormatArgument formatArgument = formatArgumentMap.get(i); + if (formatArgument == null) { + return null; + } + formatArgumentList.add(formatArgument); + } + return formatArgumentList; + } + + /** + * In a format string with format argument parameters, retrieve that parameter. + * In other words, in the following cases: "%n$" and "*n$", return n where n is + * the index of the referred argument. n cannot be less than 1; return 0 if + * there's a problem. + * + * @param formatString format String + * @param i index within formatStr + * @return formar argument parameter + */ + private int locateParameterIndex(String formatString, int i) { + + char c = formatString.charAt(i); + if (c == '%' || c == '*') { + ++i; + c = formatString.charAt(i); + } + else { + return 0; + } + String paramIndexString = ""; + while (Character.isDigit(c)) { + paramIndexString += Character.toString(c); + ++i; + c = formatString.charAt(i); + } + return c != '$' || paramIndexString.length() == 0 || Integer.parseInt(paramIndexString) == 0 + ? 0 + : Integer.parseInt(paramIndexString); + } + + /** + * Skips a series of flags within a format String. returns the index of the + * format string at the last digit before another non-digit character + * + * @param formatString format String + * @param i index into formatStr + * @return new index into formatStr + */ + private int skipFlags(String formatString, int i) { + for (; isFlag(formatString.charAt(i)); i++) { + // Iterate through chars until all flags are skipped + } + return i - 1; + } + + /** + * Skips a series of numbers (field width or precision) within a format String. + * returns the index of the format String at the last digit before another + * non-digit character + * + * @param formatString format String + * @param i index into formatStr + * @return new index into formatString + */ + private int skipIntegers(String formatString, int i) { + char c = formatString.charAt(i); + if (!Character.isDigit(c)) { + if (isLengthModifier(c) || isConversionSpecifier(c)) { + return i - 1; + } + return -1; + } + for (; Character.isDigit(formatString.charAt(i)); i++) { + // Skip chars until a non-integer is found + } + return i - 1; + } + + // If there are two consecutive '%' signs, do not evaluate the data types + private boolean emitPercent(String formatString, int i) { + if (formatString.charAt(i) == '%' && i + 1 < formatString.length() && + formatString.charAt(i + 1) == '%') { + return true; + } + return false; + } + + public DataType[] convertToOutputDataTypes(List formatArguments) { + if (formatArguments == null) { + return null; + } + List dataTypeList = formatArguments.stream().map(argument -> { + String conversionSpecifier = argument.getConversionSpecifier(); + DataType dt = convertPairToDataType(argument.getLengthModifier(), + conversionSpecifier.equals("*") ? "d" : conversionSpecifier); + return dt; + }).collect(Collectors.toList()); + return dataTypeList.contains(null) ? null + : dataTypeList.toArray(DataType[]::new); + } + + public DataType[] convertToInputDataTypes(List formatArguments) { + if (formatArguments == null) { + return null; + } + + List dataTypesList = new ArrayList<>(); + for (int i = 0; i < formatArguments.size(); i++) { + FormatArgument argument = formatArguments.get(i); + // * means to skip + if (argument.getConversionSpecifier().equals("*")) { + if (formatArguments.get(i + 1).getConversionSpecifier().equals("*")) { + return null; + } + ++i; + continue; + } + DataType dt = convertPairToDataType(argument.getLengthModifier(), + argument.getConversionSpecifier()); + if (dt == null) { + return null; + } + if (!(dt instanceof PointerDataType) || + isVoidPointer(argument.getConversionSpecifier())) { + dataTypesList.add(dataTypeManager.getPointer(dt)); + } + else { + dataTypesList.add(dt); + } + } + return dataTypesList.stream().toArray(size -> new DataType[size]); + } + + private boolean verifyConversionPair(String lengthModifier, String conversionSpecifier) { + if (lengthModifier == null || lengthModifier.equals("l")) { + return true; + } + if ((lengthModifier.equals("L") && isDouble(conversionSpecifier)) || + (!lengthModifier.equals("L") && + (isInteger(conversionSpecifier) || isIntegerPointer(conversionSpecifier)))) { + return true; + } + return false; + } + + private DataType convertPairToDataType(String lengthModifier, String conversionSpecifier) { + + if (lengthModifier == null || conversionSpecifier.equals("c") || + conversionSpecifier.equals("s") || + conversionSpecifier.equals("C") || + conversionSpecifier.equals("S")) { + return conversionSpecifierToDataType(conversionSpecifier); + } + switch (lengthModifier) { + case "h": + return shortLengthModification(conversionSpecifier); + case "hh": + return charLengthModification(conversionSpecifier); + case "l": + return longLengthModification(conversionSpecifier); + case "ll": + case "q": + return longLongLengthModification(conversionSpecifier); + case "j": + return intmax_t_LengthModification(conversionSpecifier); + case "z": + return size_t_LengthModification(conversionSpecifier); + case "t": + return ptrdiff_t_LengthModification(conversionSpecifier); + case "L": + return longDoubleLengthModification(conversionSpecifier); + default: + return null; + } + } + + private DataType conversionSpecifierToDataType(String conversionSpecifier) { + switch (conversionSpecifier.charAt(0)) { + case 'd': + case 'i': + return new IntegerDataType(dataTypeManager); + case 'o': + case 'u': + case 'x': + case 'X': + return new UnsignedIntegerDataType(dataTypeManager); + case 'p': + return dataTypeManager.getPointer(DataType.VOID); + case 's': + return dataTypeManager.getPointer(new CharDataType(dataTypeManager)); + case 'n': + return dataTypeManager.getPointer(new IntegerDataType(dataTypeManager)); + case 'c': + return new UnsignedCharDataType(dataTypeManager); + case 'a': + case 'A': + case 'g': + case 'G': + case 'e': + case 'E': + case 'f': + return new DoubleDataType(dataTypeManager); + case 'S': + case 'C': + return dataTypeManager.getPointer(new WideCharDataType(dataTypeManager)); + default: + return null; + } + } + + private DataType longLengthModification(String conversionSpecifier) { + if (isIntegerPointer(conversionSpecifier)) { + return dataTypeManager.getPointer(new LongDataType(dataTypeManager)); + } + if (conversionSpecifier.contentEquals("s") || conversionSpecifier.contentEquals("c")) { + return dataTypeManager.getPointer(new WideCharDataType(dataTypeManager)); + } + return isSignedInteger(conversionSpecifier) ? new LongDataType(dataTypeManager) + : new UnsignedLongDataType(dataTypeManager); + } + + private DataType longLongLengthModification(String conversionSpecifier) { + if (isIntegerPointer(conversionSpecifier)) { + return dataTypeManager.getPointer(new LongLongDataType(dataTypeManager)); + } + return isSignedInteger(conversionSpecifier) + ? new LongLongDataType(dataTypeManager) + : new UnsignedLongLongDataType(dataTypeManager); + } + + private DataType shortLengthModification(String conversionSpecifier) { + if (isIntegerPointer(conversionSpecifier)) { + return dataTypeManager.getPointer(new ShortDataType(dataTypeManager)); + } + return isSignedInteger(conversionSpecifier) + ? new ShortDataType(dataTypeManager) + : new UnsignedShortDataType(dataTypeManager); + } + + private DataType charLengthModification(String conversionSpecifier) { + if (isIntegerPointer(conversionSpecifier)) { + return dataTypeManager.getPointer(new CharDataType(dataTypeManager)); + } + return isSignedInteger(conversionSpecifier) ? new CharDataType(dataTypeManager) + : new UnsignedCharDataType(dataTypeManager); + } + + private TypeDef lookupTypeDef(String name) { + List typeList = new ArrayList<>(); + dataTypeManager.findDataTypes(name, typeList); + for (DataType dt : typeList) { + if (!(dt instanceof TypeDef)) { + continue; + } + TypeDef td = (TypeDef) dt; + if (td.getBaseDataType() instanceof AbstractIntegerDataType) { + return td; + } + } + return null; + } + + private TypeDef getIntMaxT() { + if (intmax_t != null) { + return intmax_t; + } + intmax_t = lookupTypeDef(INTMAX_T_NAME); + if (intmax_t == null) { + intmax_t = new TypedefDataType(INTMAX_T_NAME, new LongLongDataType(dataTypeManager)); + Msg.warn(this, INTMAX_T_NAME + " not defined. Generated as `" + intmax_t + "'"); + } + return intmax_t; + } + + private TypeDef getUIntMaxT() { + if (uintmax_t != null) { + return uintmax_t; + } + uintmax_t = lookupTypeDef(UINTMAX_T_NAME); + if (uintmax_t == null) { + uintmax_t = + new TypedefDataType(UINTMAX_T_NAME, new UnsignedLongLongDataType(dataTypeManager)); + Msg.warn(this, UINTMAX_T_NAME + " not defined. Generated as `" + uintmax_t + "'"); + } + return uintmax_t; + } + + private AbstractIntegerDataType getIntegralPointerType(boolean signed) { + DataOrganization dataOrganization = dataTypeManager.getDataOrganization(); + int size = dataOrganization.getPointerSize(); + if (size < dataOrganization.getLongSize() && size >= dataOrganization.getIntegerSize()) { + return signed ? new IntegerDataType(dataTypeManager) + : new UnsignedIntegerDataType(dataTypeManager); + } + return signed ? new LongDataType(dataTypeManager) + : new UnsignedLongDataType(dataTypeManager); + } + + private TypeDef getSizeT() { + if (size_t != null) { + return size_t; + } + size_t = lookupTypeDef(SIZE_T_NAME); + if (size_t == null) { + size_t = new TypedefDataType(SIZE_T_NAME, getIntegralPointerType(false)); + Msg.warn(this, SIZE_T_NAME + " not defined. Generated as `" + size_t + "'"); + } + return size_t; + } + + private TypeDef getPtrDiffT() { + if (ptrdiff_t != null) { + return ptrdiff_t; + } + ptrdiff_t = lookupTypeDef(PTRDIFF_T_NAME); + if (ptrdiff_t == null) { + ptrdiff_t = new TypedefDataType(PTRDIFF_T_NAME, getIntegralPointerType(true)); + Msg.warn(this, PTRDIFF_T_NAME + " not defined. Generated as `" + ptrdiff_t + "'"); + } + return ptrdiff_t; + } + + private DataType intmax_t_LengthModification(String conversionSpecifier) { + TypeDef intType = isUnsignedInteger(conversionSpecifier) ? getUIntMaxT() : getIntMaxT(); + return isIntegerPointer(conversionSpecifier) + ? dataTypeManager.getPointer(intType) + : intType; + } + + private DataType size_t_LengthModification(String conversionSpecifier) { + TypeDef sizeType = getSizeT(); + return isIntegerPointer(conversionSpecifier) + ? dataTypeManager.getPointer(sizeType) + : sizeType; + } + + private DataType ptrdiff_t_LengthModification(String conversionSpecifier) { + TypeDef type = isUnsignedInteger(conversionSpecifier) ? getSizeT() : getPtrDiffT(); + return isIntegerPointer(conversionSpecifier) + ? dataTypeManager.getPointer(type) + : type; + } + + private DataType longDoubleLengthModification(String conversionSpecifier) { + return new LongDoubleDataType(dataTypeManager); + } + + private boolean isInteger(String conversionSpecifier) { + return isUnsignedInteger(conversionSpecifier) || isSignedInteger(conversionSpecifier); + } + + private boolean isDouble(String conversionSpecifier) { + char c = conversionSpecifier.charAt(0); + String doubleConversionSpecifierSet = "aAeEfFgG"; + return doubleConversionSpecifierSet.indexOf(c) != -1; + } + + private boolean isUnsignedInteger(String conversionSpecifier) { + char c = conversionSpecifier.charAt(0); + String unsignedIntSpecifierSet = "ouxX"; + return unsignedIntSpecifierSet.indexOf(c) != -1; + } + + private boolean isSignedInteger(String conversionSpecifier) { + char c = conversionSpecifier.charAt(0); + String signedIntSpecifierSet = "di"; + return signedIntSpecifierSet.indexOf(c) != -1; + } + + private boolean isIntegerPointer(String conversionSpecifier) { + char c = conversionSpecifier.charAt(0); + String pointerSpecifierSet = "n"; + return pointerSpecifierSet.indexOf(c) != -1; + } + + private boolean isVoidPointer(String conversionSpecifier) { + char c = conversionSpecifier.charAt(0); + String voidPointerSpecifierSet = "p"; + return voidPointerSpecifierSet.indexOf(c) != -1; + } + + private boolean isFlag(char c) { + String flagSpecifierSet = "0+ -#'"; + return flagSpecifierSet.indexOf(c) != -1; + } + + private String extendLengthModifier(String lengthModifier, char nextChar) { + if ((lengthModifier.equals("h") && nextChar == 'h') || + (lengthModifier.equals("l") && nextChar == 'l')) { + return lengthModifier + Character.toString(nextChar); + } + return null; + } + + private boolean isConversionSpecifier(char c) { + return detectConversionSpecifier(c) != null; + } + + private boolean isLengthModifier(char c) { + return detectLengthModifier(c) != null; + } + + private String detectLengthModifier(char c) { + String lengthModifierSet = "hljztLq"; + return lengthModifierSet.indexOf(c) != -1 ? Character.toString(c) : null; + } + + private String detectConversionSpecifier(char c) { + String conversionSpecifierSet = "diuofeaFEApcsxXgGnCS"; + return conversionSpecifierSet.indexOf(c) != -1 ? Character.toString(c) : null; + } + + public int skipToNextWhitespace(String formatStr, int i) { + char c = formatStr.charAt(i); + while (c != ' ') { + ++i; + c = formatStr.charAt(i); + } + return i; + } + + private class FormatParsingData { + + private String conversionSpecifier = null; + private String lengthModifier = null; + private boolean fieldWidthComplete = false; + private boolean precisionComplete = false; + private boolean inConversion = false; + private int parameterIndex = 0; + + private void setParameterIndex(int parameterIndex) { + this.parameterIndex = parameterIndex; + } + + private int getParameterIndex() { + return this.parameterIndex; + } + + private void setConversionSpecifier(String conversionSpecifier) { + this.conversionSpecifier = conversionSpecifier; + } + + private String getConversionSpecifier() { + return this.conversionSpecifier; + } + + private void setLengthModifier(String lengthModifier) { + this.lengthModifier = lengthModifier; + } + + private String getLengthModifier() { + return this.lengthModifier; + } + + private boolean isFieldWidthComplete() { + return this.fieldWidthComplete; + } + + private void setFieldWidthComplete(boolean fieldWidthComplete) { + this.fieldWidthComplete = fieldWidthComplete; + } + + private boolean isPrecisionComplete() { + return this.precisionComplete; + } + + private void setPrecisionComplete(boolean precisionComplete) { + this.precisionComplete = precisionComplete; + } + + private void setInConversion(boolean inConversion) { + this.inConversion = inConversion; + } + + private boolean isInConversion() { + return this.inConversion; + } + + private void clearData() { + this.precisionComplete = false; + this.fieldWidthComplete = false; + this.lengthModifier = null; + this.conversionSpecifier = null; + } + } + +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/string/variadic/FunctionCallData.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/string/variadic/FunctionCallData.java new file mode 100644 index 0000000000..a8c82bb132 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/string/variadic/FunctionCallData.java @@ -0,0 +1,68 @@ +/* ### + * 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.string.variadic; + +import ghidra.program.model.address.*; + +/** + * Class for encapsulating a variadic function call + */ +public class FunctionCallData { + + private Address addressOfCall; + private String callFunctionName; + private String formatString; + + /** + * Constructore for FuncCallData + * + * @param addressOfCall Address of function call + * @param callFunctionName variadic function name + * @param formatString format String + */ + public FunctionCallData(Address addressOfCall, String callFunctionName, String formatString) { + this.addressOfCall = addressOfCall; + this.callFunctionName = callFunctionName; + this.formatString = formatString; + } + + /** + * addressOfCall getter + * + * @return addressOfCall + */ + public Address getAddressOfCall() { + return this.addressOfCall; + } + + /** + * callFunctionName getter + * + * @return callFunctionName + */ + public String getCallFuncName() { + return this.callFunctionName; + } + + /** + * formatString getter + * + * @return formatString + */ + public String getFormatString() { + return this.formatString; + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/string/variadic/PcodeFunctionParser.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/string/variadic/PcodeFunctionParser.java new file mode 100644 index 0000000000..1eaaab6080 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/string/variadic/PcodeFunctionParser.java @@ -0,0 +1,184 @@ +/* ### + * 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.string.variadic; + +import java.util.*; + +import ghidra.docking.settings.SettingsImpl; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.StringDataInstance; +import ghidra.program.model.data.StringDataType; +import ghidra.program.model.listing.*; +import ghidra.program.model.mem.MemoryBufferImpl; +import ghidra.program.model.pcode.PcodeOpAST; +import ghidra.program.model.pcode.Varnode; + +/** + * Class for parsing functions' Pcode representations and finding variadic + * functions being called + * + */ +public class PcodeFunctionParser { + + // All values within the range [32, 126] are ascii readable + private static final int READABLE_ASCII_LOWER_BOUND = 32; + private static final int READABLE_ASCII_UPPER_BOUND = 126; + // How many bytes to read from a memory address when initial format + // String cannot be found. This normally only happens for short format + // Strings with lengths less than 5 + private static final int BUFFER_LENGTH = 20; + private static final String CALL_INSTRUCTION = "CALL"; + + private Program program; + + public PcodeFunctionParser(Program program) { + this.program = program; + } + + /** + * Takes pcode ops of a function and parses them to determine whether there are + * any calls to variadic functions that use format Strings. + * + * @param pcodeOps List of PcodeOpAST for a function + * @param addressToCandidateData map of Addresses to format String data + * @param variadicFunctionNames Set of variadic functions to look for + * @return List of variadic functions that the current function calls + */ + public List parseFunctionForCallData(List pcodeOps, + Map addressToCandidateData, Set variadicFunctionNames) { + + if (pcodeOps == null || addressToCandidateData == null || variadicFunctionNames == null || + this.program == null) { + return null; + } + List functionCallDataList = new ArrayList<>(); + for (PcodeOpAST ast : pcodeOps) { + Varnode firstNode = ast.getInput(0); + if (firstNode == null) { + continue; + } + if (ast.getMnemonic().contentEquals(CALL_INSTRUCTION)) { + + FunctionManager functionManager = this.program.getFunctionManager(); + Function function = functionManager.getFunctionAt(firstNode.getAddress()); + if (function == null) { + return null; + } + String functionName = function.getName(); + if (variadicFunctionNames.contains(functionName)) { + Varnode[] inputs = ast.getInputs(); + if (inputs.length > 0) { + boolean hasDefinedFormatString = searchForVariadicCallData(ast, + addressToCandidateData, functionCallDataList, functionName); + if (!hasDefinedFormatString) { + searchForHiddenFormatStrings(ast, functionCallDataList, functionName); + } + } + } + } + } + return functionCallDataList; + } + + private boolean searchForVariadicCallData(PcodeOpAST ast, + Map addressToCandidateData, List functionCallDataList, + String functionName) { + + boolean hasDefinedFormatString = false; + Varnode[] inputs = ast.getInputs(); + for (int i = 1; i < inputs.length; i++) { + Varnode v = inputs[i]; + Data data = null; + Address ramSpaceAddress = convertAddressToRamSpace(v.getAddress()); + if (addressToCandidateData.containsKey(ramSpaceAddress)) { + data = addressToCandidateData.get(ramSpaceAddress); + functionCallDataList.add(new FunctionCallData(ast.getSeqnum().getTarget(), + functionName, data.getDefaultValueRepresentation())); + hasDefinedFormatString = true; + } + } + return hasDefinedFormatString; + } + + // If addrToCandidateData doesn't have format String data for this call + // and we are calling a variadic function, parse the String to determine + // whether it's a format String. + private void searchForHiddenFormatStrings(PcodeOpAST ast, + List functionCallDataList, String functionName) { + + Varnode[] inputs = ast.getInputs(); + // Initialize i = 1 to skip first input + for (int i = 1; i < inputs.length; ++i) { + Varnode v = inputs[i]; + String formatStringCandidate = findFormatString(v.getAddress()); + if (formatStringCandidate == null) { + continue; + } + if (formatStringCandidate.contains("%")) { + functionCallDataList.add(new FunctionCallData(ast.getSeqnum().getTarget(), + functionName, formatStringCandidate)); + } + break; + } + } + + private Address convertAddressToRamSpace(Address address) { + + String addressString = address.toString(false); + return this.program.getAddressFactory().getAddress(addressString); + } + + /** + * Looks at bytes at given address and converts to format String + * + * @param address Address of format String + * @return format String + */ + private String findFormatString(Address address) { + + if (!address.getAddressSpace().isConstantSpace()) { + return null; + } + + // Old address associated with constant space which doesn't work + Address ramSpaceAddress = convertAddressToRamSpace(address); + + MemoryBufferImpl memoryBuffer = + new MemoryBufferImpl(this.program.getMemory(), ramSpaceAddress); + SettingsImpl settings = new SettingsImpl(); + + StringDataInstance stringDataInstance = StringDataInstance + .getStringDataInstance(new StringDataType(), memoryBuffer, settings, BUFFER_LENGTH); + String stringValue = stringDataInstance.getStringValue(); + if (stringValue == null) { + return null; + } + + String formatStringCandidate = ""; + for (int i = 0; i < stringValue.length(); i++) { + if (!isAsciiReadable(stringValue.charAt(i))) { + break; + } + formatStringCandidate += stringValue.charAt(i); + } + return formatStringCandidate; + } + + private boolean isAsciiReadable(char c) { + + return c >= READABLE_ASCII_LOWER_BOUND && c <= READABLE_ASCII_UPPER_BOUND; + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/test/java/ghidra/app/plugin/core/string/variadic/FormatStringParserTest.java b/Ghidra/Features/DecompilerDependent/src/test/java/ghidra/app/plugin/core/string/variadic/FormatStringParserTest.java new file mode 100644 index 0000000000..a6aceccd47 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/test/java/ghidra/app/plugin/core/string/variadic/FormatStringParserTest.java @@ -0,0 +1,332 @@ +/* ### + * 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.string.variadic; + +import static org.junit.Assert.*; + +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import generic.test.AbstractGenericTest; +import ghidra.program.database.ProgramBuilder; +import ghidra.program.database.ProgramDB; +import ghidra.program.database.data.ProgramDataTypeManager; +import ghidra.program.model.data.*; + +public class FormatStringParserTest extends AbstractGenericTest { + + private ProgramBuilder builder; + private ProgramDB program; + + @Before + public void setUp() throws Exception { + + builder = new ProgramBuilder("FormatStringParserTest", ProgramBuilder._TOY, this); + assertNotNull(builder); + program = builder.getProgram(); + assertNotNull(program); + + } + + // Determines whether null is properly returned for + // invalid format Strings. Each String is invalid due to + // either (1) invalid conversion specifier, (2) invalid + // length modifier, or (3) placeholder incorrectly used + @Test + public void testInvalidFormatString() { + + runFormatTest("%r", null, true); // r is not a conversion specifier + runFormatTest("%%%lw", null, true); // w is not a conversion specifier + runFormatTest("%#0*.*ld", null, false); // scanf doesn't use flags or period + runFormatTest("%d::%%%ld%z", null, true); // z is not a conversion specifier + runFormatTest("thisisatest%%%#**u", null, true); // two consecutive astericks + runFormatTest("%#0'*rd", null, true); // r is not length modifier + runFormatTest("%%%#'*md", null, true); // m is not length modifier + runFormatTest("%*.**d", null, true); // two consecutive astericks + runFormatTest("%lD", null, true); // D is not a conversion specifier + runFormatTest("%-0+**d", null, false); // scanf doesn't use flags, two consecutive astericks + runFormatTest("%-0+*.*d", null, false); // scanf doesn't use flags or period + runFormatTest("%2.3d", null, false); // scanf doesn't use period + runFormatTest("%*1$d %d\n", null, true); // If one placeholder specifies parameter, the others must too + runFormatTest("%2$d %d\n", null, true); // If one placeholder specifies parameter, the others must too + + } + + // Tests format strings for scanf which have expected types of pointers instead + // of standard format strings + @Test + public void testScanfFormatString() { + + DataType[] expectedTypes1 = + { program.getDataTypeManager().getPointer(new IntegerDataType()) }; + runFormatTest("%d", expectedTypes1, false); + DataType[] expectedTypes2 = + { program.getDataTypeManager().getPointer(new IntegerDataType()), + program.getDataTypeManager().getPointer(new ShortDataType()) }; + + runFormatTest("%d%hi", expectedTypes2, false); + + DataType[] expectedTypes3 = + { program.getDataTypeManager().getPointer(new PointerDataType(DataType.VOID)), + program.getDataTypeManager().getPointer(new CharDataType()) }; + runFormatTest("%p%*d%s", expectedTypes3, false); + + DataType[] expectedTypes4 = + { program.getDataTypeManager().getPointer(new LongDoubleDataType()), + program.getDataTypeManager().getPointer(new CharDataType()), + program.getDataTypeManager().getPointer(new PointerDataType(DataType.VOID)) }; + + runFormatTest("!:%12La%*d+=%2s%3p%*20d", expectedTypes4, false); + + } + + // Tests format strings that are more complex, containing less commonly + // used format patterns and more '%' characters + @Test + public void testComplexFormatString() { + DataType[] expectedTypes1 = + { program.getDataTypeManager().getPointer(new IntegerDataType()), }; + runFormatTest("#12%n\nd2", expectedTypes1, true); + + DataType[] expectedTypes2 = + { program.getDataTypeManager().getPointer(new CharDataType()), new LongDataType() }; + runFormatTest("#thisisatest%+-4.12s%#.1lin\nd2", expectedTypes2, true); + + DataType[] expectedTypes3 = + { new PointerDataType(DataType.VOID), new LongDoubleDataType(), + new UnsignedCharDataType() }; + runFormatTest("%01.3pp%%%#1.2Lg%%%%%hhXxn2", expectedTypes3, true); + + DataType[] expectedTypes4 = { new IntegerDataType(), new IntegerDataType(), + new UnsignedCharDataType(), new IntegerDataType(), new LongDoubleDataType() }; + runFormatTest("%0#+-*.*hhX%%%.*La", expectedTypes4, true); + DataType[] expectedTypes5 = { new IntegerDataType(), + + program.getDataTypeManager().getPointer(new IntegerDataType()), new IntegerDataType(), + program.getDataTypeManager().getPointer(new WideCharDataType()), new IntegerDataType(), + new LongDoubleDataType() }; + runFormatTest("%.*n%*C%%%%%.*LE", expectedTypes5, true); + + } + + // Tests format strings that use astericks to add another int + // argument to determine field width or precision + @Test + public void testAsterickFormatString() { + DataType[] expectedTypes1 = { new IntegerDataType(), new IntegerDataType() }; + runFormatTest("%*d", expectedTypes1, true); + + DataType[] expectedTypes2 = { new IntegerDataType(), new LongDataType() }; + runFormatTest("%.*ld", expectedTypes2, true); + + DataType[] expectedTypes3 = + { new IntegerDataType(), new IntegerDataType(), new IntegerDataType() }; + runFormatTest("%*.*d", expectedTypes3, true); + DataType[] expectedTypes4 = + { new IntegerDataType(), new IntegerDataType(), new IntegerDataType() }; + runFormatTest("*%%%+-*.*d", expectedTypes4, true); + + } + + // Test simple format strings with different length modifiers + @Test + public void testLengthModifierFormatString() { + DataType[] expectedTypes1 = + { new LongDataType(), new PointerDataType(LongDataType.dataType) }; + runFormatTest("%ld %ln", expectedTypes1, true); + + DataType[] expectedTypes2 = + { new ShortDataType(), new CharDataType(), new PointerDataType(ShortDataType.dataType), + new PointerDataType(CharDataType.dataType) }; + runFormatTest("%hd %hhi %hn %hhn", expectedTypes2, true); + + DataType[] expectedTypes3 = { new UnsignedShortDataType(), new UnsignedCharDataType() }; + runFormatTest("%hx %hhu", expectedTypes3, true); + + DataType[] expectedTypes4 = + { new UnsignedLongDataType(), new LongLongDataType(), new UnsignedLongLongDataType(), + new PointerDataType(LongLongDataType.dataType) }; + runFormatTest("%lX %lld %llx %lln", expectedTypes4, true); + + DataType[] expectedTypes5 = + { new LongDoubleDataType(), new LongLongDataType(), new UnsignedLongLongDataType(), + new UnsignedShortDataType(), new UnsignedCharDataType() }; + runFormatTest("%LE %lli %llX %hu %hhX", expectedTypes5, true); + } + + // Test simple format strings with different special length modifiers + // using generated default typedefs + @Test + public void testSpecialLengthModifierFormatStringDefault() { + DataType[] expectedTypes1 = + { new TypedefDataType("size_t", UnsignedLongDataType.dataType) }; + runFormatTest("%zd", expectedTypes1, true); + + DataType[] expectedTypes2 = + { new TypedefDataType("size_t", UnsignedLongDataType.dataType) }; + runFormatTest("%zu", expectedTypes2, true); + + DataType[] expectedTypes3 = { new TypedefDataType("ptrdiff_t", LongDataType.dataType) }; + runFormatTest("%td", expectedTypes3, true); + + DataType[] expectedTypes4 = + { new TypedefDataType("size_t", UnsignedLongDataType.dataType) }; + runFormatTest("%tu", expectedTypes4, true); + + DataType[] expectedTypes5 = { new TypedefDataType("intmax_t", LongLongDataType.dataType) }; + runFormatTest("%jd", expectedTypes5, true); + + DataType[] expectedTypes6 = + { new TypedefDataType("uintmax_t", UnsignedLongLongDataType.dataType) }; + runFormatTest("%ju", expectedTypes6, true); + + DataType[] expectedTypes7 = + { new PointerDataType(new TypedefDataType("intmax_t", LongLongDataType.dataType)) }; + runFormatTest("%jn", expectedTypes7, true); + } + + // Test simple format strings with different special length modifiers + // using predefined typedefs + @Test + public void testSpecialLengthModifierFormatStringPredefined() { + + int txId = program.startTransaction("Add TypeDefs"); + try { + ProgramDataTypeManager dtm = program.getDataTypeManager(); + DataType sizetDt = + dtm.resolve(new TypedefDataType("size_t", UnsignedLongLongDataType.dataType), null); + DataType ptrdiftDt = + dtm.resolve(new TypedefDataType("ptrdiff_t", LongLongDataType.dataType), null); + DataType intmaxtDt = + dtm.resolve(new TypedefDataType("intmax_t", LongDataType.dataType), null); + DataType uintmaxtDt = + dtm.resolve(new TypedefDataType("uintmax_t", UnsignedLongDataType.dataType), null); + + DataType[] expectedTypes1 = { sizetDt }; + runFormatTest("%zd", expectedTypes1, true); + + DataType[] expectedTypes2 = { sizetDt }; + runFormatTest("%zu", expectedTypes2, true); + + DataType[] expectedTypes3 = { ptrdiftDt }; + runFormatTest("%td", expectedTypes3, true); + + DataType[] expectedTypes4 = { sizetDt }; + runFormatTest("%tu", expectedTypes4, true); + + DataType[] expectedTypes5 = { intmaxtDt }; + runFormatTest("%jd", expectedTypes5, true); + + DataType[] expectedTypes6 = { uintmaxtDt }; + runFormatTest("%ju", expectedTypes6, true); + + DataType[] expectedTypes7 = { new PointerDataType(intmaxtDt) }; + runFormatTest("%jn", expectedTypes7, true); + + } + finally { + program.endTransaction(txId, true); + } + } + + // Test simple format Strings with different conversion specifiers + @Test + public void testConversionSpecFormatString() { + DataType[] expectedTypes1 = { new IntegerDataType() }; + runFormatTest("%d", expectedTypes1, true); + + DataType[] expectedTypes2 = + { new IntegerDataType(), new IntegerDataType(), new UnsignedIntegerDataType(), + program.getDataTypeManager().getPointer(new CharDataType()) }; + runFormatTest("%i %i %x %s", expectedTypes2, true); + + DataType[] expectedTypes3 = { new IntegerDataType(), new IntegerDataType(), + program.getDataTypeManager().getPointer(new CharDataType()) }; + runFormatTest("%d %d %s", expectedTypes3, true); + + DataType[] expectedTypes4 = { new DoubleDataType(), new DoubleDataType(), + new DoubleDataType(), new DoubleDataType(), new UnsignedCharDataType() }; + runFormatTest("%e %f %E %G %c", expectedTypes4, true); + + DataType[] expectedTypes5 = { new UnsignedIntegerDataType(), new UnsignedIntegerDataType(), + new UnsignedIntegerDataType(), new DoubleDataType(), new DoubleDataType() }; + runFormatTest("%u %x %X %e %g", expectedTypes5, true); + DataType[] expectedTypes6 = { new IntegerDataType() }; + runFormatTest("%.d", expectedTypes6, true); + } + + // Format Strings with field widths indicated by the sequence "*m$" + // where m is an integer that determines the position in the argument + // list of an integer argument + @Test + public void testFormatParameters() { + DataType[] expectedTypes1 = { new IntegerDataType() }; + runFormatTest("%1$d", expectedTypes1, true); + + DataType[] expectedTypes2 = { new IntegerDataType(), new IntegerDataType() }; + runFormatTest("%1$*2$d", expectedTypes2, true); + + DataType[] expectedTypes3 = { new IntegerDataType(), new IntegerDataType() }; + runFormatTest("%1$.*2$d", expectedTypes3, true); + + DataType[] expectedTypes4 = { new IntegerDataType(), new IntegerDataType(), + new IntegerDataType(), new IntegerDataType() }; + runFormatTest("%1$d:%2$.*3$d:%4$.*3$d\n", expectedTypes4, true); + DataType[] expectedTypes5 = + { new UnsignedIntegerDataType(), new UnsignedIntegerDataType() }; + + runFormatTest("%2$d %2$#x; %1$d %1$#x", expectedTypes5, true); + + DataType[] expectedTypes6 = + { new UnsignedIntegerDataType(), new IntegerDataType(), new IntegerDataType() }; + runFormatTest("%2$+#*3$d:%2$#x;0-:'.~%1$0*2$d:!2%1$#x", expectedTypes6, true); + DataType[] expectedTypes7 = + { new UnsignedLongLongDataType(), new DoubleDataType(), new IntegerDataType() }; + runFormatTest("%2$+#*3$f:*;`2!%1$#qu", expectedTypes7, true); + } + + private void runFormatTest(String testString, DataType[] expected, boolean runOutputAnalyzer) { + + FormatStringParser parser = new FormatStringParser(program); + List formatArguments = + parser.convertToFormatArgumentList(testString, runOutputAnalyzer); + DataType[] dataTypes = runOutputAnalyzer ? parser.convertToOutputDataTypes(formatArguments) + : parser.convertToInputDataTypes(formatArguments); + assertEquivalent(dataTypes, expected); + + } + + private void assertEquivalent(DataType[] actual, DataType[] expected) { + + if (expected == null) { + assertNull(actual); + return; + } + assertNotNull("Expected args were not produced", actual); + assertNotNull("Unexpected args were produced", expected); + assertEquals("Expected arg count differs from actual", actual.length, expected.length); + + for (int i = 0; i < actual.length; i++) { + assertNotNull("Unexpected null arg returned", actual[i]); + if (!actual[i].isEquivalent(expected[i])) { + fail("Expected: " + expected[i] + ", Actual: " + actual[i]); + } + } + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/AnnotationElement.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/AnnotationElement.java index 7b69dc5483..6e731e06cb 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/AnnotationElement.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/AnnotationElement.java @@ -15,18 +15,14 @@ */ package ghidra.file.formats.android.dex.format; +import java.io.IOException; + import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.StructConverter; -import ghidra.file.formats.android.dex.util.Leb128; -import ghidra.program.model.data.ArrayDataType; -import ghidra.program.model.data.CategoryPath; -import ghidra.program.model.data.DataType; -import ghidra.program.model.data.Structure; -import ghidra.program.model.data.StructureDataType; +import ghidra.app.util.bin.format.dwarf4.LEB128; +import ghidra.program.model.data.*; import ghidra.util.exception.DuplicateNameException; -import java.io.IOException; - public class AnnotationElement implements StructConverter { private int nameIndex; @@ -34,10 +30,9 @@ public class AnnotationElement implements StructConverter { private EncodedValue value; public AnnotationElement( BinaryReader reader ) throws IOException { - nameIndex = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - - nameIndexLength = Leb128.unsignedLeb128Size( nameIndex ); - reader.setPointerIndex( reader.getPointerIndex( ) + nameIndexLength ); + LEB128 leb128 = LEB128.readUnsignedValue(reader); + nameIndex = leb128.asUInt32(); + nameIndexLength = leb128.getLength(); value = new EncodedValue( reader ); } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/ClassDataItem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/ClassDataItem.java index 2896ed4f30..065f7702e9 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/ClassDataItem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/ClassDataItem.java @@ -15,15 +15,15 @@ */ package ghidra.file.formats.android.dex.format; -import ghidra.app.util.bin.BinaryReader; -import ghidra.app.util.bin.StructConverter; -import ghidra.file.formats.android.dex.util.Leb128; -import ghidra.program.model.data.*; -import ghidra.util.exception.DuplicateNameException; - import java.io.IOException; import java.util.*; +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.StructConverter; +import ghidra.app.util.bin.format.dwarf4.LEB128; +import ghidra.program.model.data.*; +import ghidra.util.exception.DuplicateNameException; + public class ClassDataItem implements StructConverter { private int staticFieldsSize; @@ -36,27 +36,27 @@ public class ClassDataItem implements StructConverter { private int directMethodsSizeLength;// in bytes private int virtualMethodsSizeLength;// in bytes - private List< EncodedField > staticFields = new ArrayList< EncodedField >( ); - private List< EncodedField > instancesFields = new ArrayList< EncodedField >( ); - private List< EncodedMethod > directMethods = new ArrayList< EncodedMethod >( ); - private List< EncodedMethod > virtualMethods = new ArrayList< EncodedMethod >( ); + private List< EncodedField > staticFields = new ArrayList< >( ); + private List< EncodedField > instancesFields = new ArrayList< >( ); + private List< EncodedMethod > directMethods = new ArrayList< >( ); + private List< EncodedMethod > virtualMethods = new ArrayList< >( ); public ClassDataItem( BinaryReader reader ) throws IOException { - staticFieldsSize = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - staticFieldsSizeLength = Leb128.unsignedLeb128Size( staticFieldsSize ); - reader.readNextByteArray( staticFieldsSizeLength );// consume leb... + LEB128 leb128 = LEB128.readUnsignedValue(reader); + staticFieldsSize = leb128.asUInt32(); + staticFieldsSizeLength = leb128.getLength(); - instanceFieldsSize = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - instanceFieldsSizeLength = Leb128.unsignedLeb128Size( instanceFieldsSize ); - reader.readNextByteArray( instanceFieldsSizeLength );// consume leb... + leb128 = LEB128.readUnsignedValue(reader); + instanceFieldsSize = leb128.asUInt32(); + instanceFieldsSizeLength = leb128.getLength(); - directMethodsSize = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - directMethodsSizeLength = Leb128.unsignedLeb128Size( directMethodsSize ); - reader.readNextByteArray( directMethodsSizeLength );// consume leb... + leb128 = LEB128.readUnsignedValue(reader); + directMethodsSize = leb128.asUInt32(); + directMethodsSizeLength = leb128.getLength(); - virtualMethodsSize = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - virtualMethodsSizeLength = Leb128.unsignedLeb128Size( virtualMethodsSize ); - reader.readNextByteArray( virtualMethodsSizeLength );// consume leb... + leb128 = LEB128.readUnsignedValue(reader); + virtualMethodsSize = leb128.asUInt32(); + virtualMethodsSizeLength = leb128.getLength(); for ( int i = 0 ; i < staticFieldsSize ; ++i ) { staticFields.add( new EncodedField( reader ) ); diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/DebugInfoItem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/DebugInfoItem.java index bf61a79dcf..184f5b4913 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/DebugInfoItem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/DebugInfoItem.java @@ -15,18 +15,14 @@ */ package ghidra.file.formats.android.dex.format; +import java.io.IOException; + import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.StructConverter; -import ghidra.file.formats.android.dex.util.Leb128; -import ghidra.program.model.data.ArrayDataType; -import ghidra.program.model.data.CategoryPath; -import ghidra.program.model.data.DataType; -import ghidra.program.model.data.Structure; -import ghidra.program.model.data.StructureDataType; +import ghidra.app.util.bin.format.dwarf4.LEB128; +import ghidra.program.model.data.*; import ghidra.util.exception.DuplicateNameException; -import java.io.IOException; - public class DebugInfoItem implements StructConverter { private int lineStart; @@ -38,30 +34,25 @@ public class DebugInfoItem implements StructConverter { private byte [] stateMachineOpcodes; public DebugInfoItem( BinaryReader reader ) throws IOException { - lineStart = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - lineStartLength = Leb128.unsignedLeb128Size( lineStart ); - reader.readNextByteArray( lineStartLength );// consume leb... + LEB128 leb128 = LEB128.readUnsignedValue(reader); + lineStart = leb128.asUInt32(); + lineStartLength = leb128.getLength(); - parametersSize = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - parametersSizeLength = Leb128.unsignedLeb128Size( parametersSize ); - reader.readNextByteArray( parametersSizeLength );// consume leb... + leb128 = LEB128.readUnsignedValue(reader); + parametersSize = leb128.asUInt32(); + parametersSizeLength = leb128.getLength(); parameterNames = new int[ parametersSize ]; parameterNamesLengths = new int[ parametersSize ]; for ( int i = 0 ; i < parametersSize ; ++i ) { - int value = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - int valueLength = Leb128.unsignedLeb128Size( value ); - reader.readNextByteArray( valueLength );// consume leb... + leb128 = LEB128.readUnsignedValue(reader); - parameterNames[ i ] = value - 1;// uleb128p1 - - parameterNamesLengths[ i ] = valueLength; + parameterNames[i] = leb128.asUInt32() - 1;// uleb128p1 + parameterNamesLengths[i] = leb128.getLength(); } - long startIndex = reader.getPointerIndex( ); - int count = DebugInfoStateMachineReader.computeLength( reader ); - reader.setPointerIndex( startIndex ); + int count = DebugInfoStateMachineReader.computeLength( reader.clone() ); stateMachineOpcodes = reader.readNextByteArray( count ); } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/DebugInfoStateMachineReader.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/DebugInfoStateMachineReader.java index 21ec208813..bd8ab387c7 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/DebugInfoStateMachineReader.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/DebugInfoStateMachineReader.java @@ -15,102 +15,64 @@ */ package ghidra.file.formats.android.dex.format; -import ghidra.app.util.bin.BinaryReader; -import ghidra.file.formats.android.dex.util.Leb128; - import java.io.IOException; +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.format.dwarf4.LEB128; + class DebugInfoStateMachineReader { + private static final int MAX_SIZE = 0x10000; // 64k static int computeLength( BinaryReader reader ) throws IOException { - int length = 0; + long start = reader.getPointerIndex(); - while ( true ) { - - if ( length > 0x10000 ) {//don't loop forever! - return 0; - } + while (reader.getPointerIndex() - start < MAX_SIZE) { byte opcode = reader.readNextByte( ); - ++length; - switch( opcode ) { case DebugStateMachineOpCodes.DBG_END_SEQUENCE: { - return length;//done! + return (int) (reader.getPointerIndex() - start);//done! } case DebugStateMachineOpCodes.DBG_ADVANCE_PC: { - int advance = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - int advanceLength = Leb128.unsignedLeb128Size( advance ); - reader.setPointerIndex( reader.getPointerIndex( ) + advanceLength ); - length += advanceLength; + LEB128.readAsUInt32(reader); break; } case DebugStateMachineOpCodes.DBG_ADVANCE_LINE: { - int advance = Leb128.readSignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - int advanceLength = Leb128.signedLeb128Size( advance ); - reader.setPointerIndex( reader.getPointerIndex( ) + advanceLength ); - length += advanceLength; + LEB128.readAsUInt32(reader); break; } case DebugStateMachineOpCodes.DBG_START_LOCAL: { - int register = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - int registerLength = Leb128.unsignedLeb128Size( register ); - reader.setPointerIndex( reader.getPointerIndex( ) + registerLength ); - length += registerLength; + int register = LEB128.readAsUInt32(reader); //TODO uleb128p1 - int name = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - int nameLength = Leb128.unsignedLeb128Size( name ); - reader.setPointerIndex( reader.getPointerIndex( ) + nameLength ); - length += nameLength; + int name = LEB128.readAsUInt32(reader); //TODO uleb128p1 - int type = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - int typeLength = Leb128.unsignedLeb128Size( type ); - reader.setPointerIndex( reader.getPointerIndex( ) + typeLength ); - length += typeLength; + int type = LEB128.readAsUInt32(reader); break; } case DebugStateMachineOpCodes.DBG_START_LOCAL_EXTENDED: { - int register = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - int registerLength = Leb128.unsignedLeb128Size( register ); - reader.setPointerIndex( reader.getPointerIndex( ) + registerLength ); - length += registerLength; + int register = LEB128.readAsUInt32(reader); //TODO uleb128p1 - int name = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - int nameLength = Leb128.unsignedLeb128Size( name ); - reader.setPointerIndex( reader.getPointerIndex( ) + nameLength ); - length += nameLength; + int name = LEB128.readAsUInt32(reader); //TODO uleb128p1 - int type = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - int typeLength = Leb128.unsignedLeb128Size( type ); - reader.setPointerIndex( reader.getPointerIndex( ) + typeLength ); - length += typeLength; + int type = LEB128.readAsUInt32(reader); //TODO uleb128p1 - int signature = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - int signatureLength = Leb128.unsignedLeb128Size( signature ); - reader.setPointerIndex( reader.getPointerIndex( ) + signatureLength ); - length += signatureLength; + int signature = LEB128.readAsUInt32(reader); break; } case DebugStateMachineOpCodes.DBG_END_LOCAL: { - int register = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - int registerLength = Leb128.unsignedLeb128Size( register ); - reader.setPointerIndex( reader.getPointerIndex( ) + registerLength ); - length += registerLength; + int register = LEB128.readAsUInt32(reader); break; } case DebugStateMachineOpCodes.DBG_RESTART_LOCAL: { - int register = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - int registerLength = Leb128.unsignedLeb128Size( register ); - reader.setPointerIndex( reader.getPointerIndex( ) + registerLength ); - length += registerLength; + int register = LEB128.readAsUInt32(reader); break; } case DebugStateMachineOpCodes.DBG_SET_PROLOGUE_END: { @@ -121,10 +83,7 @@ class DebugInfoStateMachineReader { } case DebugStateMachineOpCodes.DBG_SET_FILE: { //TODO uleb128p1 - int name = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - int nameLength = Leb128.unsignedLeb128Size( name ); - reader.setPointerIndex( reader.getPointerIndex( ) + nameLength ); - length += nameLength; + int name = LEB128.readAsUInt32(reader); break; } default: { @@ -132,5 +91,7 @@ class DebugInfoStateMachineReader { } } } + + return 0; } } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedAnnotation.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedAnnotation.java index b29e8fe690..aa9f4ed5cd 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedAnnotation.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedAnnotation.java @@ -15,31 +15,31 @@ */ package ghidra.file.formats.android.dex.format; -import ghidra.app.util.bin.BinaryReader; -import ghidra.app.util.bin.StructConverter; -import ghidra.file.formats.android.dex.util.Leb128; -import ghidra.program.model.data.*; -import ghidra.util.exception.DuplicateNameException; - import java.io.IOException; import java.util.*; +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.StructConverter; +import ghidra.app.util.bin.format.dwarf4.LEB128; +import ghidra.program.model.data.*; +import ghidra.util.exception.DuplicateNameException; + public class EncodedAnnotation implements StructConverter { private int typeIndex; private int typeIndexLength;// in bytes private int size; private int sizeLength;// in bytes - private List< AnnotationElement > elements = new ArrayList< AnnotationElement >( ); + private List< AnnotationElement > elements = new ArrayList< >( ); public EncodedAnnotation( BinaryReader reader ) throws IOException { - typeIndex = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - typeIndexLength = Leb128.unsignedLeb128Size( typeIndex ); - reader.readNextByteArray( typeIndexLength );// consume leb... + LEB128 leb128 = LEB128.readUnsignedValue(reader); + typeIndex = leb128.asUInt32(); + typeIndexLength = leb128.getLength(); - size = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - sizeLength = Leb128.unsignedLeb128Size( size ); - reader.readNextByteArray( sizeLength );// consume leb... + leb128 = LEB128.readUnsignedValue(reader); + size = leb128.asUInt32(); + sizeLength = leb128.getLength(); for ( int i = 0 ; i < size ; ++i ) { elements.add( new AnnotationElement( reader ) ); diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedArray.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedArray.java index 664993ba11..fa6454f602 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedArray.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedArray.java @@ -21,7 +21,7 @@ import java.util.List; import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.StructConverter; -import ghidra.file.formats.android.dex.util.Leb128; +import ghidra.app.util.bin.format.dwarf4.LEB128; import ghidra.program.model.data.*; import ghidra.util.exception.DuplicateNameException; @@ -33,18 +33,16 @@ public class EncodedArray implements StructConverter { private byte [] values; public EncodedArray( BinaryReader reader ) throws IOException { - size = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - sizeLength = Leb128.unsignedLeb128Size( size ); - reader.readNextByteArray( sizeLength );// consume leb... + LEB128 leb128 = LEB128.readUnsignedValue(reader); + size = leb128.asUInt32(); + sizeLength = leb128.getLength(); - long oldIndex = reader.getPointerIndex( ); - List< EncodedValue > valuesList = new ArrayList< EncodedValue >( ); + BinaryReader evReader = reader.clone(); + List< EncodedValue > valuesList = new ArrayList< >( ); for ( int i = 0 ; i < size ; ++i ) { - valuesList.add( new EncodedValue( reader ) ); + valuesList.add(new EncodedValue(evReader)); } - int nBytes = (int) ( reader.getPointerIndex() - oldIndex ); - - reader.setPointerIndex(oldIndex); + int nBytes = (int) (evReader.getPointerIndex() - reader.getPointerIndex()); values = reader.readNextByteArray(nBytes); // Re-read the encoded values as a byte array } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedCatchHandler.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedCatchHandler.java index 55b22196a4..5bcf91a62c 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedCatchHandler.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedCatchHandler.java @@ -15,41 +15,37 @@ */ package ghidra.file.formats.android.dex.format; -import ghidra.app.util.bin.BinaryReader; -import ghidra.app.util.bin.StructConverter; -import ghidra.file.formats.android.dex.util.Leb128; -import ghidra.program.model.data.ArrayDataType; -import ghidra.program.model.data.CategoryPath; -import ghidra.program.model.data.DataType; -import ghidra.program.model.data.Structure; -import ghidra.program.model.data.StructureDataType; -import ghidra.util.exception.DuplicateNameException; - import java.io.IOException; import java.util.ArrayList; import java.util.List; +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.StructConverter; +import ghidra.app.util.bin.format.dwarf4.LEB128; +import ghidra.program.model.data.*; +import ghidra.util.exception.DuplicateNameException; + public class EncodedCatchHandler implements StructConverter { private int size; private int sizeLength;// in bytes - private List< EncodedTypeAddressPair > handlers = new ArrayList< EncodedTypeAddressPair >( ); + private List< EncodedTypeAddressPair > handlers = new ArrayList< >( ); private int catchAllAddress; private int catchAllAddressLength; public EncodedCatchHandler( BinaryReader reader ) throws IOException { - size = Leb128.readSignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - sizeLength = Leb128.signedLeb128Size( size ); - reader.readNextByteArray( sizeLength );// consume leb... + LEB128 leb128 = LEB128.readSignedValue(reader); + size = leb128.asInt32(); + sizeLength = leb128.getLength(); for ( int i = 0 ; i < Math.abs( size ) ; ++i ) { handlers.add( new EncodedTypeAddressPair( reader ) ); } if ( size <= 0 ) {// This element is only present if size is non-positive. - catchAllAddress = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - catchAllAddressLength = Leb128.unsignedLeb128Size( catchAllAddress ); - reader.readNextByteArray( catchAllAddressLength );// consume leb... + leb128 = LEB128.readUnsignedValue(reader); + catchAllAddress = leb128.asUInt32(); + catchAllAddressLength = leb128.getLength(); } } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedCatchHandlerList.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedCatchHandlerList.java index 958fc3bc60..5df1321781 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedCatchHandlerList.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedCatchHandlerList.java @@ -15,30 +15,26 @@ */ package ghidra.file.formats.android.dex.format; -import ghidra.app.util.bin.BinaryReader; -import ghidra.app.util.bin.StructConverter; -import ghidra.file.formats.android.dex.util.Leb128; -import ghidra.program.model.data.ArrayDataType; -import ghidra.program.model.data.CategoryPath; -import ghidra.program.model.data.DataType; -import ghidra.program.model.data.Structure; -import ghidra.program.model.data.StructureDataType; -import ghidra.util.exception.DuplicateNameException; - import java.io.IOException; import java.util.ArrayList; import java.util.List; +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.StructConverter; +import ghidra.app.util.bin.format.dwarf4.LEB128; +import ghidra.program.model.data.*; +import ghidra.util.exception.DuplicateNameException; + public class EncodedCatchHandlerList implements StructConverter { private int size; private int sizeLength;// in bytes - private List< EncodedCatchHandler > handlers = new ArrayList< EncodedCatchHandler >( ); + private List< EncodedCatchHandler > handlers = new ArrayList< >( ); public EncodedCatchHandlerList( BinaryReader reader ) throws IOException { - size = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - sizeLength = Leb128.unsignedLeb128Size( size ); - reader.readNextByteArray( sizeLength );// consume leb... + LEB128 leb128 = LEB128.readUnsignedValue(reader); + size = leb128.asUInt32(); + sizeLength = leb128.getLength(); for ( int i = 0 ; i < size ; ++i ) { handlers.add( new EncodedCatchHandler( reader ) ); diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedField.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedField.java index ff5897bb25..1b98b51a8a 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedField.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedField.java @@ -15,18 +15,14 @@ */ package ghidra.file.formats.android.dex.format; +import java.io.IOException; + import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.StructConverter; -import ghidra.file.formats.android.dex.util.Leb128; -import ghidra.program.model.data.ArrayDataType; -import ghidra.program.model.data.CategoryPath; -import ghidra.program.model.data.DataType; -import ghidra.program.model.data.Structure; -import ghidra.program.model.data.StructureDataType; +import ghidra.app.util.bin.format.dwarf4.LEB128; +import ghidra.program.model.data.*; import ghidra.util.exception.DuplicateNameException; -import java.io.IOException; - public class EncodedField implements StructConverter { private long _fileOffset; @@ -38,15 +34,15 @@ public class EncodedField implements StructConverter { private int accessFlagsLength;// in bytes public EncodedField( BinaryReader reader ) throws IOException { - _fileOffset = reader.getPointerIndex( ); - fieldIndexDifference = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - fieldIndexDifferenceLength = Leb128.unsignedLeb128Size( fieldIndexDifference ); - reader.readNextByteArray( fieldIndexDifferenceLength );// consume leb... + LEB128 leb128 = LEB128.readUnsignedValue(reader); + _fileOffset = leb128.getOffset(); + fieldIndexDifference = leb128.asUInt32(); + fieldIndexDifferenceLength = leb128.getLength(); - accessFlags = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - accessFlagsLength = Leb128.unsignedLeb128Size( accessFlags ); - reader.readNextByteArray( accessFlagsLength );// consume leb... + leb128 = LEB128.readUnsignedValue(reader); + accessFlags = leb128.asUInt32(); + accessFlagsLength = leb128.getLength(); } public long getFileOffset( ) { diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedMethod.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedMethod.java index d4abcd496a..a07e6342fc 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedMethod.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedMethod.java @@ -15,14 +15,14 @@ */ package ghidra.file.formats.android.dex.format; +import java.io.IOException; + import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.StructConverter; -import ghidra.file.formats.android.dex.util.Leb128; +import ghidra.app.util.bin.format.dwarf4.LEB128; import ghidra.program.model.data.*; import ghidra.util.exception.DuplicateNameException; -import java.io.IOException; - public class EncodedMethod implements StructConverter { private long _fileOffset; @@ -39,29 +39,22 @@ public class EncodedMethod implements StructConverter { private CodeItem codeItem; public EncodedMethod( BinaryReader reader ) throws IOException { - _fileOffset = reader.getPointerIndex( ); - methodIndexDifference = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - methodIndexDifferenceLength = Leb128.unsignedLeb128Size( methodIndexDifference ); - reader.readNextByteArray( methodIndexDifferenceLength );// consume leb... + LEB128 leb128 = LEB128.readUnsignedValue(reader); + _fileOffset = leb128.getOffset(); + methodIndexDifference = leb128.asUInt32(); + methodIndexDifferenceLength = leb128.getLength(); - accessFlags = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - accessFlagsLength = Leb128.unsignedLeb128Size( accessFlags ); - reader.readNextByteArray( accessFlagsLength );// consume leb... + leb128 = LEB128.readUnsignedValue(reader); + accessFlags = leb128.asUInt32(); + accessFlagsLength = leb128.getLength(); - codeOffset = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - codeOffsetLength = Leb128.unsignedLeb128Size( codeOffset ); - reader.readNextByteArray( codeOffsetLength );// consume leb... + leb128 = LEB128.readUnsignedValue(reader); + codeOffset = leb128.asUInt32(); + codeOffsetLength = leb128.getLength(); if ( codeOffset > 0 ) { - long oldIndex = reader.getPointerIndex( ); - try { - reader.setPointerIndex( codeOffset ); - codeItem = new CodeItem( reader ); - } - finally { - reader.setPointerIndex( oldIndex ); - } + codeItem = new CodeItem( reader.clone(codeOffset) ); } } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedTypeAddressPair.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedTypeAddressPair.java index 031cba8fa9..e8c70f5e54 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedTypeAddressPair.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/EncodedTypeAddressPair.java @@ -15,18 +15,14 @@ */ package ghidra.file.formats.android.dex.format; +import java.io.IOException; + import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.StructConverter; -import ghidra.file.formats.android.dex.util.Leb128; -import ghidra.program.model.data.ArrayDataType; -import ghidra.program.model.data.CategoryPath; -import ghidra.program.model.data.DataType; -import ghidra.program.model.data.Structure; -import ghidra.program.model.data.StructureDataType; +import ghidra.app.util.bin.format.dwarf4.LEB128; +import ghidra.program.model.data.*; import ghidra.util.exception.DuplicateNameException; -import java.io.IOException; - public class EncodedTypeAddressPair implements StructConverter { private int typeIndex; @@ -36,13 +32,13 @@ public class EncodedTypeAddressPair implements StructConverter { private int addressLength;// in bytes public EncodedTypeAddressPair( BinaryReader reader ) throws IOException { - typeIndex = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - typeIndexLength = Leb128.unsignedLeb128Size( typeIndex ); - reader.readNextByteArray( typeIndexLength );// consume leb... + LEB128 leb128 = LEB128.readUnsignedValue(reader); + typeIndex = leb128.asUInt32(); + typeIndexLength = leb128.getLength(); - address = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) ); - addressLength = Leb128.unsignedLeb128Size( address ); - reader.readNextByteArray( addressLength );// consume leb... + leb128 = LEB128.readUnsignedValue(reader); + address = leb128.asUInt32(); + addressLength = leb128.getLength(); } public int getTypeIndex( ) { diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/StringDataItem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/StringDataItem.java index 68905a5902..867182230f 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/StringDataItem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/format/StringDataItem.java @@ -15,52 +15,41 @@ */ package ghidra.file.formats.android.dex.format; -import ghidra.app.util.bin.BinaryReader; -import ghidra.app.util.bin.StructConverter; -import ghidra.file.formats.android.dex.util.Leb128; -import ghidra.program.model.data.ArrayDataType; -import ghidra.program.model.data.CategoryPath; -import ghidra.program.model.data.DataType; -import ghidra.program.model.data.Structure; -import ghidra.program.model.data.StructureDataType; -import ghidra.util.exception.DuplicateNameException; - import java.io.ByteArrayInputStream; import java.io.IOException; +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.StructConverter; +import ghidra.app.util.bin.format.dwarf4.LEB128; +import ghidra.program.model.data.*; +import ghidra.util.exception.DuplicateNameException; + public class StringDataItem implements StructConverter { + private static final int MAX_STRING_LEN = 0x200000; // 2Mb'ish private int stringLength; private int lebLength; private int actualLength; private String string; - public StringDataItem( StringIDItem stringItem, BinaryReader reader ) throws IOException { - long oldIndex = reader.getPointerIndex( ); - try { - reader.setPointerIndex( stringItem.getStringDataOffset( ) ); - stringLength = Leb128.readUnsignedLeb128( reader.readByteArray( stringItem.getStringDataOffset( ), 5 ) ); + public StringDataItem(StringIDItem stringItem, BinaryReader reader) throws IOException { + reader = reader.clone(stringItem.getStringDataOffset()); + LEB128 leb128 = LEB128.readUnsignedValue(reader); + stringLength = leb128.asUInt32(); + lebLength = leb128.getLength(); + long nullTermIndex = + getIndexOfByteValue(reader, reader.getPointerIndex(), MAX_STRING_LEN, (byte) 0); + actualLength = (int) (nullTermIndex - reader.getPointerIndex() + 1); + byte[] stringBytes = reader.readNextByteArray(actualLength); - lebLength = Leb128.unsignedLeb128Size( stringLength ); + ByteArrayInputStream in = new ByteArrayInputStream(stringBytes); - reader.readNextByteArray( lebLength );// consume leb... + char[] out = new char[stringLength]; - actualLength = computeActualLength( reader ); - - byte [] stringBytes = reader.readNextByteArray( actualLength ); - - ByteArrayInputStream in = new ByteArrayInputStream( stringBytes ); - - char [] out = new char[ stringLength ]; - - string = ModifiedUTF8.decode( in, out ); - } - finally { - reader.setPointerIndex( oldIndex ); - } + string = ModifiedUTF8.decode(in, out); } - public String getString( ) { + public String getString() { return string; } @@ -73,15 +62,18 @@ public class StringDataItem implements StructConverter { return structure; } - private int computeActualLength( BinaryReader reader ) throws IOException { - int count = 0; - while ( count < 0x200000 ) {// don't run forever! - if ( reader.readByte( reader.getPointerIndex( ) + count ) == 0x0 ) { - break; + private static long getIndexOfByteValue(BinaryReader reader, long startIndex, int maxLen, + byte byteValueToFind) throws IOException { + long maxIndex = startIndex + maxLen; + long currentIndex = startIndex; + while (currentIndex < maxIndex) { + byte b = reader.readByte(currentIndex); + if (b == byteValueToFind) { + return currentIndex; } - ++count; + currentIndex++; } - return count + 1; + return currentIndex; } } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/util/Leb128.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/util/Leb128.java deleted file mode 100644 index 5dd7e9437f..0000000000 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/util/Leb128.java +++ /dev/null @@ -1,202 +0,0 @@ -/* ### - * IP: Apache License 2.0 - */ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * 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.file.formats.android.dex.util; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; - -import com.googlecode.d2j.DexException; - -import ghidra.util.NumericUtilities; - -/** - * Reads and writes DWARFv3 LEB 128 signed and unsigned integers. See DWARF v3 section 7.6. - */ -public final class Leb128 { - private Leb128() { - } - - /** - * Gets the number of bytes in the unsigned LEB128 encoding of the given value. - * - * @param value - * the value in question - * @return its write size, in bytes - */ - public static int unsignedLeb128Size( int value ) { - // TODO: This could be much cleverer. - - int remaining = value >> 7; - int count = 0; - - while ( remaining != 0 ) { - remaining >>= 7; - count++; - } - - return count + 1; - } - - /** - * Gets the number of bytes in the signed LEB128 encoding of the given value. - * - * @param value - * the value in question - * @return its write size, in bytes - */ - public static int signedLeb128Size( int value ) { - // TODO: This could be much cleverer. - - int remaining = value >> 7; - int count = 0; - boolean hasMore = true; - int end = ( ( value & Integer.MIN_VALUE ) == 0 ) ? 0 : -1; - - while ( hasMore ) { - hasMore = ( remaining != end ) || ( ( remaining & 1 ) != ( ( value >> 6 ) & 1 ) ); - - value = remaining; - remaining >>= 7; - count++; - } - - return count; - - // ByteArrayOutputStream out = new ByteArrayOutputStream( ); - // int remaining = value >>> 7; - // - // while ( remaining != 0 ) { - // out.write( ( byte ) ( ( value & 0x7f ) | 0x80 ) ); - // value = remaining; - // remaining >>>= 7; - // } - // - // out.write( ( byte ) ( value & 0x7f ) ); - // - // return out.toByteArray( ).length; - } - - public static int readSignedLeb128( byte [] bytes ) { - return readSignedLeb128( new ByteArrayInputStream( bytes ) ); - } - - /** - * Reads an signed integer from {@code in}. - */ - public static int readSignedLeb128( ByteArrayInputStream in ) { - int result = 0; - int cur; - int count = 0; - int signBits = -1; - - do { - cur = in.read( ) & 0xff; - result |= ( cur & 0x7f ) << ( count * 7 ); - signBits <<= 7; - count++; - } - while ( ( ( cur & 0x80 ) == 0x80 ) && count < 5 ); - - if ( ( cur & 0x80 ) == 0x80 ) { - throw new DexException( "invalid LEB128 sequence" ); - } - - // Sign extend if appropriate - if ( ( ( signBits >> 1 ) & result ) != 0 ) { - result |= signBits; - } - - return result; - } - - public static int readUnsignedLeb128( byte [] bytes ) { - return readUnsignedLeb128( new ByteArrayInputStream( bytes ) ); - } - - /** - * Reads an unsigned integer from {@code in}. - */ - public static int readUnsignedLeb128( ByteArrayInputStream in ) { - int result = 0; - int cur; - int count = 0; - - do { - cur = in.read( ) & 0xff; - result |= ( cur & 0x7f ) << ( count * 7 ); - count++; - } - while ( ( ( cur & 0x80 ) == 0x80 ) && count < 5 ); - - if ( ( cur & 0x80 ) == 0x80 ) { - throw new DexException( "invalid LEB128 sequence" ); - } - - return result; - } - - /** - * Writes {@code value} as an unsigned integer to {@code out}, starting at {@code offset}. Returns the number of bytes written. - */ - public static void writeUnsignedLeb128( ByteArrayOutputStream out, int value ) { - int remaining = value >>> 7; - - while ( remaining != 0 ) { - out.write( ( byte ) ( ( value & 0x7f ) | 0x80 ) ); - value = remaining; - remaining >>>= 7; - } - - out.write( ( byte ) ( value & 0x7f ) ); - } - - /** - * Writes {@code value} as a signed integer to {@code out}, starting at {@code offset}. Returns the number of bytes written. - */ - public static void writeSignedLeb128( ByteArrayOutputStream out, int value ) { - int remaining = value >> 7; - boolean hasMore = true; - int end = ( ( value & Integer.MIN_VALUE ) == 0 ) ? 0 : -1; - - while ( hasMore ) { - hasMore = ( remaining != end ) || ( ( remaining & 1 ) != ( ( value >> 6 ) & 1 ) ); - - out.write( ( byte ) ( ( value & 0x7f ) | ( hasMore ? 0x80 : 0 ) ) ); - value = remaining; - remaining >>= 7; - } - } - - public static void main( String [] args ) { - - //ByteArrayOutputStream out = new ByteArrayOutputStream( ); - //writeSignedLeb128( out, -12345 ); - - //System.out.println( "array length: " + out.toByteArray( ).length ); - - //System.out.println( "actual length: " + signedLeb128Size( -12345 ) ); - - //System.out.println( readSignedLeb128( out.toByteArray( ) ) ); - - System.out.println( readUnsignedLeb128( NumericUtilities.convertStringToBytes("807f" ) ) ); - - - } -} diff --git a/Ghidra/Features/MicrosoftCodeAnalyzer/src/test/java/ghidra/app/cmd/data/rtti/RttiModelTest.java b/Ghidra/Features/MicrosoftCodeAnalyzer/src/test/java/ghidra/app/cmd/data/rtti/RttiModelTest.java index 481e8e728f..b38827c082 100644 --- a/Ghidra/Features/MicrosoftCodeAnalyzer/src/test/java/ghidra/app/cmd/data/rtti/RttiModelTest.java +++ b/Ghidra/Features/MicrosoftCodeAnalyzer/src/test/java/ghidra/app/cmd/data/rtti/RttiModelTest.java @@ -1,5 +1,6 @@ /* ### * IP: GHIDRA + * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,7 +52,7 @@ public class RttiModelTest extends AbstractRttiTest { setupRtti4_32(builder, 0x01001340L, 0, 0, 0, "0x01005364", "0x0100137c"); Address address = builder.addr(0x01001340L); checkInvalidModel(new Rtti4Model(program, address, defaultValidationOptions), - "No vf table pointer is defined for this TypeDescriptor model."); + "TypeDescriptor data type at 01005364 doesn't point to a vfTable address in a loaded and initialized memory block."); } @Test @@ -62,7 +63,7 @@ public class RttiModelTest extends AbstractRttiTest { setupRtti0_32(builder, 0x01001364, "0x01007700", "0x0", "stuff"); Address address = builder.addr(0x01001340L); checkInvalidModel(new Rtti4Model(program, address, defaultValidationOptions), - "No vf table pointer is defined for this TypeDescriptor model."); + "TypeDescriptor data type at 01001364 doesn't point to a vfTable address in a loaded and initialized memory block."); } @Test diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockingCheckBoxMenuItem.java b/Ghidra/Framework/Docking/src/main/java/docking/DockingCheckBoxMenuItem.java index 3e0768ae25..177db1fe8a 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockingCheckBoxMenuItem.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockingCheckBoxMenuItem.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +30,13 @@ public class DockingCheckBoxMenuItem extends JCheckBoxMenuItem { @Override protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) { - return true; // we will take care of the action ourselves + // TODO this note doesn't really make sense. I think this idea is outdated. Leaving this + // here for a bit, in case there is something we missed. This code is also in + // DockingMenuItem. + // return true; // we will take care of the action ourselves + + // Our KeyBindingOverrideKeyEventDispatcher processes actions for us, so there is no + // need to have the menu item do it + return false; } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockingMenuItem.java b/Ghidra/Framework/Docking/src/main/java/docking/DockingMenuItem.java index 8c506aa0b5..8abe8eb6c2 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockingMenuItem.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockingMenuItem.java @@ -33,7 +33,8 @@ public class DockingMenuItem extends JMenuItem implements GComponent { @Override protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) { // TODO this note doesn't really make sense. I think this idea is outdated. Leaving this - // here for a bit, in case there is something we missed + // here for a bit, in case there is something we missed. This code is also in + // DockingCheckboxMenuItemUI. // return true; // we will take care of the action ourselves // Our KeyBindingOverrideKeyEventDispatcher processes actions for us, so there is no diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionDB.java index 461abdd72e..29477489f2 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionDB.java @@ -2724,6 +2724,7 @@ public class FunctionDB extends DatabaseObject implements Function { @Override public Set getCallingFunctions(TaskMonitor monitor) { + monitor = TaskMonitor.dummyIfNull(monitor); Set set = new HashSet<>(); ReferenceIterator iter = program.getReferenceManager().getReferencesTo(getEntryPoint()); while (iter.hasNext()) { @@ -2742,6 +2743,7 @@ public class FunctionDB extends DatabaseObject implements Function { @Override public Set getCalledFunctions(TaskMonitor monitor) { + monitor = TaskMonitor.dummyIfNull(monitor); Set set = new HashSet<>(); Set references = getReferencesFromBody(monitor); for (Reference reference : references) { diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/util/task/TaskMonitor.java b/Ghidra/Framework/Utility/src/main/java/ghidra/util/task/TaskMonitor.java index 3f7ce33899..4093447c90 100644 --- a/Ghidra/Framework/Utility/src/main/java/ghidra/util/task/TaskMonitor.java +++ b/Ghidra/Framework/Utility/src/main/java/ghidra/util/task/TaskMonitor.java @@ -32,6 +32,16 @@ public interface TaskMonitor { public static final TaskMonitor DUMMY = new StubTaskMonitor(); + /** + * Returns the given task monitor if it is not {@code null}. Otherwise, a {@link #DUMMY} + * monitor is returned. + * @param tm the monitor to check for {@code null} + * @return a non-null task monitor + */ + public static TaskMonitor dummyIfNull(TaskMonitor tm) { + return tm == null ? DUMMY : tm; + } + /** A value to indicate that this monitor has no progress value set */ public static final int NO_PROGRESS_VALUE = -1; diff --git a/build.gradle b/build.gradle index bbe7667495..ecd372c4a5 100644 --- a/build.gradle +++ b/build.gradle @@ -265,6 +265,7 @@ String getCurrentPlatformName() { boolean isX86_32 = archName.equals("x86") || archName.equals("i386"); boolean isX86_64 = archName.equals("x86_64") || archName.equals("amd64"); + boolean isAARCH_64 = archName.equals("aarch64"); if (osName.startsWith("Windows")) { if (isX86_32) { @@ -283,6 +284,9 @@ String getCurrentPlatformName() { if (isX86_64) { return 'osx64' } + if (isAARCH_64) { + return 'aarch64' + } } throw new GradleException("Unrecognized current platform -> osName = $osName, archName = $archName") }