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