• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Return-To-The-Roots / s25client / 13219901875

08 Feb 2025 10:36PM UTC coverage: 50.345% (+0.01%) from 50.331%
13219901875

Pull #1739

github

web-flow
Merge cdf19fce2 into 541bc230f
Pull Request #1739: Make AIJH Actions Reproducible by Using the Games Random Seed

15 of 20 new or added lines in 4 files covered. (75.0%)

60 existing lines in 3 files now uncovered.

22380 of 44453 relevant lines covered (50.35%)

37384.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/libs/s25main/Debug.cpp
1
// Copyright (C) 2005 - 2025 Settlers Freaks (sf-team at siedler25.org)
2
//
3
// SPDX-License-Identifier: GPL-2.0-or-later
4

5
///////////////////////////////////////////////////////////////////////////////
6

7
#include "Debug.h"
8
#include "RTTR_Version.h"
9
#include "Replay.h"
10
#include "Settings.h"
11
#include "backtrace_config.h"
12
#include "network/GameClient.h"
13
#include "s25util/Log.h"
14
#include <boost/core/ignore_unused.hpp>
15
#include <boost/endian/arithmetic.hpp>
16
#include <boost/endian/conversion.hpp>
17
#include <boost/nowide/iostream.hpp>
18
#include <bzlib.h>
19
#include <memory>
20

21
#if RTTR_BACKTRACE_HAS_DBGHELP
22
#    include <windows.h>
23
// Disable warning for faulty nameless enum typedef (check sfImage.../hdBase...)
24
#    pragma warning(push)
25
#    pragma warning(disable : 4091)
26
#    include <dbghelp.h>
27
#    pragma warning(pop)
28

29
#    ifdef _MSC_VER
30
#        pragma comment(lib, "dbgHelp.lib")
31
#    else
32
typedef WINBOOL(WINAPI* SymInitializeType)(HANDLE hProcess, PSTR UserSearchPath, WINBOOL fInvadeProcess);
33
typedef WINBOOL(WINAPI* SymCleanupType)(HANDLE hProcess);
34
typedef VOID(WINAPI* RtlCaptureContextType)(PCONTEXT ContextRecord);
35
typedef WINBOOL(WINAPI* StackWalkType)(DWORD MachineType, HANDLE hProcess, HANDLE hThread, LPSTACKFRAME64 StackFrame,
36
                                       PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
37
                                       PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
38
                                       PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
39
                                       PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress);
40
#    endif // _MSC_VER
41
#endif
42

