Line data Source code
1 : // Copyright (c) 2011-2022 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program; if not, write to the Free Software
18 : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 :
20 : // self
21 : //
22 : #include "./exception.h"
23 :
24 : #include "./demangle.h"
25 :
26 :
27 : // C++ includes
28 : //
29 : #include <iostream>
30 : #include <memory>
31 : #include <vector>
32 :
33 :
34 : // C lib includes
35 : //
36 : #include <execinfo.h>
37 : #include <link.h>
38 : #include <unistd.h>
39 :
40 :
41 : /** \file
42 : * \brief Implementation of the stack trace.
43 : *
44 : * This file includes the functions that are used to gather the stack trace
45 : * from those very functions.
46 : *
47 : * By default, the stack trace is turned on in our exception library.
48 : * When turned on, we gather the information from the stack using
49 : * the functions defined in this file. This can be very helpful when
50 : * the code is able to gather the filenames and line numbers as it will
51 : * tell you exactly where each function failed.
52 : *
53 : * If you are running in release mode, it is likely that no line numbers
54 : * will be generated since they will be removed from your executables.
55 : */
56 :
57 :
58 :
59 : namespace libexcept
60 : {
61 :
62 :
63 :
64 : /** \brief Collect the raw stack trace in a list of strings.
65 : *
66 : * This function collects the current stack as a trace to log later.
67 : *
68 : * By default, the stack trace shows you a number of lines equal
69 : * to STACK_TRACE_DEPTH (which is 20 at time of writing). You may
70 : * specify another number to get more or less lines. Note that a
71 : * really large number will generally show you the entire stack since
72 : * a number larger than the number of function pointers on the stack
73 : * will return the entire stack.
74 : *
75 : * If you pass 0 as \p stack_trace_depth then the function returns an
76 : * empty list of strings.
77 : *
78 : * The \p stack_trace_depth parameter is silently clamped to 1,000, which
79 : * in most cases will be more than enough to get the entire stack trace
80 : * available.
81 : *
82 : * \note
83 : * This function is global so we can use it anywhere we'd like to get
84 : * a stack trace and not just in exceptions. Very practical in C++
85 : * to get a stack trace directly in a list of strings.
86 : *
87 : * \attention
88 : * Use the collect_stack_with_line_numbers() to get demangled function
89 : * names and line numbers. Note that this other function is considered
90 : * \em very slow so do not use it in a standard exception. Consider
91 : * using that other function only when debugging.
92 : *
93 : * \param[in] stack_trace_depth The number of lines to capture in our
94 : * stack trace.
95 : * \return The vector of strings with the stack trace.
96 : *
97 : * \sa collect_stack_trace_with_line_numbers()
98 : * \sa set_collect_stack()
99 : */
100 8 : stack_trace_t collect_stack_trace(int stack_trace_depth)
101 : {
102 8 : stack_trace_t stack_trace;
103 :
104 8 : if(stack_trace_depth > 0)
105 : {
106 14 : std::vector<void *> array;
107 7 : array.resize(std::max(stack_trace_depth, 1'000));
108 7 : int const size(backtrace(&array[0], stack_trace_depth));
109 :
110 : // save a copy of the system array in our class
111 : //
112 14 : std::unique_ptr<char *, decltype(&::free)> stack_string_list(backtrace_symbols(&array[0], size), &::free);
113 116 : for(int idx(0); idx < size; ++idx)
114 : {
115 109 : char const * stack_string(stack_string_list.get()[idx]);
116 109 : stack_trace.push_back(stack_string);
117 : }
118 : }
119 :
120 8 : return stack_trace;
121 : }
122 :
123 :
124 : /** \brief Collect the stack trace in a list of strings.
125 : *
126 : * This function collects the current stack as a trace including
127 : * the line numbers and demangled function names as available.
128 : *
129 : * The function also works like the collect_stack_trace() function.
130 : *
131 : * \note
132 : * The function makes use of the `addr2line` and `c++filt` command
133 : * line tools to convert the information. It is likely that if it
134 : * fails it means your system does not have those two tools installed.
135 : * Also, the addr2line requires the debug information in the libraries
136 : * and executables. Without that information, you will still get invalid
137 : * answers in your stacktrace.
138 : *
139 : * See also the libbacktrace library:
140 : * https://gcc.gnu.org/viewcvs/gcc/trunk/libbacktrace/
141 : *
142 : * \param[in] stack_trace_depth The number of lines to capture in our
143 : * stack trace.
144 : * \return The vector of strings with the stack trace.
145 : *
146 : * \sa collect_stack_trace()
147 : * \sa set_collect_stack()
148 : */
149 9 : stack_trace_t collect_stack_trace_with_line_numbers(int stack_trace_depth)
150 : {
151 9 : stack_trace_t stack_trace;
152 :
153 9 : if(stack_trace_depth > 0)
154 : {
155 18 : std::vector<void *> array;
156 9 : array.resize(stack_trace_depth);
157 9 : int const size(backtrace(&array[0], stack_trace_depth));
158 :
159 : // save a copy of the system array in our class
160 : //
161 18 : std::unique_ptr<char *, decltype(&::free)> stack_string_list(backtrace_symbols(&array[0], size), &::free);
162 124 : for(int idx(0); idx < size; ++idx)
163 : {
164 115 : char const * raw_stack_string(stack_string_list.get()[idx]);
165 :
166 : // the raw stack string is expected to be composed of:
167 : // <filename>(<function-name>+<offset>) [<addr>]
168 : //
169 : // we extract all those elements and use addr2line and c++filt
170 : // to convert that data to a usable function name and line number
171 : //
172 230 : std::string filename;
173 230 : std::string raw_function_name;
174 230 : std::string addr;
175 230 : std::string result;
176 :
177 : // go to end of filename
178 : //
179 115 : char const * s(raw_stack_string);
180 115 : char const * start(s);
181 8363 : while(*s != '('
182 4239 : && *s != '\0')
183 : {
184 4124 : ++s;
185 : }
186 115 : if(*s == '(')
187 : {
188 115 : filename = std::string(start, s - start);
189 115 : ++s;
190 :
191 115 : start = s;
192 2575 : while(*s != '+'
193 1230 : && *s != ')'
194 2575 : && *s != '\0')
195 : {
196 1230 : ++s;
197 : }
198 :
199 115 : if(*s == '+')
200 : {
201 115 : raw_function_name = std::string(start, s - start);
202 115 : ++s;
203 :
204 : // skip the offset
205 : //
206 1695 : while(*s != ')'
207 905 : && *s != '\0')
208 : {
209 790 : ++s;
210 : }
211 : }
212 : //else if(*s == ')') {} -- no function name
213 :
214 115 : if(*s == ')'
215 115 : && s[1] == ' '
216 115 : && s[2] == '['
217 115 : && s[3] == '0'
218 115 : && s[4] == 'x')
219 : {
220 115 : s += 5;
221 :
222 115 : start = s;
223 2875 : while(*s != ']'
224 1495 : && *s != '\0')
225 : {
226 1380 : ++s;
227 : }
228 :
229 115 : if(*s == ']')
230 : {
231 115 : addr = std::string(start, s - start);
232 :
233 115 : result.clear();
234 :
235 : // here we have our info, use it to get sane data
236 : //
237 : // TODO: look into whether we could rewrite the
238 : // eu-addr2line tool in C++ to avoid having to
239 : // run it (because that would be faster)
240 : //
241 : // I first used the "... -e <filename> ..." option
242 : // but with the --pid=..., it ought to be faster.
243 : //
244 : {
245 115 : std::string addr2line("eu-addr2line --pid="
246 230 : + std::to_string(getpid())
247 345 : + " "
248 345 : + addr);
249 230 : std::unique_ptr<FILE, decltype(&::pclose)> p(popen(addr2line.c_str(), "r"), &::pclose);
250 230 : std::string line;
251 : for(;;)
252 : {
253 4642 : int const c(fgetc(p.get()));
254 4642 : if(c == EOF)
255 : {
256 115 : break;
257 : }
258 4527 : if(c != '\n')
259 : {
260 4412 : line += c;
261 : }
262 4527 : }
263 115 : if(line == "??:0")
264 : {
265 : // this means addr2line failed to find the
266 : // debug info in your executable/.so
267 : // we fallback to the default which may help
268 : // if you have a version with debug info
269 : //
270 110 : result += filename
271 110 : + "["
272 165 : + addr
273 165 : + "]";
274 : }
275 : else
276 : {
277 60 : result = line;
278 : }
279 : }
280 :
281 115 : if(!raw_function_name.empty())
282 : {
283 27 : result += " in ";
284 27 : result += demangle_cpp_name(raw_function_name.c_str());
285 : }
286 : else
287 : {
288 88 : result += " <no function name>";
289 : }
290 : }
291 : }
292 : }
293 :
294 115 : if(result.empty())
295 : {
296 : // use the raw line as a fallback if we could not parse it
297 : // correctly or a conversion somehow fails...
298 : //
299 : stack_trace.push_back(std::string(raw_stack_string)); // LCOV_EXCL_LINE
300 : }
301 : else
302 : {
303 115 : stack_trace.push_back(result);
304 : }
305 : }
306 : }
307 :
308 9 : return stack_trace;
309 : }
310 :
311 :
312 :
313 :
314 6 : }
315 : // namespace libexcept
316 : // vim: ts=4 sw=4 et
|