NCBI C++ ToolKit
ncbi_stack_win64.cpp
Go to the documentation of this file.

Go to the SVN repository for this file.

1 /* $Id: ncbi_stack_win64.cpp 87307 2019-08-16 16:59:06Z vasilche $
2  * ===========================================================================
3  *
4  * PUBLIC DOMAIN NOTICE
5  * National Center for Biotechnology Information
6  *
7  * This software/database is a "United States Government Work" under the
8  * terms of the United States Copyright Act. It was written as part of
9  * the author's official duties as a United States Government employee and
10  * thus cannot be copyrighted. This software/database is freely available
11  * to the public for use. The National Library of Medicine and the U.S.
12  * Government have not placed any restriction on its use or reproduction.
13  *
14  * Although all reasonable efforts have been taken to ensure the accuracy
15  * and reliability of the software and data, the NLM and the U.S.
16  * Government do not and cannot warrant the performance or results that
17  * may be obtained by using this software or data. The NLM and the U.S.
18  * Government disclaim all warranties, express or implied, including
19  * warranties of performance, merchantability or fitness for any particular
20  * purpose.
21  *
22  * Please cite the author in any work or product based on this material.
23  *
24  * ===========================================================================
25  *
26  * Authors: Mike DiCuccio
27  *
28  * File Description:
29  *
30  */
31 
32 #include <ncbi_pch.hpp>
33 #include <corelib/ncbiapp_api.hpp>
34 #include <corelib/ncbidll.hpp>
36 #include <corelib/error_codes.hpp>
37 
38 #include <windows.h>
39 #include <winnt.h>
40 #include <dbghelp.h>
41 
42 
43 #define NCBI_USE_ERRCODE_X Corelib_Stack
44 
45 
47 
48 
49 #define lenof(a) (sizeof(a) / sizeof((a)[0]))
50 #define MAXNAMELEN 1024 // max name length for found symbols
51 #define IMGSYMLEN (sizeof IMAGEHLP_SYMBOL64)
52 #define TTBUFLEN 65535
53 
54 
55 struct SModuleEntry
56 {
59  DWORD64 baseAddress;
60  DWORD size;
61 };
62 
63 typedef vector<SModuleEntry> TModules;
64 typedef TModules::iterator ModuleListIter;
65 
66 
67 // declarations for PSAPI
68 // this won't be present on all systems, so we include them here
69 
70 typedef struct _MODULEINFO {
75 
76 
77 static bool s_FillModuleListPSAPI(TModules& mods, DWORD pid, HANDLE hProcess)
78 {
79  try {
80  // EnumProcessModules()
81  typedef BOOL (__stdcall *FEnumProcessModules)(HANDLE hProcess,
82  HMODULE *lphModule,
83  DWORD cb,
84  LPDWORD lpcbNeeded);
85  // GetModuleFileNameEx()
86  typedef DWORD (__stdcall *FGetModuleFileNameEx)(HANDLE hProcess,
87  HMODULE hModule,
88  LPSTR lpFilename,
89  DWORD nSize);
90  // GetModuleBaseName() -- redundant, as GMFNE() has the same prototype, but who cares?
91  typedef DWORD (__stdcall *FGetModuleBaseName)(HANDLE hProcess,
92  HMODULE hModule,
93  LPSTR lpFilename,
94  DWORD nSize);
95  // GetModuleInformation()
96  typedef BOOL (__stdcall *FGetModuleInformation)(HANDLE hProcess,
97  HMODULE hModule,
98  LPMODULEINFO pmi,
99  DWORD nSize);
100 
101  FEnumProcessModules EnumProcessModules;
102  FGetModuleFileNameEx GetModuleFileNameEx;
103  FGetModuleBaseName GetModuleBaseName;
104  FGetModuleInformation GetModuleInformation;
105 
106  mods.clear();
107  CDll dll("psapi.dll", CDll::eLoadNow, CDll::eAutoUnload);
108 
109  EnumProcessModules =
110  dll.GetEntryPoint_Func("EnumProcessModules",
111  &EnumProcessModules);
112  GetModuleFileNameEx =
113  dll.GetEntryPoint_Func("GetModuleFileNameExA",
114  &GetModuleFileNameEx);
115  GetModuleBaseName =
116  dll.GetEntryPoint_Func("GetModuleBaseNameA",
117  &GetModuleBaseName);
118  GetModuleInformation =
119  dll.GetEntryPoint_Func("GetModuleInformation",
120  &GetModuleInformation);
121 
122  if ( !EnumProcessModules ||
123  !GetModuleFileNameEx ||
124  !GetModuleBaseName ||
125  !GetModuleInformation ) {
126  return false;
127  }
128 
129  vector<HMODULE> modules;
130  modules.resize(4096);
131 
132  string tmp;
133  DWORD needed;
134  if ( !EnumProcessModules(hProcess,
135  &modules[0],
136  DWORD(modules.size()*sizeof(HMODULE)),
137  &needed) ) {
138  NCBI_THROW(CCoreException, eCore,
139  "EnumProcessModules() failed");
140  }
141 
142  if ( needed > modules.size() * sizeof(HMODULE)) {
143  NCBI_THROW(CCoreException, eCore,
144  string("More than ") +
145  NStr::Int8ToString(modules.size()) + " modules");
146  }
147 
148 
149  needed /= sizeof(HMODULE);
150  for (size_t i = 0; i < needed; ++i) {
151  // for each module, get:
152  // base address, size
153  MODULEINFO mi;
154  GetModuleInformation(hProcess, modules[i], &mi, sizeof(mi));
155 
156  SModuleEntry e;
157  e.baseAddress = (DWORD64)mi.lpBaseOfDll;
158  e.size = mi.SizeOfImage;
159 
160  // image file name
161  char tt[2048];
162  tt[0] = '\0';
163  GetModuleFileNameEx(hProcess, modules[i], tt, sizeof(tt));
164  e.imageName = tt;
165 
166  // module name
167  tt[0] = '\0';
168  GetModuleBaseName(hProcess, modules[i], tt, sizeof(tt));
169  e.moduleName = tt;
170 
171  mods.push_back(e);
172  }
173 
174  return true;
175  }
176  catch (exception& e) {
177  ERR_POST_X(3, Error << "Error getting PSAPI symbols: " << e.what());
178  }
179  catch (...) {
180  }
181 
182  return false;
183 }
184 
185 
186 static bool s_FillModuleList(TModules& modules, DWORD pid, HANDLE hProcess)
187 {
188  return s_FillModuleListPSAPI(modules, pid, hProcess);
189 }
190 
191 
192 class CSymbolGuard
193 {
194 public:
197 
198  void UpdateSymbols(void);
199 private:
200  // Remember loaded modules
202 
204 };
205 
206 
208 {
209  // our current process and thread within that process
210  HANDLE curr_proc = GetCurrentProcess();
211 
212  try {
213  string search_path(CDir::GetCwd());
214  string tmp;
215  tmp.resize(2048);
216  if (GetModuleFileNameA(0, const_cast<char*>(tmp.data()),
217  DWORD(tmp.length()))) {
218  string::size_type pos = tmp.find_last_of("\\/");
219  if (pos != string::npos) {
220  tmp.erase(pos);
221  }
222  search_path = tmp + ';' + search_path;
223  }
224 
225  const char* ptr = getenv("_NT_SYMBOL_PATH");
226  if (ptr) {
227  string tmp(ptr);
228  search_path = tmp + ';' + search_path;
229  }
230  ptr = getenv("_NT_ALTERNATE_SYMBOL_PATH");
231  if (ptr) {
232  string tmp(ptr);
233  search_path = tmp + ';' + search_path;
234  }
235  ptr = getenv("SYSTEMROOT");
236  if (ptr) {
237  string tmp(ptr);
238  search_path = tmp + ';' + search_path;
239  }
240 
241  // init symbol handler stuff (SymInitialize())
242  if ( !SymInitialize(curr_proc,
243  const_cast<char*>(search_path.c_str()),
244  false) ) {
245  NCBI_THROW(CCoreException, eCore, "SymInitialize() failed");
246  }
247 
248  // set up default options
249  DWORD symOptions = SymGetOptions();
250  symOptions &= ~SYMOPT_UNDNAME;
251  symOptions |= SYMOPT_LOAD_LINES;
252  SymSetOptions(symOptions);
253 
254  // pre-load our symbols
255  UpdateSymbols();
256  }
257  catch (exception& e) {
258  ERR_POST_X(4, Error << "Error loading symbols for stack trace: "
259  << e.what());
260  }
261  catch (...) {
262  ERR_POST_X(5, Error
263  << "Unknown error initializing symbols for stack trace.");
264  }
265 }
266 
267 
269 {
270  SymCleanup(GetCurrentProcess());
271 }
272 
273 
275 {
276  HANDLE proc = GetCurrentProcess();
277  DWORD pid = GetCurrentProcessId();
278 
279  // Enumerate modules and tell dbghelp.dll about them.
280  // On NT, this is not necessary, but it won't hurt.
281  TModules modules;
282 
283  // fill in module list
284  s_FillModuleList(modules, pid, proc);
285 
286  NON_CONST_ITERATE (TModules, it, modules) {
287  TLoadedModules::const_iterator module = m_Loaded.find(it->moduleName);
288  if (module != m_Loaded.end()) {
289  continue;
290  }
291  DWORD64 module_addr = SymLoadModule64(proc, 0,
292  const_cast<char*>(it->imageName.c_str()),
293  const_cast<char*>(it->moduleName.c_str()),
294  it->baseAddress, it->size);
295  if ( !module_addr ) {
296  ERR_POST_X(6, Error << "Error loading symbols for module: "
297  << it->moduleName);
298  } else {
299  _TRACE("Loaded symbols from " << it->moduleName);
300  m_Loaded.insert(it->moduleName);
301  }
302  }
303 }
304 
305 
307 
308 
309 class CStackTraceImpl
310 {
311 public:
314 
316 
317 private:
318  typedef STACKFRAME64 TStackFrame;
319  typedef vector<TStackFrame> TStack;
320 
321  TStack m_Stack;
322 };
323 
324 
326 {
327  s_SymbolGuard->UpdateSymbols();
328 
329  HANDLE curr_proc = GetCurrentProcess();
330  HANDLE thread = GetCurrentThread();
331 
332  // we could use ImageNtHeader() here instead
333  DWORD img_type = IMAGE_FILE_MACHINE_AMD64;
334 
335  // init CONTEXT record so we know where to start the stackwalk
336  CONTEXT c;
337  RtlCaptureContext(&c);
338 
339  // current stack frame
340  STACKFRAME64 s;
341  memset(&s, 0, sizeof s);
342  s.AddrPC.Offset = c.Rip;
343  s.AddrPC.Mode = AddrModeFlat;
344  s.AddrFrame.Offset = c.Rbp;
345  s.AddrFrame.Mode = AddrModeFlat;
346  s.AddrStack.Offset = c.Rsp;
347  s.AddrStack.Mode = AddrModeFlat;
348 
349  try {
350  unsigned int max_depth = CStackTrace::s_GetStackTraceMaxDepth();
351  for (size_t frame = 0; frame <= max_depth; ++frame) {
352  // get next stack frame
353  if ( !StackWalk64(img_type, curr_proc, thread, &s, &c, NULL,
354  SymFunctionTableAccess64,
355  SymGetModuleBase64,
356  NULL) ) {
357  break;
358  }
359 
360  // Discard the top frames describing current function
361  if (frame < 1) {
362  continue;
363  }
364 
365  // Try to skip bad frames - not very reliable.
366  if ( !s.AddrPC.Offset || !s.AddrReturn.Offset ) {
367  continue;
368  }
369  if (s.AddrPC.Offset == s.AddrReturn.Offset) {
370  continue;
371  }
372 
373  m_Stack.push_back(s);
374  }
375  }
376  catch (exception& e) {
377  ERR_POST_X(7, Error << "Error getting stack trace: " << e.what());
378  }
379  catch (...) {
380  ERR_POST_X(8, Error << "Unknown error getting stack trace");
381  }
382 }
383 
384 
386 {
387 }
388 
389 
391 {
392  if ( m_Stack.empty() ) {
393  return;
394  }
395 
396  s_SymbolGuard->UpdateSymbols();
397 
398  HANDLE curr_proc = GetCurrentProcess();
399 
400  IMAGEHLP_SYMBOL64 *pSym = NULL;
401  pSym = (IMAGEHLP_SYMBOL64 *) malloc(IMGSYMLEN + MAXNAMELEN);
402  memset(pSym, 0, IMGSYMLEN + MAXNAMELEN);
403  pSym->SizeOfStruct = IMGSYMLEN;
404  pSym->MaxNameLength = MAXNAMELEN;
405 
406  IMAGEHLP_LINE64 Line;
407  memset(&Line, 0, sizeof Line);
408  Line.SizeOfStruct = sizeof(Line);
409 
410  IMAGEHLP_MODULE64 Module;
411  memset(&Module, 0, sizeof Module);
412  Module.SizeOfStruct = sizeof(Module);
413 
414  DWORD64 offs64 = 0;
415  DWORD offs32 = 0;
416 
417  try {
418  ITERATE(TStack, it, m_Stack) {
419  CStackTrace::SStackFrameInfo sf_info((void*)it->AddrPC.Offset);
420  sf_info.func = "<cannot get function name for this address>";
421 
422  if ( !SymGetSymFromAddr64(curr_proc, it->AddrPC.Offset, &offs64, pSym) ) {
423  // Showing unresolvable frames may produce thousands of them
424  // per one stacktrace on win64.
425  //stack.push_back(sf_info);
426  continue;
427  }
428  sf_info.offs = offs64;
429 
430  // retrieve function names, if we can
431  //char undName[MAXNAMELEN];
432  char undFullName[MAXNAMELEN];
433  //UnDecorateSymbolName(pSym->Name, undName,
434  // MAXNAMELEN, UNDNAME_NAME_ONLY);
435  UnDecorateSymbolName(pSym->Name, undFullName,
436  MAXNAMELEN, UNDNAME_COMPLETE);
437 
438  sf_info.func = undFullName;
439 
440  // retrieve file and line number info
441  if (SymGetLineFromAddr(curr_proc,
442  it->AddrPC.Offset,
443  &offs32,
444  &Line)) {
445  sf_info.file = Line.FileName;
446  sf_info.line = Line.LineNumber;
447  } else {
448  _TRACE("failed to get line number for " << sf_info.func);
449  }
450 
451  // get library info, if it is available
452  if ( !SymGetModuleInfo(curr_proc,
453  it->AddrPC.Offset,
454  &Module) ) {
455  // There are too many fails on win64, lower the severity.
456  ERR_POST_X(10, Info << "failed to get module info for "
457  << sf_info.func);
458  } else {
459  sf_info.module = Module.ModuleName;
460  sf_info.module += "[";
461  sf_info.module += Module.ImageName;
462  sf_info.module += "]";
463  }
464 
465  stack.push_back(sf_info);
466  }
467  }
468  catch (exception& e) {
469  ERR_POST_X(11, Error << "Error getting stack trace: " << e.what());
470  }
471  catch (...) {
472  ERR_POST_X(12, Error << "Unknown error getting stack trace");
473  }
474 
475  free(pSym);
476 }
477 
478 
CCoreException –.
Definition: ncbiexpt.hpp:1476
CDll –.
Definition: ncbidll.hpp:107
CSafeStaticPtr<>::
~CStackTraceImpl(void)
backward::StackTrace TStack
STACKFRAME64 TStackFrame
void Expand(CStackTrace::TStack &stack)
vector< TStackFrame > TStack
list< SStackFrameInfo > TStack
Definition: ncbi_stack.hpp:78
static unsigned int s_GetStackTraceMaxDepth(void)
Definition: ncbi_stack.cpp:158
TLoadedModules m_Loaded
CSymbolGuard(void)
~CSymbolGuard(void)
void UpdateSymbols(void)
set< string > TLoadedModules
Definition: svg.hpp:432
Module manipulator.
Definition: ncbiexpt.hpp:1218
iterator_bool insert(const value_type &val)
Definition: set.hpp:149
const_iterator find(const key_type &key) const
Definition: set.hpp:137
const_iterator end() const
Definition: set.hpp:136
parent_type::const_iterator const_iterator
Definition: set.hpp:79
static const char * proc
Definition: stats.c:21
static char tmp[3200]
Definition: utf8.c:42
char * LPSTR
Definition: sqlfront.h:34
void * LPVOID
Definition: sqlfront.h:38
#define ITERATE(Type, Var, Cont)
ITERATE macro to sequence through container elements.
Definition: ncbimisc.hpp:815
#define NON_CONST_ITERATE(Type, Var, Cont)
Non constant version of ITERATE macro.
Definition: ncbimisc.hpp:822
string
Definition: cgiapp.hpp:690
#define NULL
Definition: ncbistd.hpp:225
#define _TRACE(message)
Definition: ncbidbg.hpp:122
#define ERR_POST_X(err_subcode, message)
Error posting with default error code and given error subcode.
Definition: ncbidiag.hpp:550
TFunc GetEntryPoint_Func(const string &name, TFunc *func)
Get DLLs entry point (function).
Definition: ncbidll.hpp:273
@ eAutoUnload
Definition: ncbidll.hpp:144
@ eLoadNow
Definition: ncbidll.hpp:138
void Error(CExceptionArgs_Base &args)
Definition: ncbiexpt.hpp:1197
#define NCBI_THROW(exception_class, err_code, message)
Generic macro to throw an exception, given the exception class, error code and message string.
Definition: ncbiexpt.hpp:704
void Info(CExceptionArgs_Base &args)
Definition: ncbiexpt.hpp:1185
static string GetCwd(void)
Get the current working directory.
Definition: ncbifile.cpp:3708
#define END_NCBI_SCOPE
End previously defined NCBI scope.
Definition: ncbistl.hpp:103
#define BEGIN_NCBI_SCOPE
Define ncbi namespace.
Definition: ncbistl.hpp:100
static string Int8ToString(Int8 value, TNumToStringFlags flags=0, int base=10)
Convert Int8 to string.
Definition: ncbistr.hpp:5153
#define HANDLE
An abstraction for a file handle.
Definition: mdb.c:383
Definition of all error codes used in corelib (xncbi.lib).
int i
BOOL(WINAPI * FEnumProcessModules)(HANDLE hProcess, HMODULE *lphModule, DWORD cb, LPDWORD lpcbNeeded)
Static variables safety - create on demand, destroy on application termination.
vector< SModuleEntry > TModules
#define IMGSYMLEN
struct _MODULEINFO MODULEINFO
vector< SModuleEntry > TModules
static bool s_FillModuleListPSAPI(TModules &mods, DWORD pid, HANDLE hProcess)
#define MAXNAMELEN
static bool s_FillModuleList(TModules &modules, DWORD pid, HANDLE hProcess)
static CSafeStaticPtr< CSymbolGuard > s_SymbolGuard
struct _MODULEINFO * LPMODULEINFO
TModules::iterator ModuleListIter
Define class Dll and for Portable DLL handling.
#define BOOL
Definition: odbcinst.h:18
DWORD * LPDWORD
Definition: sqltypes.h:113
unsigned int DWORD
Definition: sqltypes.h:98
Structure for holding stack trace data.
Definition: ncbi_stack.hpp:51
std::string moduleName
std::string imageName
void free(voidpf ptr)
voidp malloc(uInt size)
Modified on Fri Sep 20 14:57:01 2024 by modify_doxy.py rev. 669887