mirror of
https://github.com/1dot13/source.git
synced 2026-07-29 13:52:17 +02:00
Report crashes for every player, not only those running under Wine's WINEDEBUG.
A last-chance UnhandledExceptionFilter is no good: faults on the message-pump /
WindowProcedure path have no game __except on their stack, and under Wine the
WndProc dispatch swallows them before they ever reach "unhandled". Install a
vectored handler instead -- it runs first-chance, ahead of every frame handler,
records the fault and returns EXCEPTION_CONTINUE_SEARCH, so normal SEH is
unaffected.
The dump is deliberately heap-free. The crash most worth reporting is often heap
corruption, so anything that allocates (the game Logger, std::vector, DbgHelp's
Sym* family) would fault again and take the report down with it. Using only stack
buffers and raw Win32, writeExceptionBacktrace writes the registers, the faulting
address, a UTC timestamp, the build id, the loaded-module table and a
bounds-checked manual EBP walk into a fresh numbered crash_report_NNN.txt.
The module table is what makes the addresses mean anything. Our executables are
linked /DYNAMICBASE and keep their .reloc section, so the loader is free to move
the image: Wine leaves it at the preferred base, Windows ASLR does not. A report
listing only runtime addresses is symbolizable by luck, and silently wrong once
the luck runs out. Recording where each image actually landed turns the offset
into arithmetic:
llvm-symbolizer --obj=JA2.exe --adjust-vma=$((<JA2.exe base> - 0x400000))
It is walked off the PEB loader list because both alternatives -- EnumProcessModules
and DbgHelp's module APIs -- allocate, and this path must not. Having the table
also frees the backtrace from restricting itself to our own module's return
addresses: a fault inside ddraw/fmod/bink is exactly the case worth seeing, and
an address belonging to no module is recognizable as the frame-pointer debris it
is.
Two things go into the report beside the machine state. czVersionString stamps
the build, so a report matches the exact PDB it has to be symbolized against, and
the optional HANDLE from Ja2 Settings names the player, so a report can be tied
to whoever raises it with us. The handle is sanitized where it is set rather than
where it is used: it is player input that lands in a line-oriented text report,
so anything that could forge a line (CR/LF, control and non-ASCII characters) is
dropped and the length is capped.
Finally the player is told. The message is composed in the handler, while the
fault details and the report's filename are still in hand, but shown from
SGPExit: first-chance means the exception may yet be handled downstream, and a
message box pumps messages, which on the heap that just faulted is a second
crash. It replaces the generic "Unhandled exception. Unable to recover." box
rather than adding to it -- it says more, and it names the file we need attached
to the bug report.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
75 lines
2.8 KiB
C++
75 lines
2.8 KiB
C++
// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
// This is a cross platform interface for helper functions related to debuggers.
|
|
// You should use this to test if you're running under a debugger, and if you
|
|
// would like to yield (breakpoint) into the debugger.
|
|
|
|
#ifndef BASE_DEBUG_UTIL_H_
|
|
#define BASE_DEBUG_UTIL_H_
|
|
|
|
#include <iosfwd>
|
|
#include <vector>
|
|
|
|
#include "sgp_logger.h"
|
|
|
|
// A macro to disallow the copy constructor and operator= functions
|
|
// This should be used in the private: declarations for a class
|
|
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
|
TypeName(const TypeName&); \
|
|
void operator=(const TypeName&)
|
|
|
|
// An older, deprecated, politically incorrect name for the above.
|
|
#define DISALLOW_EVIL_CONSTRUCTORS(TypeName) DISALLOW_COPY_AND_ASSIGN(TypeName)
|
|
|
|
// A stacktrace can be helpful in debugging. For example, you can include a
|
|
// stacktrace member in a object (probably around #ifndef NDEBUG) so that you
|
|
// can later see where the given object was created from.
|
|
class StackTrace {
|
|
public:
|
|
// Create a stacktrace from the current location
|
|
StackTrace();
|
|
// Get an array of instruction pointer values.
|
|
// count: (output) the number of elements in the returned array
|
|
const void *const *Addresses(size_t* count);
|
|
// Print a backtrace to stderr
|
|
void PrintBacktrace(const char* msg);
|
|
|
|
// Resolve backtrace to symbols and write to stream.
|
|
void OutputToStream(const char* msg, sgp::Logger::LogInstance* os);
|
|
|
|
private:
|
|
std::vector<void*> trace_;
|
|
int count_;
|
|
|
|
DISALLOW_EVIL_CONSTRUCTORS(StackTrace);
|
|
};
|
|
|
|
struct _EXCEPTION_POINTERS;
|
|
|
|
namespace sgp
|
|
{
|
|
void dumpStackTrace(vfs::String const& msg);
|
|
|
|
// Write a heap-free crash report (registers + a raw return-address backtrace)
|
|
// for the faulting context to a numbered crash_report file; symbolize it
|
|
// offline against the build's PDB. Call first-chance from a vectored handler.
|
|
void writeExceptionBacktrace(_EXCEPTION_POINTERS* ep);
|
|
|
|
// Record the build-id (game version string) to stamp into crash reports, so
|
|
// a report can be matched to the exact build's PDB. Call once at startup.
|
|
void setCrashBuildId(const char* id);
|
|
|
|
// What to tell the player: why we died and where the report landed. NULL if
|
|
// no crash was recorded. Built by the handler, shown from the exit path.
|
|
const wchar_t* crashReportMessage();
|
|
|
|
// Record the player's optional self-chosen handle (HANDLE in Ja2 Settings) to
|
|
// stamp into crash reports, so a report can be tied to whoever raises it with
|
|
// us on Discord. Empty or unset simply omits the field. Call once at startup.
|
|
void setCrashUserHandle(const wchar_t* handle);
|
|
}
|
|
|
|
#endif // BASE_DEBUG_UTIL_H_
|