43
namespace {
44
#if RTTR_BACKTRACE_HAS_DBGHELP
45
#    define RTTR_CONTEXT_PTR_TYPE LPCONTEXT
46
bool captureBacktrace(DebugInfo::stacktrace_t& stacktrace, LPCONTEXT ctx) noexcept
47
{
48
    CONTEXT context;
49
#    ifndef _MSC_VER
50

51
    HMODULE kernel32 = LoadLibraryA("kernel32.dll");
52
    HMODULE dbghelp = LoadLibraryA("dbghelp.dll");
53

54
    if(!kernel32 || !dbghelp)
55
        return false;
56

57
#        if __GNUC__ >= 9
58
#            pragma GCC diagnostic push
59
#            pragma GCC diagnostic ignored "-Wcast-function-type"
60
        // error: cast between incompatible function types from
61
        // 'FARPROC' {aka 'int (__attribute__((stdcall)) *)()'}
62
        // to
63
        // 'RtlCaptureContextType' {aka 'void (__attribute__((stdcall)) *)(CONTEXT*)'}
64
        // [-Werror=cast-function-type]
65
        // and so on
66
#        endif
67
    RtlCaptureContextType RtlCaptureContext = (RtlCaptureContextType)(GetProcAddress(kernel32, "RtlCaptureContext"));
68

69
    SymInitializeType SymInitialize = (SymInitializeType)(GetProcAddress(dbghelp, "SymInitialize"));
70
    SymCleanupType SymCleanup = (SymCleanupType)(GetProcAddress(dbghelp, "SymCleanup"));
71
    StackWalkType StackWalk64 = (StackWalkType)(GetProcAddress(dbghelp, "StackWalk64"));
72
    PFUNCTION_TABLE_ACCESS_ROUTINE64 SymFunctionTableAccess64 =
73
      (PFUNCTION_TABLE_ACCESS_ROUTINE64)(GetProcAddress(dbghelp, "SymFunctionTableAccess64"));
74
    PGET_MODULE_BASE_ROUTINE64 SymGetModuleBase64 =
75
      (PGET_MODULE_BASE_ROUTINE64)(GetProcAddress(dbghelp, "SymGetModuleBase64"));
76
#        if __GNUC__ >= 9
77
#            pragma GCC diagnostic pop
78
#        endif
79

80
    if(!SymInitialize || !StackWalk64 || !SymFunctionTableAccess64 || !SymGetModuleBase64 || !RtlCaptureContext)
81
        return false;
82
#    endif
83

84
    const HANDLE process = GetCurrentProcess();
85
    if(!SymInitialize(process, nullptr, true))
86
        return false;
87

88
    if(!ctx)
89
    {
90
        context.ContextFlags = CONTEXT_FULL;
91
        RtlCaptureContext(&context);
92
        ctx = &context;
93
    }
94

95
    STACKFRAME64 frame;
96
    memset(&frame, 0, sizeof(frame));
97

98
#    ifdef _WIN64
99
    frame.AddrPC.Offset = ctx->Rip;
100
    frame.AddrStack.Offset = ctx->Rsp;
101
    frame.AddrFrame.Offset = ctx->Rbp;
102
#    else
103
    frame.AddrPC.Offset = ctx->Eip;
104
    frame.AddrStack.Offset = ctx->Esp;
105
    frame.AddrFrame.Offset = ctx->Ebp;
106
#    endif
107

108
    frame.AddrPC.Mode = AddrModeFlat;
109
    frame.AddrStack.Mode = AddrModeFlat;
110
    frame.AddrFrame.Mode = AddrModeFlat;
111

112
    HANDLE thread = GetCurrentThread();
113
#    ifdef _WIN64
114
    DWORD machineType = IMAGE_FILE_MACHINE_AMD64;
115
#    else
116
    DWORD machineType = IMAGE_FILE_MACHINE_I386;
117
#    endif
118

119
    for(unsigned i = 0; i < stacktrace.size(); i++)
120
    {
121
        if(!StackWalk64(machineType, process, thread, &frame, ctx, nullptr, SymFunctionTableAccess64,
122
                        SymGetModuleBase64, nullptr))
123
        {
124
            stacktrace.resize(i);
125
            break;
126
        }
127
        LOG.write("Reading stack frame %1%\n", LogTarget::Stdout) % i;
128
        stacktrace[i] = (void*)frame.AddrPC.Offset;
129
    }
130

131
    SymCleanup(process);
132
    return true;
133
}
134
#else
UNCOV
135
bool captureBacktrace(DebugInfo::stacktrace_t& stacktrace, void*) noexcept
×
136
{
137
#    if RTTR_BACKTRACE_HAS_FUNCTION
UNCOV
138
    const auto num_frames = backtrace(&stacktrace[0], stacktrace.size());
×
UNCOV
139
    stacktrace.resize(num_frames);
×
UNCOV
140
    return true;
×
141
#    else
142
    boost::ignore_unused(stacktrace);
143
    return false;
144
#    endif
145
}
146
#endif
147
} // namespace
148

