GP-4955: The ElfLoader imagebase option can now contain a leading 0x

This commit is contained in:
Ryan Kurtz 2024-10-01 13:43:42 -04:00
parent ff6988ad32
commit 9aba9d81be
8 changed files with 339 additions and 140 deletions

View File

@ -4,9 +4,9 @@
* 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.
@ -89,11 +89,12 @@ public class FindReferencesToFieldByNameOrOffsetAction extends AbstractFindRefer
return null; // cancelled
}
Long longChoice = parseInt(userChoice);
if (longChoice != null) {
return new FieldMatcher(dt, longChoice.intValue());
try {
return new FieldMatcher(dt, NumericUtilities.parseInt(userChoice));
}
catch (NumberFormatException e) {
return new FieldMatcher(dt, userChoice);
}
return new FieldMatcher(dt, userChoice);
}
private String[] getCompositeFieldNames(Composite composite) {
@ -112,8 +113,4 @@ public class FindReferencesToFieldByNameOrOffsetAction extends AbstractFindRefer
return names.toArray(String[]::new);
}
private Long parseInt(String s) {
return NumericUtilities.parseNumber(s, null);
}
}

View File

@ -21,6 +21,8 @@ import java.util.List;
import javax.swing.*;
import org.apache.commons.lang3.StringUtils;
import docking.DialogComponentProvider;
import docking.DockingWindowManager;
import docking.widgets.combobox.GComboBox;
@ -246,12 +248,12 @@ public class AskDialog<T> extends DialogComponentProvider {
protected Integer getValueAsInt() {
String text = getValueAsString();
return text != null ? NumericUtilities.parseInt(text) : null;
return !StringUtils.isBlank(text) ? NumericUtilities.parseInt(text) : null;
}
protected Long getValueAsLong() {
String text = getValueAsString();
return text != null ? NumericUtilities.parseLong(text) : null;
return !StringUtils.isBlank(text) ? NumericUtilities.parseLong(text) : null;
}
protected Double getValueAsDouble() {

View File

@ -4,9 +4,9 @@
* 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.
@ -220,9 +220,9 @@ public class ElfLoaderOptionsFactory {
if (!String.class.isAssignableFrom(option.getValueClass())) {
return "Invalid type for option: " + name + " - " + option.getValueClass();
}
String value = (String) option.getValue();
String value = ((String) option.getValue());
try {
space.getAddress(Long.parseUnsignedLong(value, 16), true);// verify valid address
space.getAddress(NumericUtilities.parseHexLong(value), true);// verify valid address
}
catch (NumberFormatException e) {
return "Invalid " + name + " - expecting hexidecimal address offset";

View File

@ -18,6 +18,7 @@ package ghidra.app.util.bin.format.pdb;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import ghidra.app.util.SymbolPathParser;
import ghidra.program.model.data.DataType;
@ -134,14 +135,15 @@ public class DefaultPdbMember extends PdbMember {
try {
int colonIndex = bitSizeOffsetStr.indexOf(':');
if (colonIndex > 0) {
bitFieldOffset = (int) NumericUtilities.parseNumber(
bitSizeOffsetStr.substring(colonIndex + 1));
bitFieldOffset =
NumericUtilities.parseInt(bitSizeOffsetStr.substring(colonIndex + 1));
bitSizeOffsetStr = bitSizeOffsetStr.substring(0, colonIndex);
}
else {
dataTypeParser.setMissingBitOffsetError();
}
bitFieldSize = (int) NumericUtilities.parseNumber(bitSizeOffsetStr);
NumberUtils.toInt(bitSizeOffsetStr, 0);
bitFieldSize = NumericUtilities.parseInt(bitSizeOffsetStr);
}
catch (NumberFormatException e) {
Msg.error(this, "Invalid PDB bitfield specification: " + name);

View File

@ -4,9 +4,9 @@
* 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.
@ -151,8 +151,7 @@ public class NumberRangeInputDialog extends DialogComponentProvider {
String trimmed = rangeText.trim();
if (!trimmed.contains(RANGE_DELIMITER)) {
try {
long parsedLong = NumericUtilities.parseLong(trimmed);
int intValue = (int) parsedLong;
int intValue = NumericUtilities.parseInt(trimmed);
rangeList.addRange(intValue, intValue);
}
catch (NumberFormatException e) {
@ -165,12 +164,8 @@ public class NumberRangeInputDialog extends DialogComponentProvider {
// this must be a range
String[] startAndEnd = trimmed.split(RANGE_DELIMITER);
try {
long parsedLong = NumericUtilities.parseLong(startAndEnd[0]);
int startInt = (int) parsedLong;
parsedLong = NumericUtilities.parseLong(startAndEnd[1]);
int endInt = (int) parsedLong;
int startInt = NumericUtilities.parseInt(startAndEnd[0]);
int endInt = NumericUtilities.parseInt(startAndEnd[1]);
rangeList.addRange(startInt, endInt);
}
catch (NumberFormatException e) {

View File

@ -4,9 +4,9 @@
* 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.
@ -172,8 +172,9 @@ public class GValidatedTextField extends JTextField {
@Override
public void validate(String oldText, String newText) throws ValidationFailedException {
try {
long oldLong = NumericUtilities.parseLong(oldText);
long newLong = NumericUtilities.parseLong(newText);
long oldLong = sanitizeInput(oldText);
long newLong = sanitizeInput(newText);
validateLong(oldLong, newLong);
}
catch (NumberFormatException e) {
@ -181,7 +182,52 @@ public class GValidatedTextField extends JTextField {
}
}
@SuppressWarnings("unused")
public void validateLong(long oldLong, long newLong) throws ValidationFailedException {
// do nothing
}
/**
* Similar to {@link NumericUtilities#parseLong(String)}, but we return 0 if the string
* is null, empty, or looks like a partially entered value (such as "-", "0x", etc)
*
* @param s the string to parse
* @return the parsed {@code long} value, or 0 if the string is null, empty, or looks
* like a partially entered value (such as "-", "0x", etc)
* @throws NumberFormatException if the string does not represent a valid {@code long}
* value
*/
private long sanitizeInput(String s) throws NumberFormatException {
// Trim the string and return 0 if it's empty. This could be a partial input.
s = (s == null ? "" : s.trim());
if (s.isEmpty()) {
return 0;
}
// Chop off the optional sign character of the string, we'll add it back later
long sign = 1;
if (s.startsWith("-")) {
sign = -1;
s = s.substring(1);
}
else if (s.startsWith("+")) {
s = s.substring(1);
}
// Chop off the optional hex prefix...we'll decide to do hex later
boolean hexPrefix = s.startsWith("0x") || s.startsWith("0X");
if (hexPrefix) {
s = s.substring(2);
}
// Since we might have chopped off prefixes, check again and return 0 if it's empty.
// This could be a partial input.
if (s.isEmpty()) {
return 0;
}
return sign *
(hexPrefix ? NumericUtilities.parseHexLong(s) : NumericUtilities.parseLong(s));
}
}
}

View File

@ -18,6 +18,7 @@ package ghidra.util;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -29,6 +30,7 @@ import util.CollectionUtils;
public final class NumericUtilities {
public static final BigInteger MAX_UNSIGNED_LONG = new BigInteger("ffffffffffffffff", 16);
public static final BigInteger MAX_SIGNED_LONG = new BigInteger("7fffffffffffffff", 16);
public static final BigInteger MAX_UNSIGNED_INT = new BigInteger("ffffffff", 16);
public static final long MAX_UNSIGNED_INT32_AS_LONG = 0xffffffffL;
private final static String HEX_PREFIX_X = "0X";
@ -64,7 +66,9 @@ public final class NumericUtilities {
*
* @param numStr the number string
* @return the long value or 0
* @deprecated use {@link #parseLong(String)} instead
*/
@Deprecated(since = "11.3", forRemoval = true)
public static long parseNumber(String numStr) {
return parseNumber(numStr, Long.valueOf(0));
}
@ -75,7 +79,9 @@ public final class NumericUtilities {
* @param s the string to parse
* @param defaultValue the default value to use if the string cannot be parsed
* @return the long value
* @deprecated use {@link #parseLong(String, long)} instead
*/
@Deprecated(since = "11.3", forRemoval = true)
public static Long parseNumber(String s, Long defaultValue) {
s = (s == null ? "" : s.trim());
if (s.length() == 0) {
@ -104,90 +110,149 @@ public final class NumericUtilities {
* the top bit set to be implicitly parsed as negative values.
*
* @param s the string to parse
* @return the {@code int} value, or 0 if the string to parse is null or blank
* @return the parsed {@code int} value
* @throws NumberFormatException if the string does not represent a valid {@code int} value
*/
public static int parseInt(String s) {
String origStr = s;
int sign = 1;
s = (s == null ? "" : s.trim());
if (s.length() == 0) {
return 0;
}
if (s.startsWith("-")) {
sign = -1;
s = s.substring(1);
}
int radix = 10;
if (s.startsWith(HEX_PREFIX_x) || s.startsWith(HEX_PREFIX_X)) {
if (s.length() > 10) {
throw new NumberFormatException(s + " has too many digits.");
}
s = s.substring(2);
radix = 16;
}
if (s.length() == 0) {
return 0;
}
try {
BigInteger bi = new BigInteger(s, radix);
return bi.intValue() * sign;
}
catch (NumberFormatException e) {
// This is a little hacky, but the message should be complete and report about the
// original string
NumberFormatException e2 =
new NumberFormatException("Cannot parse int from " + origStr);
e2.setStackTrace(e.getStackTrace());
throw e2;
}
catch (ArithmeticException e) {
throw new NumberFormatException(origStr + " is too big.");
}
public static int parseInt(String s) throws NumberFormatException {
return parseHelper(s, false, BigInteger::intValue, MAX_UNSIGNED_INT);
}
/**
* Parses the given decimal/hex string as a {@code long} value. This method allows values with
* Parses the given decimal/hex string as an {@code int} value. This method allows values with
* the top bit set to be implicitly parsed as negative values.
*
* @param s the string to parse
* @return the {@code long} value, or 0 if the string to parse is null or blank
* @param defaultValue the default value to return if the string does not represent a valid
* {@code int} value
* @return the parsed {@code int} value or the {@code defaultValue} if the string does not
* represent a valid {@code int} value
*/
public static int parseInt(String s, int defaultValue) {
try {
return parseInt(s);
}
catch (NumberFormatException e) {
return defaultValue;
}
}
/**
* Parses the given decimal/hex string as an {@code long} value. This method allows values with
* the top bit set to be implicitly parsed as negative values.
*
* @param s the string to parse
* @return the parsed {@code long} value
* @throws NumberFormatException if the string does not represent a valid {@code long} value
*/
public static long parseLong(String s) {
String origStr = s;
long sign = 1;
public static long parseLong(String s) throws NumberFormatException {
return parseHelper(s, false, BigInteger::longValue, MAX_UNSIGNED_LONG);
}
s = (s == null ? "" : s.trim());
if (s.length() == 0) {
return 0;
/**
* Parses the given decimal/hex string as an {@code long} value. This method allows values with
* the top bit set to be implicitly parsed as negative values.
*
* @param s the string to parse
* @param defaultValue the default value to return if the string does not represent a valid
* {@code long} value
* @return the parsed {@code long} value or the {@code defaultValue} if the string does not
* represent a valid {@code long} value
*/
public static long parseLong(String s, long defaultValue) {
try {
return parseLong(s);
}
if (s.startsWith("-")) {
sign = -1;
catch (NumberFormatException e) {
return defaultValue;
}
}
/**
* Parses the given hex string as a {@code long} value.
* <p>
* Note: The string is treated as hex regardless of whether or not it contains the {@code 0x}
* prefix.
*
* @param s the string to parse
* @return the parsed {@code long} value
* @throws NumberFormatException if the string does not represent a valid value
*/
public static long parseHexLong(String s) throws NumberFormatException {
return parseHelper(s, true, BigInteger::longValue, MAX_UNSIGNED_LONG);
}
/**
* Parses the given hex string as a {@link BigInteger} value.
* <p>
* Note: The string is treated as hex regardless of whether or not it contains the {@code 0x}
* prefix.
*
* @param s the string to parse
* @return the parsed {@link BigInteger} value
* @throws NumberFormatException if the string does not represent a valid value
* @deprecated use {@link #parseHexLong(String)} instead
*/
@Deprecated(since = "11.3", forRemoval = true)
public static BigInteger parseHexBigInteger(String s) throws NumberFormatException {
return parseHelper(s, true, Function.identity(), MAX_UNSIGNED_LONG);
}
/**
* Parses the given decimal/hex string as a custom type. This method allows values with the top
* bit set to be implicitly parsed as negative values.
*
* @param s the string to parse
* @param forceHex true if the string to parse should be treated as hex, even if it doesn't
* start with {@code 0x}; otherwise, false;
* @param func a {@link Function} used to convert the parsed {@link BigInteger} to a custom type
* @param max the maximum value that can be used to represent the type of value being parsed if
* it were treated as unsigned
* @param <T> The type of value being parsed
* @return the parsed value
* @throws NumberFormatException if the string does not represent a valid value
*/
private static <T> T parseHelper(String s, boolean forceHex, Function<BigInteger, T> func,
BigInteger max) throws NumberFormatException {
String origStr = s;
// Trim the string, and throw exception if it's null/empty
s = (s == null ? "" : s.trim());
if (s.isEmpty()) {
throw new NumberFormatException("String to parse is empty");
}
// Chop off the optional sign character of the string, we'll add it back later
String sign = "";
if (s.startsWith("-") || s.startsWith("+")) {
sign = s.substring(0, 1);
s = s.substring(1);
}
int radix = 10;
if (s.startsWith(HEX_PREFIX_x) || s.startsWith(HEX_PREFIX_X)) {
if (s.length() > 18) {
throw new NumberFormatException(s + " has too many digits.");
}
// Process the radix
boolean hexPrefix = s.startsWith(HEX_PREFIX_x) || s.startsWith(HEX_PREFIX_X);
int radix = forceHex || hexPrefix ? 16 : 10;
if (hexPrefix) {
s = s.substring(2);
radix = 16;
}
if (s.length() == 0) {
return 0;
// Make sure next character is not + or - (protects against things like "0x-ffff")
if (!s.isEmpty() && (s.charAt(0) == '-' || s.charAt(0) == '+')) {
throw new NumberFormatException("Cannot parse " + origStr);
}
// Try to convert the string to the desired type
try {
BigInteger bi = new BigInteger(s, radix);
return bi.longValue() * sign;
// Check size
if (new BigInteger(s, radix).compareTo(max) > 0) {
throw new NumberFormatException(s + " exceeds maximum data type size.");
}
return func.apply(new BigInteger(sign + s, radix));
}
catch (NumberFormatException e) {
// A little hacky, but the message should be complete and report the original string
NumberFormatException e2 =
new NumberFormatException("Cannot parse long from " + origStr);
NumberFormatException e2 = new NumberFormatException("Cannot parse " + origStr);
e2.setStackTrace(e.getStackTrace());
throw e2;
}
@ -196,47 +261,6 @@ public final class NumericUtilities {
}
}
/**
* Parses the given string as a hex long value, detecting whether or not it begins with a hex
* prefix, and if not, parses as a long int value.
* @param s the string to parse
* @return the long value
* @throws NumberFormatException if the string is blank
*/
public static long parseHexLong(String s) {
return parseHexBigInteger(s).longValue();
}
/**
* Parses the given hex string as a BigIntge value, detecting whether or not it begins with a
* hex prefix, and if not, parses as a long int value.
* @param s the string to parse
* @return the long value
* @throws NumberFormatException if the string is blank
*/
public static BigInteger parseHexBigInteger(String s) {
s = (s == null ? "" : s.trim());
if (s.length() == 0) {
throw new NumberFormatException(s + " no digits.");
}
boolean negative = false;
if (s.startsWith("-")) {
negative = true;
s = s.substring(1);
}
if (s.startsWith(HEX_PREFIX_x) || s.startsWith(HEX_PREFIX_X)) {
s = s.substring(2);
}
if (negative) {
s = "-" + s;
}
return new BigInteger(s, 16);
}
private static BigInteger decodeMagnitude(int p, String s) {
// Special case, so it doesn't get chewed by octal parser
if ("0".equals(s)) {
@ -314,7 +338,7 @@ public final class NumericUtilities {
* @return the string
*/
public final static String toSignedHexString(long value) {
StringBuffer buf = new StringBuffer();
StringBuilder buf = new StringBuilder();
if (value < 0) {
buf.append("-");
}

View File

@ -15,8 +15,7 @@
*/
package ghidra.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.junit.Assert.*;
import java.math.BigInteger;
import java.util.*;
@ -362,60 +361,194 @@ public class NumericUtilitiesTest {
fail();
}
catch (NumberFormatException e) {
// do nothing
}
try {
NumericUtilities.decodeBigInteger("+");
fail();
}
catch (NumberFormatException e) {
// do nothing
}
try {
NumericUtilities.decodeBigInteger("-");
fail();
}
catch (NumberFormatException e) {
// do nothing
}
try {
NumericUtilities.decodeBigInteger("0x");
fail();
}
catch (NumberFormatException e) {
// do nothing
}
try {
NumericUtilities.decodeBigInteger("0b");
fail();
}
catch (NumberFormatException e) {
// do nothing
}
try {
NumericUtilities.decodeBigInteger("a01");
fail();
}
catch (NumberFormatException e) {
// do nothing
}
try {
NumericUtilities.decodeBigInteger("081");
fail();
}
catch (NumberFormatException e) {
// do nothing
}
try {
NumericUtilities.decodeBigInteger("0x9g");
fail();
}
catch (NumberFormatException e) {
// do nothing
}
try {
NumericUtilities.decodeBigInteger(" 10");
fail();
}
catch (NumberFormatException e) {
// do nothing
}
try {
NumericUtilities.decodeBigInteger("10 ");
fail();
}
catch (NumberFormatException e) {
// do nothing
}
}
@Test
public void testParse() {
// parseInt()
assertEquals(-1, NumericUtilities.parseInt("0xffffffff"));
assertEquals(-1, NumericUtilities.parseInt("+0xffffffff"));
assertEquals(1, NumericUtilities.parseInt("-0xffffffff"));
assertEquals(0, NumericUtilities.parseInt("0x100000000", 0));
assertEquals(-1, NumericUtilities.parseInt("4294967295")); // 0xffffffff
try {
assertEquals(0, NumericUtilities.parseInt("0x100000000"));
fail();
}
catch (NumberFormatException e) {
// do nothing
}
try {
assertEquals(0, NumericUtilities.parseInt("4294967296")); // 0x100000000
fail();
}
catch (NumberFormatException e) {
// do nothing
}
try {
assertEquals(0, NumericUtilities.parseInt("0x-5"));
fail();
}
catch (NumberFormatException e) {
// do nothing
}
try {
assertEquals(0, NumericUtilities.parseInt(null));
fail();
}
catch (NumberFormatException e) {
// do nothing
}
try {
assertEquals(0, NumericUtilities.parseInt(" "));
fail();
}
catch (NumberFormatException e) {
// do nothing
}
// parseLong()
assertEquals(-1, NumericUtilities.parseLong("0xffffffffffffffff"));
assertEquals(-1, NumericUtilities.parseLong("+0xffffffffffffffff"));
assertEquals(1, NumericUtilities.parseLong("-0xffffffffffffffff"));
assertEquals(0, NumericUtilities.parseLong("0x10000000000000000", 0));
assertEquals(-1, NumericUtilities.parseLong("18446744073709551615")); // 0xffffffffffffffff
try {
assertEquals(0, NumericUtilities.parseLong("0x10000000000000000"));
fail();
}
catch (NumberFormatException e) {
// do nothing
}
try {
assertEquals(0, NumericUtilities.parseLong("18446744073709551616")); // 0x10000000000000000
fail();
}
catch (NumberFormatException e) {
// do nothing
}
try {
assertEquals(0, NumericUtilities.parseInt("0x-5"));
fail();
}
catch (NumberFormatException e) {
// do nothing
}
try {
assertEquals(0, NumericUtilities.parseLong(null));
fail();
}
catch (NumberFormatException e) {
// do nothing
}
try {
assertEquals(0, NumericUtilities.parseLong(" "));
fail();
}
catch (NumberFormatException e) {
// do nothing
}
// parseHexLong()
assertEquals(-1, NumericUtilities.parseHexLong("ffffffffffffffff"));
assertEquals(-1, NumericUtilities.parseHexLong("0xffffffffffffffff"));
assertEquals(-1, NumericUtilities.parseHexLong("+ffffffffffffffff"));
assertEquals(-1, NumericUtilities.parseHexLong("+0xffffffffffffffff"));
assertEquals(1, NumericUtilities.parseHexLong("-ffffffffffffffff"));
assertEquals(1, NumericUtilities.parseHexLong("-0xffffffffffffffff"));
try {
assertEquals(0, NumericUtilities.parseHexLong("0x10000000000000000"));
fail();
}
catch (NumberFormatException e) {
// do nothing
}
try {
assertEquals(0, NumericUtilities.parseHexLong("0x-5"));
fail();
}
catch (NumberFormatException e) {
// do nothing
}
try {
assertEquals(0, NumericUtilities.parseHexLong(null));
fail();
}
catch (NumberFormatException e) {
// do nothing
}
try {
assertEquals(0, NumericUtilities.parseHexLong(" "));
fail();
}
catch (NumberFormatException e) {
// do nothing
}
}
}