From 7e63dc45bfdc3deb73ce1bfd31ebb7a51b3940ac Mon Sep 17 00:00:00 2001 From: hz050504 Date: Fri, 20 Oct 2023 19:52:38 +1000 Subject: [PATCH] update Interpreter - added returnBlock stack to store the return address in DirectCall jump. (function example) - modified the way to assign nextBlock in interpretProcedure() when no block in procedure, assign the last returnBlock instead. To fix bug when a DirectCall only have returnTarget but the DirectCall.target does not have any block. (DirectCall for printf in function1 example) - added expect Map for all interpreter test cases with update runInterpret() to testInterpret() to ensures the expected values match those in Interpreter memory. - moved LogLevel setting from Interpreter to Test. --- src/main/scala/ir/Interpreter.scala | 49 +++++-- src/test/scala/ir/InterpreterTests.scala | 174 ++++++++++++++++------- 2 files changed, 155 insertions(+), 68 deletions(-) diff --git a/src/main/scala/ir/Interpreter.scala b/src/main/scala/ir/Interpreter.scala index 96b32e71a..d0ed5f977 100644 --- a/src/main/scala/ir/Interpreter.scala +++ b/src/main/scala/ir/Interpreter.scala @@ -1,10 +1,10 @@ package ir import analysis.BitVectorEval.* -import util.{LogLevel, Logger} +import util.Logger import scala.collection.mutable -import scala.util.control.Breaks.{breakable, break} +import scala.util.control.Breaks.{break, breakable} class Interpreter() { val regs: mutable.Map[Variable, BitVecLiteral] = mutable.Map() @@ -13,7 +13,7 @@ class Interpreter() { private val FP: BitVecLiteral = BitVecLiteral(4096 - 16, 64) private val LR: BitVecLiteral = BitVecLiteral(BigInt("FF", 16), 64) private var nextBlock: Option[Block] = None - private val logLevel: LogLevel = LogLevel.DEBUG + private val returnBlock: mutable.Stack[Block] = mutable.Stack() def eval(exp: Expr, env: mutable.Map[Variable, BitVecLiteral]): Literal = { exp match { @@ -57,7 +57,9 @@ class Interpreter() { case bin: BinaryExpr => val left: BitVecLiteral = eval(bin.arg1, env).asInstanceOf[BitVecLiteral] val right: BitVecLiteral = eval(bin.arg2, env).asInstanceOf[BitVecLiteral] - Logger.debug(s"\tBinaryExpr(0x${left.value.toString(16)}[u${left.size}] ${bin.op} 0x${right.value.toString(16)}[u${right.size}])") + Logger.debug( + s"\tBinaryExpr(0x${left.value.toString(16)}[u${left.size}] ${bin.op} 0x${right.value.toString(16)}[u${right.size}])" + ) bin.op match { case BVAND => smt_bvand(left, right) case BVOR => smt_bvor(left, right) @@ -112,7 +114,8 @@ class Interpreter() { case ms: MemoryStore => val index: Int = eval(ms.index, env).asInstanceOf[BitVecLiteral].value.toInt val value: BitVecLiteral = eval(ms.value, env).asInstanceOf[BitVecLiteral] - Logger.debug(s"\tMemoryStore(mem:${ms.mem}, index:0x${index.toHexString}, value:0x${value.value.toString(16)}[u${value.size}], size:${ms.size})") + Logger.debug(s"\tMemoryStore(mem:${ms.mem}, index:0x${index.toHexString}, value:0x${value.value + .toString(16)}[u${value.size}], size:${ms.size})") setMemory(index, ms.size, ms.endian, value, mems) } } @@ -132,7 +135,13 @@ class Interpreter() { BitVecLiteral(BigInt(newValue, 2), newSize) } - def setMemory(index: Int, size: Int, endian: Endian, value: BitVecLiteral, env: mutable.Map[Int, BitVecLiteral]): BitVecLiteral = { + def setMemory( + index: Int, + size: Int, + endian: Endian, + value: BitVecLiteral, + env: mutable.Map[Int, BitVecLiteral] + ): BitVecLiteral = { val binaryString: String = value.value.toString(2).reverse.padTo(size, '0').reverse val data: List[BitVecLiteral] = endian match { @@ -163,7 +172,10 @@ class Interpreter() { } // Procedure.Block - nextBlock = Some(p.blocks.head) + p.blocks.headOption match { + case Some(block) => nextBlock = Some(block) + case None => nextBlock = Some(returnBlock.pop()) + } } private def interpretBlock(b: Block): Unit = { @@ -199,18 +211,29 @@ class Interpreter() { case Some(value) => eval(value, regs) match { case TrueLiteral => + if (dc.returnTarget.isDefined) { + returnBlock.push(dc.returnTarget.get) + } interpretProcedure(dc.target) break case FalseLiteral => } case None => + if (dc.returnTarget.isDefined) { + returnBlock.push(dc.returnTarget.get) + } interpretProcedure(dc.target) break } case ic: IndirectCall => Logger.debug(s"$ic") - if (ic.target == Register("R30", BitVecType(64)) & ic.condition.isEmpty & ic.returnTarget.isEmpty) { - nextBlock = None + if (ic.target == Register("R30", BitVecType(64)) && ic.condition.isEmpty && ic.returnTarget.isEmpty) { + if (returnBlock.nonEmpty) { + nextBlock = Some(returnBlock.pop()) + } else { + //Exit Interpreter + nextBlock = None + } break } else { ??? @@ -236,11 +259,11 @@ class Interpreter() { Logger.debug(s"MemoryAssign ${assign.lhs} = ${assign.rhs}") val evalRight = eval(assign.rhs, regs) evalRight match { - case BitVecLiteral(value, size) => Logger.debug(s"MemoryAssign ${assign.lhs} := 0x${value.toString(16)}[u$size]\n") - case _ => throw new Exception("cannot register non-bitvectors") + case BitVecLiteral(value, size) => + Logger.debug(s"MemoryAssign ${assign.lhs} := 0x${value.toString(16)}[u$size]\n") + case _ => throw new Exception("cannot register non-bitvectors") } - //TODO: ASSERT case assert: Assert => Logger.debug(assert) ??? @@ -248,8 +271,6 @@ class Interpreter() { } def interpret(IRProgram: Program): mutable.Map[Variable, BitVecLiteral] = { - Logger.setLevel(logLevel) - // initialize memory array from IRProgram var currentAddress = 0 IRProgram.initialMemory diff --git a/src/test/scala/ir/InterpreterTests.scala b/src/test/scala/ir/InterpreterTests.scala index b4563c6fc..d448b132e 100644 --- a/src/test/scala/ir/InterpreterTests.scala +++ b/src/test/scala/ir/InterpreterTests.scala @@ -3,21 +3,19 @@ package ir import analysis.BitVectorEval.* import org.scalatest.funsuite.AnyFunSuite import org.scalatest.BeforeAndAfter -import bap.BAPProgram import specification.SpecGlobal import translating.BAPToIR -import util.Logger +import util.{LogLevel, Logger} import util.RunUtils.{loadBAP, loadReadELF} -import scala.collection.mutable - class InterpreterTests extends AnyFunSuite with BeforeAndAfter { var i: Interpreter = Interpreter() + Logger.setLevel(LogLevel.DEBUG) def getProgram(name: String): (Program, Set[SpecGlobal]) = { val bapProgram = loadBAP(s"examples/$name/$name.adt") - val (externalFunctions, globals, globalOffsets, mainAddress) = loadReadELF(s"examples/$name/$name.relf") + val (externalFunctions, globals, _, mainAddress) = loadReadELF(s"examples/$name/$name.relf") val IRTranslator = BAPToIR(bapProgram, mainAddress) var IRProgram = IRTranslator.translate IRProgram = ExternalRemover(externalFunctions.map(e => e.name)).visitProgram(IRProgram) @@ -26,71 +24,48 @@ class InterpreterTests extends AnyFunSuite with BeforeAndAfter { IRProgram.stackIdentification() IRProgram.setModifies(Map()) - (IRProgram, globals) } - def runInterpret(name: String): Unit = { + def testInterpret(name: String, expected: Map[String, Int]): Unit = { val (program, globals) = getProgram(name) - val i = Interpreter() val regs = i.interpret(program) + + // Show interpreted result + Logger.info("Registers:") regs.foreach { (key, value) => Logger.info(s"$key := $value") } + + Logger.info("Globals:") globals.foreach { global => val mem = i.getMemory(global.address.toInt, global.size, Endian.LittleEndian, i.mems) Logger.info(s"$global := $mem") } + + // Test expected value + expected.foreach { (name, expected) => + globals.find(_.name == name) match { + case Some(global) => + val actual = i.getMemory(global.address.toInt, global.size, Endian.LittleEndian, i.mems).value.toInt + assert(actual == expected) + case None => assert("None" == name) + } + } } before { i = Interpreter() } - test("basic_assign_assign") { - runInterpret("basic_assign_assign") - } - - test("basicassign") { - runInterpret("basicassign") - } - - test("function") { - runInterpret("function") - } - - test("iflocal") { - runInterpret("iflocal") - } - - test("ifglobal") { - runInterpret("ifglobal") - } - - test("array") { - runInterpret("ifglobal") - } - - test("nestedif") { - runInterpret("nestedif") - } - - test("nestedifglobal") { - runInterpret("nestedifglobal") - } - - test("simple_jump") { - runInterpret("simple_jump") - } - test("getMemory in LittleEndian") { i.mems(0) = BitVecLiteral(BigInt("0D", 16), 8) i.mems(1) = BitVecLiteral(BigInt("0C", 16), 8) i.mems(2) = BitVecLiteral(BigInt("0B", 16), 8) i.mems(3) = BitVecLiteral(BigInt("0A", 16), 8) - val expect: BitVecLiteral = BitVecLiteral(BigInt("0A0B0C0D", 16), 32) + val expected: BitVecLiteral = BitVecLiteral(BigInt("0A0B0C0D", 16), 32) val actual: BitVecLiteral = i.getMemory(0, 32, Endian.LittleEndian, i.mems) - assert(actual == expect) + assert(actual == expected) } test("getMemory in BigEndian") { @@ -98,9 +73,9 @@ class InterpreterTests extends AnyFunSuite with BeforeAndAfter { i.mems(1) = BitVecLiteral(BigInt("0B", 16), 8) i.mems(2) = BitVecLiteral(BigInt("0C", 16), 8) i.mems(3) = BitVecLiteral(BigInt("0D", 16), 8) - val expect: BitVecLiteral = BitVecLiteral(BigInt("0A0B0C0D", 16), 32) + val expected: BitVecLiteral = BitVecLiteral(BigInt("0A0B0C0D", 16), 32) val actual: BitVecLiteral = i.getMemory(0, 32, Endian.BigEndian, i.mems) - assert(actual == expect) + assert(actual == expected) } test("setMemory in LittleEndian") { @@ -108,10 +83,10 @@ class InterpreterTests extends AnyFunSuite with BeforeAndAfter { i.mems(1) = BitVecLiteral(BigInt("FF", 16), 8) i.mems(2) = BitVecLiteral(BigInt("FF", 16), 8) i.mems(3) = BitVecLiteral(BigInt("FF", 16), 8) - val expect: BitVecLiteral = BitVecLiteral(BigInt("0A0B0C0D", 16), 32) - i.setMemory(0, 32, Endian.LittleEndian, expect, i.mems) + val expected: BitVecLiteral = BitVecLiteral(BigInt("0A0B0C0D", 16), 32) + i.setMemory(0, 32, Endian.LittleEndian, expected, i.mems) val actual: BitVecLiteral = i.getMemory(0, 32, Endian.LittleEndian, i.mems) - assert(actual == expect) + assert(actual == expected) } test("setMemory in BigEndian") { @@ -119,9 +94,100 @@ class InterpreterTests extends AnyFunSuite with BeforeAndAfter { i.mems(1) = BitVecLiteral(BigInt("FF", 16), 8) i.mems(2) = BitVecLiteral(BigInt("FF", 16), 8) i.mems(3) = BitVecLiteral(BigInt("FF", 16), 8) - val expect: BitVecLiteral = BitVecLiteral(BigInt("0A0B0C0D", 16), 32) - i.setMemory(0, 32, Endian.BigEndian, expect, i.mems) + val expected: BitVecLiteral = BitVecLiteral(BigInt("0A0B0C0D", 16), 32) + i.setMemory(0, 32, Endian.BigEndian, expected, i.mems) val actual: BitVecLiteral = i.getMemory(0, 32, Endian.BigEndian, i.mems) - assert(actual == expect) + assert(actual == expected) + } + + test("basic_arrays_read") { + val expected = Map( + "arr" -> 0 + ) + testInterpret("basic_arrays_read", expected) + } + + test("basic_assign_assign") { + val expected = Map( + "x" -> 5 + ) + testInterpret("basic_assign_assign", expected) + } + + test("basic_assign_increment") { + val expected = Map( + "x" -> 1 + ) + testInterpret("basic_assign_increment", expected) + } + + test("basic_loop_loop") { + val expected = Map( + "x" -> 10 + ) + testInterpret("basic_loop_loop", expected) + } + + test("basicassign") { + val expected = Map( + "x" -> 0, + "z" -> 0, + "secret" -> 0 + ) + testInterpret("basicassign", expected) + } + + test("function") { + val expected = Map( + "x" -> 1, + "y" -> 2 + ) + testInterpret("function", expected) + } + + test("function1") { + val expected = Map( + "x" -> 1, + "y" -> 1410065515 // 10000000107 % 2147483648 = 1410065515 + ) + testInterpret("function1", expected) + } + + test("secret_write") { + val expected = Map( + "z" -> 2, + "x" -> 0, + "secret" -> 0 + ) + testInterpret("secret_write", expected) + } + + test("ifglobal") { + val expected = Map( + "x" -> 1 + ) + testInterpret("ifglobal", expected) + } + + test("cjump") { + val expected = Map( + "x" -> 1, + "y" -> 3 + ) + testInterpret("cjump", expected) + } + + test("no_interference_update_x") { + val expected = Map( + "x" -> 1 + ) + testInterpret("no_interference_update_x", expected) + } + + test("no_interference_update_y") { + val expected = Map( + "y" -> 1 + ) + testInterpret("no_interference_update_y", expected) } }