frame_msvc.ipp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. // Copyright Antony Polukhin, 2016-2025.
  2. //
  3. // Distributed under the Boost Software License, Version 1.0. (See
  4. // accompanying file LICENSE_1_0.txt or copy at
  5. // http://www.boost.org/LICENSE_1_0.txt)
  6. #ifndef BOOST_STACKTRACE_DETAIL_FRAME_MSVC_IPP
  7. #define BOOST_STACKTRACE_DETAIL_FRAME_MSVC_IPP
  8. #include <boost/config.hpp>
  9. #ifdef BOOST_HAS_PRAGMA_ONCE
  10. # pragma once
  11. #endif
  12. #include <boost/stacktrace/frame.hpp>
  13. #include <boost/core/demangle.hpp>
  14. #include <boost/core/noncopyable.hpp>
  15. #include <boost/stacktrace/detail/to_dec_array.hpp>
  16. #include <boost/stacktrace/detail/to_hex_array.hpp>
  17. #ifndef BOOST_STACKTRACE_DISABLE_OFFSET_ADDR_BASE
  18. #include <boost/stacktrace/detail/addr_base_msvc.hpp>
  19. #endif
  20. #ifdef WIN32_LEAN_AND_MEAN
  21. #include <windows.h>
  22. #else
  23. // Prevent inclusion of extra Windows SDK headers which can cause conflict
  24. // with other code using Windows SDK
  25. #define WIN32_LEAN_AND_MEAN
  26. #include <windows.h>
  27. #undef WIN32_LEAN_AND_MEAN
  28. #endif
  29. #include "dbgeng.h"
  30. #include <mutex>
  31. #if defined(__clang__) || defined(BOOST_MSVC)
  32. # pragma comment(lib, "ole32.lib")
  33. # pragma comment(lib, "Dbgeng.lib")
  34. #endif
  35. #ifdef __CRT_UUID_DECL // for __MINGW32__
  36. #if !defined(__MINGW32__) || \
  37. (!defined(__clang__) && __GNUC__ < 12) || \
  38. (defined(__clang__) && __clang_major__ < 16)
  39. __CRT_UUID_DECL(IDebugClient,0x27fe5639,0x8407,0x4f47,0x83,0x64,0xee,0x11,0x8f,0xb0,0x8a,0xc8)
  40. __CRT_UUID_DECL(IDebugControl,0x5182e668,0x105e,0x416e,0xad,0x92,0x24,0xef,0x80,0x04,0x24,0xba)
  41. __CRT_UUID_DECL(IDebugSymbols,0x8c31e98c,0x983a,0x48a5,0x90,0x16,0x6f,0xe5,0xd6,0x67,0xa9,0x50)
  42. #endif
  43. #elif defined(DEFINE_GUID) && !defined(BOOST_MSVC)
  44. DEFINE_GUID(IID_IDebugClient,0x27fe5639,0x8407,0x4f47,0x83,0x64,0xee,0x11,0x8f,0xb0,0x8a,0xc8);
  45. DEFINE_GUID(IID_IDebugControl,0x5182e668,0x105e,0x416e,0xad,0x92,0x24,0xef,0x80,0x04,0x24,0xba);
  46. DEFINE_GUID(IID_IDebugSymbols,0x8c31e98c,0x983a,0x48a5,0x90,0x16,0x6f,0xe5,0xd6,0x67,0xa9,0x50);
  47. #endif
  48. // Testing. Remove later
  49. //# define __uuidof(x) ::IID_ ## x
  50. namespace boost { namespace stacktrace { namespace detail {
  51. template <class T>
  52. class com_holder: boost::noncopyable {
  53. T* holder_;
  54. public:
  55. com_holder() noexcept
  56. : holder_(0)
  57. {}
  58. T* operator->() const noexcept {
  59. return holder_;
  60. }
  61. void** to_void_ptr_ptr() noexcept {
  62. return reinterpret_cast<void**>(&holder_);
  63. }
  64. bool is_inited() const noexcept {
  65. return !!holder_;
  66. }
  67. ~com_holder() noexcept {
  68. if (holder_) {
  69. holder_->Release();
  70. }
  71. }
  72. };
  73. inline std::string mingw_demangling_workaround(const std::string& s) {
  74. #ifdef BOOST_GCC
  75. if (s.empty()) {
  76. return s;
  77. }
  78. if (s[0] != '_') {
  79. return boost::core::demangle(('_' + s).c_str());
  80. }
  81. return boost::core::demangle(s.c_str());
  82. #else
  83. return s;
  84. #endif
  85. }
  86. inline void trim_right_zeroes(std::string& s) {
  87. // MSVC-9 does not have back() and pop_back() functions in std::string
  88. while (!s.empty()) {
  89. const std::size_t last = static_cast<std::size_t>(s.size() - 1);
  90. if (s[last] != '\0') {
  91. break;
  92. }
  93. s.resize(last);
  94. }
  95. }
  96. class debugging_symbols: boost::noncopyable {
  97. static void try_init_com(com_holder< ::IDebugSymbols>& idebug) noexcept {
  98. com_holder< ::IDebugClient> iclient;
  99. if (S_OK != ::DebugCreate(__uuidof(IDebugClient), iclient.to_void_ptr_ptr())) {
  100. return;
  101. }
  102. com_holder< ::IDebugControl> icontrol;
  103. const bool res0 = (S_OK == iclient->QueryInterface(
  104. __uuidof(IDebugControl),
  105. icontrol.to_void_ptr_ptr()
  106. ));
  107. if (!res0) {
  108. return;
  109. }
  110. const bool res1 = (S_OK == iclient->AttachProcess(
  111. 0,
  112. ::GetCurrentProcessId(),
  113. DEBUG_ATTACH_NONINVASIVE | DEBUG_ATTACH_NONINVASIVE_NO_SUSPEND
  114. ));
  115. if (!res1) {
  116. return;
  117. }
  118. if (S_OK != icontrol->WaitForEvent(DEBUG_WAIT_DEFAULT, INFINITE)) {
  119. return;
  120. }
  121. // No checking: QueryInterface sets the output parameter to NULL in case of error.
  122. iclient->QueryInterface(__uuidof(IDebugSymbols), idebug.to_void_ptr_ptr());
  123. }
  124. #ifndef BOOST_STACKTRACE_USE_WINDBG_CACHED
  125. static std::mutex& get_mutex_inst() noexcept {
  126. static std::mutex m;
  127. return m;
  128. }
  129. static com_holder< ::IDebugSymbols>& get_static_debug_inst() noexcept {
  130. // [class.mfct]: A static local variable or local type in a member function always refers to the same entity, whether
  131. // or not the member function is inline.
  132. static com_holder< ::IDebugSymbols> idebug;
  133. if (!idebug.is_inited()) {
  134. try_init_com(idebug);
  135. }
  136. return idebug;
  137. }
  138. std::lock_guard<std::mutex> guard_;
  139. com_holder< ::IDebugSymbols>& idebug_;
  140. public:
  141. debugging_symbols() noexcept
  142. : guard_( get_mutex_inst() )
  143. , idebug_( get_static_debug_inst() )
  144. {}
  145. #else
  146. #ifdef BOOST_NO_CXX11_THREAD_LOCAL
  147. # error Your compiler does not support C++11 thread_local storage. It`s impossible to build with BOOST_STACKTRACE_USE_WINDBG_CACHED.
  148. #endif
  149. static com_holder< ::IDebugSymbols>& get_thread_local_debug_inst() noexcept {
  150. // [class.mfct]: A static local variable or local type in a member function always refers to the same entity, whether
  151. // or not the member function is inline.
  152. static thread_local com_holder< ::IDebugSymbols> idebug;
  153. if (!idebug.is_inited()) {
  154. try_init_com(idebug);
  155. }
  156. return idebug;
  157. }
  158. com_holder< ::IDebugSymbols>& idebug_;
  159. public:
  160. debugging_symbols() noexcept
  161. : idebug_( get_thread_local_debug_inst() )
  162. {}
  163. #endif // #ifndef BOOST_STACKTRACE_USE_WINDBG_CACHED
  164. bool is_inited() const noexcept {
  165. return idebug_.is_inited();
  166. }
  167. std::string get_name_impl(const void* addr, std::string* module_name = 0) const {
  168. std::string result;
  169. if (!is_inited()) {
  170. return result;
  171. }
  172. const ULONG64 offset = reinterpret_cast<ULONG64>(addr);
  173. char name[256];
  174. name[0] = '\0';
  175. ULONG size = 0;
  176. bool res = (S_OK == idebug_->GetNameByOffset(
  177. offset,
  178. name,
  179. sizeof(name),
  180. &size,
  181. 0
  182. ));
  183. if (!res && size != 0) {
  184. result.resize(size);
  185. res = (S_OK == idebug_->GetNameByOffset(
  186. offset,
  187. &result[0],
  188. static_cast<ULONG>(result.size()),
  189. &size,
  190. 0
  191. ));
  192. // According to https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/dbgeng/nf-dbgeng-idebugsymbols-getnamebyoffset
  193. // "This size includes the space for the '\0' terminating character."
  194. result.resize(size - 1);
  195. } else if (res) {
  196. result.assign(name, size - 1);
  197. }
  198. if (!res) {
  199. result.clear();
  200. return result;
  201. }
  202. const std::size_t delimiter = result.find_first_of('!');
  203. if (module_name) {
  204. *module_name = result.substr(0, delimiter);
  205. if (!module_name->empty()) {
  206. ULONG64 base = 0;
  207. res = (S_OK == idebug_->GetModuleByOffset(
  208. offset,
  209. 0,
  210. nullptr,
  211. &base
  212. ));
  213. if (res) {
  214. name[0] = '\0';
  215. size = 0;
  216. res = (S_OK == idebug_->GetModuleNames(
  217. DEBUG_ANY_ID,
  218. base,
  219. name,
  220. sizeof(name),
  221. &size,
  222. nullptr,
  223. 0,
  224. nullptr,
  225. nullptr,
  226. 0,
  227. nullptr
  228. ));
  229. }
  230. if (!res && size != 0)
  231. {
  232. std::string module_path(size, char());
  233. res = (S_OK == idebug_->GetModuleNames(
  234. DEBUG_ANY_ID,
  235. base,
  236. &module_path[0],
  237. static_cast<ULONG>(module_path.size()),
  238. &size,
  239. nullptr,
  240. 0,
  241. nullptr,
  242. nullptr,
  243. 0,
  244. nullptr
  245. ));
  246. if (res && size > 1) {
  247. module_name->assign(module_path, size - 1);
  248. }
  249. }
  250. else if (res && size > 1) {
  251. module_name->assign(name, size - 1);
  252. }
  253. }
  254. }
  255. if (delimiter == std::string::npos) {
  256. // If 'delimiter' is equal to 'std::string::npos' then we have only module name.
  257. result.clear();
  258. return result;
  259. }
  260. result = mingw_demangling_workaround(
  261. result.substr(delimiter + 1)
  262. );
  263. return result;
  264. }
  265. std::size_t get_line_impl(const void* addr) const noexcept {
  266. ULONG result = 0;
  267. if (!is_inited()) {
  268. return result;
  269. }
  270. const bool is_ok = (S_OK == idebug_->GetLineByOffset(
  271. reinterpret_cast<ULONG64>(addr),
  272. &result,
  273. 0,
  274. 0,
  275. 0,
  276. 0
  277. ));
  278. return (is_ok ? result : 0);
  279. }
  280. std::pair<std::string, std::size_t> get_source_file_line_impl(const void* addr) const {
  281. std::pair<std::string, std::size_t> result;
  282. if (!is_inited()) {
  283. return result;
  284. }
  285. const ULONG64 offset = reinterpret_cast<ULONG64>(addr);
  286. char name[256];
  287. name[0] = 0;
  288. ULONG size = 0;
  289. ULONG line_num = 0;
  290. bool res = (S_OK == idebug_->GetLineByOffset(
  291. offset,
  292. &line_num,
  293. name,
  294. sizeof(name),
  295. &size,
  296. 0
  297. ));
  298. if (res) {
  299. result.first = name;
  300. result.second = line_num;
  301. return result;
  302. }
  303. if (!res && size == 0) {
  304. return result;
  305. }
  306. result.first.resize(size);
  307. res = (S_OK == idebug_->GetLineByOffset(
  308. offset,
  309. &line_num,
  310. &result.first[0],
  311. static_cast<ULONG>(result.first.size()),
  312. &size,
  313. 0
  314. ));
  315. trim_right_zeroes(result.first);
  316. result.second = line_num;
  317. if (!res) {
  318. result.first.clear();
  319. result.second = 0;
  320. }
  321. return result;
  322. }
  323. void to_string_impl(const void* addr, std::string& res) const {
  324. if (!is_inited()) {
  325. return;
  326. }
  327. std::string module_name;
  328. std::string name = this->get_name_impl(addr, &module_name);
  329. if (!name.empty()) {
  330. res += name;
  331. } else {
  332. #ifdef BOOST_STACKTRACE_DISABLE_OFFSET_ADDR_BASE
  333. res += to_hex_array(addr).data();
  334. #else
  335. // Get own base address
  336. const uintptr_t base_addr = get_own_proc_addr_base(addr);
  337. res += to_hex_array(reinterpret_cast<uintptr_t>(addr) - base_addr).data();
  338. #endif
  339. }
  340. std::pair<std::string, std::size_t> source_line = this->get_source_file_line_impl(addr);
  341. if (!source_line.first.empty() && source_line.second) {
  342. res += " at ";
  343. res += source_line.first;
  344. res += ':';
  345. res += boost::stacktrace::detail::to_dec_array(source_line.second).data();
  346. } else if (!module_name.empty()) {
  347. res += " in ";
  348. res += module_name;
  349. }
  350. }
  351. };
  352. std::string to_string(const frame* frames, std::size_t size) {
  353. boost::stacktrace::detail::debugging_symbols idebug;
  354. if (!idebug.is_inited()) {
  355. return std::string();
  356. }
  357. std::string res;
  358. res.reserve(64 * size);
  359. for (std::size_t i = 0; i < size; ++i) {
  360. if (i < 10) {
  361. res += ' ';
  362. }
  363. res += boost::stacktrace::detail::to_dec_array(i).data();
  364. res += '#';
  365. res += ' ';
  366. idebug.to_string_impl(frames[i].address(), res);
  367. res += '\n';
  368. }
  369. return res;
  370. }
  371. } // namespace detail
  372. std::string frame::name() const {
  373. boost::stacktrace::detail::debugging_symbols idebug;
  374. return idebug.get_name_impl(addr_);
  375. }
  376. std::string frame::source_file() const {
  377. boost::stacktrace::detail::debugging_symbols idebug;
  378. return idebug.get_source_file_line_impl(addr_).first;
  379. }
  380. std::size_t frame::source_line() const {
  381. boost::stacktrace::detail::debugging_symbols idebug;
  382. return idebug.get_line_impl(addr_);
  383. }
  384. std::string to_string(const frame& f) {
  385. std::string res;
  386. boost::stacktrace::detail::debugging_symbols idebug;
  387. idebug.to_string_impl(f.address(), res);
  388. return res;
  389. }
  390. }} // namespace boost::stacktrace
  391. #endif // BOOST_STACKTRACE_DETAIL_FRAME_MSVC_IPP