/*
* ProgramGraphWriter.java - This file is part of the Jakstab project.
* Copyright 2007-2015 Johannes Kinder <jk@jakstab.org>
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, see <http://www.gnu.org/licenses/>.
*/
package org.jakstab;
import java.awt.Color;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;
import org.jakstab.analysis.*;
import org.jakstab.analysis.explicit.VpcTrackingAnalysis;
import org.jakstab.asm.AbsoluteAddress;
import org.jakstab.asm.BranchInstruction;
import org.jakstab.asm.Instruction;
import org.jakstab.asm.ReturnInstruction;
import org.jakstab.asm.SymbolFinder;
import org.jakstab.cfa.AsmCFG;
import org.jakstab.cfa.CFAEdge;
import org.jakstab.cfa.CFAEdge.Kind;
import org.jakstab.cfa.ControlFlowGraph;
import org.jakstab.cfa.Location;
import org.jakstab.cfa.RTLLabel;
import org.jakstab.cfa.VpcLocation;
import org.jakstab.rtl.statements.BasicBlock;
import org.jakstab.rtl.statements.RTLGoto;
import org.jakstab.rtl.statements.RTLHalt;
import org.jakstab.rtl.statements.RTLSkip;
import org.jakstab.rtl.statements.RTLStatement;
import org.jakstab.transformation.VpcCfgReconstruction;
import org.jakstab.util.*;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;
/**
* Writes various graphs from a program structure.
*
* @author Johannes Kinder
*/
public class ProgramGraphWriter {
private static final Logger logger = Logger.getLogger(ProgramGraphWriter.class);
private Program program;
private Set<Location> mustLeaves;
public ProgramGraphWriter(Program program) {
this.program = program;
ControlFlowGraph cfg = program.getCFG();
mustLeaves = new HashSet<Location>();
// Find locations which have an incoming MUST edge, but no outgoing one
for (Location l : cfg.getNodes()) {
boolean foundMust = false;
for (CFAEdge e : cfg.getInEdges(l)) {
foundMust |= e.getKind() == Kind.MUST;
}
if (!foundMust)
continue;
foundMust = false;
for (CFAEdge e : cfg.getOutEdges(l)) {
foundMust |= e.getKind() == Kind.MUST;
}
if (!foundMust) {
mustLeaves.add(l);
}
}
if (!mustLeaves.isEmpty())
logger.debug("Leaves of MUST-analysis: " + mustLeaves);
}
// Does not write a real graph, but still fits best into this class
public void writeDisassembly(String filename) {
logger.info("Writing assembly file to " + filename);
SetMultimap<AbsoluteAddress, CFAEdge> branchEdges = HashMultimap.create();
SetMultimap<AbsoluteAddress, CFAEdge> branchEdgesRev = HashMultimap.create();
if (!Options.noGraphs.getValue()) {
for (CFAEdge e : program.getCFG().getEdges()) {
AbsoluteAddress sourceAddr = e.getSource().getAddress();
AbsoluteAddress targetAddr = e.getTarget().getAddress();
if (program.getInstruction(sourceAddr) instanceof BranchInstruction && !sourceAddr.equals(targetAddr)) {
branchEdges.put(sourceAddr, e);
branchEdgesRev.put(targetAddr, e);
}
}
}
try {
FileWriter out = new FileWriter(filename);
VpcTrackingAnalysis vpcAnalysis = (VpcTrackingAnalysis)AnalysisManager.getInstance()
.getAnalysis(VpcTrackingAnalysis.class);
for (Map.Entry<AbsoluteAddress,Instruction> entry : program.getAssemblyMap().entrySet()) {
AbsoluteAddress pc = entry.getKey();
Instruction instr = entry.getValue();
StringBuilder sb = new StringBuilder();
SymbolFinder symFinder = program.getModule(pc).getSymbolFinder();
if (symFinder.hasSymbolFor(pc)) {
sb.append(Characters.NEWLINE);
sb.append(symFinder.getSymbolFor(pc));
sb.append(":").append(Characters.NEWLINE);
}
if (vpcAnalysis != null) {
ValueContainer vpc = vpcAnalysis.getVpc(new RTLLabel(pc));
sb.append(vpc == null ? "" : vpc);
sb.append("\t");
}
sb.append(pc).append(":\t");
sb.append(instr.toString(pc.getValue(), symFinder));
if (instr instanceof BranchInstruction) {
Set<CFAEdge> targets = branchEdges.get(pc);
sb.append("\t; targets: ");
if (targets.isEmpty()) {
sb.append("unresolved");
} else {
boolean first = true;
for (CFAEdge e : targets) {
if (first) first = false;
else sb.append(", ");
sb.append(e.getTarget().getAddress());
sb.append('(').append(e.getKind()).append(')');
}
}
}
if (branchEdgesRev.containsKey(pc)) {
Set<CFAEdge> referers = branchEdgesRev.get(pc);
sb.append("\t; from: ");
boolean first = true;
for (CFAEdge e : referers) {
if (first) first = false;
else sb.append(", ");
sb.append(e.getSource().getAddress());
sb.append('(').append(e.getKind()).append(')');
}
}
sb.append(Characters.NEWLINE);
if (instr instanceof ReturnInstruction) sb.append(Characters.NEWLINE);
out.write(sb.toString());
}
out.close();
} catch (IOException e) {
logger.fatal(e);
return;
}
}
public void writeAssemblyVCFG(String filename, VpcCfgReconstruction vCfgRec) {
AsmCFG cfg = vCfgRec.getTransformedAsmCfg();
ControlFlowGraph ilCfg = vCfgRec.getTransformedCfg();
// Create dot file
GraphWriter gwriter = createGraphWriter(filename);
if (gwriter == null) return;
logger.info("Writing assembly CFG to " + gwriter.getFilename());
try {
for (Map.Entry<Location, Instruction> entry : cfg.getNodes().entrySet()) {
Location l = entry.getKey();
Instruction instr = entry.getValue();
String nodeName = l.toString();
String nodeLabel = program.getSymbolFor(l.getAddress());
if (nodeLabel.length() > 20) nodeLabel = nodeLabel.substring(0, 20) + "...";
if (instr != null) {
String instrString = program.getInstructionString(l.getAddress(), instr);
instrString = instrString.replace("\t", " ");
gwriter.writeNode(nodeName, nodeLabel + "\\n" + instrString, getNodeProperties(ilCfg, l));
} else {
gwriter.writeNode(nodeName, nodeLabel, getNodeProperties(ilCfg, l));
}
}
for (Map.Entry<Location, Pair<Location, Object>> entry : cfg.getEdges().entries()) {
Location source = entry.getKey();
Location target = entry.getValue().getLeft();
Object label = entry.getValue().getRight();
gwriter.writeEdge(source.toString(),
target.toString(),
label.toString());
}
gwriter.close();
} catch (IOException e) {
logger.error("Cannot write to output file", e);
return;
}
}
public void writeAssemblyCFG(ControlFlowGraph cfg, String filename) {
Set<CFAEdge> edges = new HashSet<CFAEdge>();
Set<RTLLabel> nodes = new HashSet<RTLLabel>();
for (CFAEdge e : cfg.getEdges()) {
AbsoluteAddress sourceAddr = e.getSource().getAddress();
AbsoluteAddress targetAddr = e.getTarget().getAddress();
if (!sourceAddr.equals(targetAddr)) {
edges.add(e);
nodes.add(e.getSource().getLabel());
nodes.add(e.getTarget().getLabel());
}
}
// Create dot file
GraphWriter gwriter = createGraphWriter(filename);
if (gwriter == null) return;
logger.info("Writing assembly CFG to " + gwriter.getFilename());
try {
for (RTLLabel node : nodes) {
AbsoluteAddress nodeAddr = node.getAddress();
Instruction instr = program.getInstruction(nodeAddr);
String nodeName = nodeAddr.toString();
String nodeLabel = program.getSymbolFor(nodeAddr);
if (instr != null) {
String instrString = program.getInstructionString(nodeAddr);
instrString = instrString.replace("\t", " ");
gwriter.writeNode(nodeName, nodeLabel + "\\n" + instrString, getNodeProperties(cfg, node));
} else {
gwriter.writeNode(nodeName, nodeLabel, getNodeProperties(cfg, node));
}
}
for (CFAEdge e : edges) {
if (e.getKind() == null) logger.error("Null kind? " + e);
AbsoluteAddress sourceAddr = e.getSource().getAddress();
AbsoluteAddress targetAddr = e.getTarget().getAddress();
String label = null;
Instruction instr = program.getInstruction(sourceAddr);
if (instr instanceof BranchInstruction) {
BranchInstruction bi = (BranchInstruction)instr;
if (bi.isConditional()) {
// Get the original goto from the program (not the converted assume)
RTLStatement rtlGoto = program.getStatement(e.getSource().getLabel());
// If this is the fall-through edge, output F, otherwise T
label = targetAddr.equals(rtlGoto.getNextLabel().getAddress()) ? "F" : "T";
}
}
gwriter.writeEdge(sourceAddr.toString(),
targetAddr.toString(),
label,
e.getKind().equals(CFAEdge.Kind.MAY) ? Color.BLACK : Color.GREEN);
}
gwriter.close();
} catch (IOException e) {
logger.error("Cannot write to output file", e);
return;
}
}
public void writeAssemblyBasicBlockGraph(ControlFlowGraph cfg, String filename) {
logger.info("Writing assembly basic block graph to " + filename);
writeAssemblyBBCFG(cfg, filename);
}
public void writeTopologyGraph(ControlFlowGraph cfg, String filename) {
logger.info("Writing toplogy graph to " + filename);
writeTopologicalBBCFG(cfg, filename);
}
public void writeControlFlowAutomaton(ControlFlowGraph cfg, String filename) {
writeControlFlowAutomaton(cfg, filename, (ReachedSet)null);
}
public void writeControlFlowAutomaton(ControlFlowGraph cfg, String filename, ReachedSet reached) {
logger.info("Writing CFA to " + filename);
writeControlFlowGraph(cfg, filename, reached);
}
public void writeVpcBasicBlockGraph(String filename, VpcCfgReconstruction vCfgRec) {
ControlFlowGraph vCfg = vCfgRec.getTransformedCfg();
// Create dot file
GraphWriter gwriter = createGraphWriter(filename);
if (gwriter == null) return;
logger.info("Writing VPC-lifted basic block graph to " + gwriter.getFilename());
try {
for (Location loc : vCfg.getBasicBlockNodes()) {
VpcLocation vpcLoc = (VpcLocation)loc;
RTLLabel nodeAddr = vpcLoc.getLabel();
String nodeName = vpcLoc.toString();
StringBuilder labelBuilder = new StringBuilder();
String locLabel = program.getSymbolFor(nodeAddr);
if (locLabel.length() > 20) locLabel = locLabel.substring(0, 20) + "...";
labelBuilder.append(locLabel).append(" @ ").append(vpcLoc.getVPC()).append("\\n");
BasicBlock bb = vCfg.getBasicBlock(vpcLoc);
for (RTLStatement stmt : bb) {
labelBuilder.append(stmt.toString() + "\\l");
}
gwriter.writeNode(nodeName, labelBuilder.toString(), getNodeProperties(vCfg, vpcLoc));
}
for (CFAEdge e : vCfg.getBasicBlockEdges()) {
VpcLocation sourceAddr = (VpcLocation)e.getSource();
VpcLocation targetAddr = (VpcLocation)e.getTarget();
if (e.getTransformer() instanceof RTLSkip)
gwriter.writeEdge(sourceAddr.toString(),
targetAddr.toString());
else
gwriter.writeEdge(sourceAddr.toString(),
targetAddr.toString(),
e.getTransformer().toString());
}
gwriter.close();
} catch (IOException e) {
logger.error("Cannot write to output file", e);
return;
}
}
public void writeART(String filename, AbstractReachabilityTree art) {
Map<String,String> startNode = new HashMap<String, String>();
Map<String,String> endNode = new HashMap<String, String>();
startNode.put("color", "green");
startNode.put("style", "filled,bold");
endNode.put("color", "red");
endNode.put("style", "filled,bold");
// Create dot file
GraphWriter gwriter = createGraphWriter(filename);
if (gwriter == null) return;
logger.info("Writing ART to " + gwriter.getFilename());
try {
Deque<AbstractState> worklist = new LinkedList<AbstractState>();
//Set<AbstractState> visited = new HashSet<AbstractState>();
worklist.add(art.getRoot());
//visited.addAll(worklist);
while (!worklist.isEmpty()) {
AbstractState curState = worklist.removeFirst();
String nodeName = curState.getIdentifier();
Map<String, String> properties = null;
if (curState == art.getRoot())
properties = startNode;
if (program.getStatement((RTLLabel)curState.getLocation()) instanceof RTLHalt)
properties = endNode;
StringBuilder nodeLabel = new StringBuilder();
nodeLabel.append(curState.getIdentifier());
gwriter.writeNode(nodeName, nodeLabel.toString(), properties);
for (Pair<CFAEdge, AbstractState> sPair : art.getChildren(curState)) {
AbstractState nextState = sPair.getRight();
//if (!visited.contains(nextState)) {
worklist.add(nextState);
//visited.add(nextState);
gwriter.writeEdge(nodeName, nextState.getIdentifier());
//}
}
}
gwriter.close();
} catch (IOException e) {
logger.error("Cannot write to output file", e);
return;
}
}
public void writeCallGraph(String filename, SetMultimap<Location, Location> callGraph) {
// Create dot file
GraphWriter gwriter = createGraphWriter(filename);
if (gwriter == null) return;
Set<Location> nodes = new HashSet<Location>();
logger.info("Writing callgraph to " + gwriter.getFilename());
try {
for (Map.Entry<Location, Location> e : callGraph.entries()) {
nodes.add(e.getKey());
nodes.add(e.getValue());
gwriter.writeEdge(e.getKey().toString(),
e.getValue().toString());
}
for (Location node : nodes) {
gwriter.writeNode(node.toString(), node.toString(), getNodeProperties(null, node));
}
gwriter.close();
} catch (IOException e) {
logger.error("Cannot write to output file", e);
return;
}
}
private GraphWriter createGraphWriter(String filename) {
try {
if (Options.graphML.getValue()) {
return new GraphMLWriter(filename);
} else {
return new GraphvizWriter(filename);
}
} catch (IOException e) {
logger.error("Cannot open output file!", e);
return null;
}
}
private Map<String,String> getNodeProperties(ControlFlowGraph cfg, Location loc) {
RTLStatement curStmt = program.getStatement(loc.getLabel());
Map<String,String> properties = new HashMap<String, String>();
if (curStmt != null) {
AbsoluteAddress curAddr = loc.getAddress();
if (program.isStub(curAddr) || program.getHarness().contains(curAddr)) {
properties.put("color", "lightgrey");
properties.put("fillcolor", "lightgrey");
}
if (program.getUnresolvedBranches().contains(loc.getLabel())) {
properties.put("fillcolor", "red");
}
if (mustLeaves.contains(loc.getLabel())) {
properties.put("fillcolor", "green");
}
if (loc.equals(cfg.getEntryPoint())) {
properties.put("color", "green");
properties.put("style", "filled,bold");
} else if (curStmt instanceof RTLHalt) {
properties.put("color", "orange");
properties.put("style", "filled,bold");
}
} else {
logger.info("No real statement for location " + loc);
}
return properties;
}
private void writeControlFlowGraph(ControlFlowGraph cfg, String filename, ReachedSet reached) {
// Create dot file
GraphWriter gwriter = createGraphWriter(filename);
if (gwriter == null) return;
try {
for (Location node : cfg.getNodes()) {
String nodeName = node.toString();
StringBuilder labelBuilder = new StringBuilder();
labelBuilder.append(nodeName);
if (reached != null) {
labelBuilder.append("\n");
if (reached.where(node).isEmpty()) {
logger.warn("No reached states for location " + node);
}
for (AbstractState a : reached.where(node)) {
labelBuilder.append(a.toString());
labelBuilder.append("\n");
}
}
gwriter.writeNode(nodeName, labelBuilder.toString(), getNodeProperties(cfg, node));
}
for (CFAEdge e : cfg.getEdges()) {
if (e.getKind() == null) logger.error("Null kind? " + e);
gwriter.writeEdge(e.getSource().toString(),
e.getTarget().toString(),
e.getTransformer().toString(),
e.getKind().equals(CFAEdge.Kind.MAY) ? Color.BLACK : Color.GREEN);
}
gwriter.close();
} catch (IOException e) {
logger.error("Cannot write to output file", e);
return;
}
}
private void writeAssemblyBBCFG(ControlFlowGraph cfg, String filename) {
// Create dot file
GraphWriter gwriter = createGraphWriter(filename);
if (gwriter == null) return;
TreeMap<Location, BasicBlock> blockMap = new TreeMap<Location, BasicBlock>();
blockMap.putAll(cfg.getBasicBlocks());
try {
//for (Map.Entry<Location, BasicBlock> entry : cfg.getBasicBlocks().entrySet()) {
for (Map.Entry<Location, BasicBlock> entry : blockMap.entrySet()) {
Location nodeLoc = entry.getKey();
BasicBlock bb = entry.getValue();
String nodeName = nodeLoc.toString();
StringBuilder labelBuilder = new StringBuilder();
String locLabel = program.getSymbolFor(nodeLoc.getAddress());
if (locLabel.length() > 20) locLabel = locLabel.substring(0, 20) + "...";
labelBuilder.append(locLabel).append("\\n");
for (Iterator<AbsoluteAddress> addrIt = bb.addressIterator(); addrIt.hasNext();) {
AbsoluteAddress curAddr = addrIt.next();
Instruction instr = program.getInstruction(curAddr);
if (instr != null) {
String instrString = program.getInstructionString(curAddr);
instrString = instrString.replace("\t", " ");
labelBuilder.append(instrString).append("\\l");
} else {
//labelBuilder.append(curAddr.toString() + "\\l");
}
}
gwriter.writeNode(nodeName, labelBuilder.toString(), getNodeProperties(cfg, nodeLoc));
}
for (CFAEdge e : cfg.getBasicBlockEdges()) {
if (e.getKind() == null) logger.error("Null kind? " + e);
Location sourceLoc = e.getSource();
Location targetLoc = e.getTarget();
RTLStatement stmt = (RTLStatement)e.getTransformer();
String label = null;
RTLLabel lastLoc = stmt.getLabel();
Instruction instr = program.getInstruction(lastLoc.getAddress());
boolean weak = false;
// Get the original instruction from the program, i.e., possibly a goto and not the converted assume
RTLStatement origStmt = program.getStatement(lastLoc);
if (origStmt instanceof RTLGoto) {
if (instr instanceof BranchInstruction) {
BranchInstruction bi = (BranchInstruction)instr;
if (bi.isConditional()) {
// If the assume in the edge has the same nextlabel as Goto, then it's the fall-through
label = stmt.getNextLabel().equals(origStmt.getNextLabel()) ? "F" : "T";
}
}
switch (((RTLGoto)origStmt).getType()) {
case CALL: case RETURN:
if (stmt.getNextLabel().equals(origStmt.getNextLabel())) {
label = "call return";
} else {
weak = true;
}
default: // nothing
}
}
// Only draw edges as weak if we generated fall-through edges
if (Options.procedureAbstraction.getValue() != 1)
weak = false;
gwriter.writeEdge(
sourceLoc.toString(),
targetLoc.toString(),
label,
e.getKind().equals(CFAEdge.Kind.MAY) ? Color.BLACK : Color.GREEN,
weak
);
}
gwriter.close();
} catch (IOException e) {
logger.error("Cannot write to output file", e);
return;
}
}
private void writeTopologicalBBCFG(ControlFlowGraph cfg, String filename) {
// Create dot file
GraphWriter gwriter = createGraphWriter(filename);
if (gwriter == null) return;
try {
for (Map.Entry<Location, BasicBlock> entry : cfg.getBasicBlocks().entrySet()) {
Location nodeLoc = entry.getKey();
String nodeName = nodeLoc.toString();
StringBuilder labelBuilder = new StringBuilder();
String locLabel = program.getSymbolFor(nodeLoc.getAddress());
if (locLabel.length() > 20) locLabel = locLabel.substring(0, 20) + "...";
labelBuilder.append(locLabel).append("\\n");
gwriter.writeNode(nodeName, labelBuilder.toString(), getNodeProperties(cfg, nodeLoc));
}
for (CFAEdge e : cfg.getBasicBlockEdges()) {
if (e.getKind() == null) logger.error("Null kind? " + e);
Location sourceLoc = e.getSource();
Location targetLoc = e.getTarget();
RTLStatement stmt = (RTLStatement)e.getTransformer();
String label = null;
RTLLabel lastLoc = stmt.getLabel();
Instruction instr = program.getInstruction(lastLoc.getAddress());
if (instr instanceof BranchInstruction) {
BranchInstruction bi = (BranchInstruction)instr;
if (bi.isConditional()) {
// Get the original goto from the program (not the converted assume)
RTLStatement rtlGoto = program.getStatement(lastLoc);
// If this is the fall-through edge, output F, otherwise T
//label = targetLoc.getAddress().equals(rtlGoto.getNextLabel().getAddress()) ? "F" : "T";
// If the assume in the edge has the same nextlabel as Goto, then it's the fall-through
label = stmt.getNextLabel().equals(rtlGoto.getNextLabel()) ? "F" : "T";
}
}
gwriter.writeEdge(
sourceLoc.toString(),
targetLoc.toString(),
label,
e.getKind().equals(CFAEdge.Kind.MAY) ? Color.BLACK : Color.GREEN
);
}
gwriter.close();
} catch (IOException e) {
logger.error("Cannot write to output file", e);
return;
}
}
}