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