]>
Commit | Line | Data |
---|---|---|
1 | ///////////////////////////////////////////////////////////////////////////// | |
2 | // Name: msw/stackwalk.cpp | |
3 | // Purpose: wxStackWalker implementation for Win32 | |
4 | // Author: Vadim Zeitlin | |
5 | // Modified by: | |
6 | // Created: 2005-01-08 | |
7 | // RCS-ID: $Id$ | |
8 | // Copyright: (c) 2003-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 | #endif | |
31 | ||
32 | #include "wx/stackwalk.h" | |
33 | ||
34 | #include "wx/msw/debughlp.h" | |
35 | ||
36 | #if wxUSE_DBGHELP | |
37 | ||
38 | // ============================================================================ | |
39 | // implementation | |
40 | // ============================================================================ | |
41 | ||
42 | // ---------------------------------------------------------------------------- | |
43 | // wxStackFrame | |
44 | // ---------------------------------------------------------------------------- | |
45 | ||
46 | void wxStackFrame::OnGetName() | |
47 | { | |
48 | if ( m_hasName ) | |
49 | return; | |
50 | ||
51 | m_hasName = true; | |
52 | ||
53 | // get the name of the function for this stack frame entry | |
54 | static const size_t MAX_NAME_LEN = 1024; | |
55 | BYTE symbolBuffer[sizeof(SYMBOL_INFO) + MAX_NAME_LEN]; | |
56 | wxZeroMemory(symbolBuffer); | |
57 | ||
58 | PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)symbolBuffer; | |
59 | pSymbol->SizeOfStruct = sizeof(symbolBuffer); | |
60 | pSymbol->MaxNameLen = MAX_NAME_LEN; | |
61 | ||
62 | DWORD64 symDisplacement = 0; | |
63 | if ( !wxDbgHelpDLL::SymFromAddr | |
64 | ( | |
65 | ::GetCurrentProcess(), | |
66 | GetSymAddr(), | |
67 | &symDisplacement, | |
68 | pSymbol | |
69 | ) ) | |
70 | { | |
71 | wxDbgHelpDLL::LogError(_T("SymFromAddr")); | |
72 | return; | |
73 | } | |
74 | ||
75 | m_name = wxString::FromAscii(pSymbol->Name); | |
76 | m_offset = symDisplacement; | |
77 | } | |
78 | ||
79 | void wxStackFrame::OnGetLocation() | |
80 | { | |
81 | if ( m_hasLocation ) | |
82 | return; | |
83 | ||
84 | m_hasLocation = true; | |
85 | ||
86 | // get the source line for this stack frame entry | |
87 | IMAGEHLP_LINE lineInfo = { sizeof(IMAGEHLP_LINE) }; | |
88 | DWORD dwLineDisplacement; | |
89 | if ( !wxDbgHelpDLL::SymGetLineFromAddr | |
90 | ( | |
91 | ::GetCurrentProcess(), | |
92 | GetSymAddr(), | |
93 | &dwLineDisplacement, | |
94 | &lineInfo | |
95 | ) ) | |
96 | { | |
97 | // it is normal that we don't have source info for some symbols, | |
98 | // notably all the ones from the system DLLs... | |
99 | //wxDbgHelpDLL::LogError(_T("SymGetLineFromAddr")); | |
100 | return; | |
101 | } | |
102 | ||
103 | m_filename = wxString::FromAscii(lineInfo.FileName); | |
104 | m_line = lineInfo.LineNumber; | |
105 | } | |
106 | ||
107 | bool | |
108 | wxStackFrame::GetParam(size_t n, | |
109 | wxString *type, | |
110 | wxString *name, | |
111 | wxString *value) const | |
112 | { | |
113 | if ( !DoGetParamCount() ) | |
114 | ConstCast()->OnGetParam(); | |
115 | ||
116 | if ( n >= DoGetParamCount() ) | |
117 | return false; | |
118 | ||
119 | if ( type ) | |
120 | *type = m_paramTypes[n]; | |
121 | if ( name ) | |
122 | *name = m_paramNames[n]; | |
123 | if ( value ) | |
124 | *value = m_paramValues[n]; | |
125 | ||
126 | return true; | |
127 | } | |
128 | ||
129 | void wxStackFrame::OnParam(PSYMBOL_INFO pSymInfo) | |
130 | { | |
131 | m_paramTypes.Add(_T("")); | |
132 | ||
133 | m_paramNames.Add(wxString::FromAscii(pSymInfo->Name)); | |
134 | ||
135 | // if symbol information is corrupted and we crash, the exception is going | |
136 | // to be ignored when we're called from WalkFromException() because of the | |
137 | // except handler there returning EXCEPTION_CONTINUE_EXECUTION, but we'd be | |
138 | // left in an inconsistent state, so deal with it explicitely here (even if | |
139 | // normally we should never crash, of course...) | |
140 | #ifdef _CPPUNWIND | |
141 | try | |
142 | #else | |
143 | __try | |
144 | #endif | |
145 | { | |
146 | // as it is a parameter (and not a global var), it is always offset by | |
147 | // the frame address | |
148 | DWORD_PTR pValue = m_addrFrame + pSymInfo->Address; | |
149 | m_paramValues.Add(wxDbgHelpDLL::DumpSymbol(pSymInfo, (void *)pValue)); | |
150 | } | |
151 | #ifdef _CPPUNWIND | |
152 | catch ( ... ) | |
153 | #else | |
154 | __except ( EXCEPTION_EXECUTE_HANDLER ) | |
155 | #endif | |
156 | { | |
157 | m_paramValues.Add(_T("")); | |
158 | } | |
159 | } | |
160 | ||
161 | BOOL CALLBACK | |
162 | EnumSymbolsProc(PSYMBOL_INFO pSymInfo, ULONG WXUNUSED(SymSize), PVOID data) | |
163 | { | |
164 | wxStackFrame *frame = wx_static_cast(wxStackFrame *, data); | |
165 | ||
166 | // we're only interested in parameters | |
167 | if ( pSymInfo->Flags & IMAGEHLP_SYMBOL_INFO_PARAMETER ) | |
168 | { | |
169 | frame->OnParam(pSymInfo); | |
170 | } | |
171 | ||
172 | // return true to continue enumeration, false would have stopped it | |
173 | return TRUE; | |
174 | } | |
175 | ||
176 | void wxStackFrame::OnGetParam() | |
177 | { | |
178 | // use SymSetContext to get just the locals/params for this frame | |
179 | IMAGEHLP_STACK_FRAME imagehlpStackFrame; | |
180 | wxZeroMemory(imagehlpStackFrame); | |
181 | imagehlpStackFrame.InstructionOffset = GetSymAddr(); | |
182 | if ( !wxDbgHelpDLL::SymSetContext | |
183 | ( | |
184 | ::GetCurrentProcess(), | |
185 | &imagehlpStackFrame, | |
186 | 0 // unused | |
187 | ) ) | |
188 | { | |
189 | // for symbols from kernel DLL we might not have access to their | |
190 | // address, this is not a real error | |
191 | if ( ::GetLastError() != ERROR_INVALID_ADDRESS ) | |
192 | { | |
193 | wxDbgHelpDLL::LogError(_T("SymSetContext")); | |
194 | } | |
195 | ||
196 | return; | |
197 | } | |
198 | ||
199 | if ( !wxDbgHelpDLL::SymEnumSymbols | |
200 | ( | |
201 | ::GetCurrentProcess(), | |
202 | NULL, // DLL base: use current context | |
203 | NULL, // no mask, get all symbols | |
204 | EnumSymbolsProc, // callback | |
205 | this // data to pass to it | |
206 | ) ) | |
207 | { | |
208 | wxDbgHelpDLL::LogError(_T("SymEnumSymbols")); | |
209 | } | |
210 | } | |
211 | ||
212 | ||
213 | // ---------------------------------------------------------------------------- | |
214 | // wxStackWalker | |
215 | // ---------------------------------------------------------------------------- | |
216 | ||
217 | void wxStackWalker::WalkFrom(const CONTEXT *pCtx, size_t skip) | |
218 | { | |
219 | if ( !wxDbgHelpDLL::Init() ) | |
220 | { | |
221 | wxLogError(_("Failed to get stack backtrace:\n%s"), | |
222 | wxDbgHelpDLL::GetErrorMessage().c_str()); | |
223 | } | |
224 | ||
225 | // according to MSDN, the first parameter should be just a unique value and | |
226 | // not process handle (although the parameter is prototyped as "HANDLE | |
227 | // hProcess") and actually it advises to use the process id and not handle | |
228 | // for Win9x, but then we need to use the same value in StackWalk() call | |
229 | // below which should be a real handle... so this is what we use | |
230 | const HANDLE hProcess = ::GetCurrentProcess(); | |
231 | ||
232 | if ( !wxDbgHelpDLL::SymInitialize | |
233 | ( | |
234 | hProcess, | |
235 | NULL, // use default symbol search path | |
236 | TRUE // load symbols for all loaded modules | |
237 | ) ) | |
238 | { | |
239 | wxDbgHelpDLL::LogError(_T("SymInitialize")); | |
240 | ||
241 | return; | |
242 | } | |
243 | ||
244 | CONTEXT ctx = *pCtx; // will be modified by StackWalk() | |
245 | ||
246 | DWORD dwMachineType; | |
247 | ||
248 | // initialize the initial frame: currently we can do it for x86 only | |
249 | STACKFRAME sf; | |
250 | wxZeroMemory(sf); | |
251 | ||
252 | #ifdef _M_IX86 | |
253 | sf.AddrPC.Offset = ctx.Eip; | |
254 | sf.AddrPC.Mode = AddrModeFlat; | |
255 | sf.AddrStack.Offset = ctx.Esp; | |
256 | sf.AddrStack.Mode = AddrModeFlat; | |
257 | sf.AddrFrame.Offset = ctx.Ebp; | |
258 | sf.AddrFrame.Mode = AddrModeFlat; | |
259 | ||
260 | dwMachineType = IMAGE_FILE_MACHINE_I386; | |
261 | #else | |
262 | #error "Need to initialize STACKFRAME on non x86" | |
263 | #endif // _M_IX86 | |
264 | ||
265 | // iterate over all stack frames | |
266 | for ( size_t nLevel = 0; ; nLevel++ ) | |
267 | { | |
268 | // get the next stack frame | |
269 | if ( !wxDbgHelpDLL::StackWalk | |
270 | ( | |
271 | dwMachineType, | |
272 | hProcess, | |
273 | ::GetCurrentThread(), | |
274 | &sf, | |
275 | &ctx, | |
276 | NULL, // read memory function (default) | |
277 | wxDbgHelpDLL::SymFunctionTableAccess, | |
278 | wxDbgHelpDLL::SymGetModuleBase, | |
279 | NULL // address translator for 16 bit | |
280 | ) ) | |
281 | { | |
282 | if ( ::GetLastError() ) | |
283 | wxDbgHelpDLL::LogError(_T("StackWalk")); | |
284 | ||
285 | break; | |
286 | } | |
287 | ||
288 | // don't show this frame itself in the output | |
289 | if ( nLevel >= skip ) | |
290 | { | |
291 | wxStackFrame frame(nLevel - skip, | |
292 | (void *)sf.AddrPC.Offset, | |
293 | sf.AddrFrame.Offset); | |
294 | ||
295 | OnStackFrame(frame); | |
296 | } | |
297 | } | |
298 | ||
299 | // this results in crashes inside ntdll.dll when called from | |
300 | // exception handler ... | |
301 | #if 0 | |
302 | if ( !wxDbgHelpDLL::SymCleanup(hProcess) ) | |
303 | { | |
304 | wxDbgHelpDLL::LogError(_T("SymCleanup")); | |
305 | } | |
306 | #endif | |
307 | } | |
308 | ||
309 | void wxStackWalker::WalkFrom(const _EXCEPTION_POINTERS *ep, size_t skip) | |
310 | { | |
311 | WalkFrom(ep->ContextRecord, skip); | |
312 | } | |
313 | ||
314 | void wxStackWalker::WalkFromException() | |
315 | { | |
316 | extern EXCEPTION_POINTERS *wxGlobalSEInformation; | |
317 | ||
318 | wxCHECK_RET( wxGlobalSEInformation, | |
319 | _T("wxStackWalker::WalkFromException() can only be called from wxApp::OnFatalException()") ); | |
320 | ||
321 | // don't skip any frames, the first one is where we crashed | |
322 | WalkFrom(wxGlobalSEInformation, 0); | |
323 | } | |
324 | ||
325 | void wxStackWalker::Walk(size_t skip) | |
326 | { | |
327 | // to get a CONTEXT for the current location, simply force an exception and | |
328 | // get EXCEPTION_POINTERS from it | |
329 | // | |
330 | // note: | |
331 | // 1. we additionally skip RaiseException() and WalkFromException() frames | |
332 | // 2. explicit cast to EXCEPTION_POINTERS is needed with VC7.1 even if it | |
333 | // shouldn't have been according to the docs | |
334 | __try | |
335 | { | |
336 | RaiseException(0x1976, 0, 0, NULL); | |
337 | } | |
338 | __except( WalkFrom((EXCEPTION_POINTERS *)GetExceptionInformation(), | |
339 | skip + 2), EXCEPTION_CONTINUE_EXECUTION ) | |
340 | { | |
341 | // never executed because of WalkFromException() return value | |
342 | } | |
343 | } | |
344 | ||
345 | #else // !wxUSE_DBGHELP | |
346 | ||
347 | // TODO: implement stubs | |
348 | ||
349 | #endif // wxUSE_DBGHELP/!wxUSE_DBGHELP | |
350 | ||
351 | #endif // wxUSE_STACKWALKER | |
352 |