1 /**
2  * This module helps to decide whether an appropriate execinfo implementation
3  * is available in the underling C runtime or in an external library. In the
4  * latter case exactly one of the following version identifiers should be
5  * set at the time of building druntime.
6  *
7  * Possible external execinfo version IDs based on possible backtrace output
8  * formats:
9  * $(TABLE
10  * $(THEAD Version ID, Backtrace format)
11  * $(TROW $(B ExtExecinfo_BSDFmt), 0x00000000 <_D6module4funcAFZv+0x78> at module)
12  * $(TROW $(B ExtExecinfo_DarwinFmt), 1  module    0x00000000 D6module4funcAFZv + 0)
13  * $(TROW $(B ExtExecinfo_GNUFmt), module(_D6module4funcAFZv) [0x00000000] $(B or)
14  * module(_D6module4funcAFZv+0x78) [0x00000000] $(B or) module(_D6module4funcAFZv-0x78) [0x00000000])
15  * $(TROW $(B ExtExecinfo_SolarisFmt), object'symbol+offset [pc])
16  * )
17  *
18  * The code also ensures that at most one format is selected (either by automatic
19  * C runtime detection or by $(B ExtExecinfo_) version IDs) and stores the
20  * corresponding values in $(LREF BacktraceFmt).
21  *
22  * With $(LREF getMangledSymbolName) we can get the original mangled symbol name
23  * from `backtrace_symbols` output of any supported version.
24  *
25  * Copyright: Copyright Digital Mars 2019.
26  * License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
27  * Source: $(DRUNTIMESRC core/internal/_execinfo.d)
28  */
29 
30 module core.internal.execinfo;
31 
32 version (OSX)
33     version = Darwin;
34 else version (iOS)
35     version = Darwin;
36 else version (TVOS)
37     version = Darwin;
38 else version (WatchOS)
39     version = Darwin;
40 
41 version (ExtExecinfo_BSDFmt)
42     version = _extExecinfo;
43 else version (ExtExecinfo_DarwinFmt)
44     version = _extExecinfo;
45 else version (ExtExecinfo_GNUFmt)
46     version = _extExecinfo;
47 else version (ExtExecinfo_SolarisFmt)
48     version = _extExecinfo;
49 
50 version (linux)
51 {
52     version (CRuntime_Glibc)
53         import _execinfo = core.sys.linux.execinfo;
54     else version (CRuntime_UClibc)
55         import _execinfo = core.sys.linux.execinfo;
56     else version (_extExecinfo)
57         import _execinfo = core.sys.linux.execinfo;
58 }
59 else version (Darwin)
60     import _execinfo = core.sys.darwin.execinfo;
61 else version (FreeBSD)
62     import _execinfo = core.sys.freebsd.execinfo;
63 else version (NetBSD)
64     import _execinfo = core.sys.netbsd.execinfo;
65 else version (OpenBSD)
66     import _execinfo = core.sys.openbsd.execinfo;
67 else version (DragonFlyBSD)
68     import _execinfo = core.sys.dragonflybsd.execinfo;
69 else version (Solaris)
70     import _execinfo = core.sys.solaris.execinfo;
71 
72 /// Indicates the availability of backtrace functions
73 enum bool hasExecinfo = is(_execinfo == module);
74 
75 static if (hasExecinfo)
76 {
77     /// Always points to the platform's backtrace function.
78     alias backtrace = _execinfo.backtrace;
79 
80     /// Always points to the platform's backtrace_symbols function. The
81     /// supported output format can be obtained by testing
82     /// $(LREF BacktraceFmt) enum values.
83     alias backtrace_symbols = _execinfo.backtrace_symbols;
84 
85     /// Always points to the platform's backtrace_symbols_fd function.
86     alias backtrace_symbols_fd = _execinfo.backtrace_symbols_fd;
87 }
88 
89 // Inspect possible backtrace formats
90 private
91 {
92     version (FreeBSD)
93         enum _BTFmt_BSD = true;
94     else version (DragonFlyBSD)
95         enum _BTFmt_BSD = true;
96     else version (NetBSD)
97         enum _BTFmt_BSD = true;
98     else version (OpenBSD)
99         enum _BTFmt_BSD = true;
100     else version (ExtExecinfo_BSDFmt)
101         enum _BTFmt_BSD = true;
102     else
103         enum _BTFmt_BSD = false;
104 
105     version (Darwin)
106         enum _BTFmt_Darwin = true;
107     else version (ExtExecinfo_DarwinFmt)
108         enum _BTFmt_Darwin = true;
109     else
110         enum _BTFmt_Darwin = false;
111 
112     version (CRuntime_Glibc)
113         enum _BTFmt_GNU = true;
114     else version (CRuntime_UClibc)
115         enum _BTFmt_GNU = true;
116     else version (ExtExecinfo_GNUFmt)
117         enum _BTFmt_GNU = true;
118     else
119         enum _BTFmt_GNU = false;
120 
121     version (Solaris)
122         enum _BTFmt_Solaris = true;
123     else version (ExtExecinfo_SolarisFmt)
124         enum _BTFmt_Solaris = true;
125     else
126         enum _BTFmt_Solaris = false;
127 }
128 
129 /**
130  * Indicates the backtrace format of the actual execinfo implementation.
131  * At most one of the values is allowed to be set to `true` the
132  * others should be `false`.
133  */
134 enum BacktraceFmt : bool
135 {
136     /// 0x00000000 <_D6module4funcAFZv+0x78> at module
137     BSD = _BTFmt_BSD,
138 
139     /// 1  module    0x00000000 D6module4funcAFZv + 0
140     Darwin = _BTFmt_Darwin,
141 
142     /// module(_D6module4funcAFZv) [0x00000000]
143     /// $(B or) module(_D6module4funcAFZv+0x78) [0x00000000]
144     /// $(B or) module(_D6module4funcAFZv-0x78) [0x00000000]
145     GNU = _BTFmt_GNU,
146 
147     /// object'symbol+offset [pc]
148     Solaris = _BTFmt_Solaris
149 }
150 
151 private bool atMostOneBTFmt()
152 {
153     size_t trueCnt = 0;
154 
155     foreach (fmt; __traits(allMembers, BacktraceFmt))
156         if (__traits(getMember, BacktraceFmt, fmt)) ++trueCnt;
157 
158     return trueCnt <= 1;
159 }
160 
161 static assert(atMostOneBTFmt, "Cannot be set more than one BacktraceFmt at the same time.");
162 
163 /**
164   * Takes a `backtrace_symbols` output and identifies the mangled symbol
165   * name in it. Optionally, also sets the begin and end indices of the symbol name in
166   * the input buffer.
167   *
168   * Params:
169   *  btBuf = The input buffer containing the output of `backtrace_symbols`
170   *  symBeg = Output parameter indexing the first character of the symbol's name
171   *  symEnd = Output parameter indexing the first character after the symbol's name
172   *
173   * Returns:
174   *  The name of the symbol
175   */
176 static if (hasExecinfo)
177 const(char)[] getMangledSymbolName(const(char)[] btBuf, out size_t symBeg,
178         out size_t symEnd) @nogc nothrow
179 {
180     static if (BacktraceFmt.Darwin)
181     {
182         for (size_t i = 0, n = 0; i < btBuf.length; i++)
183         {
184             if (' ' == btBuf[i])
185             {
186                 n++;
187                 while (i < btBuf.length && ' ' == btBuf[i])
188                     i++;
189                 if (3 > n)
190                     continue;
191 
192                 symBeg = i;
193                 while (i < btBuf.length && ' ' != btBuf[i])
194                     i++;
195                 symEnd = i;
196                 break;
197             }
198         }
199     }
200     else
201     {
202         static if (BacktraceFmt.GNU)
203         {
204             enum bChar = '(';
205             enum eChar = ')';
206         }
207         else static if (BacktraceFmt.BSD)
208         {
209             enum bChar = '<';
210             enum eChar = '>';
211         }
212         else static if (BacktraceFmt.Solaris)
213         {
214             enum bChar = '\'';
215             enum eChar = '+';
216         }
217 
218         foreach (i; 0 .. btBuf.length)
219         {
220             if (btBuf[i] == bChar)
221             {
222                 foreach (j; i+1 .. btBuf.length)
223                 {
224                     const e = btBuf[j];
225                     if (e == eChar || e == '+' || e == '-')
226                     {
227                         symBeg = i + 1;
228                         symEnd = j;
229                         break;
230                     }
231                 }
232                 break;
233             }
234         }
235     }
236 
237     assert(symBeg <= symEnd);
238     assert(symEnd < btBuf.length);
239 
240     return btBuf[symBeg .. symEnd];
241 }
242 
243 /// ditto
244 static if (hasExecinfo)
245 const(char)[] getMangledSymbolName(const(char)[] btBuf) @nogc nothrow
246 {
247     size_t symBeg, symEnd;
248     return getMangledSymbolName(btBuf, symBeg, symEnd);
249 }
250 
251 @nogc nothrow unittest
252 {
253     size_t symBeg, symEnd;
254 
255     static if (BacktraceFmt.BSD)
256     {
257         enum bufBSD = "0x00000000 <_D6module4funcAFZv+0x78> at module";
258         auto resBSD = getMangledSymbolName(bufBSD, symBeg, symEnd);
259         assert("_D6module4funcAFZv" == resBSD);
260         assert(12 == symBeg);
261         assert(30 == symEnd);
262     }
263     else static if (BacktraceFmt.Darwin)
264     {
265         enum bufDarwin = "1  module    0x00000000 D6module4funcAFZv + 0";
266         auto resDarwin = getMangledSymbolName(bufDarwin, symBeg, symEnd);
267         assert("D6module4funcAFZv" == resDarwin);
268         assert(24 == symBeg);
269         assert(41 == symEnd);
270     }
271     else static if (BacktraceFmt.GNU)
272     {
273         enum bufGNU0 = "module(_D6module4funcAFZv) [0x00000000]";
274         auto resGNU0 = getMangledSymbolName(bufGNU0, symBeg, symEnd);
275         assert("_D6module4funcAFZv" == resGNU0);
276         assert(7 == symBeg);
277         assert(25 == symEnd);
278 
279         enum bufGNU1 = "module(_D6module4funcAFZv+0x78) [0x00000000]";
280         auto resGNU1 = getMangledSymbolName(bufGNU1, symBeg, symEnd);
281         assert("_D6module4funcAFZv" == resGNU1);
282         assert(7 == symBeg);
283         assert(25 == symEnd);
284 
285         enum bufGNU2 = "/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main-0x78) [0x00000000]";
286         auto resGNU2 = getMangledSymbolName(bufGNU2, symBeg, symEnd);
287         assert("__libc_start_main" == resGNU2);
288         assert(32 == symBeg);
289         assert(49 == symEnd);
290     }
291     else static if (BacktraceFmt.Solaris)
292     {
293         enum bufSolaris = "object'symbol+offset [pc]";
294         auto resSolaris = getMangledSymbolName(bufSolaris, symBeg, symEnd);
295         assert("symbol" == resSolaris);
296         assert(7 == symBeg);
297         assert(13 == symEnd);
298     }
299     else
300         assert(!__traits(compiles, getMangledSymbolName));
301 }