From 92477e33f9004643399bedb53f93b7a31744c68c Mon Sep 17 00:00:00 2001 From: Brendan Edmonds Date: Thu, 5 Oct 2023 10:01:59 +1000 Subject: [PATCH] BASIL-19: infinite loop detection --- .../analysis/NonReturningFunctions.scala | 125 ++++++++++++++++++ src/main/scala/ir/Program.scala | 11 +- src/main/scala/translating/BAPToIR.scala | 36 ++--- src/main/scala/util/RunUtils.scala | 3 +- 4 files changed, 145 insertions(+), 30 deletions(-) create mode 100644 src/main/scala/analysis/NonReturningFunctions.scala diff --git a/src/main/scala/analysis/NonReturningFunctions.scala b/src/main/scala/analysis/NonReturningFunctions.scala new file mode 100644 index 000000000..6cb4a0b4d --- /dev/null +++ b/src/main/scala/analysis/NonReturningFunctions.scala @@ -0,0 +1,125 @@ +package analysis + +import bap.{BAPJump, BAPSubroutine} +import ir.{Block, DirectCall, GoTo, IndirectCall, Jump, Procedure, Statement} + +import scala.collection.mutable.{Map, Queue} +import collection.parallel.CollectionConverters.seqIsParallelizable +import scala.collection.mutable.ArrayBuffer +import scala.collection.parallel.CollectionConverters.* + +class NonReturningFunctions { + + def transform(procedures: ArrayBuffer[Procedure]): Unit = { + val blocksToRemove: Queue[String] = Queue() + val mapJumpsToBlocks: Map[String, ArrayBuffer[(Jump, Block)]] = Map() + val mapBlocksToProcedure: Map[String, (Procedure, Integer)] = Map() + + def isEndlessLoop(proc: Procedure, goTo: GoTo, index: Integer): Boolean = { + + if (goTo.condition.isEmpty && mapBlocksToProcedure.contains(goTo.target.label) && mapBlocksToProcedure(goTo.target.label)._2 < index) { + + val (_, idx) = mapBlocksToProcedure(goTo.target.label) + + for (loopIndex <- idx.toInt to index.toInt) { + val blockAtLoopIndex = proc.blocks(loopIndex) + for (loopJump <- blockAtLoopIndex.jumps) { + loopJump match { + case loopGoTo: GoTo => + if (!mapBlocksToProcedure.contains(loopGoTo.target.label) || mapBlocksToProcedure(loopGoTo.target.label)._2 >= index) + return false + case call: IndirectCall => + if (call.target.name == "R30") { + return false + } + case _ => + } + } + + } + return true + } + false + } + + for (proc <- procedures) { + var numberOfReturns = 0 + for ((block, index) <- proc.blocks.zipWithIndex) { + mapBlocksToProcedure.addOne(block.label, (proc, index)) + for (jump <- block.jumps) { + + jump match { + case call: IndirectCall => + if (call.target.name == "R30") { + block.countOfReturnStatements += 1 + numberOfReturns += 1 + } + case directCall: DirectCall => + mapJumpsToBlocks.put(directCall.target.name, mapJumpsToBlocks.getOrElse(directCall.target.name, ArrayBuffer()).addOne((directCall, block))) + case goTo: GoTo => + mapJumpsToBlocks.put(goTo.target.label, mapJumpsToBlocks.getOrElse(goTo.target.label, ArrayBuffer()).addOne((goTo, block))) + if (isEndlessLoop(proc, goTo, index)) { + + } + case _ => + } + + } + } + } + var blocksDeleted = true; + + while (blocksDeleted) { + blocksDeleted = false + for (proc <- procedures) { + + if (proc.calculateReturnCount() == 0) { + mapJumpsToBlocks.get(proc.name) match { + case Some(v) => for (block <- v) { + val (_, containingBlock) = block + for (jump <- containingBlock.jumps) { + jump match { + case directCall: DirectCall => + directCall.returnTarget match { + case Some(t) => + blocksToRemove.enqueue(t.label) + case _ => + } + directCall.returnTarget = None + case _ => + } + } + } + case _ => + } + } + } + + while (blocksToRemove.nonEmpty) { + val label = blocksToRemove.dequeue() + val (procedure, _) = mapBlocksToProcedure(label) + if (!mapJumpsToBlocks.contains(label) || mapJumpsToBlocks(label).length <= 1) { + + var procedureBlock: Integer = null + for ((block, index) <- procedure.blocks.zipWithIndex) { + if (block.label == label) { + procedureBlock = index + for (jump <- block.jumps) { + jump match { + case goTo: GoTo => + blocksToRemove.enqueue(goTo.target.label) + case _ => + } + } + } + } + if (procedureBlock != null) { + procedure.blocks.remove(procedureBlock) + blocksDeleted = true + } + } + } + } + + } +} diff --git a/src/main/scala/ir/Program.scala b/src/main/scala/ir/Program.scala index 943c996be..ff1cc5271 100644 --- a/src/main/scala/ir/Program.scala +++ b/src/main/scala/ir/Program.scala @@ -106,16 +106,18 @@ class Procedure( var address: Option[Int], var blocks: ArrayBuffer[Block], var in: ArrayBuffer[Parameter], - var out: ArrayBuffer[Parameter], - var nonReturning: Boolean + var out: ArrayBuffer[Parameter] ) { def calls: Set[Procedure] = blocks.flatMap(_.calls).toSet override def toString: String = { - s"Procedure $name at ${address.getOrElse("None")} with ${blocks.size} blocks and ${in.size} in and ${out.size} out parameters" + (if (nonReturning) " Non-Returning" else "") + s"Procedure $name at ${address.getOrElse("None")} with ${blocks.size} blocks and ${in.size} in and ${out.size} out parameters") } var modifies: mutable.Set[Global] = mutable.Set() + def calculateReturnCount(): Int = { + return blocks.foldLeft(0)(_ + _.countOfReturnStatements) + } def stackIdentification(): Unit = { val stackPointer = Register("R31", BitVecType(64)) val stackRefs: mutable.Set[Variable] = mutable.Set(stackPointer) @@ -174,7 +176,8 @@ class Block( var label: String, var address: Option[Int], var statements: ArrayBuffer[Statement], - var jumps: ArrayBuffer[Jump] + var jumps: ArrayBuffer[Jump], + var countOfReturnStatements: Int ) { def calls: Set[Procedure] = jumps.flatMap(_.calls).toSet def modifies: Set[Global] = statements.flatMap(_.modifies).toSet diff --git a/src/main/scala/translating/BAPToIR.scala b/src/main/scala/translating/BAPToIR.scala index 4a23d407c..0f43a545c 100644 --- a/src/main/scala/translating/BAPToIR.scala +++ b/src/main/scala/translating/BAPToIR.scala @@ -1,11 +1,11 @@ package translating -import bap._ -import ir._ -import specification._ +import analysis.NonReturningFunctions +import bap.* +import ir.* +import specification.* + import scala.collection.mutable -import collection.parallel.CollectionConverters.seqIsParallelizable -import scala.collection.parallel.CollectionConverters._ import scala.collection.mutable.Map import scala.collection.mutable.ArrayBuffer @@ -20,7 +20,7 @@ class BAPToIR(var program: BAPProgram, mainAddress: Int) { for (s <- program.subroutines) { val blocks: ArrayBuffer[Block] = ArrayBuffer() for (b <- s.blocks) { - val block = Block(b.label, b.address, ArrayBuffer(), ArrayBuffer()) + val block = Block(b.label, b.address, ArrayBuffer(), ArrayBuffer(), 0) blocks.append(block) labelToBlock.addOne(b.label, block) } @@ -32,7 +32,7 @@ class BAPToIR(var program: BAPProgram, mainAddress: Int) { for (p <- s.out) { out.append(p.toIR) } - val procedure = Procedure(s.name, Some(s.address), blocks, in, out, BAPLoader.isNonReturning(s.name)) + val procedure = Procedure(s.name, Some(s.address), blocks, in, out) if (s.address == mainAddress) { mainProcedure = Some(procedure) } @@ -40,30 +40,19 @@ class BAPToIR(var program: BAPProgram, mainAddress: Int) { nameToProcedure.addOne(s.name, procedure) } - for (s <- program.subroutines.par) { - - var isReturning = false + for (s <- program.subroutines) { for (b <- s.blocks) { val block = labelToBlock(b.label) + for (st <- b.statements) { block.statements.append(translate(st)) } + for (j <- b.jumps) { val translated = translate(j) - if (translated.isInstanceOf[DirectCall] && translated.asInstanceOf[DirectCall].target.nonReturning) { - translated.asInstanceOf[DirectCall].returnTarget = None - } - if (j.isInstanceOf[BAPIndirectCall] && j.asInstanceOf[BAPIndirectCall].target.name == "R30") { - isReturning = true - } - block.jumps.append(translated) - } } - if (!isReturning) { - nameToProcedure(s.name).nonReturning = true - } } val memorySections: ArrayBuffer[MemorySection] = ArrayBuffer() @@ -87,10 +76,7 @@ class BAPToIR(var program: BAPProgram, mainAddress: Int) { DirectCall( nameToProcedure(b.target), coerceToBool(b.condition), - if (nameToProcedure(b.target).nonReturning) - Option.empty - else - b.returnTarget.map { (t: String) => labelToBlock(t) } + b.returnTarget.map { (t: String) => labelToBlock(t) } ) case b: BAPIndirectCall => IndirectCall(b.target.toIR, coerceToBool(b.condition), b.returnTarget.map { (t: String) => labelToBlock(t) }) diff --git a/src/main/scala/util/RunUtils.scala b/src/main/scala/util/RunUtils.scala index 138b85f29..113c35d55 100644 --- a/src/main/scala/util/RunUtils.scala +++ b/src/main/scala/util/RunUtils.scala @@ -75,6 +75,7 @@ object RunUtils { val IRTranslator = BAPToIR(bapProgram, mainAddress) var IRProgram = IRTranslator.translate + NonReturningFunctions().transform(IRProgram.procedures) val specification = loadSpecification(specFileName, IRProgram, globals) @@ -315,7 +316,7 @@ object RunUtils { } def addFakeProcedure(name: String): Unit = { - IRProgram.procedures += Procedure(name, None, ArrayBuffer(), ArrayBuffer(), ArrayBuffer(), true) + IRProgram.procedures += Procedure(name, None, ArrayBuffer(), ArrayBuffer(), ArrayBuffer()) } def resolveAddresses(valueSet: Set[Value]): Set[AddressValue] = {