1 // The libMesh Finite Element Library.
2 // Copyright (C) 2002-2020 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3 
4 // This library is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU Lesser General Public
6 // License as published by the Free Software Foundation; either
7 // version 2.1 of the License, or (at your option) any later version.
8 
9 // This library is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 // Lesser General Public License for more details.
13 
14 // You should have received a copy of the GNU Lesser General Public
15 // License along with this library; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17 
18 // Code partially copyright Edd Dawson 2007
19 //
20 // Boost Software License - Version 1.0 - August 17th, 2003
21 //
22 // Permission is hereby granted, free of charge, to any person or organization
23 // obtaining a copy of the software and accompanying documentation covered by
24 // this license (the "Software") to use, reproduce, display, distribute,
25 // execute, and transmit the Software, and to prepare derivative works of the
26 // Software, and to permit third-parties to whom the Software is furnished to
27 // do so, all subject to the following:
28 //
29 // The copyright notices in the Software and this entire statement, including
30 // the above license grant, this restriction and the following disclaimer,
31 // must be included in all copies of the Software, in whole or in part, and
32 // all derivative works of the Software, unless such copies or derivative
33 // works are solely in the form of machine-executable object code generated by
34 // a source language processor.
35 //
36 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
37 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
38 // FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
39 // SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
40 // FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
41 // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
42 // DEALINGS IN THE SOFTWARE.
43 
44 
45 #include "libmesh/libmesh_config.h"
46 #include "libmesh/print_trace.h"
47 #include "libmesh/libmesh.h"
48 
49 #include <unistd.h>  // needed for getpid()
50 #include <fstream>
51 #include <sstream>
52 #include <string>
53 #include <cstdio> // std::remove
54 #include <cstdlib> // std::system
55 #include <sys/types.h> // pid_t
56 
57 #if defined(LIBMESH_HAVE_GLIBC_BACKTRACE)
58 #include <execinfo.h>
59 #endif
60 
61 #if defined(LIBMESH_HAVE_GCC_ABI_DEMANGLE)
62 #include <cxxabi.h>
63 #endif
64 
65 // Anonymous namespace for print_trace() helper functions
66 namespace
67 {
68 
69 // process_trace() is a helper function used by
70 // libMesh::print_trace().  It is only available if configure
71 // determined your compiler supports backtrace(), which is a GLIBC
72 // extension.
73 #if defined(LIBMESH_HAVE_GLIBC_BACKTRACE)
process_trace(const char * name)74 std::string process_trace(const char * name)
75 {
76   std::string fullname = name;
77   std::string saved_begin, saved_end;
78   size_t namestart, nameend;
79 
80   // The Apple backtrace function returns more information than the Linux version.
81   // We need to pass only the function name to the demangler or it won't decode it for us.
82   //
83   // lineno: stackframeno address functionname + offset
84 
85 #ifdef __APPLE__
86   namestart = fullname.find("0x");
87   if (namestart != std::string::npos)
88     {
89       namestart = fullname.find(' ', namestart) + 1;
90       saved_begin = fullname.substr(0, namestart);
91     }
92   else
93     namestart = 0;
94   nameend = fullname.find('+');
95   if (nameend == std::string::npos ||
96       nameend <= namestart)
97     nameend = fullname.size();
98   else
99     {
100       nameend -= 1;
101       saved_end = fullname.substr(nameend, fullname.length());
102     }
103 #else
104   namestart = fullname.find('(');
105   if (namestart == std::string::npos)
106     return fullname;
107   else
108     namestart++;
109   nameend = fullname.find('+');
110   if (nameend == std::string::npos ||
111       nameend <= namestart)
112     return fullname;
113 #endif
114 
115   std::string type_name = fullname.substr(namestart, nameend - namestart);
116 
117   // Try to demangle now
118   return saved_begin + libMesh::demangle(type_name.c_str()) + saved_end;
119 }
120 #endif
121 
122 
123 
124 // gdb_backtrace() is used by libMesh::print_trace() to try and get a
125 // "better" backtrace than what the backtrace() function provides.
126 // GDB backtraces are a bit slower, but they provide line numbers in
127 // source code, a really helpful feature when debugging something...
gdb_backtrace(std::ostream & out_stream)128 bool gdb_backtrace(std::ostream & out_stream)
129 {
130 #ifdef LIBMESH_GDB_COMMAND
131   // Eventual return value, true if gdb succeeds, false otherwise.
132   bool success = true;
133 
134   // The system() call does not allow us to redirect the output to a
135   // C++ ostream, so we redirect gdb's output to a (known) temporary
136   // file, and then send output that to the user's stream.
137   char temp_file[] = "temp_print_trace.XXXXXX";
138   int fd = mkstemp(temp_file);
139 
140   // If mkstemp fails, we failed.
141   if (fd == -1)
142     success = false;
143   else
144     {
145       // Run gdb using a system() call, redirecting the output to our
146       // temporary file.
147       pid_t this_pid = getpid();
148 
149       int exit_status = 1;
150 
151       libmesh_try
152         {
153           std::string gdb_command =
154             libMesh::command_line_value("gdb",std::string(LIBMESH_GDB_COMMAND));
155 
156           std::ostringstream command;
157           command << gdb_command
158                   << " -p "
159                   << this_pid
160                   << " -batch -ex bt -ex detach 2>/dev/null 1>"
161                   << temp_file;
162           exit_status = std::system(command.str().c_str());
163         }
164       libmesh_catch (...)
165         {
166           std::cerr << "Unable to run gdb" << std::endl;
167         }
168 
169       // If we can open the temp_file, the file is not empty, and the
170       // exit status from the system call is 0, we'll assume that gdb
171       // worked, and copy the file's contents to the user's requested
172       // stream.  This rdbuf() thing is apparently how you do
173       // this... Otherwise, report failure.
174       std::ifstream fin(temp_file);
175       if (fin && (fin.peek() != std::ifstream::traits_type::eof()) && (exit_status == 0))
176         out_stream << fin.rdbuf();
177       else
178         success = false;
179     }
180 
181   // Clean up the temporary file, regardless of whether it was opened successfully.
182   std::remove(temp_file);
183 
184   return success;
185 #else
186   return false;
187 #endif
188 }
189 
190 } // end anonymous namespace
191 
192 
193 namespace libMesh
194 {
195 
print_trace(std::ostream & out_stream)196 void print_trace(std::ostream & out_stream)
197 {
198   // First try a GDB backtrace.  They are better than what you get
199   // from calling backtrace() because you don't have to do any
200   // demangling, and they include line numbers!  If the GDB backtrace
201   // fails, for example if your system does not have GDB, fall back to
202   // calling backtrace().
203   bool gdb_worked = false;
204 
205   // Let the user disable GDB backtraces by configuring with
206   // --without-gdb-command or with a command line option.
207   if (std::string(LIBMESH_GDB_COMMAND) != std::string("no") &&
208       !libMesh::on_command_line("--no-gdb-backtrace"))
209     gdb_worked = gdb_backtrace(out_stream);
210 
211   // This part requires that your compiler at least supports
212   // backtraces.  Demangling is also nice, but it will still run
213   // without it.
214 #if defined(LIBMESH_HAVE_GLIBC_BACKTRACE)
215   if (!gdb_worked)
216     {
217       void * addresses[40];
218       char ** strings;
219 
220       int size = backtrace(addresses, 40);
221       strings = backtrace_symbols(addresses, size);
222       out_stream << "Stack frames: " << size << std::endl;
223       for (int i = 0; i < size; i++)
224         out_stream << i << ": " << process_trace(strings[i]) << std::endl;
225       std::free(strings);
226     }
227 #endif
228 }
229 
230 
231 // If tracefiles are enabled, calls print_trace() and sends the
232 // result to file.  Otherwise, does nothing.
write_traceout()233 void write_traceout()
234 {
235 #ifdef LIBMESH_ENABLE_TRACEFILES
236   std::stringstream outname;
237   outname << "traceout_" << static_cast<std::size_t>(libMesh::global_processor_id()) << '_' << getpid() << ".txt";
238   std::ofstream traceout(outname.str().c_str(), std::ofstream::app);
239   libMesh::print_trace(traceout);
240 #endif
241 }
242 
243 
244 
245 // demangle() is used by the process_trace() helper function.  It is
246 // also used by the Parameters class for demangling typeid's.  If
247 // configure determined that your compiler does not support
248 // demangling, it simply returns the input string.
249 #if defined(LIBMESH_HAVE_GCC_ABI_DEMANGLE)
demangle(const char * name)250 std::string demangle(const char * name)
251 {
252   int status = 0;
253   std::string ret = name;
254 
255   // Actually do the demangling
256   char * demangled_name = abi::__cxa_demangle(name, 0, 0, &status);
257 
258   // If demangling returns non-nullptr, save the result in a string.
259   if (demangled_name)
260     ret = demangled_name;
261 
262   // According to cxxabi.h docs, the caller is responsible for
263   // deallocating memory.
264   std::free(demangled_name);
265 
266   return ret;
267 }
268 #else
demangle(const char * name)269 std::string demangle(const char * name) { return std::string(name); }
270 #endif
271 
272 } // namespace libMesh
273