1 module tagion.tools.callstack.callstack; 2 import std.algorithm : each, map; 3 import std.array : array, join; 4 import std.conv; 5 import std.demangle; 6 import std.digest : toHexString; 7 import std.file : exists; 8 import std.format; 9 import std.getopt; 10 import std.json; 11 import std.path : setExtension; 12 import std.process : Config, execute; 13 import std.range : chain; 14 import std.regex; 15 import std.stdio; 16 import std.string : lineSplitter; 17 import std.uni : isWhite; 18 import tagion.tools.Basic; 19 void call_stack_dump(string exefile, string stacktrace) { 20 @safe static class Symbol { 21 immutable(string) filename; 22 immutable(string) mangle; 23 ulong addr; 24 this(string filename, string mangle) { 25 this.mangle = mangle; 26 this.filename = filename; 27 } 28 } 29 30 struct Code { 31 const(Symbol) symbol; 32 ulong offset; 33 ulong mem_addr; 34 } 35 36 alias Symbols = Symbol[const(char[])]; 37 // scope const(Code)[] backtrace; 38 Symbols symbols; 39 enum hex = 16; 40 const(Code[]) get_backtrace(string stacktrace) { 41 Code[] backtrace; 42 enum file_symbol = regex(`^([^\(]+)\(([^\)\+]+)?(?:\+0x([^\)]+))?\)\s+\[0x([0-9a-f]+)`); 43 scope file = File(stacktrace); // Open for reading 44 // uint call_level; 45 void add_symbol(const(char[]) str) @safe { 46 auto m = str.matchFirst(file_symbol); 47 48 auto mangle = m[2].idup; 49 // auto s=new Symbol(m[1].idup, m[2].idup, m[3].idup, m[4].idup, call_level); 50 if (mangle.length) { 51 auto file = m[1].idup; 52 const s = symbols.require(mangle, new Symbol(file, mangle)); 53 ulong offset; 54 if (m[3].length) { 55 offset = m[3].to!ulong(hex); 56 } 57 backtrace ~= const(Code)(s, offset, m[4].to!ulong(hex)); 58 } 59 } 60 61 file.byLine().each!(add_symbol); 62 return backtrace; 63 } 64 65 const backtrace = get_backtrace(stacktrace); 66 67 // scope code_sym=new Code[symbols.length]; 68 void obj_linedump(const(char[]) line) { 69 if (line.length) { 70 enum whiteregex = regex(`\s+`); 71 scope line_split = line.split(whiteregex); 72 if (line_split.length >= 7) { 73 const mangle = line_split[6]; 74 auto symbol = symbols.get(mangle, null); 75 if (symbol) { 76 symbol.addr = line_split[0].to!ulong(hex); 77 } 78 } 79 } 80 } 81 82 execute([ 83 "objdump", 84 "-T", 85 exefile, 86 ], 87 null, Config.init, uint.max) 88 .output 89 .lineSplitter 90 .each!obj_linedump; 91 92 writeln("Call stack"); 93 enum notfound = "??:?"; 94 95 static void code_write(const(Code) code) { 96 writefln("- %s %s", code.symbol.filename, code.symbol.mangle); 97 } 98 99 foreach (code; backtrace) { 100 if (code.symbol.addr == code.symbol.addr.init) { 101 code_write(code); 102 } 103 else { 104 static ulong actual_addr(const(Code) code) { 105 return code.symbol.addr + code.offset; 106 } 107 108 scope addr2line_log = execute([ 109 "addr2line", 110 "-e", 111 code.symbol.filename, 112 actual_addr(code).to!string(hex) 113 ], 114 null, Config.init, uint.max); 115 if (addr2line_log.output[0 .. notfound.length] == notfound) { 116 code_write(code); 117 } 118 else { 119 writef("%s", addr2line_log.output); 120 } 121 } 122 writefln("\t%s\n", demangle(code.symbol.mangle)); 123 } 124 } 125 126 mixin Main!_main; 127 128 enum backtrace_ext = "callstack"; 129 130 int _main(string[] args) { 131 immutable program = args[0]; 132 bool version_switch; 133 string exefile; 134 string call_stack_file; 135 auto main_args = getopt(args, 136 std.getopt.config.caseSensitive, 137 std.getopt.config.bundling, 138 "version", "display the version", &version_switch, // "gitlog:g", format("Git log file %s", git_log_json_file), &git_log_json_file, 139 "trace|t", "Name of callstack file", &call_stack_file, // "date|d", format("Recorde the date in the checkout default %s", set_date), &set_date 140 141 142 143 ); 144 145 void help() { 146 enum default_program = "tagionwave"; 147 defaultGetoptPrinter( 148 [ 149 // format("%s version %s", program, REVNO), 150 "Documentation: https://tagion.org/", 151 "", 152 "Usage:", 153 format("%s <exe-file> [-t <backtrace-file>]", program), 154 ].join("\n"), 155 main_args.options); 156 writefln([ 157 "", 158 "Ex.", 159 format("%s %s -t %s", 160 program, 161 default_program, 162 "backtrace".setExtension(backtrace_ext)), 163 "or", 164 format("%s %s # the default backtrace '%s'", 165 program, 166 default_program, 167 default_program.setExtension(backtrace_ext)), 168 ].join("\n")); 169 170 } 171 172 if (main_args.helpWanted) { 173 help; 174 return 0; 175 } 176 177 if (args.length <= 1) { 178 help; 179 return 1; 180 } 181 182 exefile = args[1]; 183 184 if (exefile.length is 0) { 185 help; 186 writeln("ERROR: Exe file missing"); 187 return 1; 188 } 189 190 if (!exefile.exists) { 191 help; 192 writefln("ERROR: Exe file '%s' not found", exefile); 193 return 2; 194 } 195 196 if (call_stack_file.length is 0) { 197 call_stack_file = exefile.setExtension(backtrace_ext); 198 } 199 200 if (!call_stack_file.exists) { 201 help; 202 writefln("ERROR: Call stack file '%s' not found", call_stack_file); 203 return 3; 204 } 205 206 call_stack_dump(exefile, call_stack_file); 207 return 0; 208 }