1 /** 2 Copyright: Copyright (c) 2017, Joakim Brännström. All rights reserved. 3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 partof: #SPC-llvm_hiwrap_debug-cfg_viewer 7 8 Manually tested to pass: 9 partof: #TST-llvm_hiwrap_debug_cfg_viewer_readfile 10 11 This file contains a simple program to view the CFG's for functions in a LLVM 12 IR bitcode file (.bc) or a c/c++ source file. 13 */ 14 module app; 15 16 import std.algorithm; 17 import std.format; 18 import std.getopt; 19 import std.path; 20 import std.stdio; 21 import std.typecons : Nullable; 22 import std.exception : collectException; 23 import std.process : execute; 24 25 int main(string[] args) { 26 import std.array : array; 27 import std.file : exists; 28 import std.range : drop; 29 30 bool help; 31 bool view_cfg; 32 bool view_cfg2; 33 bool dump_cfg; 34 bool dump_onlycfg; 35 bool dump_ir_as_txt; 36 string[] cflags; 37 GetoptResult help_info; 38 try { 39 // dfmt off 40 help_info = getopt(args, std.getopt.config.passThrough, 41 std.getopt.config.keepEndOfOptions, 42 "view-cfg", "View the CFG in GhostView", &view_cfg, 43 "view-cfg2", "View the CFG kind 2", &view_cfg2, 44 "dump-ir", "View the IR as text", &dump_ir_as_txt, 45 "dump-cfg", "Dump the cfg to a file as plantuml", &dump_cfg, 46 "dump-onlycfg", "Dump the cfg to a file as plantuml without the content of basic blocks", &dump_onlycfg, 47 ); 48 // dfmt on 49 help = help_info.helpWanted; 50 } 51 catch (Exception ex) { 52 help = true; 53 } 54 55 void printHelp() { 56 defaultGetoptPrinter(format("usage: %s FILE -- [CFLAGS]\n", 57 args[0].baseName), help_info.options); 58 } 59 60 if (help) { 61 printHelp; 62 return 0; 63 } else if (args.length <= 1) { 64 writeln("Missing the required option FILE"); 65 printHelp; 66 return 1; 67 } 68 69 cflags = args.find("--").drop(1).array(); 70 71 const auto infile = AbsolutePath(args[1]); 72 if (!infile.exists) { 73 writeln("File do not exist: ", infile); 74 return 1; 75 } 76 77 const auto irfile = makeIRFile(infile, cflags); 78 if (!irfile.hasValue) { 79 writeln("Unable to read/undestand input: ", infile); 80 return 1; 81 } 82 83 writeln("input LLVM IR: ", irfile.path); 84 85 if (view_cfg) 86 viewCFG(irfile); 87 if (dump_ir_as_txt) 88 viewIR(irfile); 89 if (view_cfg2) 90 viewCFG2(irfile); 91 if (dump_cfg || dump_onlycfg) 92 dumpCFG(irfile, dump_onlycfg); 93 94 return 0; 95 } 96 97 struct AbsolutePath { 98 this(string f) { 99 this.path = f.absolutePath; 100 } 101 102 string path; 103 alias path this; 104 } 105 106 struct IRFile { 107 bool hasValue; 108 AbsolutePath path; 109 alias path this; 110 private AbsolutePath tmpdir; 111 private bool remove_tmpdir; 112 113 @disable this(this); 114 115 ~this() nothrow { 116 import std.file : rmdirRecurse; 117 118 if (remove_tmpdir) { 119 try { 120 rmdirRecurse(tmpdir); 121 } 122 catch (Exception e) { 123 collectException(writeln("error: ", e.msg)); 124 } 125 } 126 } 127 } 128 129 IRFile makeIRFile(AbsolutePath p, string[] cflags) { 130 import std..string : toStringz; 131 132 if (p.extension == ".bc") { 133 return IRFile(true, p); 134 } else if (p.extension.among(".c", ".cxx", ".cpp")) { 135 import core.sys.posix.stdlib : mkdtemp; 136 137 char[] t = "cfg_viewer_XXXXXX".dup; 138 mkdtemp(t.ptr); 139 140 auto ir = AbsolutePath(buildPath(t, p.baseName.stripExtension ~ ".bc")); 141 142 try { 143 auto res = execute(["clang", "-emit-llvm", "-c", p, "-o", ir] ~ cflags); 144 if (res.status != 0) { 145 writeln(res.output); 146 return IRFile(); 147 } 148 149 writeln("creating temporary LLVM IR: ", ir); 150 return IRFile(true, ir, AbsolutePath(t.idup), true); 151 } 152 catch (Exception e) { 153 collectException(writeln("error: ", e.msg)); 154 } 155 } 156 157 return IRFile(); 158 } 159 160 /** 161 * partof: #TST-llvm_hiwrap_test_api_read_llvm_bc 162 */ 163 void viewCFG(ref const IRFile ir) { 164 import llvm_hiwrap; 165 166 auto ctx = Context.make; 167 168 auto modres = File(ir.path).readModule(ctx, ir.path); 169 if (!modres.isValid) { 170 writeln("error: unable to create a LLVM IR module from input. Diagnostic:"); 171 writefln("%( %s%)", modres.diagnostic); 172 return; 173 } 174 175 auto mod = modres.value; 176 177 foreach (v; mod.functions) { 178 llvm_hiwrap.viewCFG(v); 179 } 180 } 181 182 void viewCFG2(ref const IRFile ir) { 183 import std.conv; 184 import std.range; 185 import llvm_hiwrap; 186 187 import llvm_hiwrap.ast.tree; 188 189 struct MVisitor { 190 alias Self = ModuleVisitor!MVisitor; 191 size_t line; 192 193 void visit(ref Module n, ref Self self) { 194 writefln("ModuleID %s", n.identifier); 195 accept(n, self); 196 } 197 198 void visit(ref FunctionValue n, ref Self self) { 199 writefln("ID:%s name:%s BB:%s", n.asValue.id, n.asValue.name, n.countBasicBlocks); 200 accept(n, self); 201 } 202 203 void visit(ref EntryBasicBlock n, ref Self self) { 204 writefln("entryBlock ID:%s name:%s", n.asBasicBlock.id, n.asBasicBlock.name); 205 line = 0; 206 accept(n, self); 207 } 208 209 void visit(ref BasicBlock n, ref Self self) { 210 writefln("ID:%s name:%s", n.id, n.name); 211 line = 0; 212 accept(n, self); 213 } 214 215 void visit(ref InstructionValue n, ref Self self) { 216 writefln("%s %s: %s", line, n.opcode.to!string, n.asValue.spelling.toChar); 217 line++; 218 } 219 } 220 221 auto ctx = Context.make; 222 223 auto modres = File(ir.path).readModule(ctx, ir.path); 224 if (!modres.isValid) { 225 writeln("error: unable to create a LLVM IR module from input. Diagnostic:"); 226 writefln("%( %s%)", modres.diagnostic); 227 return; 228 } 229 230 auto mod = modres.value; 231 232 ModuleVisitor!MVisitor visitor; 233 visitor.visit(mod); 234 } 235 236 void viewIR(ref const IRFile ir) { 237 import llvm_hiwrap; 238 239 auto ctx = Context.make; 240 241 auto modres = File(ir.path).readModule(ctx, ir.path); 242 if (!modres.isValid) { 243 writeln("error: unable to create a LLVM IR module from input. Diagnostic:"); 244 writefln("%( %s%)", modres.diagnostic); 245 return; 246 } 247 248 auto mod = modres.value; 249 writeln(mod.toString); 250 } 251 252 void dumpCFG(ref const IRFile ir, bool dump_onlycfg) { 253 import std.array : Appender; 254 import std.ascii; 255 import std.conv; 256 import std.range; 257 import std.stdio; 258 import std.format; 259 import std..string; 260 import std.typecons : NullableRef; 261 import llvm_hiwrap; 262 263 import llvm_hiwrap.ast.tree; 264 265 struct BBLabel { 266 private size_t nextId; 267 private size_t[size_t] bbIdToGraphId; 268 269 string make(BasicBlock bb) { 270 if (auto result = bb.asValue.id in bbIdToGraphId) 271 return (*result).to!string; 272 273 bbIdToGraphId[bb.asValue.id] = nextId; 274 auto result = nextId++; 275 return result.to!string; 276 } 277 } 278 279 /** 280 * TODO because it uses baseName for the module it won't correctly work 281 * with multiple modules with the same baseName but residing in different 282 * directories. 283 */ 284 struct MVisitor { 285 alias Self = ModuleVisitor!MVisitor; 286 import std.container : Array; 287 288 this(File* f, bool dump_onlycfg) { 289 app = f; 290 no_bb_content = dump_onlycfg; 291 bbLabel = BBLabel.init; 292 } 293 294 private bool no_bb_content; 295 296 File* app; 297 Array!string parents; 298 299 // intermediate data for basic blocks when visiting a function. 300 // required to avoid infinite loops because the graph is not a DAG. 301 bool[size_t] visited_bbs; 302 string last_block; 303 304 BBLabel bbLabel; 305 306 string lastParent() { 307 return parents[$ - 1]; 308 } 309 310 void pushParent(string p) { 311 parents.insertBack(p); 312 } 313 314 void pushParent(const(char)[] p) { 315 parents.insertBack(p.idup); 316 } 317 318 void popParent() { 319 parents.removeBack; 320 } 321 322 void visit(ref Module n, ref Self self) { 323 import std.path; 324 325 pushParent(n.identifier.baseName); 326 scope (exit) 327 popParent; 328 329 app.writeln("@startuml"); 330 app.writefln("[*] --> %s", lastParent); 331 app.writefln("%s : Module", lastParent); 332 accept(n, self); 333 app.writeln("@enduml"); 334 } 335 336 void visit(ref FunctionValue n, ref Self self) { 337 auto parent = lastParent; 338 auto me = n.asValue.name; 339 pushParent(me); 340 scope (exit) 341 popParent; 342 343 app.writefln("%s : Function", me); 344 app.writefln("%s : BBs %s", me, n.countBasicBlocks); 345 app.writefln("%s --> %s", parent, me); 346 347 accept(n, self); 348 } 349 350 void visit(ref EntryBasicBlock n, ref Self self) { 351 auto parent = lastParent; 352 auto me = bbLabel.make(n.asBasicBlock).to!string; 353 last_block = me; 354 pushParent(me); 355 scope (exit) 356 popParent; 357 358 app.writefln("%s : EntryBasicBlock", me); 359 app.writefln("%s --> %s", parent, me); 360 361 visited_bbs[n.asBasicBlock.id] = true; 362 accept(n, self); 363 364 if (last_block.length != 0) 365 app.writefln("%s --> [*]", last_block); 366 } 367 368 void visit(ref BasicBlock n, ref Self self) { 369 auto parent = lastParent; 370 auto me = bbLabel.make(n).to!string; 371 last_block = me; 372 pushParent(me); 373 scope (exit) 374 popParent; 375 376 app.writefln("%s --> %s", parent, me); 377 378 if (n.id in visited_bbs) { 379 return; 380 } 381 382 app.writefln("%s : BasicBlock", me); 383 384 if (n.terminator.isNull || n.terminator.successors.length == 0) { 385 last_block = null; 386 app.writefln("%s --> [*]", me); 387 } 388 389 visited_bbs[n.id] = true; 390 accept(n, self); 391 } 392 393 void visit(ref InstructionValue n, ref Self self) { 394 if (no_bb_content) 395 return; 396 397 import std.algorithm : splitter; 398 399 auto msg = n.asValue.spelling; 400 foreach (l; msg.toChar.splitter(newline)) { 401 app.writefln("%s : %s", lastParent, l); 402 } 403 } 404 } 405 406 auto ctx = Context.make; 407 408 auto modres = File(ir.path).readModule(ctx, ir.path); 409 if (!modres.isValid) { 410 writeln("error: unable to create a LLVM IR module from input. Diagnostic:"); 411 writefln("%( %s%)", modres.diagnostic); 412 return; 413 } 414 415 auto mod = modres.value; 416 417 auto fout = File("dump.uml", "w"); 418 auto visitor = ModuleVisitor!(MVisitor)(MVisitor(&fout, dump_onlycfg)); 419 visitor.visit(mod); 420 }