Teuchos Package Browser (Single Doxygen Collection) Version of the Day
Loading...
Searching...
No Matches
Teuchos_stacktrace.cpp
Go to the documentation of this file.
1/*
2 Copyright (c) 2010, Ondrej Certik
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
7
8 * Redistributions of source code must retain the above copyright notice, this
9 list of conditions and the following disclaimer.
10 * Redistributions in binary form must reproduce the above copyright notice,
11 this list of conditions and the following disclaimer in the documentation
12 and/or other materials provided with the distribution.
13 * Neither the name of the Sandia Corporation nor the names of its contributors
14 may be used to endorse or promote products derived from this software without
15 specific prior written permission.
16
17 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
21 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27*/
28
29
31#include "Teuchos_RCP.hpp"
33
34
35#ifdef HAVE_TEUCHOS_STACKTRACE
36
37
38#include <string>
39#include <iostream>
40#include <fstream>
41
42// free() and abort() functions
43#include <cstdlib>
44
45// For handling variable number of arguments using va_start/va_end functions
46#include <cstdarg>
47
48// For registering SIGSEGV callbacks
49#include <csignal>
50
51
52// The following C headers are needed for some specific C functionality (see
53// the comments), which is not available in C++:
54
55// backtrace() function for retrieving the stacktrace
56#include <execinfo.h>
57
58// For demangling function names
59#include <cxxabi.h>
60
61#ifdef HAVE_TEUCHOS_LINK
62// For dl_iterate_phdr() functionality
63#include <link.h>
64#endif
65
66#ifdef HAVE_TEUCHOS_BFD
67// For bfd_* family of functions for loading debugging symbols from the binary
68// This is the only nonstandard header file and the binary needs to be linked
69// with "-lbfd".
70# include <bfd.h>
71#else
72typedef long long unsigned bfd_vma;
73#endif
74
75using Teuchos::RCP;
76using Teuchos::rcp;
77using Teuchos::null;
78
79namespace {
80
81/* This struct is used to pass information between
82 addr2str() and process_section().
83*/
84struct line_data {
85#ifdef HAVE_TEUCHOS_BFD
86 asymbol **symbol_table; /* Symbol table. */
87#endif
88 bfd_vma addr;
89 std::string filename;
90 std::string function_name;
91 unsigned int line;
92 int line_found;
93};
94
95
96/* Return if given char is whitespace or not. */
97bool is_whitespace_char(const char c)
98{
99 return c == ' ' || c == '\t';
100}
101
102
103/* Removes the leading whitespace from a string and returnes the new
104 * string.
105 */
106std::string remove_leading_whitespace(const std::string &str)
107{
108 if (str.length() && is_whitespace_char(str[0])) {
109 int first_nonwhitespace_index = 0;
110 for (int i = 0; i < static_cast<int>(str.length()); ++i) {
111 if (!is_whitespace_char(str[i])) {
112 first_nonwhitespace_index = i;
113 break;
114 }
115 }
116 return str.substr(first_nonwhitespace_index);
117 }
118 return str;
119}
120
121
122/* Reads the 'line_number'th line from the file filename. */
123std::string read_line_from_file(std::string filename, unsigned int line_number)
124{
125 std::ifstream in(filename.c_str());
126 if (!in.is_open()) {
127 return "";
128 }
129 if (line_number == 0) {
130 return "Line number must be positive";
131 }
132 unsigned int n = 0;
133 std::string line;
134 while (n < line_number) {
135 if (in.eof())
136 return "Line not found";
137 getline(in, line);
138 n += 1; // loop update
139 }
140 return line;
141}
142
143/* Demangles the function name if needed (if the 'name' is coming from C, it
144 doesn't have to be demangled, if it's coming from C++, it needs to be).
145
146 Makes sure that it ends with (), which is automatic in C++, but it has to be
147 added by hand in C.
148*/
149std::string demangle_function_name(std::string name)
150{
151 std::string s;
152
153 if (name.length() == 0) {
154 s = "??";
155 } else {
156 int status = 0;
157 char *d = 0;
158 d = abi::__cxa_demangle(name.c_str(), 0, 0, &status);
159 if (d) {
160 s = d;
161 free(d);
162 } else {
163 s = name + "()";
164 }
165 }
166
167 return s;
168}
169
170
171#ifdef HAVE_TEUCHOS_BFD
172
173
174/* Look for an address in a section. This is called via
175 bfd_map_over_sections over all sections in abfd.
176
177 If the correct line is found, store the result in 'data' and set
178 data->line_found, so that subsequent calls to process_section exit
179 immediately.
180*/
181void process_section(bfd *abfd, asection *section, void *_data)
182{
183 line_data *data = (line_data*)_data;
184 if (data->line_found) {
185 // If we already found the line, exit
186 return;
187 }
188 if ((bfd_get_section_flags(abfd, section) & SEC_ALLOC) == 0) {
189 return;
190 }
191
192 bfd_vma section_vma = bfd_get_section_vma(abfd, section);
193 if (data->addr < section_vma) {
194 // If the addr lies above the section, exit
195 return;
196 }
197
198 bfd_size_type section_size = bfd_section_size(abfd, section);
199 if (data->addr >= section_vma + section_size) {
200 // If the addr lies below the section, exit
201 return;
202 }
203
204 // Calculate the correct offset of our line in the section
205 bfd_vma offset = data->addr - section_vma - 1;
206
207 // Finds the line corresponding to the offset
208
209 const char *filename=NULL, *function_name=NULL;
210 data->line_found = bfd_find_nearest_line(abfd, section, data->symbol_table,
211 offset, &filename, &function_name, &data->line);
212
213 if (filename == NULL)
214 data->filename = "";
215 else
216 data->filename = filename;
217
218 if (function_name == NULL)
219 data->function_name = "";
220 else
221 data->function_name = function_name;
222}
223
224
225/* Loads the symbol table into 'data->symbol_table'. */
226int load_symbol_table(bfd *abfd, line_data *data)
227{
228 if ((bfd_get_file_flags(abfd) & HAS_SYMS) == 0)
229 // If we don't have any symbols, return
230 return 0;
231
232 void **symbol_table_ptr = reinterpret_cast<void **>(&data->symbol_table);
233 long n_symbols;
234 unsigned int symbol_size;
235 n_symbols = bfd_read_minisymbols(abfd, false, symbol_table_ptr, &symbol_size);
236 if (n_symbols == 0) {
237 // If the bfd_read_minisymbols() already allocated the table, we need
238 // to free it first:
239 if (data->symbol_table != NULL)
240 free(data->symbol_table);
241 // dynamic
242 n_symbols = bfd_read_minisymbols(abfd, true, symbol_table_ptr, &symbol_size);
243 }
244
245 if (n_symbols < 0) {
246 // bfd_read_minisymbols() failed
247 return 1;
248 }
249
250 return 0;
251}
252
253
254#endif // HAVE_TEUCHOS_BFD
255
256
257/* Returns a string of 2 lines for the function with address 'addr' in the file
258 'file_name'.
259
260 Example:
261
262 File "/home/ondrej/repos/rcp/src/Teuchos_RCP.hpp", line 428, in Teuchos::RCP<A>::assert_not_null() const
263 throw_null_ptr_error(typeName(*this));
264*/
265std::string addr2str(std::string file_name, bfd_vma addr)
266{
267#ifdef HAVE_TEUCHOS_BFD
268 // Initialize 'abfd' and do some sanity checks
269 bfd *abfd;
270 abfd = bfd_openr(file_name.c_str(), NULL);
271 if (abfd == NULL)
272 return "Cannot open the binary file '" + file_name + "'\n";
273 if (bfd_check_format(abfd, bfd_archive))
274 return "Cannot get addresses from the archive '" + file_name + "'\n";
275 char **matching;
276 if (!bfd_check_format_matches(abfd, bfd_object, &matching))
277 return "Unknown format of the binary file '" + file_name + "'\n";
278 line_data data;
279 data.addr = addr;
280 data.symbol_table = NULL;
281 data.line_found = false;
282 // This allocates the symbol_table:
283 if (load_symbol_table(abfd, &data) == 1)
284 return "Failed to load the symbol table from '" + file_name + "'\n";
285 // Loops over all sections and try to find the line
286 bfd_map_over_sections(abfd, process_section, &data);
287 // Deallocates the symbol table
288 if (data.symbol_table != NULL) free(data.symbol_table);
289 bfd_close(abfd);
290#else
291 line_data data;
292 data.line_found = 0;
293#endif
294
295 std::ostringstream s;
296 // Do the printing --- print as much information as we were able to
297 // find out
298 if (!data.line_found) {
299 // If we didn't find the line, at least print the address itself
300 s << " File unknown, address: 0x" << (long long unsigned int) addr;
301 } else {
302 std::string name = demangle_function_name(data.function_name);
303 if (data.filename.length() > 0) {
304 // Nicely format the filename + function name + line
305 s << " File \"" << data.filename << "\", line "
306 << data.line << ", in " << name;
307 const std::string line_text = remove_leading_whitespace(
308 read_line_from_file(data.filename, data.line));
309 if (line_text != "") {
310 s << "\n " << line_text;
311 }
312 } else {
313 // The file is unknown (and data.line == 0 in this case), so the
314 // only meaningful thing to print is the function name:
315 s << " File unknown, in " << name;
316 }
317 }
318 s << "\n";
319 return s.str();
320}
321
322struct match_data {
323 bfd_vma addr;
324
325 std::string filename;
326 bfd_vma addr_in_file;
327};
328
329
330#ifdef HAVE_TEUCHOS_LINK
331
332
333/* Tries to find the 'data.addr' in the current shared lib (as passed in
334 'info'). If it succeeds, returns (in the 'data') the full path to the shared
335 lib and the local address in the file.
336*/
337int shared_lib_callback(struct dl_phdr_info *info,
338 size_t size, void *_data)
339{
340 struct match_data *data = (struct match_data *)_data;
341 for (int i=0; i < info->dlpi_phnum; i++) {
342 if (info->dlpi_phdr[i].p_type == PT_LOAD) {
343 ElfW(Addr) min_addr = info->dlpi_addr + info->dlpi_phdr[i].p_vaddr;
344 ElfW(Addr) max_addr = min_addr + info->dlpi_phdr[i].p_memsz;
345 if ((data->addr >= min_addr) && (data->addr < max_addr)) {
346 data->filename = info->dlpi_name;
347 data->addr_in_file = data->addr - info->dlpi_addr;
348 // We found a match, return a non-zero value
349 return 1;
350 }
351 }
352 }
353 // We didn't find a match, return a zero value
354 return 0;
355}
356
357
358#endif // HAVE_TEUCHOS_LINK
359
360// Class for creating a safe C++ interface to the raw void** stacktrace
361// pointers, that we get from the backtrace() libc function. We make a copy of
362// the addresses, so the caller can free the memory. We use std::vector to
363// store the addresses internally, but this can be changed.
364class StacktraceAddresses {
365 std::vector<bfd_vma> stacktrace_buffer;
366 int impl_stacktrace_depth;
367public:
368 StacktraceAddresses(void *const *_stacktrace_buffer, int _size, int _impl_stacktrace_depth)
369 : impl_stacktrace_depth(_impl_stacktrace_depth)
370 {
371 for (int i=0; i < _size; i++)
372 stacktrace_buffer.push_back((bfd_vma) _stacktrace_buffer[i]);
373 }
374 bfd_vma get_address(int i) const {
375 return this->stacktrace_buffer[i];
376 }
377 int get_size() const {
378 return this->stacktrace_buffer.size();
379 }
380 int get_impl_stacktrace_depth() const {
381 return this->impl_stacktrace_depth;
382 }
383};
384
385
386/*
387 Returns a std::string with the stacktrace corresponding to the
388 list of addresses (of functions on the stack) in 'buffer'.
389
390 It converts addresses to filenames, line numbers, function names and the
391 line text.
392*/
393std::string stacktrace2str(const StacktraceAddresses &stacktrace_addresses)
394{
395 int stack_depth = stacktrace_addresses.get_size() - 1;
396
397 std::string full_stacktrace_str("Traceback (most recent call last):\n");
398
399#ifdef HAVE_TEUCHOS_BFD
400 bfd_init();
401#endif
402 // Loop over the stack
403 const int stack_depth_start = stack_depth;
404 const int stack_depth_end = stacktrace_addresses.get_impl_stacktrace_depth();
405 for (int i=stack_depth_start; i >= stack_depth_end; i--) {
406 // Iterate over all loaded shared libraries (see dl_iterate_phdr(3) -
407 // Linux man page for more documentation)
408 struct match_data match;
409 match.addr = stacktrace_addresses.get_address(i);
410#ifdef HAVE_TEUCHOS_BFD
411 if (dl_iterate_phdr(shared_lib_callback, &match) == 0)
412 return "dl_iterate_phdr() didn't find a match\n";
413#else
414 match.filename = "";
415 match.addr_in_file = match.addr;
416#endif
417
418 if (match.filename.length() > 0) {
419 // This happens for shared libraries (like /lib/libc.so.6, or any
420 // other shared library that the project uses). 'match.filename'
421 // then contains the full path to the .so library.
422 full_stacktrace_str += addr2str(match.filename, match.addr_in_file);
423 } else {
424 // The 'addr_in_file' is from the current executable binary, that
425 // one can find at '/proc/self/exe'. So we'll use that.
426 full_stacktrace_str += addr2str("/proc/self/exe", match.addr_in_file);
427 }
428 }
429
430 return full_stacktrace_str;
431}
432
433
434void loc_segfault_callback_print_stack(int sig_num)
435{
438 *out << "\nSegfault caught. Printing stacktrace:\n\n";
439 Teuchos::show_stacktrace();
440 *out << "\nDone. Exiting the program.\n";
441 // Deregister our abort callback:
442 signal(SIGABRT, SIG_DFL);
443 abort();
444}
445
446
447void loc_abort_callback_print_stack(int sig_num)
448{
451 *out << "\nAbort caught. Printing stacktrace:\n\n";
452 Teuchos::show_stacktrace();
453 *out << "\nDone.\n";
454}
455
456
457RCP<StacktraceAddresses> get_stacktrace_addresses(int impl_stacktrace_depth)
458{
459 const int STACKTRACE_ARRAY_SIZE = 100; // 2010/05/22: rabartl: Is this large enough?
460 void *stacktrace_array[STACKTRACE_ARRAY_SIZE];
461 const size_t stacktrace_size = backtrace(stacktrace_array,
462 STACKTRACE_ARRAY_SIZE);
463 return rcp(new StacktraceAddresses(stacktrace_array, stacktrace_size,
464 impl_stacktrace_depth+1));
465}
466
467
468RCP<StacktraceAddresses> last_stacktrace;
469
470} // Unnamed namespace
471
472
473// Public functions
474
475
476void Teuchos::store_stacktrace()
477{
478 const int impl_stacktrace_depth=1;
479 last_stacktrace = get_stacktrace_addresses(impl_stacktrace_depth);
480}
481
482
483std::string Teuchos::get_stored_stacktrace()
484{
485 if (last_stacktrace == null) {
486 return "";
487 }
488 else {
489 return stacktrace2str(*last_stacktrace);
490 }
491}
492
493
494std::string Teuchos::get_stacktrace(int impl_stacktrace_depth)
495{
496 RCP<StacktraceAddresses> addresses =
497 get_stacktrace_addresses(impl_stacktrace_depth+1);
498 return stacktrace2str(*addresses);
499}
500
501
502void Teuchos::show_stacktrace()
503{
506 const int impl_stacktrace_depth=1;
507 *out << Teuchos::get_stacktrace(impl_stacktrace_depth);
508}
509
510
511void Teuchos::print_stack_on_segfault()
512{
513 signal(SIGSEGV, loc_segfault_callback_print_stack);
514 signal(SIGABRT, loc_abort_callback_print_stack);
515}
516
517
518#endif // HAVE_TEUCHOS_STACKTRACE
519
Reference-counted pointer class and non-member templated function implementations.
Functions for returning stacktrace info (GCC only initially).
Smart reference counting pointer class for automatic garbage collection.
static RCP< FancyOStream > getDefaultOStream()
Get the default output stream object.
TEUCHOS_DEPRECATED RCP< T > rcp(T *p, Dealloc_T dealloc, bool owns_mem)
Deprecated.