From 910bb572d375c4e82f65497acc58713cdcf6dac3 Mon Sep 17 00:00:00 2001 From: Robert Xiao Date: Sat, 13 May 2023 19:43:39 -0700 Subject: [PATCH] Accept unsigned BigIntegers, and produce unsigned BigIntegers by default. Unsigned BigIntegers are a bit more ergonomic, particularly for bitwise operations. reg_write still accepts negative BigIntegers (and will automatically sign extend them), but reg_read will produce unsigned BigIntegers by default. --- bindings/java/tests/RegTests.java | 98 ++++++++++++++++++++++++++++++ bindings/java/unicorn/Unicorn.java | 23 +++++-- 2 files changed, 115 insertions(+), 6 deletions(-) diff --git a/bindings/java/tests/RegTests.java b/bindings/java/tests/RegTests.java index 9eb653f3..206604f5 100644 --- a/bindings/java/tests/RegTests.java +++ b/bindings/java/tests/RegTests.java @@ -1,10 +1,14 @@ package tests; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import java.math.BigInteger; import org.junit.Test; import unicorn.Unicorn; +import unicorn.UnicornException; import unicorn.X86_Float80; public class RegTests { @@ -47,4 +51,98 @@ public class RegTests { assertEquals(Math.sin(-1.1), reg.toDouble(), 1e-12); u.close(); } + + @Test + public void testBigIntegerRegister() { + Unicorn uc = + new Unicorn(Unicorn.UC_ARCH_ARM64, Unicorn.UC_MODE_ARM); + int reg = Unicorn.UC_ARM64_REG_V0; + + assertThrows(UnicornException.class, () -> uc.reg_read(reg)); + assertThrows(UnicornException.class, () -> uc.reg_write(reg, 1L)); + assertThrows(ClassCastException.class, + () -> uc.reg_write(reg, (Long) 1L)); + + BigInteger b127 = BigInteger.valueOf(2).pow(127); + BigInteger bmax = + BigInteger.valueOf(2).pow(128).subtract(BigInteger.ONE); + + uc.reg_write(reg, BigInteger.ZERO); + assertEquals("write 0, get 0", BigInteger.ZERO, uc.reg_read(reg, null)); + + uc.reg_write(reg, BigInteger.ONE); + assertEquals("write 1, get 1", BigInteger.ONE, uc.reg_read(reg, null)); + assertEquals("get 1 from alias", BigInteger.ONE, + uc.reg_read(Unicorn.UC_ARM64_REG_Q0, null)); + + uc.reg_write(reg, BigInteger.ONE.negate()); + assertEquals("write -1, get 2^128 - 1", bmax, uc.reg_read(reg, null)); + + uc.reg_write(reg, b127); + assertEquals("write 2^127, get 2^127", b127, uc.reg_read(reg, null)); + + uc.reg_write(reg, b127.negate()); + assertEquals("write -2^127, get 2^127", b127, uc.reg_read(reg, null)); + + uc.reg_write(reg, bmax); + assertEquals("write 2^128 - 1, get 2^128 - 1", bmax, + uc.reg_read(reg, null)); + + assertThrows("reject 2^128", IllegalArgumentException.class, + () -> uc.reg_write(reg, bmax.add(BigInteger.ONE))); + assertEquals("reg unchanged", bmax, + uc.reg_read(reg, null)); + + assertThrows("reject -2^127 - 1", IllegalArgumentException.class, + () -> uc.reg_write(reg, b127.negate().subtract(BigInteger.ONE))); + assertEquals("reg unchanged", bmax, + uc.reg_read(reg, null)); + + byte[] b = new byte[0x80]; + b[0x70] = -0x80; + uc.reg_write(reg, new BigInteger(b)); + assertEquals("write untrimmed value", b127, uc.reg_read(reg, null)); + + uc.close(); + } + + @Test + public void testArm64Vector() { + // add v0.8h, v1.8h, v2.8h + final byte[] ARM64_CODE = { 0x20, (byte) 0x84, 0x62, 0x4e }; + + long ADDRESS = 0x100000; + + Unicorn uc = new Unicorn(Unicorn.UC_ARCH_ARM64, Unicorn.UC_MODE_ARM); + uc.mem_map(ADDRESS, 2 * 1024 * 1024, Unicorn.UC_PROT_ALL); + uc.mem_write(ADDRESS, ARM64_CODE); + + uc.reg_write(Unicorn.UC_ARM64_REG_V0, + new BigInteger("0cc175b9c0f1b6a831c399e269772661", 16)); // MD5("a") + uc.reg_write(Unicorn.UC_ARM64_REG_V1, + new BigInteger("92eb5ffee6ae2fec3ad71c777531578f", 16)); // MD5("b") + uc.reg_write(Unicorn.UC_ARM64_REG_V2, + new BigInteger("-4a8a08f09d37b73795649038408b5f33", 16)); // -MD5("c") + assertThrows("rejects overly large values", + IllegalArgumentException.class, + () -> uc.reg_write(Unicorn.UC_ARM64_REG_V2, + new BigInteger("1111222233334444aaaabbbbccccdddde", 16))); + + assertEquals("v0 value", + new BigInteger("0cc175b9c0f1b6a831c399e269772661", 16), + uc.reg_read(Unicorn.UC_ARM64_REG_V0, null)); + assertEquals("v1 value", + new BigInteger("92eb5ffee6ae2fec3ad71c777531578f", 16), + uc.reg_read(Unicorn.UC_ARM64_REG_V1, null)); + assertEquals("v2 value", + new BigInteger("b575f70f62c848c86a9b6fc7bf74a0cd", 16), + uc.reg_read(Unicorn.UC_ARM64_REG_V2, null)); + + uc.emu_start(ADDRESS, ADDRESS + ARM64_CODE.length, 0, 0); + assertEquals("v0.8h = v1.8h + v2.8h", + new BigInteger("4860570d497678b4a5728c3e34a5f85c", 16), + uc.reg_read(Unicorn.UC_ARM64_REG_V0, null)); + + uc.close(); + } } diff --git a/bindings/java/unicorn/Unicorn.java b/bindings/java/unicorn/Unicorn.java index fb23320b..e89bd1df 100644 --- a/bindings/java/unicorn/Unicorn.java +++ b/bindings/java/unicorn/Unicorn.java @@ -372,20 +372,24 @@ public class Unicorn j++; } } - return new BigInteger(buf); + return new BigInteger(1, buf); } private static void do_reg_write_bigint(long ptr, int isContext, int regid, BigInteger value, int nbits) { byte[] val = value.toByteArray(); + byte[] buf = new byte[nbits >> 3]; + if (val.length == ((nbits >> 3) + 1) && val[0] == 0x00) { + // unsigned value >= 2^(nbits - 1): has a zero sign bit + val = Arrays.copyOfRange(val, 1, val.length); + } else if (val[0] < 0) { + Arrays.fill(buf, (byte) 0xff); + } + if (val.length > (nbits >> 3)) { throw new IllegalArgumentException( "input integer is too large for a " + nbits + - "-bit register (got " + (val.length * 8) + " bits)"); - } - byte[] buf = new byte[nbits >> 3]; - if (val[0] < 0) { - Arrays.fill(buf, (byte) 0xff); + "-bit register (got " + (value.bitLength() + 1) + " bits)"); } if (ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN)) { @@ -430,6 +434,9 @@ public class Unicorn *
  • {@code UC_ARM64_REG_V*} => {@link BigInteger} (128 bits) * * + * {@link BigInteger} registers always produce non-negative results (i.e. + * they read as unsigned integers). + * * @param regid Register ID that is to be retrieved. * @param opt Options for this register, or {@code null} if no options * are required. @@ -472,6 +479,10 @@ public class Unicorn *
  • {@code UC_ARM64_REG_Q*} => {@link BigInteger} (128 bits) *
  • {@code UC_ARM64_REG_V*} => {@link BigInteger} (128 bits) * + * + * {@link BigInteger} values can be signed or unsigned, as long as the + * value fits in the target register size. Values that are too large will + * be rejected. * * @param regid Register ID that is to be modified. * @param value Object containing the new register value.