summaryrefslogtreecommitdiffstats
path: root/src/testlib/qtestcrashhandler_p.h
blob: 02e19bfaa93578cede66048b0bb93e0700f685a6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists purely as an
// implementation detail.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

#ifndef QTESTCRASHHANDLER_H
#define QTESTCRASHHANDLER_H

#include <QtTest/qttestglobal.h>

#include <QtCore/private/qtools_p.h>

#ifdef Q_OS_UNIX
#include <signal.h>
#include <sys/mman.h>
#include <sys/uio.h>
#include <unistd.h>
#endif

#ifdef Q_OS_WIN
#include <iostream>
# if !defined(Q_CC_MINGW) || (defined(Q_CC_MINGW) && defined(__MINGW64_VERSION_MAJOR))
#  include <crtdbg.h>
# endif
#include <qt_windows.h> // for Sleep
#endif

QT_BEGIN_NAMESPACE
namespace QTest {
namespace CrashHandler {
#if defined(Q_OS_UNIX) && (!defined(Q_OS_WASM) || QT_CONFIG(thread))
    struct iovec IoVec(struct iovec vec);
    struct iovec IoVec(const char *str);

    template <typename... Args> static ssize_t writeToStderr(Args &&... args)
    {
        struct iovec vec[] = { IoVec(std::forward<Args>(args))... };
        return ::writev(STDERR_FILENO, vec, std::size(vec));
    }

    // async-signal-safe conversion from int to string
    struct AsyncSafeIntBuffer
    {
        // digits10 + 1 for all possible digits
        // +1 for the sign
        // +1 for the terminating null
        static constexpr int Digits10 = std::numeric_limits<int>::digits10 + 3;
        std::array<char, Digits10> array;
        constexpr AsyncSafeIntBuffer() : array{} {}     // initializes array
        AsyncSafeIntBuffer(Qt::Initialization) {}       // leaves array uninitialized
    };

    struct iovec asyncSafeToString(int n, AsyncSafeIntBuffer &&result = Qt::Uninitialized);
#elif defined(Q_OS_WIN)
    // Windows doesn't need to be async-safe
    template <typename... Args> static void writeToStderr(Args &&... args)
    {
        (std::cerr << ... << args);
    }

    inline std::string asyncSafeToString(int n)
    {
        return std::to_string(n);
    }
#endif // defined(Q_OS_UNIX) && (!defined(Q_OS_WASM) || QT_CONFIG(thread))

    bool alreadyDebugging();
    void blockUnixSignals();

#if !defined(Q_OS_WASM) || QT_CONFIG(thread)
    void printTestRunTime();
    void generateStackTrace();
#endif

    void maybeDisableCoreDump();
    Q_TESTLIB_EXPORT void prepareStackTrace();

#if defined(Q_OS_WIN)
    // Helper class for resolving symbol names by dynamically loading "dbghelp.dll".
    class DebugSymbolResolver
    {
        Q_DISABLE_COPY_MOVE(DebugSymbolResolver)
    public:
        struct Symbol
        {
            Symbol() : name(nullptr), address(0) {}

            const char *name; // Must be freed by caller.
            DWORD64 address;
        };

        explicit DebugSymbolResolver(HANDLE process);
        ~DebugSymbolResolver() { cleanup(); }

        bool isValid() const { return m_symFromAddr; }

        Symbol resolveSymbol(DWORD64 address) const;

    private:
        // typedefs from DbgHelp.h/.dll
        struct DBGHELP_SYMBOL_INFO { // SYMBOL_INFO
            ULONG       SizeOfStruct;
            ULONG       TypeIndex;        // Type Index of symbol
            ULONG64     Reserved[2];
            ULONG       Index;
            ULONG       Size;
            ULONG64     ModBase;          // Base Address of module comtaining this symbol
            ULONG       Flags;
            ULONG64     Value;            // Value of symbol, ValuePresent should be 1
            ULONG64     Address;          // Address of symbol including base address of module
            ULONG       Register;         // register holding value or pointer to value
            ULONG       Scope;            // scope of the symbol
            ULONG       Tag;              // pdb classification
            ULONG       NameLen;          // Actual length of name
            ULONG       MaxNameLen;
            CHAR        Name[1];          // Name of symbol
        };

        typedef BOOL (__stdcall *SymInitializeType)(HANDLE, PCSTR, BOOL);
        typedef BOOL (__stdcall *SymFromAddrType)(HANDLE, DWORD64, PDWORD64, DBGHELP_SYMBOL_INFO *);

