44
|
1 module doodle.core.backtrace;
|
|
2
|
|
3 //
|
|
4 // Provides support for a readable backtrace on a program crash.
|
|
5 //
|
|
6 // Everything is private - you build this into a library and
|
|
7 // link to the library, and bingo (via static this).
|
|
8 //
|
|
9 // It works by registering a stacktrace handler with the runtime,
|
|
10 // which, unlike the default one, provides demangled symbols
|
|
11 // rather than just a list of addresses.
|
|
12 //
|
|
13
|
|
14 private {
|
|
15 import core.sys.posix.unistd;
|
|
16 import core.sys.posix.fcntl;
|
|
17 import core.sys.posix.sys.stat;
|
|
18 import core.sys.posix.sys.mman;
|
|
19
|
|
20 import core.stdc.string;
|
|
21 import core.stdc.signal;
|
|
22
|
|
23 import core.runtime;
|
|
24
|
|
25 import std.stdio;
|
|
26 import std.string;
|
|
27 import std.demangle;
|
|
28 import std.algorithm;
|
|
29
|
45
|
30 immutable int EI_NIDENT = 16;
|
44
|
31
|
|
32 immutable int SHT_SYMTAB = 2;
|
|
33 immutable int SHT_STRTAB = 3;
|
|
34
|
45
|
35 immutable int STT_FUNC = 2;
|
44
|
36
|
|
37 alias ushort Elf32_Half;
|
45
|
38 alias uint Elf32_Word;
|
|
39 alias uint Elf32_Addr;
|
|
40 alias uint Elf32_Off;
|
44
|
41 alias ushort Elf32_Section;
|
|
42
|
|
43 struct Elf32_Ehdr {
|
|
44 ubyte[EI_NIDENT] e_ident; /* Magic number and other info */
|
|
45 Elf32_Half e_type; /* Object file type */
|
|
46 Elf32_Half e_machine; /* Architecture */
|
|
47 Elf32_Word e_version; /* Object file version */
|
|
48 Elf32_Addr e_entry; /* Entry point virtual address */
|
|
49 Elf32_Off e_phoff; /* Program header table file offset */
|
|
50 Elf32_Off e_shoff; /* Section header table file offset */
|
|
51 Elf32_Word e_flags; /* Processor-specific flags */
|
|
52 Elf32_Half e_ehsize; /* ELF header size in bytes */
|
|
53 Elf32_Half e_phentsize; /* Program header table entry size */
|
|
54 Elf32_Half e_phnum; /* Program header table entry count */
|
|
55 Elf32_Half e_shentsize; /* Section header table entry size */
|
|
56 Elf32_Half e_shnum; /* Section header table entry count */
|
|
57 Elf32_Half e_shstrndx; /* Section header string table index */
|
|
58 };
|
|
59
|
|
60 struct Elf32_Shdr {
|
|
61 Elf32_Word sh_name; /* Section name (string tbl index) */
|
|
62 Elf32_Word sh_type; /* Section type */
|
|
63 Elf32_Word sh_flags; /* Section flags */
|
|
64 Elf32_Addr sh_addr; /* Section virtual addr at execution */
|
|
65 Elf32_Off sh_offset; /* Section file offset */
|
|
66 Elf32_Word sh_size; /* Section size in bytes */
|
|
67 Elf32_Word sh_link; /* Link to another section */
|
|
68 Elf32_Word sh_info; /* Additional section information */
|
|
69 Elf32_Word sh_addralign; /* Section alignment */
|
|
70 Elf32_Word sh_entsize; /* Entry size if section holds table */
|
|
71 };
|
|
72
|
|
73 struct Elf32_Sym {
|
|
74 Elf32_Word st_name; /* Symbol name (string tbl index) */
|
|
75 Elf32_Addr st_value; /* Symbol value */
|
|
76 Elf32_Word st_size; /* Symbol size */
|
|
77 ubyte st_info; /* Symbol type and binding */
|
|
78 ubyte st_other; /* Symbol visibility */
|
|
79 Elf32_Section st_shndx; /* Section index */
|
|
80 };
|
|
81
|
|
82 extern (C) int backtrace(void**, size_t);
|
|
83
|
|
84 struct Symbol {
|
|
85 this(void * a, size_t s, char * n) {
|
|
86 address = a;
|
|
87 size = s;
|
|
88 name = n;
|
|
89 }
|
|
90 void * address;
|
|
91 size_t size;
|
|
92 char * name;
|
|
93 void * end_address() { return address + size; }
|
|
94 };
|
|
95
|
|
96 // Newton-Raphson
|
|
97 Symbol nr_lookup(Symbol[] symbols, void * addr, int depth = 0) {
|
|
98 if (symbols.length == 0 ||
|
|
99 addr < symbols[0].address ||
|
|
100 addr >= symbols[$-1].end_address)
|
|
101 {
|
|
102 throw new Exception("Symbol not found");
|
|
103 }
|
|
104 else if (symbols.length == 1) {
|
|
105 return symbols[0];
|
|
106 }
|
|
107 else {
|
|
108 void * begin_addr = symbols[0].address;
|
|
109 void * end_addr = symbols[$-1].end_address;
|
|
110
|
|
111 int i = ((addr - begin_addr) * symbols.length) / (end_addr - begin_addr);
|
45
|
112 if (!(depth % 2) && i < symbols.length - 1) { ++i; } // Some wiggle to force convergence
|
44
|
113
|
|
114 if (addr < symbols[i].address) {
|
|
115 return nr_lookup(symbols[0..i], addr, depth + 1);
|
|
116 }
|
|
117 else {
|
|
118 return nr_lookup(symbols[i..$], addr, depth + 1);
|
|
119 }
|
|
120 }
|
|
121 }
|
|
122
|
|
123 int generate(void*[] addresses, scope int delegate(ref char[]) dg) {
|
|
124 static char[] get_exe() {
|
|
125 char buf[1024];
|
|
126 ssize_t s = readlink("/proc/self/exe", buf.ptr, buf.length);
|
|
127 if (s == -1) { throw new Exception(""); }
|
|
128 return buf[0..s];
|
|
129 }
|
|
130
|
|
131 int fd = open(toStringz(get_exe()), O_RDONLY);
|
|
132 if (fd == -1) { throw new Exception(""); }
|
|
133 scope(exit) close(fd);
|
|
134
|
|
135 stat_t st;
|
|
136 if (fstat(fd, &st) == -1) { throw new Exception(""); }
|
|
137
|
|
138 void * contents = mmap(null, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
|
|
139 if (!contents) { throw new Exception(""); }
|
|
140 scope(exit) munmap(contents, st.st_size);
|
|
141
|
|
142 Elf32_Ehdr * elf_hdr = cast(Elf32_Ehdr *)(contents);
|
|
143
|
|
144 Elf32_Shdr * sec_hdr = cast(Elf32_Shdr *)(contents + elf_hdr.e_shoff);
|
|
145
|
|
146 int symtab_idx = 0;
|
|
147 for (int i = 0; i < elf_hdr.e_shnum; ++i) {
|
|
148 if (sec_hdr[i].sh_type == SHT_SYMTAB) {
|
|
149 symtab_idx = i;
|
|
150 break;
|
|
151 }
|
|
152 }
|
|
153 if (symtab_idx == 0) { throw new Exception(""); }
|
|
154
|
|
155 int strtab_idx = sec_hdr[symtab_idx].sh_link;
|
|
156 if (strtab_idx == 0) { throw new Exception(""); } // No associated string table
|
|
157
|
|
158 if (sec_hdr[strtab_idx].sh_type != SHT_STRTAB) { throw new Exception(""); } // invalid string table
|
|
159
|
|
160 Elf32_Sym * sym_ent = cast(Elf32_Sym *)(contents + sec_hdr[symtab_idx].sh_offset);
|
|
161 char * strtab = cast(char *)(contents + sec_hdr[strtab_idx].sh_offset);
|
|
162 int num_syms = sec_hdr[symtab_idx].sh_size / sec_hdr[symtab_idx].sh_entsize;
|
|
163
|
|
164 if (num_syms == 0) { throw new Exception(""); } // No symbols
|
|
165
|
|
166 Symbol symbols[];
|
|
167
|
|
168 for (int i = 0; i < num_syms; ++i) {
|
|
169 if ((sym_ent[i].st_info & 0xf) != STT_FUNC) {
|
|
170 continue;
|
|
171 }
|
|
172
|
|
173 if (sym_ent[i].st_shndx == 0) {
|
|
174 continue;
|
|
175 }
|
|
176
|
|
177 void * address = cast(void *)(sym_ent[i].st_value); // inclusive
|
|
178 size_t size = sym_ent[i].st_size;
|
|
179 char * name = strtab + sym_ent[i].st_name;
|
|
180
|
|
181 symbols ~= Symbol(address, size, name);
|
|
182 }
|
|
183
|
|
184 sort!("a.address < b.address")(symbols);
|
|
185
|
|
186 int ret;
|
|
187 foreach (address; addresses) {
|
|
188 string str = format("[0x%0.8x] ", address);
|
|
189 try {
|
|
190 Symbol symbol = nr_lookup(symbols, address);
|
|
191 char[] s1 = symbol.name[0..strlen(symbol.name)];
|
|
192 str ~= format("%s", demangle(s1.idup));
|
|
193 }
|
|
194 catch (Exception e) {
|
|
195 str ~= "<unknown>";
|
|
196 }
|
|
197 char[] cstr = str.dup;
|
|
198 ret = dg(cstr);
|
|
199 if (ret) {
|
|
200 break;
|
|
201 }
|
|
202 }
|
|
203
|
|
204 return ret;
|
|
205 }
|
|
206
|
|
207
|
|
208 // signal handler for otherwise-fatal thread-specific signals
|
|
209 extern (C) void arghh(int sig) {
|
|
210 string name() {
|
|
211 switch (sig) {
|
|
212 case SIGSEGV: return "SIGSEGV";
|
|
213 case SIGFPE: return "SIGFPE";
|
|
214 case SIGILL: return "SIGILL";
|
|
215 case SIGABRT: return "SIGABRT";
|
|
216 default: return "";
|
|
217 }
|
|
218 }
|
|
219
|
|
220 throw new Error(format("Got signal %s %s", sig, name));
|
|
221 }
|
|
222
|
|
223 shared static this() {
|
|
224 // set up shared signal handlers for fatal thread-specific signals
|
|
225 //writeln("setting up shared signal handlers for ABRT, FPE, ILL, SEGV");
|
|
226 signal(SIGABRT, &arghh);
|
|
227 signal(SIGFPE, &arghh);
|
|
228 signal(SIGILL, &arghh);
|
|
229 signal(SIGSEGV, &arghh);
|
|
230 }
|
|
231
|
|
232 static this() {
|
|
233 // register our trace handler for each thread
|
|
234 //writeln("installing our traceHandler");
|
|
235 Runtime.traceHandler = &traceHandler;
|
|
236 }
|
|
237
|
|
238 // Captures backtrace info at the point of construction, stripping
|
|
239 // off the bits that concern itself and the ininteresting early stuff.
|
|
240 // Also provides opApply to traverse a nice text representation of the backtrace.
|
|
241 class TraceInfo : Throwable.TraceInfo {
|
|
242 void*[256] callstack;
|
|
243 int numframes;
|
|
244
|
|
245 this() {
|
|
246 numframes = backtrace(callstack.ptr, callstack.length);
|
|
247 }
|
|
248
|
|
249 override string toString() const {
|
|
250 return "Why does dmd require an override of this?";
|
|
251 }
|
|
252
|
|
253 override int opApply(scope int delegate(ref char[]) dg) {
|
|
254 return generate(callstack[4..numframes-5], dg);
|
|
255 }
|
|
256 }
|
|
257
|
|
258 Throwable.TraceInfo traceHandler(void * ptr = null) {
|
|
259 return new TraceInfo;
|
|
260 }
|
|
261 }
|