• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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