GP-877 LEB128 enhancements, cleanup

This commit is contained in:
dev747368 2023-03-07 19:33:43 -05:00
parent 0380709881
commit 0e45354f24
58 changed files with 1363 additions and 1234 deletions

View file

@ -0,0 +1,124 @@
/* ###
* 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.program.model.data;
import java.io.IOException;
import java.io.InputStream;
import ghidra.docking.settings.*;
import ghidra.program.model.mem.MemBuffer;
import ghidra.program.model.scalar.Scalar;
import ghidra.util.classfinder.ClassTranslator;
/**
* An abstract base class for a LEB128 variable length integer data type.
* <p>
* See {@link LEB128}.
*/
public abstract class AbstractLeb128DataType extends BuiltIn implements Dynamic {
/* package */ static final FormatSettingsDefinition FORMAT = FormatSettingsDefinition.DEF_HEX;
private static SettingsDefinition[] SETTINGS_DEFS = { FORMAT };
private final boolean signed;
/**
* Base constructor for a little endian based 128 data type.
* @param name name of the leb128 data type that extends this class.
* @param signed true if it is signed. false if unsigned.
* @param dtm the data type manager to associate with this data type.
*/
public AbstractLeb128DataType(String name, boolean signed, DataTypeManager dtm) {
super(null, name, dtm);
this.signed = signed;
}
@Override
protected SettingsDefinition[] getBuiltInSettingsDefinitions() {
return SETTINGS_DEFS;
}
@Override
public int getLength() {
return -1;
}
@Override
public int getLength(MemBuffer buf, int maxLength) {
if (maxLength < 0) {
maxLength = LEB128.MAX_SUPPORTED_LENGTH;
}
try (InputStream is = buf.getInputStream(0, maxLength)) {
return LEB128.getLength(is);
}
catch (IOException e) {
return -1;
}
}
@Override
public Class<?> getValueClass(Settings settings) {
return Scalar.class;
}
@Override
public Object getValue(MemBuffer buf, Settings settings, int maxLength) {
if (maxLength < 0) {
maxLength = LEB128.MAX_SUPPORTED_LENGTH;
}
try (InputStream is = buf.getInputStream(0, maxLength)) {
long val = LEB128.read(is, signed);
return new Scalar(64 - Long.numberOfLeadingZeros(val), val, signed);
}
catch (IOException e) {
return null; // memory error, or more than 10 bytes long
}
}
@Override
public String getRepresentation(MemBuffer buf, Settings settings, int length) {
Scalar val = (Scalar) getValue(buf, settings, length);
if (val == null) {
return "??";
}
int radix = FORMAT.getRadix(settings);
String postfix = FORMAT.getRepresentationPostfix(settings);
String valStr = val.toString(radix, false, signed, "", "");
return valStr.toUpperCase() + postfix;
}
@Override
public DataType getReplacementBaseType() {
return ByteDataType.dataType;
}
@Override
public boolean canSpecifyLength() {
return true;
}
@Override
public String getDefaultLabelPrefix() {
return name;
}
}

View file

