Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Non-deterministic GoTos for indirect call resolution #132

Merged
merged 25 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9cceafb
Added reassign and stack ptr examples
yousifpatti Sep 26, 2023
21948c4
Adding parent function to CFG statements
yousifpatti Sep 26, 2023
fedb667
Added Stack and Data renaming
yousifpatti Sep 26, 2023
3208cdd
Identifying constant regions
yousifpatti Oct 10, 2023
b1f3d15
Merge branch 'analysis-devel' into yousif-memory-region-analysis
yousifpatti Oct 10, 2023
1aad929
Fixes to merge comments
yousifpatti Oct 10, 2023
f48395a
Merge branch 'main' into yousif-memory-region-analysis
yousifpatti Oct 17, 2023
c9f37e3
Fixed merge issues
yousifpatti Oct 17, 2023
9610dea
add non-deterministic goto to IR, use it when indirect calls have mul…
Oct 30, 2023
3a6a93f
Merge branch 'main' into indirect-calls-nondet
Oct 30, 2023
42b2973
printer for analysis results that is ok and much more readable than t…
Oct 30, 2023
185ed71
clean up MRA/VSA a bit
Oct 30, 2023
d9c5e39
minor VSA improvement
Oct 31, 2023
d21425b
syntax cleanup
Oct 31, 2023
595c284
more type cleanup
Oct 31, 2023
d1de241
improve analysis printer
Oct 31, 2023
936596a
add *.txt and *.csv to gitignore
Oct 31, 2023
4e0ced0
Merge branch 'main' into indirect-calls-nondet
Nov 1, 2023
0f15ae5
add back dot output and wrap bubble labels
ailrst Nov 2, 2023
e1a5022
fix memory region equals/hashcode
Nov 2, 2023
cc08880
remove conditions from calls since they should not appear in the ARM6…
Nov 2, 2023
2a5fd77
Merge branch 'main' into indirect-calls-nondet
Nov 2, 2023
9be3c66
Merge remote-tracking branch 'origin/indirect-calls-nondet' into indi…
Nov 2, 2023
99b9bd8
add flags for printing analysis results, make it so separate results …
Nov 2, 2023
fb919aa
fix noted issues
Nov 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions src/main/scala/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import util.RunUtils
import scala.collection.mutable.{ArrayBuffer, Set}
import scala.collection.{immutable, mutable}
import scala.language.postfixOps
import scala.sys.process._
import scala.sys.process.*
import util.*
import mainargs.{main, arg, ParserForClass, Flag}

Expand All @@ -35,7 +35,11 @@ object Main {
@arg(name = "dump-il", doc = "Dump the Intermediate Language to text.")
dumpIL: Option[String],
@arg(name = "help", short = 'h', doc = "Show this help message.")
help: Flag
help: Flag,
@arg(name = "analysis-results", doc = "Log analysis results in files at specified path.")
analysisResults: Option[String],
@arg(name = "analysis-results-dot", doc = "Log analysis results in .dot form at specified path.")
analysisResultsDot: Option[String]
)

