Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

740 lines
17 KiB

  1. // Copyright (c) 1997-1999 Microsoft Corporation
  2. //
  3. // stack backtracing stuff
  4. //
  5. // 22-Nov-1999 sburns (refactored)
  6. #include "headers.hxx"
  7. #include <strsafe.h>
  8. // prevent us from calling ASSERT in this file: use RTLASSERT instead
  9. #ifdef ASSERT
  10. #undef ASSERT
  11. #endif
  12. // Since we call some of this code from Burnslib::FireAssertionFailure,
  13. // we use our own even more private ASSERT
  14. #if DBG
  15. #define RTLASSERT( exp ) \
  16. if (!(exp)) \
  17. RtlAssert( #exp, __FILE__, __LINE__, NULL )
  18. #else
  19. #define RTLASSERT( exp )
  20. #endif // DBG
  21. static HMODULE imageHelpDll = 0;
  22. // function pointers to be dynamically resolved by the Initialize function.
  23. typedef DWORD (*SymSetOptionsFunc)(DWORD);
  24. static SymSetOptionsFunc MySymSetOptions = 0;
  25. typedef BOOL (*SymInitializeFunc)(HANDLE, PSTR, BOOL);
  26. static SymInitializeFunc MySymInitialize = 0;
  27. typedef BOOL (*SymCleanupFunc)(HANDLE);
  28. static SymCleanupFunc MySymCleanup = 0;
  29. typedef BOOL (*SymGetModuleInfoFunc)(HANDLE, DWORD64, PIMAGEHLP_MODULE64);
  30. static SymGetModuleInfoFunc MySymGetModuleInfo = 0;
  31. typedef BOOL (*SymGetLineFromAddrFunc)(HANDLE, DWORD64, PDWORD, PIMAGEHLP_LINE64);
  32. static SymGetLineFromAddrFunc MySymGetLineFromAddr = 0;
  33. typedef BOOL (*StackWalkFunc)(
  34. DWORD, HANDLE, HANDLE, LPSTACKFRAME64, PVOID,
  35. PREAD_PROCESS_MEMORY_ROUTINE64, PFUNCTION_TABLE_ACCESS_ROUTINE64,
  36. PGET_MODULE_BASE_ROUTINE64, PTRANSLATE_ADDRESS_ROUTINE64);
  37. static StackWalkFunc MyStackWalk = 0;
  38. typedef BOOL (*SymGetSymFromAddrFunc)(
  39. HANDLE, DWORD64, PDWORD64, PIMAGEHLP_SYMBOL64);
  40. static SymGetSymFromAddrFunc MySymGetSymFromAddr = 0;
  41. typedef PVOID (*SymFunctionTableAccess64Func)(HANDLE, DWORD64);
  42. static SymFunctionTableAccess64Func MySymFunctionTableAccess64 = 0;
  43. typedef DWORD64 (*SymGetModuleBase64Func)(HANDLE, DWORD64);
  44. static SymGetModuleBase64Func MySymGetModuleBase64 = 0;
  45. namespace Burnslib
  46. {
  47. namespace StackTrace
  48. {
  49. // This must be called before any of the other functions in this
  50. // namespace
  51. void
  52. Initialize();
  53. bool
  54. IsInitialized()
  55. {
  56. return imageHelpDll != 0;
  57. }
  58. }
  59. } // namespace Burnslib
  60. // Determines the path to the parent folder of the binary from whence this
  61. // process was loaded.
  62. //
  63. // returns 0 on failure.
  64. //
  65. // Caller needs to free the result with delete[].
  66. char*
  67. GetModuleFolderPath()
  68. {
  69. HRESULT hr = S_OK;
  70. char* result = 0;
  71. do
  72. {
  73. result = new char[MAX_PATH + 1];
  74. ::ZeroMemory(result, MAX_PATH + 1);
  75. char tempBuf[MAX_PATH + 1] = {0};
  76. DWORD res = ::GetModuleFileNameA(0, tempBuf, MAX_PATH);
  77. if (res != 0)
  78. {
  79. char driveBuf[_MAX_DRIVE] = {0};
  80. char folderBuf[_MAX_DIR] = {0};
  81. _splitpath(tempBuf, driveBuf, folderBuf, 0, 0);
  82. char* end1 = 0;
  83. hr = StringCchCatExA(result, MAX_PATH, driveBuf, &end1, 0, 0);
  84. BREAK_ON_FAILED_HRESULT(hr);
  85. RTLASSERT(end1 < result + MAX_PATH);
  86. char* end2 = 0;
  87. hr = StringCchCatExA(result, MAX_PATH, folderBuf, &end2, 0, 0);
  88. BREAK_ON_FAILED_HRESULT(hr);
  89. RTLASSERT(end2 < result + MAX_PATH);
  90. if (end2 - end1 > 1 && *(end2 - 1) == '\\')
  91. {
  92. // the folder is not the root folder, which means it also has a
  93. // trailing \ which we want to remove
  94. *(end2 - 1) = 0;
  95. }
  96. }
  97. else
  98. {
  99. hr = HRESULT_FROM_WIN32(::GetLastError());
  100. }
  101. }
  102. while (0);
  103. if (FAILED(hr))
  104. {
  105. delete[] result;
  106. result = 0;
  107. }
  108. return result;
  109. }
  110. // Expands an environment variable. Returns 0 on error. Caller must free
  111. // the result with delete[]
  112. char*
  113. ExpandEnvironmentVar(const char* var)
  114. {
  115. RTLASSERT(var);
  116. RTLASSERT(*var);
  117. // determine the length of the expanded string
  118. DWORD len = ::ExpandEnvironmentStringsA(var, 0, 0);
  119. RTLASSERT(len);
  120. if (!len)
  121. {
  122. return 0;
  123. }
  124. char* result = new char[len + 1];
  125. // REVIEWED-2002/03/14-sburns correct byte count passed
  126. ::ZeroMemory(result, len + 1);
  127. DWORD len1 =
  128. ::ExpandEnvironmentStringsA(
  129. var,
  130. result,
  131. // REVIEWED-2002/03/14-sburns correct character count passed
  132. len);
  133. RTLASSERT(len1 + 1 == len);
  134. if (!len1)
  135. {
  136. delete[] result;
  137. return 0;
  138. }
  139. return result;
  140. }
  141. void
  142. InitHelper()
  143. {
  144. // we want to look for symbols first in the folder the app started from,
  145. // then on %_NT_SYMBOL_PATH%;%_NT_ALTERNATE_SYMBOL_PATH%;
  146. // MAX_PATH * 3 for load + sym + alt sym, + 2 for semis, and +1 for null
  147. static const size_t PATH_BUF_SIZE = MAX_PATH * 3 + 2 + 1;
  148. char* symSearchPath = new char[PATH_BUF_SIZE];
  149. // REVIEWED-2002/03/14-sburns correct byte count passed
  150. ::ZeroMemory(symSearchPath, PATH_BUF_SIZE);
  151. HRESULT hr = S_OK;
  152. do
  153. {
  154. char* moduleFolderPath = GetModuleFolderPath();
  155. char* end = 0;
  156. hr =
  157. StringCchCatExA(
  158. symSearchPath,
  159. // -1 to make sure that there's space for a semi at the end
  160. PATH_BUF_SIZE - 1,
  161. moduleFolderPath,
  162. &end,
  163. 0,
  164. STRSAFE_IGNORE_NULLS);
  165. delete[] moduleFolderPath;
  166. BREAK_ON_FAILED_HRESULT(hr);
  167. // Since we know there will be space for it, just poke in a semi
  168. *end = ';';
  169. char* env = ExpandEnvironmentVar("%_NT_SYMBOL_PATH%");
  170. if (env)
  171. {
  172. end = 0;
  173. hr =
  174. StringCchCatExA(
  175. symSearchPath,
  176. // -1 to make sure that there's space for a semi at the end
  177. PATH_BUF_SIZE - 1,
  178. env,
  179. &end,
  180. 0,
  181. STRSAFE_IGNORE_NULLS);
  182. delete[] env;
  183. if (SUCCEEDED(hr))
  184. {
  185. // Since we know there will be space for it, just poke in a semi
  186. *end = ';';
  187. }
  188. else
  189. {
  190. // even if this part of the path is absent, use the others
  191. hr = S_OK;
  192. }
  193. }
  194. env = ExpandEnvironmentVar("%_NT_ALTERNATE_SYMBOL_PATH%");
  195. if (env)
  196. {
  197. end = 0;
  198. // return code unchecked because even if this part of the path is
  199. // absent, we will use the others
  200. (void) StringCchCatExA(
  201. symSearchPath,
  202. PATH_BUF_SIZE,
  203. env,
  204. &end,
  205. 0,
  206. STRSAFE_IGNORE_NULLS);
  207. delete[] env;
  208. }
  209. }
  210. while (0);
  211. BOOL succeeded =
  212. MySymInitialize(
  213. ::GetCurrentProcess(),
  214. SUCCEEDED(hr) ? symSearchPath : 0,
  215. TRUE);
  216. RTLASSERT(succeeded);
  217. delete[] symSearchPath;
  218. }
  219. void
  220. Burnslib::StackTrace::Initialize()
  221. {
  222. RTLASSERT(!IsInitialized());
  223. // load the dbghelp dll -- not the imagehlp dll. The latter is merely a
  224. // delayload-enabled wrapper of dbghelp, and in low-resource situations
  225. // loading imagehlp will succeed, but the its delayload of dbghelp will
  226. // fail, leading to calls to stubs that do nothing.
  227. // NTRAID#NTBUG9-572904-2002/03/12-sburns
  228. imageHelpDll = static_cast<HMODULE>(::LoadLibrary(L"dbghelp.dll"));
  229. if (!imageHelpDll)
  230. {
  231. return;
  232. }
  233. // resolve the function pointers
  234. MySymSetOptions =
  235. reinterpret_cast<SymSetOptionsFunc>(
  236. ::GetProcAddress(imageHelpDll, "SymSetOptions"));
  237. MySymInitialize =
  238. reinterpret_cast<SymInitializeFunc>(
  239. ::GetProcAddress(imageHelpDll, "SymInitialize"));
  240. MySymCleanup =
  241. reinterpret_cast<SymCleanupFunc>(
  242. ::GetProcAddress(imageHelpDll, "SymCleanup"));
  243. MySymGetModuleInfo =
  244. reinterpret_cast<SymGetModuleInfoFunc>(
  245. ::GetProcAddress(imageHelpDll, "SymGetModuleInfo64"));
  246. MySymGetLineFromAddr =
  247. reinterpret_cast<SymGetLineFromAddrFunc>(
  248. ::GetProcAddress(imageHelpDll, "SymGetLineFromAddr64"));
  249. MyStackWalk =
  250. reinterpret_cast<StackWalkFunc>(
  251. ::GetProcAddress(imageHelpDll, "StackWalk64"));
  252. MySymGetSymFromAddr =
  253. reinterpret_cast<SymGetSymFromAddrFunc>(
  254. ::GetProcAddress(imageHelpDll, "SymGetSymFromAddr64"));
  255. MySymFunctionTableAccess64 =
  256. reinterpret_cast<SymFunctionTableAccess64Func>(
  257. ::GetProcAddress(imageHelpDll, "SymFunctionTableAccess64"));
  258. MySymGetModuleBase64 =
  259. reinterpret_cast<SymGetModuleBase64Func>(
  260. ::GetProcAddress(imageHelpDll, "SymGetModuleBase64"));
  261. if (
  262. !MySymSetOptions
  263. || !MySymInitialize
  264. || !MySymCleanup
  265. || !MySymGetModuleInfo
  266. || !MySymGetLineFromAddr
  267. || !MyStackWalk
  268. || !MySymGetSymFromAddr
  269. || !MySymFunctionTableAccess64
  270. || !MySymGetModuleBase64)
  271. {
  272. return;
  273. }
  274. // Init the stack trace facilities
  275. //lint -e(534) we're not interested in the return value.
  276. MySymSetOptions(
  277. SYMOPT_DEFERRED_LOADS
  278. | SYMOPT_UNDNAME
  279. | SYMOPT_LOAD_LINES);
  280. InitHelper();
  281. }
  282. void
  283. Burnslib::StackTrace::Cleanup()
  284. {
  285. if (IsInitialized())
  286. {
  287. BOOL succeeded = MySymCleanup(::GetCurrentProcess());
  288. RTLASSERT(succeeded);
  289. ::FreeLibrary(imageHelpDll);
  290. imageHelpDll = 0;
  291. }
  292. }
  293. // a SEH filter function that walks the stack, and stuffs the offset pointers
  294. // into the provided array.
  295. DWORD
  296. GetStackTraceFilter(
  297. DWORD64 stackTrace[],
  298. size_t traceMax,
  299. CONTEXT* context,
  300. size_t levelsToSkip)
  301. {
  302. RTLASSERT(Burnslib::StackTrace::IsInitialized());
  303. RTLASSERT(MyStackWalk);
  304. RTLASSERT(context);
  305. // REVIEWED: correct byte count passed
  306. ::ZeroMemory(stackTrace, traceMax * sizeof DWORD64);
  307. if (!MyStackWalk)
  308. {
  309. // initialization failed in some way, so do nothing.
  310. return EXCEPTION_EXECUTE_HANDLER;
  311. }
  312. STACKFRAME64 frame;
  313. DWORD dwMachineType;
  314. // REVIEWED: correct byte count passed
  315. ::ZeroMemory(&frame, sizeof frame);
  316. #if defined(_M_IX86)
  317. dwMachineType = IMAGE_FILE_MACHINE_I386;
  318. frame.AddrPC.Offset = context->Eip;
  319. frame.AddrPC.Mode = AddrModeFlat;
  320. frame.AddrFrame.Offset = context->Ebp;
  321. frame.AddrFrame.Mode = AddrModeFlat;
  322. frame.AddrStack.Offset = context->Esp;
  323. frame.AddrStack.Mode = AddrModeFlat;
  324. #elif defined(_M_AMD64)
  325. dwMachineType = IMAGE_FILE_MACHINE_AMD64;
  326. frame.AddrPC.Offset = context->Rip;
  327. frame.AddrPC.Mode = AddrModeFlat;
  328. frame.AddrStack.Offset = context->Rsp;
  329. frame.AddrStack.Mode = AddrModeFlat;
  330. #elif defined(_M_IA64)
  331. dwMachineType = IMAGE_FILE_MACHINE_IA64;
  332. frame.AddrPC.Offset = context->StIIP;
  333. frame.AddrPC.Mode = AddrModeFlat;
  334. frame.AddrStack.Offset = context->IntSp;
  335. frame.AddrStack.Mode = AddrModeFlat;
  336. #else
  337. #error( "unknown target machine" );
  338. #endif
  339. HANDLE process = ::GetCurrentProcess();
  340. HANDLE thread = ::GetCurrentThread();
  341. // On ia64, the context struct can be whacked by StackWalk64 (thanks to
  342. // drewb for cluing me in to that subtle point). If the context record is a
  343. // pointer to the one gathered from GetExceptionInformation, whacking it is
  344. // a Very Bad Thing To Do. Stack corruption results. So in order to get
  345. // successive calls to work, we have to copy the struct, and let the copy
  346. // get whacked.
  347. CONTEXT dupContext;
  348. // REVIEWED-2002/03/06-sburns correct byte count passed.
  349. ::CopyMemory(&dupContext, context, sizeof dupContext);
  350. for (size_t i = 0, top = 0; top < traceMax; ++i)
  351. {
  352. BOOL result =
  353. MyStackWalk(
  354. dwMachineType,
  355. process,
  356. thread,
  357. &frame,
  358. &dupContext,
  359. 0,
  360. MySymFunctionTableAccess64,
  361. MySymGetModuleBase64,
  362. 0);
  363. if (!result)
  364. {
  365. break;
  366. }
  367. // skip the n most recent frames
  368. if (i >= levelsToSkip)
  369. {
  370. stackTrace[top++] = frame.AddrPC.Offset;
  371. }
  372. }
  373. return EXCEPTION_EXECUTE_HANDLER;
  374. }
  375. DWORD
  376. Burnslib::StackTrace::TraceFilter(
  377. DWORD64 stackTrace[],
  378. size_t traceMax,
  379. CONTEXT* context)
  380. {
  381. RTLASSERT(stackTrace);
  382. RTLASSERT(traceMax);
  383. RTLASSERT(context);
  384. if (!Burnslib::StackTrace::IsInitialized())
  385. {
  386. Burnslib::StackTrace::Initialize();
  387. }
  388. return
  389. GetStackTraceFilter(stackTrace, traceMax, context, 0);
  390. }
  391. void
  392. Burnslib::StackTrace::Trace(DWORD64 stackTrace[], size_t traceMax)
  393. {
  394. RTLASSERT(stackTrace);
  395. RTLASSERT(traceMax);
  396. if (!Burnslib::StackTrace::IsInitialized())
  397. {
  398. Burnslib::StackTrace::Initialize();
  399. }
  400. // the only way to get the context of a running thread is to raise an
  401. // exception....
  402. __try
  403. {
  404. RaiseException(0, 0, 0, 0);
  405. }
  406. __except (
  407. GetStackTraceFilter(
  408. stackTrace,
  409. traceMax,
  410. //lint --e(*) GetExceptionInformation is like a compiler intrinsic
  411. (GetExceptionInformation())->ContextRecord,
  412. // skip the 2 most recent function calls, as those correspond to
  413. // this function itself.
  414. 2))
  415. {
  416. // do nothing in the handler
  417. }
  418. }
  419. // ISSUE-2002/03/06-sburns consider replacing with strsafe function
  420. // strncpy that will not overflow the buffer.
  421. inline
  422. void
  423. SafeStrncpy(char* dest, const char* src, size_t bufmax)
  424. {
  425. ::ZeroMemory(dest, bufmax);
  426. strncpy(dest, src, bufmax - 1);
  427. }
  428. void
  429. Burnslib::StackTrace::LookupAddress(
  430. DWORD64 traceAddress,
  431. char moduleName[],
  432. char fullImageName[],
  433. char symbolName[], // must be SYMBOL_NAME_MAX bytes
  434. DWORD64* displacement,
  435. DWORD* line,
  436. char fullpath[]) // must be MAX_PATH bytes
  437. {
  438. if (!Burnslib::StackTrace::IsInitialized())
  439. {
  440. Burnslib::StackTrace::Initialize();
  441. }
  442. RTLASSERT(traceAddress);
  443. HANDLE process = ::GetCurrentProcess();
  444. if (moduleName || fullImageName)
  445. {
  446. IMAGEHLP_MODULE64 module;
  447. // REVIEWED-2002/03/06-sburns correct byte count passed
  448. ::ZeroMemory(&module, sizeof module);
  449. module.SizeOfStruct = sizeof(module);
  450. if (MySymGetModuleInfo(process, traceAddress, &module))
  451. {
  452. if (moduleName)
  453. {
  454. SafeStrncpy(moduleName, module.ModuleName, MODULE_NAME_MAX);
  455. }
  456. if (fullImageName)
  457. {
  458. SafeStrncpy(
  459. fullImageName,
  460. module.LoadedImageName,
  461. MAX_PATH);
  462. }
  463. }
  464. }
  465. if (symbolName || displacement)
  466. {
  467. // CODEWORK: use SymFromAddr instead?
  468. // +1 for paranoid terminating null
  469. BYTE buf[SYMBOL_NAME_MAX + sizeof IMAGEHLP_SYMBOL64 + 1];
  470. // REVIEWED-2002/03/06-sburns correct byte count passed
  471. ::ZeroMemory(buf, SYMBOL_NAME_MAX + sizeof IMAGEHLP_SYMBOL64 + 1);
  472. IMAGEHLP_SYMBOL64* symbol = reinterpret_cast<IMAGEHLP_SYMBOL64*>(buf);
  473. symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
  474. symbol->MaxNameLength = SYMBOL_NAME_MAX;
  475. if (MySymGetSymFromAddr(process, traceAddress, displacement, symbol))
  476. {
  477. if (symbolName)
  478. {
  479. SafeStrncpy(symbolName, symbol->Name, SYMBOL_NAME_MAX);
  480. }
  481. }
  482. }
  483. if (line || fullpath)
  484. {
  485. DWORD disp2 = 0;
  486. IMAGEHLP_LINE64 lineinfo;
  487. // REVIEWED-2002/03/06-sburns correct byte count passed
  488. ::ZeroMemory(&lineinfo, sizeof lineinfo);
  489. lineinfo.SizeOfStruct = sizeof(lineinfo);
  490. if (MySymGetLineFromAddr(process, traceAddress, &disp2, &lineinfo))
  491. {
  492. // disp2 ?= displacement
  493. if (line)
  494. {
  495. *line = lineinfo.LineNumber;
  496. }
  497. if (fullpath)
  498. {
  499. SafeStrncpy(fullpath, lineinfo.FileName, MAX_PATH);
  500. }
  501. }
  502. }
  503. }
  504. String
  505. Burnslib::StackTrace::LookupAddress(
  506. DWORD64 traceAddress,
  507. const wchar_t* format)
  508. {
  509. RTLASSERT(traceAddress);
  510. RTLASSERT(format);
  511. String result;
  512. if (!format || !traceAddress)
  513. {
  514. return result;
  515. }
  516. char ansiSymbol[Burnslib::StackTrace::SYMBOL_NAME_MAX];
  517. char ansiModule[Burnslib::StackTrace::MODULE_NAME_MAX];
  518. char ansiSource[MAX_PATH];
  519. DWORD64 displacement = 0;
  520. DWORD line = 0;
  521. // REVIEWED-2002/03/06-sburns correct byte counts passed
  522. ::ZeroMemory(ansiSymbol, Burnslib::StackTrace::SYMBOL_NAME_MAX);
  523. ::ZeroMemory(ansiModule, Burnslib::StackTrace::MODULE_NAME_MAX);
  524. ::ZeroMemory(ansiSource, MAX_PATH);
  525. Burnslib::StackTrace::LookupAddress(
  526. traceAddress,
  527. ansiModule,
  528. 0,
  529. ansiSymbol,
  530. &displacement,
  531. &line,
  532. ansiSource);
  533. String module(ansiModule);
  534. String symbol(ansiSymbol);
  535. String source(ansiSource);
  536. result =
  537. String::format(
  538. format,
  539. module.c_str(),
  540. symbol.c_str(),
  541. source.c_str(),
  542. line);
  543. return result;
  544. }