1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        msw/stackwalk.cpp
3 // Purpose:     wxStackWalker implementation for Unix/glibc
4 // Author:      Vadim Zeitlin
5 // Modified by:
6 // Created:     2005-01-18
7 // RCS-ID:      $Id: stackwalk.cpp 43965 2006-12-13 13:04:44Z VZ $
8 // Copyright:   (c) 2005 Vadim Zeitlin <vadim@wxwindows.org>
9 // Licence:     wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11 
12 // ============================================================================
13 // declarations
14 // ============================================================================
15 
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19 
20 #include "wx/wxprec.h"
21 
22 #ifdef __BORLANDC__
23     #pragma hdrstop
24 #endif
25 
26 #if wxUSE_STACKWALKER
27 
28 #ifndef WX_PRECOMP
29     #include "wx/string.h"
30     #include "wx/app.h"
31     #include "wx/log.h"
32     #include "wx/utils.h"
33 #endif
34 
35 #include "wx/stackwalk.h"
36 #include "wx/stdpaths.h"
37 
38 #include <execinfo.h>
39 
40 #ifdef HAVE_CXA_DEMANGLE
41     #include <cxxabi.h>
42 #endif // HAVE_CXA_DEMANGLE
43 
44 // ----------------------------------------------------------------------------
45 // tiny helper wrapper around popen/pclose()
46 // ----------------------------------------------------------------------------
47 
48 class wxStdioPipe
49 {
50 public:
51     // ctor parameters are passed to popen()
wxStdioPipe(const char * command,const char * type)52     wxStdioPipe(const char *command, const char *type)
53     {
54         m_fp = popen(command, type);
55     }
56 
57     // conversion to stdio FILE
operator FILE*() const58     operator FILE *() const { return m_fp; }
59 
60     // dtor closes the pipe
~wxStdioPipe()61     ~wxStdioPipe()
62     {
63         if ( m_fp )
64             pclose(m_fp);
65     }
66 
67 private:
68     FILE *m_fp;
69 
70     DECLARE_NO_COPY_CLASS(wxStdioPipe)
71 };
72 
73 // ============================================================================
74 // implementation
75 // ============================================================================
76 
77 // ----------------------------------------------------------------------------
78 // wxStackFrame
79 // ----------------------------------------------------------------------------
80 
OnGetName()81 void wxStackFrame::OnGetName()
82 {
83     if ( !m_name.empty() )
84         return;
85 
86     // we already tried addr2line in wxStackWalker::InitFrames: it always
87     // gives us demangled names (even if __cxa_demangle is not available) when
88     // the function is part of the ELF (when it's in a shared object addr2line
89     // will give "??") and because it seems less error-prone.
90     // when it works, backtrace_symbols() sometimes returns incorrect results
91 
92     // format is: "module(funcname+offset) [address]" but the part in
93     // parentheses can be not present
94     wxString syminfo = wxString::FromAscii(m_syminfo);
95     const size_t posOpen = syminfo.find(_T('('));
96     if ( posOpen != wxString::npos )
97     {
98         const size_t posPlus = syminfo.find(_T('+'), posOpen + 1);
99         if ( posPlus != wxString::npos )
100         {
101             const size_t posClose = syminfo.find(_T(')'), posPlus + 1);
102             if ( posClose != wxString::npos )
103             {
104                 if ( m_name.empty() )
105                 {
106                     m_name.assign(syminfo, posOpen + 1, posPlus - posOpen - 1);
107 
108 #ifdef HAVE_CXA_DEMANGLE
109                     int rc = -1;
110                     char *cppfunc = __cxxabiv1::__cxa_demangle
111                                     (
112                                         m_name.mb_str(),
113                                         NULL, // output buffer (none, alloc it)
114                                         NULL, // [out] len of output buffer
115                                         &rc
116                                     );
117                     if ( rc == 0 )
118                         m_name = wxString::FromAscii(cppfunc);
119 
120                     free(cppfunc);
121 #endif // HAVE_CXA_DEMANGLE
122                 }
123 
124                 unsigned long ofs;
125                 if ( wxString(syminfo, posPlus + 1, posClose - posPlus - 1).
126                         ToULong(&ofs, 0) )
127                     m_offset = ofs;
128             }
129         }
130 
131         m_module.assign(syminfo, posOpen);
132     }
133     else // not in "module(funcname+offset)" format
134     {
135         m_module = syminfo;
136     }
137 }
138 
139 
140 // ----------------------------------------------------------------------------
141 // wxStackWalker
142 // ----------------------------------------------------------------------------
143 
144 // that many frames should be enough for everyone
145 #define MAX_FRAMES          200
146 
147 // we need a char buffer big enough to contain a call to addr2line with
148 // up to MAX_FRAMES addresses !
149 // NB: %p specifier will print the pointer in hexadecimal form
150 //     and thus will require 2 chars for each byte + 3 for the
151 //     " 0x" prefix
152 #define CHARS_PER_FRAME    (sizeof(void*) * 2 + 3)
153 
154 // BUFSIZE will be 2250 for 32 bit machines
155 #define BUFSIZE            (50 + MAX_FRAMES*CHARS_PER_FRAME)
156 
157 // static data
158 void *wxStackWalker::ms_addresses[MAX_FRAMES];
159 char **wxStackWalker::ms_symbols = NULL;
160 int wxStackWalker::m_depth = 0;
161 wxString wxStackWalker::ms_exepath;
162 static char g_buf[BUFSIZE];
163 
164 
SaveStack(size_t maxDepth)165 void wxStackWalker::SaveStack(size_t maxDepth)
166 {
167     // read all frames required
168     maxDepth = wxMin(WXSIZEOF(ms_addresses)/sizeof(void*), maxDepth);
169     m_depth = backtrace(ms_addresses, maxDepth*sizeof(void*));
170     if ( !m_depth )
171         return;
172 
173     ms_symbols = backtrace_symbols(ms_addresses, m_depth);
174 }
175 
ProcessFrames(size_t skip)176 void wxStackWalker::ProcessFrames(size_t skip)
177 {
178     wxStackFrame frames[MAX_FRAMES];
179 
180     if (!ms_symbols || !m_depth)
181         return;
182 
183     // we have 3 more "intermediate" frames which the calling code doesn't know
184     // about, account for them
185     skip += 3;
186 
187     // call addr2line only once since this call may be very slow
188     // (it has to load in memory the entire EXE of this app which may be quite
189     //  big, especially if it contains debug info and is compiled statically!)
190     int towalk = InitFrames(frames, m_depth - skip, &ms_addresses[skip], &ms_symbols[skip]);
191 
192     // now do user-defined operations on each frame
193     for ( int n = 0; n < towalk - (int)skip; n++ )
194         OnStackFrame(frames[n]);
195 }
196 
FreeStack()197 void wxStackWalker::FreeStack()
198 {
199     // ms_symbols has been allocated by backtrace_symbols() and it's the responsibility
200     // of the caller, i.e. us, to free that pointer
201     if (ms_symbols)
202         free( ms_symbols );
203     ms_symbols = NULL;
204     m_depth = 0;
205 }
206 
InitFrames(wxStackFrame * arr,size_t n,void ** addresses,char ** syminfo)207 int wxStackWalker::InitFrames(wxStackFrame *arr, size_t n, void **addresses, char **syminfo)
208 {
209     // we need to launch addr2line tool to get this information and we need to
210     // have the program name for this
211     wxString exepath = wxStackWalker::GetExePath();
212     if ( exepath.empty() )
213     {
214         exepath = wxStandardPaths::Get().GetExecutablePath();
215         if ( exepath.empty() )
216         {
217             wxLogDebug(wxT("Cannot parse stack frame because the executable ")
218                        wxT("path could not be detected"));
219             return 0;
220         }
221     }
222 
223     // build the (long) command line for executing addr2line in an optimized way
224     // (e.g. use always chars, even in Unicode build: popen() always takes chars)
225     int len = snprintf(g_buf, BUFSIZE, "addr2line -C -f -e \"%s\"", (const char*) exepath.mb_str());
226     len = (len <= 0) ? strlen(g_buf) : len;     // in case snprintf() is broken
227     for (size_t i=0; i<n; i++)
228     {
229         snprintf(&g_buf[len], BUFSIZE - len, " %p", addresses[i]);
230         len = strlen(g_buf);
231     }
232 
233     //wxLogDebug(wxT("piping the command '%s'"), g_buf);  // for debug only
234 
235     wxStdioPipe fp(g_buf, "r");
236     if ( !fp )
237         return 0;
238 
239     // parse addr2line output (should be exactly 2 lines for each address)
240     // reusing the g_buf used for building the command line above
241     wxString name, filename;
242     unsigned long line = 0,
243                   curr = 0;
244     for  ( size_t i = 0; i < n; i++ )
245     {
246         // 1st line has function name
247         if ( fgets(g_buf, WXSIZEOF(g_buf), fp) )
248         {
249             name = wxString::FromAscii(g_buf);
250             name.RemoveLast(); // trailing newline
251 
252             if ( name == _T("??") )
253                 name.clear();
254         }
255         else
256         {
257             wxLogDebug(_T("cannot read addr2line output for stack frame #%lu"),
258                        (unsigned long)i);
259             return false;
260         }
261 
262         // 2nd one -- the file/line info
263         if ( fgets(g_buf, WXSIZEOF(g_buf), fp) )
264         {
265             filename = wxString::FromAscii(g_buf);
266             filename.RemoveLast();
267 
268             const size_t posColon = filename.find(_T(':'));
269             if ( posColon != wxString::npos )
270             {
271                 // parse line number (it's ok if it fails, this will just leave
272                 // line at its current, invalid, 0 value)
273                 wxString(filename, posColon + 1, wxString::npos).ToULong(&line);
274 
275                 // remove line number from 'filename'
276                 filename.erase(posColon);
277                 if ( filename == _T("??") )
278                     filename.clear();
279             }
280             else
281             {
282                 wxLogDebug(_T("Unexpected addr2line format: \"%s\" - ")
283                            _T("the semicolon is missing"),
284                            filename.c_str());
285             }
286         }
287 
288         // now we've got enough info to initialize curr-th stack frame
289         // (at worst, only addresses[i] and syminfo[i] have been initialized,
290         //  but wxStackFrame::OnGetName may still be able to get function name):
291         arr[curr++].Set(name, filename, syminfo[i], i, line, addresses[i]);
292     }
293 
294     return curr;
295 }
296 
Walk(size_t skip,size_t maxDepth)297 void wxStackWalker::Walk(size_t skip, size_t maxDepth)
298 {
299     // read all frames required
300     SaveStack(maxDepth);
301 
302     // process them
303     ProcessFrames(skip);
304 
305     // cleanup
306     FreeStack();
307 }
308 
309 #endif // wxUSE_STACKWALKER
310