def main(args: Array[String]): Unit = {
Expand All @@ -61,12 +65,12 @@ object Main {
val q = BASILConfig(
loading = ILLoadingConfig(conf.adtFileName, conf.relfFileName, conf.specFileName, conf.dumpIL),
runInterpret = conf.interpret.value,
staticAnalysis = if (conf.analyse.value) then Some(StaticAnalysisConfig(conf.dumpIL)) else None,
boogieTranslation = BoogieGeneratorConfig(if (conf.lambdaStores.value) then BoogieMemoryAccessMode.LambdaStoreSelect else BoogieMemoryAccessMode.SuccessiveStoreSelect),
outputPrefix = conf.outFileName
staticAnalysis = if conf.analyse.value then Some(StaticAnalysisConfig(conf.dumpIL, conf.analysisResults, conf.analysisResultsDot)) else None,
boogieTranslation = BoogieGeneratorConfig(if conf.lambdaStores.value then BoogieMemoryAccessMode.LambdaStoreSelect else BoogieMemoryAccessMode.SuccessiveStoreSelect),
outputPrefix = conf.outFileName,
)

RunUtils.run(q);
RunUtils.run(q)
}

}
81 changes: 1 addition & 80 deletions src/main/scala/cfg_visualiser/Output.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,71 +7,6 @@ import analysis._
*/
object Output {

/** Generate an output to a file.
* @param file
* the output file
* @param kind
* output kind (determines the file name suffix)
* @param outFolder
* the output directory
*/
def output(kind: OutputKind, content: String, fileName: String): Unit = {
val extension = kind match {
case OtherOutput(OutputKindE.`cfg`) => "_cfg.dot"
case OtherOutput(OutputKindE.`icfg`) => "_icfg.dot"
// case DataFlowOutput(k) =>
// s"_$k.dot"
case _ => ???
}
val outFile = new File(s"$fileName.dot")
val pw = new PrintWriter(outFile, "UTF-8")
pw.write(content)
pw.close()
}

/** Escapes special characters in the given string. Special characters are all Unicode chars except 0x20-0x7e but
* including \, ", {, and }.
*/
def escape(s: String): String = {
if (s == null)
return null
val b = new StringBuilder()
for (i <- 0 until s.length) {
val c = s.charAt(i)
c match {
case '"' =>
b.append("\\\"")
case '\\' =>
b.append("\\\\")
case '\b' =>
b.append("\\b")
case '\t' =>
b.append("\\t")
case '\n' =>
b.append("\\n")
case '\r' =>
b.append("\\r")
case '\f' =>
b.append("\\f")
case '<' =>
b.append("\\<")
case '>' =>
b.append("\\>")
case '{' =>
b.append("\\{")
case '}' =>
b.append("\\}")
case _ =>
if (c >= 0x20 && c <= 0x7e)
b.append(c)
else {
b.append("\\%04X".format(c.toInt))
}
}
}
b.toString()
}

/** Helper function for producing string output for a control-flow graph node after an analysis.
* @param res
* map from control-flow graph nodes to strings, as produced by the analysis
Expand Down Expand Up @@ -99,18 +34,4 @@ object Output {
case callRet: CfgCallReturnNode => s"callreturn_$uniqueId"
case _ => ???
}
}

/** Different kinds of output (determine output file names).
*/
object OutputKindE extends Enumeration {
val cfg, icfg = Value
}

sealed trait OutputKind

/** Other output kinds (for other processing phases than the actual analysis).
*/
case class OtherOutput(kind: OutputKindE.Value) extends OutputKind {
override def toString: String = kind.toString
}
}
2 changes: 1 addition & 1 deletion src/main/scala/translating/SpecificationLoader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ case class SpecificationLoader(symbols: Set[SpecGlobal], program: Program) {
val params: Map[String, Parameter] = irProc match {
case None => Map()
case Some(p) =>
p.in.map { (p: Parameter) => p.name -> p }.toMap ++ p.out.map { (p: Parameter) => p.name -> p }.toMap
p.in.map(p => p.name -> p).toMap ++ p.out.map(p => p.name -> p).toMap
}

val requires = ctx.requires.asScala.collect { case r: ParsedRequiresContext =>
Expand Down
15 changes: 7 additions & 8 deletions src/main/scala/util/BASILConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ package util

case class ILLoadingConfig(adtFile: String, relfFile: String, specFile: Option[String], dumpIL: Option[String])
case class BoogieGeneratorConfig(memoryFunctionType: BoogieMemoryAccessMode = BoogieMemoryAccessMode.SuccessiveStoreSelect)
case class StaticAnalysisConfig(dumpILToPath: Option[String] = None)
case class StaticAnalysisConfig(dumpILToPath: Option[String] = None, analysisResultsPath: Option[String] = None, analysisDotPath: Option[String] = None)
enum BoogieMemoryAccessMode:
case SuccessiveStoreSelect, LambdaStoreSelect

case class BASILConfig(
loading: ILLoadingConfig,
runInterpret: Boolean = false,
staticAnalysis: Option[StaticAnalysisConfig] = None,
boogieTranslation: BoogieGeneratorConfig = BoogieGeneratorConfig(),
outputPrefix: String
)
case class BASILConfig(loading: ILLoadingConfig,
runInterpret: Boolean = false,
staticAnalysis: Option[StaticAnalysisConfig] = None,
boogieTranslation: BoogieGeneratorConfig = BoogieGeneratorConfig(),
outputPrefix: String,
)

95 changes: 51 additions & 44 deletions src/main/scala/util/RunUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import java.io.{BufferedWriter, FileWriter, IOException}
import scala.jdk.CollectionConverters.*
import analysis.solvers.*
import analysis.*
import cfg_visualiser.{OtherOutput, Output, OutputKindE}
import cfg_visualiser.Output
import bap.*
import ir.*
import boogie.*
Expand All @@ -24,8 +24,6 @@ import scala.collection.mutable
object RunUtils {
var memoryRegionAnalysisResults: Map[CfgNode, Set[MemoryRegion]] = Map()

var iterations = 0

// ids reserved by boogie
val reserved: Set[String] = Set("free")

Expand Down Expand Up @@ -93,16 +91,16 @@ object RunUtils {
IRProgram = renamer.visitProgram(IRProgram)

q.loading.dumpIL match {
case Some(s) => writeToFile(serialiseIL(IRProgram), s + "-before-analysis.il")
case Some(s: String) => writeToFile(serialiseIL(IRProgram), s"$s-before-analysis.il")
case _ =>
}

q.staticAnalysis match {
case Some(analysisConfig) =>
IRProgram = analyse(IRProgram, externalFunctions, globals, globalOffsets)
IRProgram = analyse(IRProgram, externalFunctions, globals, globalOffsets, analysisConfig, 1)

analysisConfig.dumpILToPath match {
case Some(s) => writeToFile(serialiseIL(IRProgram), s + "-after-analysis.il")
case Some(s: String) => writeToFile(serialiseIL(IRProgram), s"$s-after-analysis.il")
case _ =>
}
case None =>
Expand Down Expand Up @@ -131,15 +129,16 @@ object RunUtils {
IRProgram: Program,
externalFunctions: Set[ExternalFunction],
globals: Set[SpecGlobal],
globalOffsets: Map[BigInt, BigInt]
globalOffsets: Map[BigInt, BigInt],
config: StaticAnalysisConfig,
iteration: Int
): Program = {
iterations += 1
val subroutines = IRProgram.procedures
.filter(p => p.address.isDefined)
.map { (p: Procedure) => BigInt(p.address.get) -> p.name }
.map(p => BigInt(p.address.get) -> p.name)
.toMap
val globalAddresses = globals.map { (s: SpecGlobal) => s.address -> s.name }.toMap
val externalAddresses = externalFunctions.map { (e: ExternalFunction) => e.offset -> e.name }.toMap
val globalAddresses = globals.map(s => s.address -> s.name).toMap
val externalAddresses = externalFunctions.map(e => e.offset -> e.name).toMap
Logger.info("Globals:")
Logger.info(globalAddresses)
Logger.info("Global Offsets: ")
Expand All @@ -156,25 +155,29 @@ object RunUtils {
Logger.info("[!] Running Constant Propagation")
val constPropSolver = ConstantPropagationAnalysis.WorklistSolver(cfg)
val constPropResult: Map[CfgNode, Map[Variable, ConstantPropagationLattice.Element]] = constPropSolver.analyze(true)
/*
Output.output(
OtherOutput(OutputKindE.cfg),
cfg.toDot(Output.labeler(constPropResult, constPropSolver.stateAfterNode), Output.dotIder),
"cpa"
)
*/
writeToFile(printAnalysisResults(cfg, constPropResult), "cpa")

config.analysisDotPath match {
case Some(s) => writeToFile(cfg.toDot(Output.labeler(constPropResult, constPropSolver.stateAfterNode), Output.dotIder), s"${s}_constprop$iteration.dot")
case None =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick but it might be cleaner to do a .map()

}
config.analysisResultsPath match {
case Some(s) => writeToFile(printAnalysisResults(cfg, constPropResult, iteration), s"${s}_constprop$iteration.txt")
case None =>
}

Logger.info("[!] Running MRA")
val mraSolver = MemoryRegionAnalysis.WorklistSolver(cfg, globalAddresses, globalOffsets, mergedSubroutines, constPropResult)
val mraResult: Map[CfgNode, Set[MemoryRegion]] = mraSolver.analyze(true)
memoryRegionAnalysisResults = mraResult
/* Output.output(
OtherOutput(OutputKindE.cfg),
cfg.toDot(Output.labeler(mraResult, mraSolver.stateAfterNode), Output.dotIder),
"mra"
)*/
writeToFile(printAnalysisResults(cfg, mraResult), "mra")

config.analysisDotPath match {
case Some(s) => writeToFile(cfg.toDot(Output.labeler(mraResult, mraSolver.stateAfterNode), Output.dotIder), s"${s}_mra$iteration.dot")
case None =>
}
config.analysisResultsPath match {
case Some(s) => writeToFile(printAnalysisResults(cfg, mraResult, iteration), s"${s}_mra$iteration.txt")
case None =>
}

Logger.info("[!] Running MMM")
val mmm = MemoryModelMap()
Expand All @@ -184,35 +187,39 @@ object RunUtils {
val vsaSolver =
ValueSetAnalysis.WorklistSolver(cfg, globalAddresses, externalAddresses, globalOffsets, subroutines, mmm, constPropResult)
val vsaResult: Map[CfgNode, Map[Variable | MemoryRegion, Set[Value]]] = vsaSolver.analyze(false)
/*
Output.output(
OtherOutput(OutputKindE.cfg),
cfg.toDot(Output.labeler(vsaResult, vsaSolver.stateAfterNode), Output.dotIder),
"vsa"
)
*/
writeToFile(printAnalysisResults(cfg, vsaResult), "vsa")

config.analysisDotPath match {
case Some(s) => writeToFile(cfg.toDot(Output.labeler(vsaResult, vsaSolver.stateAfterNode), Output.dotIder), s"${s}_vsa$iteration.dot")
case None =>
}
config.analysisResultsPath match {
case Some(s) => writeToFile(printAnalysisResults(cfg, vsaResult, iteration), s"${s}_vsa$iteration.txt")
case None =>
}

Logger.info("[!] Resolving CFG")
val (newIR, modified): (Program, Boolean) = resolveCFG(cfg, vsaResult, IRProgram)
/*
if (modified) {
Logger.info(s"[!] Analysing again (iter $iterations)")
return analyse(newIR, externalFunctions, globals, globalOffsets)
Logger.info(s"[!] Analysing again (iter $iteration)")
return analyse(newIR, externalFunctions, globals, globalOffsets, config, iteration + 1)
}
*/
val newCFG = ProgramCfgFactory().fromIR(newIR)
Output.output(OtherOutput(OutputKindE.cfg), newCFG.toDot(x => x.toString, Output.dotIder), "resolvedCFG")

Logger.info(s"[!] Finished indirect call resolution after $iterations iterations")
config.analysisDotPath match {
case Some(s) =>
val newCFG = ProgramCfgFactory().fromIR(newIR)
writeToFile(newCFG.toDot(x => x.toString, Output.dotIder), s"${s}_resolvedCFG.dot")
case None =>
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to remove this? We iterate to also analyse the code now made reachable by resolving the indirect calls, in general it should only run twice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed it when testing because it was overwriting the logged analysis outputs, but I'll add a better solution there.


Logger.info(s"[!] Finished indirect call resolution after $iteration iterations")

newIR
}

def printAnalysisResults(cfg: ProgramCfg, result: Map[CfgNode, _]): String = {
def printAnalysisResults(cfg: ProgramCfg, result: Map[CfgNode, _], iteration: Int): String = {
val functionEntries = cfg.nodes.collect { case n: CfgFunctionEntryNode => n }.toSeq.sortBy(_.data.name)
val s = StringBuilder()

s.append(System.lineSeparator())
for (f <- functionEntries) {
val stack: mutable.Stack[CfgNode] = mutable.Stack()
val visited: mutable.Set[CfgNode] = mutable.Set()
Expand Down Expand Up @@ -406,8 +413,8 @@ object RunUtils {
(IRProgram, modified)
}

def writeToFile(content: String, name: String): Unit = {
val outFile = File(name)
def writeToFile(content: String, fileName: String): Unit = {
val outFile = File(fileName)
val pw = PrintWriter(outFile, "UTF-8")
pw.write(content)
pw.close()
Expand Down