Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
provide a backtrace implementation that uses boost.stacktrace
[simgrid.git] / src / xbt / backtrace.cpp
1 /* Copyright (c) 2005-2018. The SimGrid Team. All rights reserved.          */
2
3 /* This program is free software; you can redistribute it and/or modify it
4  * under the terms of the license (GNU LGPL) which comes with this package. */
5
6 #include "src/internal_config.h"
7
8 #include "simgrid/simix.h" /* SIMIX_process_self_get_name() */
9 #include <xbt/backtrace.hpp>
10 #include <xbt/log.h>
11 #include <xbt/string.hpp>
12 #include <xbt/sysdep.h>
13
14 #include <cstddef>
15 #include <cstdlib>
16 #include <cstring>
17 #include <fstream>
18 #include <sstream>
19 #include <sys/stat.h>
20 #include <vector>
21
22 #include <boost/algorithm/string.hpp>
23
24 // Try to detect and use the C++ itanium ABI for name demangling:
25 #ifdef __GXX_ABI_VERSION
26 #include <cxxabi.h>
27 #endif
28 #if HAVE_EXECINFO_H
29 #include <execinfo.h>
30 #endif
31
32 #if HAVE_BOOST_STACKTRACE
33 #define BOOST_STACKTRACE_USE_BACKTRACE
34 #include <boost/stacktrace.hpp>
35 #endif
36
37 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(xbt_backtrace, xbt, "Backtrace");
38
39 static bool startWith(std::string str, const char* prefix)
40 {
41   return strncmp(str.c_str(), prefix, strlen(prefix)) == 0;
42 }
43
44 void xbt_backtrace_display(const simgrid::xbt::Backtrace& bt)
45 {
46   std::vector<std::string> backtrace = simgrid::xbt::resolve_backtrace(bt);
47   if (backtrace.empty()) {
48     fprintf(stderr, "(backtrace not set -- maybe unavailable on this architecture?)\n");
49     return;
50   }
51   fprintf(stderr, "Backtrace (displayed in process %s):\n", SIMIX_process_self_get_name());
52   for (std::string const& s : backtrace) {
53     if (startWith(s, "xbt_backtrace_display_current"))
54       continue;
55
56     std::fprintf(stderr, "---> '%s'\n", s.c_str());
57     if (startWith(s, "SIMIX_simcall_handle") ||
58         startWith(s, "simgrid::xbt::MainFunction") /* main used with thread factory */)
59       break;
60   }
61 }
62
63 /** @brief show the backtrace of the current point (lovely while debugging) */
64 void xbt_backtrace_display_current()
65 {
66   simgrid::xbt::Backtrace bt = simgrid::xbt::Backtrace();
67   xbt_backtrace_display(bt);
68 }
69
70 namespace simgrid {
71 namespace xbt {
72
73 std::unique_ptr<char, void(*)(void*)> demangle(const char* name)
74 {
75 #ifdef __GXX_ABI_VERSION
76   int status;
77   auto res = std::unique_ptr<char, void(*)(void*)>(
78     abi::__cxa_demangle(name, nullptr, nullptr, &status),
79     std::free
80   );
81   if (res != nullptr)
82     return res;
83   // We did not manage to resolve this. Probably because this is not a mangled symbol:
84 #endif
85   // Return the symbol:
86   return std::unique_ptr<char, void(*)(void*)>(xbt_strdup(name), std::free);
87 }
88
89 class BacktraceImpl {
90   short refcount_ = 1;
91
92 public:
93   void ref() { refcount_++; }
94   bool unref()
95   {
96     refcount_--;
97     return refcount_ == 0;
98   }
99 #if HAVE_BOOST_STACKTRACE
100   boost::stacktrace::stacktrace st;
101 #elif HAVE_BACKTRACE
102   std::vector<void*> frames;
103 #endif
104 };
105
106 Backtrace::Backtrace()
107 {
108 #if HAVE_BOOST_STACKTRACE
109   impl_     = new BacktraceImpl();
110   impl_->st = boost::stacktrace::stacktrace();
111 #elif HAVE_BACKTRACE
112   impl_ = new BacktraceImpl();
113   impl_->frames.resize(15);
114   int used = backtrace(impl_->frames.data(), impl_->frames.size());
115   if (used == 0) {
116     std::fprintf(stderr, "The backtrace() function failed, which probably means that the memory is exhausted\n.");
117     std::fprintf(stderr, "Bailing out now since there is nothing I can do without a decent amount of memory\n.");
118     std::fprintf(stderr, "Please go fix the memleaks\n");
119     std::exit(1);
120   }
121   impl_->frames.shrink_to_fit();
122 #endif
123 }
124 Backtrace::Backtrace(const Backtrace& bt)
125 {
126   impl_ = bt.impl_;
127   impl_->ref();
128 }
129
130 Backtrace::~Backtrace()
131 {
132   if (impl_ != nullptr && impl_->unref()) {
133     delete impl_;
134   }
135 }
136 } // namespace xbt
137 } // namespace simgrid
138
139 namespace simgrid {
140 namespace xbt {
141
142 /** Find the path of the binary file from the name found in argv */
143 static std::string get_binary_path()
144 {
145   struct stat stat_buf;
146
147   if (xbt_binary_name == nullptr)
148     return "";
149
150   // We found it, we are happy:
151   if (stat(xbt_binary_name, &stat_buf) == 0)
152     return xbt_binary_name;
153
154   // Not found, look in the PATH:
155   char* path = getenv("PATH");
156   if (path == nullptr)
157     return "";
158
159   XBT_DEBUG("Looking in the PATH: %s\n", path);
160   std::vector<std::string> path_list;
161   boost::split(path_list, path, boost::is_any_of(":;"));
162
163   for (std::string const& path_item : path_list) {
164     std::string binary_name = simgrid::xbt::string_printf("%s/%s", path_item.c_str(), xbt_binary_name);
165     bool found              = (stat(binary_name.c_str(), &stat_buf) == 0);
166     XBT_DEBUG("Looked in the PATH for the binary. %s %s", found ? "Found" : "Not found", binary_name.c_str());
167     if (found)
168       return binary_name;
169   }
170
171   // Not found at all:
172   return "";
173 }
174
175 std::vector<std::string> resolve_backtrace(const Backtrace& bt)
176 {
177   std::vector<std::string> result;
178
179 #if HAVE_BOOST_STACKTRACE
180   std::stringstream ss;
181   ss << bt.impl_->st;
182   result.push_back(ss.str());
183 #elif HAVE_BACKTRACE && HAVE_EXECINFO_H && HAVE_POPEN && defined(ADDR2LINE)
184   // FIXME: This code could be greatly improved/simplified with
185   //   http://cairo.sourcearchive.com/documentation/1.9.4/backtrace-symbols_8c-source.html
186   if (bt.impl_->frames.size() == 0)
187     return result;
188
189   if (xbt_binary_name == nullptr)
190     XBT_WARN("XBT not initialized, the backtrace will not be resolved.");
191
192   char** backtrace_syms   = backtrace_symbols(bt.impl_->frames.data(), bt.impl_->frames.size());
193   std::string binary_name = get_binary_path();
194
195   if (binary_name.empty()) {
196     for (std::size_t i = 1; i < bt.impl_->frames.size(); i++) // the first one is not interesting
197       result.push_back(simgrid::xbt::string_printf("%p", bt.impl_->frames[i]));
198     return result;
199   }
200
201   // Create the system command for add2line:
202   std::ostringstream stream;
203   stream << ADDR2LINE << " -f -e " << binary_name << ' ';
204   std::vector<std::string> addrs(bt.impl_->frames.size());
205   for (std::size_t i = 1; i < bt.impl_->frames.size(); i++) { // the first one is not interesting
206     /* retrieve this address */
207     XBT_DEBUG("Retrieving address number %zu from '%s'", i, backtrace_syms[i]);
208     char buff[256];
209     snprintf(buff, 256, "%s", strchr(backtrace_syms[i], '[') + 1);
210     char* p = strchr(buff, ']');
211     *p      = '\0';
212     if (strcmp(buff, "(nil)"))
213       addrs[i] = buff;
214     else
215       addrs[i] = "0x0";
216     XBT_DEBUG("Set up a new address: %zu, '%s'", i, addrs[i].c_str());
217     /* Add it to the command line args */
218     stream << addrs[i] << ' ';
219   }
220   std::string cmd = stream.str();
221
222   /* size (in char) of pointers on this arch */
223   int addr_len = addrs[0].size();
224
225   XBT_VERB("Fire a first command: '%s'", cmd.c_str());
226   FILE* pipe = popen(cmd.c_str(), "r");
227   xbt_assert(pipe, "Cannot fork addr2line to display the backtrace");
228
229   /* To read the output of addr2line */
230   char line_func[1024];
231   char line_pos[1024];
232   for (std::size_t i = 1; i < bt.impl_->frames.size(); i++) { // The first one is not interesting
233     XBT_DEBUG("Looking for symbol %zu, addr = '%s'", i, addrs[i].c_str());
234     if (fgets(line_func, 1024, pipe)) {
235       line_func[strlen(line_func) - 1] = '\0';
236     } else {
237       XBT_VERB("Cannot run fgets to look for symbol %zu, addr %s", i, addrs[i].c_str());
238       strncpy(line_func, "???", 4);
239     }
240     if (fgets(line_pos, 1024, pipe)) {
241       line_pos[strlen(line_pos) - 1] = '\0';
242     } else {
243       XBT_VERB("Cannot run fgets to look for symbol %zu, addr %s", i, addrs[i].c_str());
244       strncpy(line_pos, backtrace_syms[i], 1024);
245     }
246
247     if (strcmp("??", line_func) != 0) {
248       auto name = simgrid::xbt::demangle(line_func);
249       XBT_DEBUG("Found static symbol %s at %s", name.get(), line_pos);
250       result.push_back(simgrid::xbt::string_printf("%s at %s, %p", name.get(), line_pos, bt.impl_->frames[i]));
251     } else {
252       /* Damn. The symbol is in a dynamic library. Let's get wild */
253
254       unsigned long int offset = 0;
255       int found                = 0;
256
257       /* let's look for the offset of this library in our addressing space */
258       std::string maps_name = std::string("/proc/") + std::to_string(getpid()) + "/maps";
259       std::ifstream maps(maps_name);
260       if (not maps) {
261         XBT_CRITICAL("open(\"%s\") failed: %s", maps_name.c_str(), strerror(errno));
262         continue;
263       }
264       size_t pos;
265       unsigned long int addr = std::stoul(addrs[i], &pos, 16);
266       if (pos != addrs[i].length()) {
267         XBT_CRITICAL("Cannot parse backtrace address '%s' (addr=%#lx)", addrs[i].c_str(), addr);
268       }
269       XBT_DEBUG("addr=%s (as string) =%#lx (as number)", addrs[i].c_str(), addr);
270
271       while (not found) {
272         unsigned long int first;
273         unsigned long int last;
274
275         std::string maps_buff;
276         if (not std::getline(maps, maps_buff))
277           break;
278         if (i == 0) {
279           XBT_DEBUG("map line: %s", maps_buff.c_str());
280         }
281         first = std::stoul(maps_buff, &pos, 16);
282         maps_buff.erase(0, pos + 1);
283         last = std::stoul(maps_buff, nullptr, 16);
284         if (first < addr && addr < last) {
285           offset = first;
286           found  = 1;
287         }
288         if (found) {
289           XBT_DEBUG("%#lx in [%#lx-%#lx]", addr, first, last);
290           XBT_DEBUG("Symbol found, map lines not further displayed (even if looking for next ones)");
291         }
292       }
293       maps.close();
294       addrs[i].clear();
295
296       if (not found) {
297         XBT_VERB("Problem while reading the maps file. Following backtrace will be mangled.");
298         XBT_DEBUG("No dynamic. Static symbol: %s", backtrace_syms[i]);
299         result.push_back(simgrid::xbt::string_printf("?? (%s)", backtrace_syms[i]));
300         continue;
301       }
302
303       /* Ok, Found the offset of the maps line containing the searched symbol.
304          We now need to substract this from the address we got from backtrace.
305        */
306
307       addrs[i] = simgrid::xbt::string_printf("0x%0*lx", addr_len - 2, addr - offset);
308       XBT_DEBUG("offset=%#lx new addr=%s", offset, addrs[i].c_str());
309
310       /* Got it. We have our new address. Let's get the library path and we are set */
311       std::string p(backtrace_syms[i]);
312       if (p[0] == '[') {
313         /* library path not displayed in the map file either... */
314         snprintf(line_func, 3, "??");
315       } else {
316         size_t p2 = p.find_first_of("( ");
317         if (p2 != std::string::npos)
318           p.erase(p2);
319
320         /* Here we go, fire an addr2line up */
321         std::string subcmd = std::string(ADDR2LINE) + " -f -e " + p + " " + addrs[i];
322         XBT_VERB("Fire another command: '%s'", subcmd.c_str());
323         FILE* subpipe = popen(subcmd.c_str(), "r");
324         if (not subpipe) {
325           xbt_die("Cannot fork addr2line to display the backtrace");
326         }
327         if (fgets(line_func, 1024, subpipe)) {
328           line_func[strlen(line_func) - 1] = '\0';
329         } else {
330           XBT_VERB("Cannot read result of subcommand %s", subcmd.c_str());
331           strncpy(line_func, "???", 4);
332         }
333         if (fgets(line_pos, 1024, subpipe)) {
334           line_pos[strlen(line_pos) - 1] = '\0';
335         } else {
336           XBT_VERB("Cannot read result of subcommand %s", subcmd.c_str());
337           strncpy(line_pos, backtrace_syms[i], 1024);
338         }
339         pclose(subpipe);
340       }
341
342       /* check whether the trick worked */
343       if (strcmp("??", line_func)) {
344         auto name = simgrid::xbt::demangle(line_func);
345         XBT_DEBUG("Found dynamic symbol %s at %s", name.get(), line_pos);
346         result.push_back(simgrid::xbt::string_printf("%s at %s, %p", name.get(), line_pos, bt.impl_->frames[i]));
347       } else {
348         /* damn, nothing to do here. Let's print the raw address */
349         XBT_DEBUG("Dynamic symbol not found. Raw address = %s", backtrace_syms[i]);
350         result.push_back(simgrid::xbt::string_printf("?? at %s", backtrace_syms[i]));
351       }
352     }
353     addrs[i].clear();
354
355     /* Mask the bottom of the stack */
356     const char* const breakers[] = {
357         "main",
358         "_ZN7simgrid6kernel7context13ThreadContext7wrapperE", // simgrid::kernel::context::ThreadContext::wrapper
359         "_ZN7simgrid6kernel7context8UContext7wrapperE"        // simgrid::kernel::context::UContext::wrapper
360     };
361     bool do_break = false;
362     for (const char* b : breakers) {
363       if (strncmp(b, line_func, strlen(b)) == 0) {
364         do_break = true;
365         break;
366       }
367     }
368     if (do_break)
369       break;
370   }
371   pclose(pipe);
372   xbt_free(backtrace_syms);
373 #endif /* ADDR2LINE usable to resolve the backtrace */
374   return result;
375 }
376
377 } // namespace xbt
378 } // namespace simgrid