├── Makefile ├── README.md ├── bin ├── examples └── ir.ll ├── google92d9ec43886c0107.html ├── images └── src └── visualizer.cpp /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | g++ -std=c++11 src/visualizer.cpp -o bin/visualizer.out 3 | 4 | example: 5 | g++ -std=c++11 src/visualizer.cpp -o bin/visualizer.out 6 | bin/visualizer.out examples/ir.ll examples/ir.dot 7 | dot examples/ir.dot -Tpng -o images/ir.png 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LLVM-Metadata-Visualizer 2 | This project will help you to visualize LLVM metadata dumped by front-end like Clang. 3 | 4 | Metadata node emitted in IR file usually refers to other metadata nodes and this may make it hard to comprehend the information. This tool will take a LLVM IR file, parse it to find metadata nodes and dump a dot file which will have a graphical representation of whole metadata. Each metadata node present in the file will be a vertex in the graph. An undirected edge connects two nodes if one of them refers to the other. Vertex in the graph tries to depict as detailed information as possible in the form of a table where each row is an attribute present in the metadata node. 5 | 6 | Please report issues. 7 | 8 | # Pre-requisites 9 | * It has just one make file so Linux! (or Windows if you can run makefile with cygwin or so) 10 | * GCC 7.2/Clang 5 (for improved regex support) 11 | * `dot` program (to get a .png file) 12 | 13 | # How to use the tool? 14 | * `make all` will give you an executable called `visualizer` in `bin/` 15 | * Invoke it as 16 | `bin/visualizer ` 17 | * Next, use the standard `dot` program to get a .png file like 18 | `dot -Tpng -o ` 19 | 20 | if you think you have the right setup, go ahead and fire `make example` to experience quickly. 21 | Image file will be present in `images` folder. 22 | 23 | # Example 24 | The following C code 25 | 26 | ```C++ 27 | void foo() { 28 | int X = 21; 29 | int Y = 22; 30 | { 31 | int Z = 23; 32 | Z = X; 33 | } 34 | X = Y; 35 | } 36 | ``` 37 | when compiled to LLVM IR with `-g` will look like 38 | 39 | ```llvm 40 | ; ModuleID = 'a.cpp' 41 | target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" 42 | target triple = "x86_64-pc-linux-gnu" 43 | 44 | %"class.std::ios_base::Init" = type { i8 } 45 | 46 | @_ZStL8__ioinit = internal global %"class.std::ios_base::Init" zeroinitializer, align 1 47 | @__dso_handle = external global i8 48 | @llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] [{ i32, void ()*, i8* } { i32 65535, void ()* @_GLOBAL__sub_I_a.cpp, i8* null }] 49 | ; Function Attrs: uwtable define internal void @__cxx_global_var_init() #0 section ".text.startup" !dbg !37 { 50 | call void @_ZNSt8ios_base4InitC1Ev(%"class.std::ios_base::Init"* @_ZStL8__ioinit), !dbg !410 %1 = call i32 @__cxa_atexit(void (i8*)* bitcast (void (%"class.std::ios_base::Init"*)* @_ZNSt8ios_base4InitD1Ev to void (i8*)*), i8* getelementptr inbounds (%"class.std::ios_base::Init", %"class.std::ios_base::Init"* @_ZStL8__ioinit, i32 0, i32 0), i8* @__dso_handle) #2, !dbg !411 51 | ret void, !dbg !410 } 52 | declare void @_ZNSt8ios_base4InitC1Ev(%"class.std::ios_base::Init"*) #1 53 | 54 | declare void @_ZNSt8ios_base4InitD1Ev(%"class.std::ios_base::Init"*) #1 55 | ... 56 | !0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !1, producer: "clang version 3.8.0-2ubuntu4 (tags/RELEASE_380/final)", isOptimized: false, runtimeVersion: 0, emissionKind: 1, enums: !2, retainedTypes: !3, subprograms: !36, globals: !46, imports: !48) 57 | !1 = !DIFile(filename: "a.cpp", directory: "/home/madhura/visualizer") 58 | !2 = !{} 59 | !3 = !{!4, !6, !8, !16, !17, !19, !23} 60 | !4 = !DICompositeType(tag: DW_TAG_structure_type, file: !5, line: 82, size: 64, align: 32, flags: DIFlagFwdDecl, identifier: "_ZTS11__mbstate_t") 61 | !5 = !DIFile(filename: "/usr/include/wchar.h", directory: "/home/madhura/visualizer") 62 | !6 = !DICompositeType(tag: DW_TAG_structure_type, name: "_IO_FILE", file: !7, line: 44, flags: DIFlagFwdDecl, identifier: "_ZTS8_IO_FILE") 63 | !7 = !DIFile(filename: "/usr/include/stdio.h", directory: "/home/madhura/visualizer") 64 | !8 = !DICompositeType(tag: DW_TAG_structure_type, name: "__va_list_tag", file: !1, size: 192, align: 64, elements: !9, identifier: "_ZTS13__va_list_tag") 65 | !9 = !{!10, !12, !13, !15} 66 | !10 = !DIDerivedType(tag: DW_TAG_member, name: "gp_offset", scope: !"_ZTS13__va_list_tag", file: !1, baseType: !11, size: 32, align: 32) 67 | 68 | ``` 69 | (The IR is truncated for demonstration purpose) 70 | 71 | This tool will give you the following picture. 72 | ![Image description](https://user-images.githubusercontent.com/4083456/33067905-a0085b78-ced5-11e7-8295-f1fef3648c0f.png) 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /bin: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/ir.ll: -------------------------------------------------------------------------------- 1 | ; Function Attrs: nounwind ssp uwtable 2 | define void @foo() #0 !dbg !4 { 3 | entry: 4 | %X = alloca i32, align 4 5 | %Y = alloca i32, align 4 6 | %Z = alloca i32, align 4 7 | call void @llvm.dbg.declare(metadata i32* %X, metadata !11, metadata !13), !dbg !14 8 | store i32 21, i32* %X, align 4, !dbg !14 9 | call void @llvm.dbg.declare(metadata i32* %Y, metadata !15, metadata !13), !dbg !16 10 | store i32 22, i32* %Y, align 4, !dbg !16 11 | call void @llvm.dbg.declare(metadata i32* %Z, metadata !17, metadata !13), !dbg !19 12 | store i32 23, i32* %Z, align 4, !dbg !19 13 | %0 = load i32, i32* %X, align 4, !dbg !20 14 | store i32 %0, i32* %Z, align 4, !dbg !21 15 | %1 = load i32, i32* %Y, align 4, !dbg !22 16 | store i32 %1, i32* %X, align 4, !dbg !23 17 | ret void, !dbg !24 18 | } 19 | 20 | ; Function Attrs: nounwind readnone 21 | declare void @llvm.dbg.declare(metadata, metadata, metadata) #1 22 | 23 | attributes #0 = { nounwind ssp uwtable "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" } 24 | attributes #1 = { nounwind readnone } 25 | 26 | !llvm.dbg.cu = !{!0} 27 | !llvm.module.flags = !{!7, !8, !9} 28 | !llvm.ident = !{!10} 29 | 30 | !0 = !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 3.7.0 (trunk 231150) (llvm/trunk 231154)", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, retainedTypes: !2, subprograms: !3, globals: !2, imports: !2) 31 | !1 = !DIFile(filename: "/dev/stdin", directory: "/Users/dexonsmith/data/llvm/debug-info") 32 | !2 = !{} 33 | !3 = !{!4} 34 | !4 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 1, type: !5, isLocal: false, isDefinition: true, scopeLine: 1, isOptimized: false, variables: !2) 35 | !5 = !DISubroutineType(types: !6) 36 | !6 = !{null} 37 | !7 = !{i32 2, !"Dwarf Version", i32 2} 38 | !8 = !{i32 2, !"Debug Info Version", i32 3} 39 | !9 = !{i32 1, !"PIC Level", i32 2} 40 | !10 = !{!"clang version 3.7.0 (trunk 231150) (llvm/trunk 231154)"} 41 | !11 = !DILocalVariable(name: "X", scope: !4, file: !1, line: 2, type: !12) 42 | !12 = !DIBasicType(name: "int", size: 32, align: 32, encoding: DW_ATE_signed) 43 | !13 = !DIExpression() 44 | !14 = !DILocation(line: 2, column: 9, scope: !4) 45 | !15 = !DILocalVariable(name: "Y", scope: !4, file: !1, line: 3, type: !12) 46 | !16 = !DILocation(line: 3, column: 9, scope: !4) 47 | !17 = !DILocalVariable(name: "Z", scope: !18, file: !1, line: 5, type: !12) 48 | !18 = distinct !DILexicalBlock(scope: !4, file: !1, line: 4, column: 5) 49 | !19 = !DILocation(line: 5, column: 11, scope: !18) 50 | !20 = !DILocation(line: 6, column: 11, scope: !18) 51 | !21 = !DILocation(line: 6, column: 9, scope: !18) 52 | !22 = !DILocation(line: 8, column: 9, scope: !4) 53 | !23 = !DILocation(line: 8, column: 7, scope: !4) 54 | !24 = !DILocation(line: 9, column: 3, scope: !4) 55 | -------------------------------------------------------------------------------- /google92d9ec43886c0107.html: -------------------------------------------------------------------------------- 1 | google-site-verification: google92d9ec43886c0107.html -------------------------------------------------------------------------------- /images: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/visualizer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | bool isSupported(string s) 9 | { 10 | if (s.find("DILocation") != string::npos || \ 11 | s.find("DILexicalBlock") != string::npos || \ 12 | s.find("DILocalVariable") != string::npos || \ 13 | s.find("DIBasicType") != string::npos || \ 14 | s.find("DICompositeType") != string::npos || \ 15 | s.find("DIDerivedType") != string::npos || \ 16 | s.find("DISubprogram") != string::npos) return true; 17 | else return false; 18 | } 19 | 20 | string emitHTMLTitle(string sourceNode, string s) 21 | { 22 | assert(isSupported(s)); 23 | 24 | std::regex attributes(R"(([a-zA-Z0-9_! ]+)[(]{1}(.+)[)]{1})"); 25 | std::regex split(R"((\w+)[:]{1}([a-zA-Z0-9!""_ ]+))"); 26 | std::sregex_iterator end; 27 | string tableString = "", tableTitle=""; 28 | 29 | std::sregex_iterator iter1(s.begin(), s.end(), attributes); 30 | while(iter1 != end) 31 | { 32 | if (iter1->size() > 1) { 33 | tableTitle = (*iter1)[1]; 34 | tableTitle = tableTitle.erase(0, 1); 35 | tableString += "" + ""; 36 | 37 | std::string dest = (*iter1)[2]; 38 | std::sregex_iterator iter2(dest.begin(), dest.end(), split); 39 | while (iter2 != end) { 40 | if (iter2->size() > 1) { 41 | tableString += ""; 42 | tableString += ""; 43 | tableString += ""; 44 | tableString += ""; 45 | } 46 | ++iter2; 47 | } 48 | } 49 | ++iter1; 50 | } 51 | tableString += "
" + sourceNode + "" + tableTitle + "
"; tableString += (*iter2)[1]; tableString += ""; tableString += (*iter2)[2]; tableString += "
"; 52 | return tableString; 53 | } 54 | 55 | int main(int argc, char** argv) 56 | { 57 | std::regex e(R"(^!\d+)"); 58 | std::ifstream infile(argv[1]); 59 | std::ofstream outfile(argv[2]); 60 | std::string line; 61 | std::string strippedLine, sourceNode; 62 | int number = 0; 63 | 64 | if (argc < 3) { 65 | cout << "You seemed to miss an argument. Syntax is \"filename.out IRfileName.ll DotFile.dot\"" << endl; 66 | exit(0); 67 | } 68 | 69 | if (!infile) { 70 | cout << "File not found.\n"; 71 | exit(1); 72 | } else { 73 | outfile << "graph IRGraph {\n"; 74 | } 75 | 76 | while (getline(infile, line)) { 77 | std::sregex_iterator iter(line.begin(), line.end(), e); 78 | std::sregex_iterator end; 79 | 80 | if (iter != end) { 81 | sourceNode = (*iter)[0]; 82 | strippedLine = sourceNode.erase(0, 1); 83 | 84 | std::size_t i = line.find("="); 85 | std::string sub = line.substr(i+1); 86 | if (isSupported(sub)) { 87 | outfile << strippedLine << " [label=<" << emitHTMLTitle(sourceNode, sub) <<">]\n"; 88 | } else { 89 | outfile << strippedLine << " [label=<" << line << "
>]" << endl; 90 | } 91 | 92 | // regex to get pointees. 93 | std::regex vertices(R"(!\d+)"); 94 | std::sregex_iterator iter1(sub.begin(), sub.end(), vertices); 95 | 96 | while(iter1 != end) { 97 | for(unsigned i = 0; i < iter1->size(); ++i) { 98 | string dest = (*iter1)[i]; 99 | outfile << sourceNode << " -- " << dest.erase(0, 1) << ";" << endl; 100 | } 101 | ++iter1; 102 | } 103 | } else { 104 | continue; 105 | } 106 | } 107 | outfile << "}" << endl; 108 | return 0; 109 | } 110 | --------------------------------------------------------------------------------