@ -15,9 +15,10 @@
*/
package ghidra.program.model.data;
import java.net.URL;
import java.util.Collection;
import java.net.URL;
import ghidra.docking.settings.Settings;
import ghidra.docking.settings.SettingsDefinition;
import ghidra.program.model.mem.MemBuffer;
@ -290,16 +291,18 @@ public interface DataType {
public URL getDocs();
/**
* Get the interpreted data value in the form of the appropriate Object for this DataType.
* This method must return a value consistent with {@link #getValueClass(Settings)}.
* Returns the interpreted data value as an instance of the
* {@link #getValueClass(Settings) advertised value class}.
* <p>
* For instance, if this datatype is a {@link Pointer} an Address object or null should be returned.
* A Byte, returns a {@link Scalar} object.
* For instance, {@link Pointer} data types should return an Address object (or null), or
* integer data types should return a {@link Scalar} object.
*
* @param buf the data buffer.
* @param buf the data buffer
* @param settings the settings to use.
* @param length the number of bytes to get the value from.
* @return the data Object.
* @param length indicates the maximum number of bytes that may be consumed by a
* {@link Dynamic} datatype, otherwise this value is ignored. A value of -1 may be specified
* to allow a Dynamic datatype to determine the length based upon the actual data bytes
* @return the data object, or null if data is invalid
*/
public Object getValue(MemBuffer buf, Settings settings, int length);

View file

@ -0,0 +1,153 @@
/* ###
* 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.program.model.data;
import java.io.*;
/**
* Logic for reading LEB128 values.
* <p>
* LEB128 is a variable length integer encoding that uses 7 bits per byte, with the high bit
* being reserved as a continuation flag, with the least significant bytes coming first
* (<b>L</b>ittle <b>E</b>ndian <B>B</b>ase <b>128</b>).
* <p>
* This implementation only supports reading values that decode to at most 64 bits (to fit into
* a java long).
* <p>
* When reading a value, you must already know if it was written as a signed or unsigned value to
* be able to decode it correctly.
*/
public class LEB128 {
/**
* Max number of bytes that is supported by the deserialization code.
*/
public static final int MAX_SUPPORTED_LENGTH = 10;
/**
* Reads an unsigned LEB128 variable length integer from the stream.
*
* @param is {@link InputStream} to get bytes from
* @return leb128 value, as a long
* @throws IOException if an I/O error occurs or decoded value is outside the range of a java
* 64 bit int (or it used more than {@value #MAX_SUPPORTED_LENGTH} bytes to be encoded), or
* there is an error or EOF getting a byte from the InputStream before reaching the end of the
* encoded value
*/
public static long unsigned(InputStream is) throws IOException {
return read(is, false);
}
/**
* Reads a signed LEB128 variable length integer from the stream.
*
* @param is {@link InputStream} to get bytes from
* @return leb128 value, as a long
* @throws IOException if an I/O error occurs or decoded value is outside the range of a java
* 64 bit int (or it used more than {@value #MAX_SUPPORTED_LENGTH} bytes to be encoded), or
* there is an error or EOF getting a byte from the InputStream before reaching the end of the
* encoded value
*/
public static long signed(InputStream is) throws IOException {
return read(is, true);
}
/**
* Reads a LEB128 number from the stream and returns it as a java 64 bit long int.
* <p>
* Large unsigned integers that use all 64 bits are returned in a java native
* 'long' type, which is signed. It is up to the caller to treat the value as unsigned.
* <p>
* Large integers that use more than 64 bits will cause an IOException to be thrown.
* <p>
* @param is {@link InputStream} to get 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 decoded value is outside the range of a java
* 64 bit int (or it used more than {@value #MAX_SUPPORTED_LENGTH} bytes to be encoded), or
* there is an error or EOF getting a byte from the InputStream before reaching the end of the
* encoded value
*/
public static long read(InputStream is, boolean isSigned) throws IOException {
int nextByte = 0;
int shift = 0;
long value = 0;
while (true) {
nextByte = is.read();
if (nextByte < 0) {
throw new EOFException();
}
if (shift == 70 || (isSigned == false && shift == 63 && nextByte > 1)) {
throw new IOException(
"Unsupported LEB128 value, too large to fit in 64bit java long variable");
}
// 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 ((isSigned) && (shift < Long.SIZE) && ((nextByte & 0x40) != 0)) {
// 0x40 is the new 'high' sign bit since 0x80 is the continuation flag.
// bitwise-or in all the sign-extension bits we need for the value
value |= (-1L << shift);
}
return value;
}
/**
* Returns the length of the variable length LEB128 value.
*
* @param is InputStream to get bytes from
* @return length of the LEB128 value, or -1 if the end of the value is not found
* @throws IOException if error getting next byte from stream
*/
public static int getLength(InputStream is) throws IOException {
int length = 0;
int nextByte;
while ((nextByte = is.read()) >= 0 && length < MAX_SUPPORTED_LENGTH) {
length++;
if ((nextByte & 0x80) == 0) {
return length;
}
}
return -1;
}
/**
* Decodes a LEB128 number from a byte array and returns it as a long.
* <p>
* See {@link #read(InputStream, 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 array offset is invalid, decoded value is outside the range of a java
* 64 bit int (or it used more than {@value #MAX_SUPPORTED_LENGTH} bytes to be encoded), or
* the end of the array was reached before reaching the end of the encoded value
*/
public static long decode(byte[] bytes, int offset, boolean isSigned) throws IOException {
InputStream is = new ByteArrayInputStream(bytes, offset, bytes.length - offset);
return read(is, isSigned);
}
}

View file

@ -0,0 +1,59 @@
/* ###
* 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.program.model.data;
import ghidra.util.classfinder.ClassTranslator;
/**
* A Signed Little Endian Base 128 integer data type.
*/
public class SignedLeb128DataType extends AbstractLeb128DataType {
static {
ClassTranslator.put("ghidra.app.plugin.exceptionhandlers.gcc.datatype.SignedLeb128DataType",
SignedLeb128DataType.class.getName());
}
/** A statically defined SignedLeb128DataType instance.*/
public final static SignedLeb128DataType dataType = new SignedLeb128DataType();
/**
* Creates a signed little endian base 128 integer data type.
*/
public SignedLeb128DataType() {
this(null);
}
/**
* Creates a signed little endian base 128 integer data type.
* @param dtm the data type manager to associate with this data type.
*/
public SignedLeb128DataType(DataTypeManager dtm) {
super("sleb128", true, dtm);
}
@Override
public DataType clone(DataTypeManager dtm) {
if (dtm == getDataTypeManager()) {
return this;
}
return new SignedLeb128DataType(dtm);
}
@Override
public String getDescription() {
return "Signed LEB128-Encoded Number";
}
}

View file

@ -0,0 +1,60 @@
/* ###
* 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.program.model.data;
import ghidra.util.classfinder.ClassTranslator;
/**
* An Unsigned Little Endian Base 128 integer data type.
*/
public class UnsignedLeb128DataType extends AbstractLeb128DataType {
static {
ClassTranslator.put(
"ghidra.app.plugin.exceptionhandlers.gcc.datatype.UnsignedLeb128DataType",
UnsignedLeb128DataType.class.getName());
}
/** A statically defined UnsignedLeb128DataType instance.*/
public final static UnsignedLeb128DataType dataType = new UnsignedLeb128DataType();
/**
* Creates an unsigned little endian base 128 integer data type.
*/
public UnsignedLeb128DataType() {
this(null);
}
/**
* Creates an unsigned little endian base 128 integer data type.
* @param dtm the data type manager to associate with this data type.
*/
public UnsignedLeb128DataType(DataTypeManager dtm) {
super("uleb128", false, dtm);
}
@Override
public DataType clone(DataTypeManager dtm) {
if (dtm == getDataTypeManager()) {
return this;
}
return new UnsignedLeb128DataType(dtm);
}
@Override
public String getDescription() {
return "Unsigned LEB128-Encoded Number";
}
}

View file

@ -15,6 +15,7 @@
*/
package ghidra.program.model.mem;
import java.io.InputStream;
import java.math.BigInteger;
import ghidra.program.model.address.Address;
@ -86,7 +87,7 @@ public interface MemBuffer {
* @throws MemoryAccessException if memory cannot be read at the specified offset
*/
default public int getUnsignedByte(int offset) throws MemoryAccessException {
return getByte(offset) & 0xff;
return Byte.toUnsignedInt(getByte(offset));
}
/**
@ -139,7 +140,7 @@ public interface MemBuffer {
* @throws MemoryAccessException if a 2-byte short value cannot be read at the specified offset
*/
default public int getUnsignedShort(int offset) throws MemoryAccessException {
return getShort(offset) & 0xffff;
return Short.toUnsignedInt(getShort(offset));
}
/**
@ -157,7 +158,7 @@ public interface MemBuffer {
* @throws MemoryAccessException if a 4-byte integer value cannot be read at the specified offset
*/
default public long getUnsignedInt(int offset) throws MemoryAccessException {
return getInt(offset) & 0xFFFF_FFFFL;
return Integer.toUnsignedLong(getInt(offset));
}
/**
@ -222,4 +223,31 @@ public interface MemBuffer {
throw new MemoryAccessException("Invalid length for read: " + len);
}
}
/**
* Returns a stream that supplies the bytes of this buffer, starting at offset 0.
* <p>
* Note: the default implementation will produce invalid results if the underlying
* MemBuffer instance is is mutated to point to different memory.
*
* @return an InputStream that returns the bytes of this mem buffer
*/
default public InputStream getInputStream() {
return new MemBufferInputStream(this);
}
/**
* Returns a stream that supplies the bytes of this buffer, starting at the specified offset.
* <p>
* Note: the default implementation will produce invalid results if the underlying
* MemBuffer instance is is mutated to point to different memory.
*
* @param initialPosition location in membuffer where the stream should start
* @param length number of bytes to limit the stream to
* @return an InputSTream that returns the bytes of this mem buffer
*/
default public InputStream getInputStream(int initialPosition, int length) {
return new MemBufferInputStream(this, initialPosition, length);
}
}

View file

@ -0,0 +1,85 @@
/* ###
* 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.program.model.mem;
import java.io.IOException;
import java.io.InputStream;
/**
* Adapter between {@link MemBuffer membuffers} and {@link InputStream inputstreams}.
*/
public class MemBufferInputStream extends InputStream {
private MemBuffer membuf;
private int currentPosition;
private long maxPosition; // exclusive
/**
* Creates a new instance, starting a offset 0 of the membuffer, limited to the first 2Gb
* of the membuffer.
*
* @param membuf {@link MemBuffer} to wrap as an inputstream
*/
public MemBufferInputStream(MemBuffer membuf) {
this(membuf, 0, Integer.MAX_VALUE);
}
/**
* Creates a new instance of {@link MemBufferInputStream}, starting at the specified offset,
* limited to the first {@code length} bytes.
*
* @param membuf {@link MemBuffer} to wrap as an inputstream
* @param initialPosition starting position in the membuffer
* @param length number of bytes to limit this inputstream to. The sum of
* {@code initialPosition} and {@code length} must not exceed {@link Integer#MAX_VALUE}+1
*/
public MemBufferInputStream(MemBuffer membuf, int initialPosition, int length) {
this.maxPosition = initialPosition + length;
if (initialPosition < 0 || length < 0 || maxPosition > (long) Integer.MAX_VALUE + 1) {
throw new IllegalArgumentException();
}
this.membuf = membuf;
this.currentPosition = initialPosition;
}
@Override
public void close() throws IOException {
this.maxPosition = 0;
}
@Override
public int available() throws IOException {
return currentPosition >= 0 && currentPosition < maxPosition
? (int) (maxPosition - currentPosition)
: 0;
}
@Override
public int read() throws IOException {
try {
if (currentPosition < 0 || currentPosition >= maxPosition) {
return -1;
}
int result = Byte.toUnsignedInt(membuf.getByte(currentPosition));
currentPosition++;
return result;
}
catch (MemoryAccessException e) {
throw new IOException(e);
}
}
}

View file

@ -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.
@ -32,7 +31,6 @@ public class MemoryAccessException extends UsrException
super();
}
/**
* <p>Constructs an MemoryAccessException with the specified
* detail message.<p>
@ -42,4 +40,15 @@ public class MemoryAccessException extends UsrException
public MemoryAccessException(String message) {
super(message);
}
/**
* Creates a {@link MemoryAccessException} with a message and cause.
* @param msg message
* @param cause nested cause
*/
public MemoryAccessException(String msg, Throwable cause) {
super(msg, cause);
}
} // MemoryAccessException