UNCOV
149
DebugInfo::DebugInfo()
×
150
{
UNCOV
151
    sock.Connect("debug.rttr.info", 4123, false, SETTINGS.proxy);
×
152

153
    Send("RTTRDBG", 7);
×
154

155
    // Protocol Version
UNCOV
156
    SendUnsigned(2);
×
157

158
// OS
159
#if defined _WIN32 || defined __CYGWIN__
160
    SendString("WIN");
161
// TODO: These should be based on uname(3) output.
162
#elif defined __APPLE__
163
    SendString("MAC");
164
#elif defined __FreeBSD__
165
    SendString("BSD");
166
#else
UNCOV
167
    SendString("LNX");
×
168
#endif
169

170
    // Bits
UNCOV
171
    SendUnsigned(sizeof(void*) * 8u);
×
172

UNCOV
173
    SendString(rttr::version::GetVersion());
×
UNCOV
174
    SendString(rttr::version::GetRevision());
×
175

176
    SendUnsigned(GAMECLIENT.GetGFNumber());
×
UNCOV
177
}
×
178

UNCOV
179
DebugInfo::~DebugInfo()
×
180
{
UNCOV
181
    SendString("DONE");
×
182
    sock.Close();
×
183
}
×
184

185
DebugInfo::stacktrace_t DebugInfo::GetStackTrace(void* ctx) noexcept
×
186
{
187
#ifndef RTTR_CONTEXT_PTR_TYPE
188
    using RTTR_CONTEXT_PTR_TYPE = void*;
189
#endif
190
    stacktrace_t stacktrace(stacktrace_t::static_capacity);
191
    if(!captureBacktrace(stacktrace, static_cast<RTTR_CONTEXT_PTR_TYPE>(ctx)))
×
192
        stacktrace.clear();
×
UNCOV
193
    return stacktrace;
×
194
}
195

196
bool DebugInfo::Send(const void* buffer, size_t length)
×
197
{
UNCOV
198
    const auto* ptr = (const char*)buffer;
×
199

UNCOV
200
    while(length > 0)
×
201
    {
202
        int res = sock.Send(ptr, length);
×
203

204
        if(res >= 0)
×
205
        {
UNCOV
206
            size_t numSend = res;
×
207
            if(numSend >= length)
×
UNCOV
208
                break;
×
209
            ptr += numSend;
×
UNCOV
210
            length -= numSend;
×
211
        } else
212
        {
213
            boost::nowide::cerr << (boost::format("failed to send: %1% left\n") % length).str();
×
UNCOV
214
            return false;
×
215
        }
216
    }
217

218
    return true;
×
219
}
220

221
bool DebugInfo::SendUnsigned(uint32_t i)
×
222
{
223
    // Debug server does not handle endianness and is little endian... TODO: Fix server
224
    boost::endian::native_to_little_inplace(i);
×
225
    return (Send(&i, 4));
×
226
}
227

UNCOV
228
bool DebugInfo::SendSigned(int32_t i)
×
229
{
230
    // Debug server does not handle endianness and is little endian... TODO: Fix server
UNCOV
231
    boost::endian::native_to_little_inplace(i);
×
232
    return (Send(&i, 4));
×
233
}
234

235
bool DebugInfo::SendString(const char* str, size_t len)
×
236
{
UNCOV
237
    if(len == 0)
×
UNCOV
238
        len = strlen(str) + 1;
×
239

UNCOV
240
    if(!SendUnsigned(len))
×
UNCOV
241
        return (false);
×
242

243
    return (Send(str, len));
×
244
}
245

246
bool DebugInfo::SendString(const std::string& str)
×
247
{
248
    return SendString(str.c_str(), str.length() + 1); // +1 to include nullptr terminator
×
249
}
250