        void cleanup();

        const HANDLE m_process;
        HMODULE m_dbgHelpLib;
        SymFromAddrType m_symFromAddr;
    };

    class Q_TESTLIB_EXPORT WindowsFaultHandler
    {
    public:
        WindowsFaultHandler();

    private:
        static LONG WINAPI windowsFaultHandler(struct _EXCEPTION_POINTERS *exInfo);
    };
    using FatalSignalHandler = WindowsFaultHandler;
#elif defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
    class Q_TESTLIB_EXPORT FatalSignalHandler
    {
    public:
    #  define OUR_SIGNALS(F)    \
                F(HUP)              \
                F(INT)              \
                F(QUIT)             \
                F(ABRT)             \
                F(ILL)              \
                F(BUS)              \
                F(FPE)              \
                F(SEGV)             \
                F(PIPE)             \
                F(TERM)             \
        /**/
    #  define CASE_LABEL(S)             case SIG ## S:  return QT_STRINGIFY(S);
    #  define ENUMERATE_SIGNALS(S)      SIG ## S,
                static const char *signalName(int signum) noexcept
        {
            switch (signum) {
                OUR_SIGNALS(CASE_LABEL)
            }

    #  if defined(__GLIBC_MINOR__) && (__GLIBC_MINOR__ >= 32 || __GLIBC__ > 2)
            // get the other signal names from glibc 2.32
            // (accessing the sys_sigabbrev variable causes linker warnings)
            if (const char *p = sigabbrev_np(signum))
                return p;
    #  endif
            return "???";
        }
        static constexpr std::array fatalSignals = {
            OUR_SIGNALS(ENUMERATE_SIGNALS)
        };
    #  undef CASE_LABEL
    #  undef ENUMERATE_SIGNALS

        static constexpr std::array crashingSignals = {
            // Crash signals are special, because if we return from the handler
            // without adjusting the machine state, the same instruction that
            // originally caused the crash will get re-executed and will thus cause
            // the same crash again. This is useful if our parent process logs the
            // exit result or if core dumps are enabled: the core file will point
            // to the actual instruction that crashed.
            SIGILL, SIGBUS, SIGFPE, SIGSEGV
        };
        using OldActionsArray = std::array<struct sigaction, fatalSignals.size()>;

        FatalSignalHandler();
        ~FatalSignalHandler();

    private:
        Q_DISABLE_COPY_MOVE(FatalSignalHandler)

        static OldActionsArray &oldActions();
        auto alternateStackSize();
        int setupAlternateStack();
        void freeAlternateStack();

        template <typename T> static
                std::enable_if_t<sizeof(std::declval<T>().si_pid) + sizeof(std::declval<T>().si_uid) >= 1>
                printSentSignalInfo(T *info)
        {
            writeToStderr(" sent by PID ", asyncSafeToString(info->si_pid),
                          " UID ", asyncSafeToString(info->si_uid));
        }
        static void printSentSignalInfo(...) {}

        template <typename T> static
                std::enable_if_t<sizeof(std::declval<T>().si_addr) >= 1> printCrashingSignalInfo(T *info)
        {
            using HexString = std::array<char, sizeof(quintptr) * 2>;
            auto toHexString = [](quintptr u, HexString &&r = {}) {
                int shift = sizeof(quintptr) * 8 - 4;
                for (size_t i = 0; i < sizeof(quintptr) * 2; ++i, shift -= 4)
                    r[i] = QtMiscUtils::toHexLower(u >> shift);
                struct iovec vec;
                vec.iov_base = r.data();
                vec.iov_len = r.size();
                return vec;
            };
            writeToStderr(", code ", asyncSafeToString(info->si_code),
                          ", for address 0x", toHexString(quintptr(info->si_addr)));
        }
        static void printCrashingSignalInfo(...) {}
        static void actionHandler(int signum, siginfo_t *info, void * /* ucontext */);

        [[maybe_unused]] static void regularHandler(int signum)
        {
            actionHandler(signum, nullptr, nullptr);
        }

        void *alternateStackBase = MAP_FAILED;
        static bool pauseOnCrash;
    };
#else // Q_OS_WASM or weird systems
class Q_TESTLIB_EXPORT FatalSignalHandler {};
inline void blockUnixSignals() {}
#endif // Q_OS_* choice
} // namespace CrashHandler
} // namespace QTest
QT_END_NAMESPACE

#endif // QTESTCRASHHANDLER_H