View file

@ -27,16 +27,8 @@ import ghidra.program.model.mem.MemBuffer;
public class IntegerDataTypeTest extends AbstractGenericTest {
private static byte[] arr(int... vals) {
byte[] result = new byte[vals.length];
for (int i = 0; i < vals.length; i++) {
result[i] = (byte) vals[i];
}
return result;
}
private static MemBuffer buf(boolean bigEndian, int... vals) {
return new ByteMemBufferImpl(Address.NO_ADDRESS, arr(vals), bigEndian);
return new ByteMemBufferImpl(Address.NO_ADDRESS, bytes(vals), bigEndian);
}
private static Settings format(FormatSettingsDefinition setDef) {
@ -74,13 +66,13 @@ public class IntegerDataTypeTest extends AbstractGenericTest {
DataType type = AbstractIntegerDataType.getUnsignedDataType(1, null);
// Technically, these two are exactly the same test, just different Java syntax
assertArrayEquals(arr(0xff), type.encodeValue((byte) 0xff, BE, HEX, 1));
assertArrayEquals(arr(0xff), type.encodeValue((byte) -1, BE, HEX, 1));
assertArrayEquals(bytes(0xff), type.encodeValue((byte) 0xff, BE, HEX, 1));
assertArrayEquals(bytes(0xff), type.encodeValue((byte) -1, BE, HEX, 1));
assertFails(() -> type.encodeValue((short) 0x100, BE, HEX, 1));
assertFails(() -> type.encodeValue((short) -1, BE, HEX, 1));
assertArrayEquals(arr(0xff), type.encodeValue(0xff, BE, HEX, 1));
assertArrayEquals(bytes(0xff), type.encodeValue(0xff, BE, HEX, 1));
// This fails, because (int)-1 is 4294967295 when treated unsigned
assertFails(() -> type.encodeValue(-1, BE, HEX, 1));
}
@ -92,10 +84,10 @@ public class IntegerDataTypeTest extends AbstractGenericTest {
// Sanity check: Renders unsigned
assertEquals("80h", type.getRepresentation(buf(true, 0x80), HEX, 1));
assertArrayEquals(arr(0x00), type.encodeRepresentation("0h", BE, HEX, 1));
assertArrayEquals(arr(0x7f), type.encodeRepresentation("7fh", BE, HEX, 1));
assertArrayEquals(arr(0x80), type.encodeRepresentation("80h", BE, HEX, 1));
assertArrayEquals(arr(0xff), type.encodeRepresentation("ffh", BE, HEX, 1));
assertArrayEquals(bytes(0x00), type.encodeRepresentation("0h", BE, HEX, 1));
assertArrayEquals(bytes(0x7f), type.encodeRepresentation("7fh", BE, HEX, 1));
assertArrayEquals(bytes(0x80), type.encodeRepresentation("80h", BE, HEX, 1));
assertArrayEquals(bytes(0xff), type.encodeRepresentation("ffh", BE, HEX, 1));
assertFails(() -> type.encodeRepresentation("100h", BE, HEX, 1));
assertFails(() -> type.encodeRepresentation("-1h", BE, HEX, 1));
@ -108,13 +100,13 @@ public class IntegerDataTypeTest extends AbstractGenericTest {
// Sanity check: Negative hex values render unsigned
assertEquals("8000h", type.getRepresentation(buf(true, 0x80, 0x00), HEX, 2));
assertArrayEquals(arr(0x00, 0x00), type.encodeRepresentation("0h", BE, HEX, 2));
assertArrayEquals(arr(0x7f, 0xff), type.encodeRepresentation("7fffh", BE, HEX, 2));
assertArrayEquals(arr(0x80, 0x00), type.encodeRepresentation("8000h", BE, HEX, 2));
assertArrayEquals(arr(0xff, 0xff), type.encodeRepresentation("ffffh", BE, HEX, 2));
assertArrayEquals(bytes(0x00, 0x00), type.encodeRepresentation("0h", BE, HEX, 2));
assertArrayEquals(bytes(0x7f, 0xff), type.encodeRepresentation("7fffh", BE, HEX, 2));
assertArrayEquals(bytes(0x80, 0x00), type.encodeRepresentation("8000h", BE, HEX, 2));
assertArrayEquals(bytes(0xff, 0xff), type.encodeRepresentation("ffffh", BE, HEX, 2));
assertArrayEquals(arr(0xff, 0xff), type.encodeRepresentation("-1h", BE, HEX, 2));
assertArrayEquals(arr(0x80, 0x00), type.encodeRepresentation("-8000h", BE, HEX, 2));
assertArrayEquals(bytes(0xff, 0xff), type.encodeRepresentation("-1h", BE, HEX, 2));
assertArrayEquals(bytes(0x80, 0x00), type.encodeRepresentation("-8000h", BE, HEX, 2));
assertFails(() -> type.encodeRepresentation("10000h", BE, HEX, 2));
assertFails(() -> type.encodeRepresentation("-8001h", BE, HEX, 2));
@ -127,13 +119,13 @@ public class IntegerDataTypeTest extends AbstractGenericTest {
// Sanity check: Negative hex values render unsigned
assertEquals("8000h", type.getRepresentation(buf(false, 0x00, 0x80), HEX, 2));
assertArrayEquals(arr(0x00, 0x00), type.encodeRepresentation("0h", LE, HEX, 2));
assertArrayEquals(arr(0xff, 0x7f), type.encodeRepresentation("7fffh", LE, HEX, 2));
assertArrayEquals(arr(0x00, 0x80), type.encodeRepresentation("8000h", LE, HEX, 2));
assertArrayEquals(arr(0xff, 0xff), type.encodeRepresentation("ffffh", LE, HEX, 2));
assertArrayEquals(bytes(0x00, 0x00), type.encodeRepresentation("0h", LE, HEX, 2));
assertArrayEquals(bytes(0xff, 0x7f), type.encodeRepresentation("7fffh", LE, HEX, 2));
assertArrayEquals(bytes(0x00, 0x80), type.encodeRepresentation("8000h", LE, HEX, 2));
assertArrayEquals(bytes(0xff, 0xff), type.encodeRepresentation("ffffh", LE, HEX, 2));
assertArrayEquals(arr(0xff, 0xff), type.encodeRepresentation("-1h", LE, HEX, 2));
assertArrayEquals(arr(0x00, 0x80), type.encodeRepresentation("-8000h", LE, HEX, 2));
assertArrayEquals(bytes(0xff, 0xff), type.encodeRepresentation("-1h", LE, HEX, 2));
assertArrayEquals(bytes(0x00, 0x80), type.encodeRepresentation("-8000h", LE, HEX, 2));
assertFails(() -> type.encodeRepresentation("10000h", LE, HEX, 2));
assertFails(() -> type.encodeRepresentation("-8001h", LE, HEX, 2));
@ -146,10 +138,10 @@ public class IntegerDataTypeTest extends AbstractGenericTest {
// Sanity check: Renders unsigned
assertEquals("8000h", type.getRepresentation(buf(true, 0x80, 0x00), HEX, 2));
assertArrayEquals(arr(0x00, 0x00), type.encodeRepresentation("0h", BE, HEX, 2));
assertArrayEquals(arr(0x7f, 0xff), type.encodeRepresentation("7fffh", BE, HEX, 2));
assertArrayEquals(arr(0x80, 0x00), type.encodeRepresentation("8000h", BE, HEX, 2));
assertArrayEquals(arr(0xff, 0xff), type.encodeRepresentation("ffffh", BE, HEX, 2));
assertArrayEquals(bytes(0x00, 0x00), type.encodeRepresentation("0h", BE, HEX, 2));
assertArrayEquals(bytes(0x7f, 0xff), type.encodeRepresentation("7fffh", BE, HEX, 2));
assertArrayEquals(bytes(0x80, 0x00), type.encodeRepresentation("8000h", BE, HEX, 2));
assertArrayEquals(bytes(0xff, 0xff), type.encodeRepresentation("ffffh", BE, HEX, 2));
assertFails(() -> type.encodeRepresentation("-1h", BE, HEX, 2));
assertFails(() -> type.encodeRepresentation("-8000h", BE, HEX, 2));
@ -164,10 +156,10 @@ public class IntegerDataTypeTest extends AbstractGenericTest {
// Sanity check: Negative hex values render signed
assertEquals("-32768", type.getRepresentation(buf(true, 0x80, 0x00), DEC, 2));
assertArrayEquals(arr(0x00, 0x00), type.encodeRepresentation("0", BE, DEC, 2));
assertArrayEquals(arr(0x7f, 0xff), type.encodeRepresentation("32767", BE, DEC, 2));
assertArrayEquals(arr(0x80, 0x00), type.encodeRepresentation("-32768", BE, DEC, 2));
assertArrayEquals(arr(0xff, 0xff), type.encodeRepresentation("-1", BE, DEC, 2));
assertArrayEquals(bytes(0x00, 0x00), type.encodeRepresentation("0", BE, DEC, 2));
assertArrayEquals(bytes(0x7f, 0xff), type.encodeRepresentation("32767", BE, DEC, 2));
assertArrayEquals(bytes(0x80, 0x00), type.encodeRepresentation("-32768", BE, DEC, 2));
assertArrayEquals(bytes(0xff, 0xff), type.encodeRepresentation("-1", BE, DEC, 2));
assertFails(() -> type.encodeRepresentation("32768", BE, DEC, 2));
assertFails(() -> type.encodeRepresentation("-32769", BE, DEC, 2));
@ -180,10 +172,10 @@ public class IntegerDataTypeTest extends AbstractGenericTest {
// Sanity check: Renders unsigned
assertEquals("32768", type.getRepresentation(buf(true, 0x80, 0x00), DEC, 2));
assertArrayEquals(arr(0x00, 0x00), type.encodeRepresentation("0", BE, DEC, 2));
assertArrayEquals(arr(0x7f, 0xff), type.encodeRepresentation("32767", BE, DEC, 2));
assertArrayEquals(arr(0x80, 0x00), type.encodeRepresentation("32768", BE, DEC, 2));
assertArrayEquals(arr(0xff, 0xff), type.encodeRepresentation("65535", BE, DEC, 2));
assertArrayEquals(bytes(0x00, 0x00), type.encodeRepresentation("0", BE, DEC, 2));
assertArrayEquals(bytes(0x7f, 0xff), type.encodeRepresentation("32767", BE, DEC, 2));
assertArrayEquals(bytes(0x80, 0x00), type.encodeRepresentation("32768", BE, DEC, 2));
assertArrayEquals(bytes(0xff, 0xff), type.encodeRepresentation("65535", BE, DEC, 2));
assertFails(() -> type.encodeRepresentation("-1", BE, DEC, 2));
assertFails(() -> type.encodeRepresentation("65536", BE, DEC, 2));
@ -196,7 +188,7 @@ public class IntegerDataTypeTest extends AbstractGenericTest {
// Sanity check
assertEquals("100000011b", type.getRepresentation(buf(true, 0x01, 0x03), BIN, 2));
assertArrayEquals(arr(0x01, 0x03), type.encodeRepresentation("100000011b", BE, BIN, 2));
assertArrayEquals(bytes(0x01, 0x03), type.encodeRepresentation("100000011b", BE, BIN, 2));
}
@Test
@ -206,7 +198,7 @@ public class IntegerDataTypeTest extends AbstractGenericTest {
// Sanity check
assertEquals("403o", type.getRepresentation(buf(true, 0x01, 0x03), OCT, 2));
assertArrayEquals(arr(0x01, 0x03), type.encodeRepresentation("403o", BE, OCT, 2));
assertArrayEquals(bytes(0x01, 0x03), type.encodeRepresentation("403o", BE, OCT, 2));
}
@Test
@ -218,7 +210,7 @@ public class IntegerDataTypeTest extends AbstractGenericTest {
assertEquals("'A'", stype.getRepresentation(buf(true, 0x41), CHR, 1));
assertEquals("'A'", utype.getRepresentation(buf(true, 0x41), CHR, 1));
assertArrayEquals(arr(0x41), stype.encodeRepresentation("'A'", BE, CHR, 1));
assertArrayEquals(arr(0x41), utype.encodeRepresentation("'A'", BE, CHR, 1));
assertArrayEquals(bytes(0x41), stype.encodeRepresentation("'A'", BE, CHR, 1));
assertArrayEquals(bytes(0x41), utype.encodeRepresentation("'A'", BE, CHR, 1));
}
}

View file

@ -0,0 +1,109 @@
/* ###
* 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.program.model.data;
import static ghidra.program.model.data.LEB128Test.te;
import static org.junit.Assert.*;
import java.io.IOException;
import org.junit.Test;
import generic.test.AbstractGenericTest;
import ghidra.docking.settings.FormatSettingsDefinition;
import ghidra.program.model.data.LEB128Test.TestEntry;
import ghidra.program.model.mem.MemBuffer;
import ghidra.program.model.scalar.Scalar;
public class LEB128DataTypeTest extends AbstractGenericTest {
static SettingsBuilder HEX_SETTINGS = new SettingsBuilder()
.setFormat(FormatSettingsDefinition.HEX, AbstractLeb128DataType.FORMAT);
static SettingsBuilder DECIMAL_SETTINGS = new SettingsBuilder()
.setFormat(FormatSettingsDefinition.DECIMAL, AbstractLeb128DataType.FORMAT);
@Test
public void testStringRep() throws IOException {
assertRepresentation(te(1L, 0x01), false, "1h", "1");
assertRepresentation(te(0L, 0x80, 0x80, 0x80, 0x80, 0x80, 0x0), false, "0h", "0");
assertRepresentation(te(1L, 0x81, 0x80, 0x80, 0x80, 0x80, 0x0), false, "1h", "1");
assertRepresentation(te(-1L, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01),
false, "FFFFFFFFFFFFFFFFh", "18446744073709551615");
assertRepresentation(te(63L, 0x3f), false, "3Fh", "63");
assertRepresentation(te(128L, 0x80, 0x01), false, "80h", "128");
assertRepresentation(te(2097151L, 0xff, 0xff, 0x7f), false, "1FFFFFh", "2097151");
assertRepresentation(te(-1L, 0x7f), true, "-1h", "-1");
assertRepresentation(te(-2L, 0x7e), true, "-2h", "-2");
assertRepresentation(te(-64L, 0x40), true, "-40h", "-64");
assertRepresentation(te(-127, 0x81, 0x7f), true, "-7Fh", "-127");
assertRepresentation(te(-128, 0x80, 0x7f), true, "-80h", "-128");
assertRepresentation(te(-8193L, 0xff, 0xbf, 0x7f), true, "-2001h", "-8193");
}
@Test
public void testNotEnoughBytes() throws IOException {
// tests what happens when this var length data type is contained within a dtc
// that is too small for what the data demands.
MemBuffer mb = te(0L, 0x80, 0x0, 0x0, 0x0).mb();
UnsignedLeb128DataType uleb128 = UnsignedLeb128DataType.dataType;
Scalar value = (Scalar) uleb128.getValue(mb, HEX_SETTINGS, 1);
String hexRep = uleb128.getRepresentation(mb, HEX_SETTINGS, 1);
assertNull(value);
assertEquals("??", hexRep);
}
@Test
public void testTooManyBytes() throws IOException {
// tests what happens when this var length data type is contained within a dtc
// that has more bytes than what the data demands.
MemBuffer mb = te(0L, 0x80, 0x0, 0x0, 0x0).mb();
UnsignedLeb128DataType uleb128 = UnsignedLeb128DataType.dataType;
Scalar value = (Scalar) uleb128.getValue(mb, HEX_SETTINGS, 4);
String hexRep = uleb128.getRepresentation(mb, HEX_SETTINGS, 4);
assertEquals(0, value.getUnsignedValue());
assertEquals("0h", hexRep);
}
@Test
public void testGetValueWithoutLength() throws IOException {
MemBuffer mb = te(0L, 0x80, 0x0, 0x0, 0x0).mb();
UnsignedLeb128DataType uleb128 = UnsignedLeb128DataType.dataType;
Scalar value = (Scalar) uleb128.getValue(mb, HEX_SETTINGS, -1);
assertNotNull(value);
}
private void assertRepresentation(TestEntry te, boolean signed, String hexExpected,
String decExpected) throws IOException {
MemBuffer mb = te.mb();
//Scalar value = (Scalar) dt.getValue(mb, settings, length);
AbstractLeb128DataType lebdt = signed
? SignedLeb128DataType.dataType
: UnsignedLeb128DataType.dataType;
int length = lebdt.getLength(mb, -1);
//Scalar value = (Scalar) dt.getValue(mb, settings, length);
String hexRep = lebdt.getRepresentation(mb, HEX_SETTINGS, length);
String decRep = lebdt.getRepresentation(mb, DECIMAL_SETTINGS, length);
assertEquals(te.bytes().length, length);
assertEquals(hexExpected, hexRep);
assertEquals(decExpected, decRep);
}
}

View file

@ -0,0 +1,215 @@
/* ###
* 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.program.model.data;
import static org.junit.Assert.assertEquals;
import java.util.List;
import java.io.*;
import org.junit.Assert;
import org.junit.Test;
import generic.test.AbstractGTest;
import ghidra.program.model.mem.ByteMemBufferImpl;
import ghidra.program.model.mem.MemBuffer;
import ghidra.util.NumericUtilities;
public class LEB128Test extends AbstractGTest {
static record TestEntry(long expectedValue, byte[] bytes) {
MemBuffer mb() {
return new ByteMemBufferImpl(null, bytes, false /* don't matter */);
}
}
private static InputStream is(int... intBytes) {
return is(bytes(intBytes));
}
private static InputStream is(byte[] bytes) {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
return bais;
}
static TestEntry te(long expectedValue, int... intBytes) {
return new TestEntry(expectedValue, bytes(intBytes));
}
/* package */ List<TestEntry> 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, 0x81, 0x80, 0x80, 0x80, 0x80, 0x0), // Tests reading a 1 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)
//
);
/* package */ List<TestEntry> 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 testSignedTestEntries() throws IOException {
testTestEntries(signedTestEntries, true, "Signed TestEntry");
}
public void testTestEntries(List<TestEntry> testEntries, boolean signed, String name)
throws IOException {
for (int i = 0; i < testEntries.size(); i++) {
TestEntry te = testEntries.get(i);
InputStream is = is(te.bytes);
long actualValue = LEB128.read(is, signed);
int remainder = is.available();
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, remainder),
0, is.available());
}
}
@Test(expected = IOException.class)
public void testToolargeUnsigned() throws IOException {
// Test reading a unsigned LEB128 that is just 1 bit too large for a java 64 bit long int.
InputStream is = is(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02);
long value = LEB128.read(is, 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
public void testTooLargeValueBinaryReaderStreamPosition() throws IOException {
// 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.
byte[] bytes =
bytes(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02, 0x1, 0x2);
InputStream is = is(bytes);
try {
long value = LEB128.read(is, 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));
}
catch (IOException ioe) {
// good
}
Assert.assertEquals(bytes.length - 10, is.available());
}
}

View file

@ -73,6 +73,18 @@ public class SettingsBuilder implements Settings {
return this;
}
/**
* Sets the {@link FormatSettingsDefinition} value.
*
* @param formatEnum int such as {@link FormatSettingsDefinition#DECIMAL}
* @param defaultSettings the default settings that apply
* @return chainable SettingsBuilder
*/
public SettingsBuilder setFormat(int formatEnum, FormatSettingsDefinition defaultSettings) {
defaultSettings.setChoice(settings, formatEnum);
return this;
}
@Override
public Long getLong(String name) {
return settings.getLong(name);