251
bool DebugInfo::SendStackTrace(const stacktrace_t& stacktrace)
×
252
{
UNCOV
253
    if(stacktrace.empty())
×
254
        return false;
×
255

UNCOV
256
    LOG.write("Will now send %1% stack frames\n") % stacktrace.size();
×
257

UNCOV
258
    if(!SendString("StackTrace"))
×
259
        return false;
×
260

261
    using littleVoid_t =
262
      std::conditional_t<sizeof(void*) == 4, boost::endian::little_int32_t, boost::endian::little_int64_t>;
263
    static_assert(sizeof(void*) <= sizeof(littleVoid_t), "Size of pointer did not fit!");
264
    std::vector<littleVoid_t> endStacktrace;
×
265
    endStacktrace.reserve(stacktrace.size());
×
UNCOV
266
    for(void* ptr : stacktrace)
×
267
        endStacktrace.push_back(reinterpret_cast<littleVoid_t::value_type>(ptr));
×
268

269
    unsigned stacktraceLen = sizeof(littleVoid_t) * endStacktrace.size();
×
270
    return SendString(reinterpret_cast<const char*>(&endStacktrace[0]), stacktraceLen);
×
271
}
272

UNCOV
273
bool DebugInfo::SendReplay()
×
274
{
275
    LOG.write("Sending replay...\n");
×
276

277
    // Replay mode is on, no recording of replays active
278
    if(!GAMECLIENT.IsReplayModeOn())
×
279
    {
280
        Replay* rpl = GAMECLIENT.GetReplay();
×
281

UNCOV
282
        if(!rpl || !rpl->IsRecording())
×
UNCOV
283
            return true;
×
284
        const auto replayPath = rpl->GetPath();
×
UNCOV
285
        rpl->Close();
×
286

UNCOV
287
        BinaryFile f;
×
UNCOV
288
        if(f.Open(replayPath, OpenFileMode::Read))
×
289
        {
UNCOV
290
            if(!SendString("Replay"))
×
291
                return false;
×
UNCOV
292
            if(SendFile(f))
×
293
                return true;
×
294
        }
295
        // Empty replay
296
        SendUnsigned(0);
×
UNCOV
297
        return false;
×
298
    } else
299
    {
UNCOV
300
        LOG.write("-> Already in replay mode, do not send replay\n");
×
301
    }
302

303
    return true;
×
304
}
305

UNCOV
306
bool DebugInfo::SendAsyncLog(const boost::filesystem::path& asyncLogFilepath)
×
307
{
308
    BinaryFile file;
×
UNCOV
309
    if(!file.Open(asyncLogFilepath, OpenFileMode::Read))
×
UNCOV
310
        return false;
×
311

UNCOV
312
    if(!SendString("AsyncLog"))
×
UNCOV
313
        return false;
×
314

UNCOV
315
    if(SendFile(file))
×
UNCOV
316
        return true;
×
317
    // Empty
UNCOV
318
    SendUnsigned(0);
×
319
    return false;
×
320
}
321

UNCOV
322
bool DebugInfo::SendFile(BinaryFile& file)
×
323
{
324
    file.Seek(0, SEEK_END);
×
UNCOV
325
    unsigned fileSize = file.Tell();
×
326

327
    LOG.write("- File size: %u\n") % fileSize;
×
328

329
    auto fileData = std::unique_ptr<char[]>(new char[fileSize]);
×
330
    unsigned compressed_len = fileSize * 2 + 600;
×
UNCOV
331
    auto compressed = std::unique_ptr<char[]>(new char[compressed_len]);
×
332

333
    file.Seek(0, SEEK_SET);
×
UNCOV
334
    file.ReadRawData(fileData.get(), fileSize);
×
335

336
    LOG.write("- Compressing...\n");
×
UNCOV
337
    if(BZ2_bzBuffToBuffCompress(compressed.get(), &compressed_len, fileData.get(), fileSize, 9, 0, 250) == BZ_OK)
×
338
    {
UNCOV
339
        LOG.write("- Sending...\n");
×
340

341
        if(SendString(compressed.get(), compressed_len))
×
342
        {
UNCOV
343
            LOG.write("-> success\n");
×
344
            return true;
×
345
        }
346

347
        LOG.write("-> Sending file failed :(\n");
×
348
    } else
UNCOV
349
        LOG.write("-> BZ2 compression failed.\n");
×
350
    return false;
×
351
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc