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 }