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 }