Back to Blog Home

Contents

Share

Share on Twitter
Share on Bluesky
Share on HackerNews
Share on LinkedIn

Not so "mini"-dumps: How we found missing crashes on SteamOS

Amir Mujacic image

Amir Mujacic -

Not so "mini"-dumps: How we found missing crashes on SteamOS

Not so "mini"-dumps: How we found missing crashes on SteamOS

We shipped an improvement to Sentry's game engine and native SDKs that most developers probably didn’t even notice until now – unless they were explicitly aiming to test their Windows-built games on Linux with Wine/Proton compatibility layers. That's exactly the point.

While we were focused on improving our game engine SDKs, our learnings while investigating a mysterious issue are applicable for any Windows application running on Linux via Wine or compatibility layer. And the journey to get there is a story worth telling in its own right.

The Mystery: Why aren't we getting crash reports?

It started with a support ticket from a game studio: "Sentry works great on Windows, but we're getting nothing from Steam Deck crashes."

Odd. We ran our test game on SteamOS. We tested Linux builds, and it worked just fine. Next step was running our test game in the way most games are running on SteamOS, through compatibility layers. Non-fatal errors worked perfectly – we could see logs, exceptions, performance data, everything. But actual crashes? Radio silence.

Our first instinct: check the upload pipeline. Network issues? Authentication? Nope. Everything looked fine for non-crash events.

Then we looked at local storage on the Steam Deck test device.

We found 15 crash dumps. Each one was over 500 MB, which was unexpectedly large for a demo game, with no heap memory being dumped.

For context, a typical Windows crash dump from the same game was 50-80 KB. Something was catastrophically wrong.

The size limit connection

So, we now knew that crashes are actually being caught and processed by the backend. We found the minidumps but they didn’t seem to be anything mini. They were several hundreds of megabytes, over Sentry’s current minidump size limit. Fun fact: we’re increasing that limit and those freak minidumps would have actually been ingested in the future (with a lot of noise in them), but let me get back to the story.

Down the rabbit hole: Thread stacks shouldn't be 11 MB

We pulled one of those massive minidumps apart. Most crash reporting formats include metadata about what they captured, and buried in there we found the smoking gun:

Click to Copy
Thread 0 - Game Thread
  Stack region captured: 11,768,000 bytes

Thread 1 - Background Worker Thread #1
  Stack region captured: 11,768,000 bytes

Thread 2 - Background Worker Thread #2
  Stack region captured: 11,768,000 bytes

Wait. Eleven megabytes of stack? For a single thread?

On Windows, thread stacks are typically 1 MB reserved with only 4-64 KB actually committed and in use. Our dumps were capturing 10+ MB per thread. With a 50+ threads in a typical game, that's 500+ MB just from stacks, plus all the heap memory, loaded modules, and other state.

Something was telling Crashpad (a utility from the Chromium project that we forked and is our default crash reporting backend on desktop) to capture way too much memory.

The TEB investigation

Crashpad determines how much stack to capture by reading the Windows Thread Environment Block (TEB), a per-thread data structure that Windows maintains. Specifically:

Click to Copy
// From the TEB:
StackLimit = 0x00007FF8B000  // Bottom of stack allocation
StackBase  = 0x00007FF9B000  // Top of stack allocation

// How much to capture:
StackSize = StackBase - StackLimit

We added logging to see what TEB values Crashpad was reading on Proton, and there it was. Wine's TEB implementation was actually setting StackLimit and StackBase to these large values, causing Crashpad to try capturing everything from the bottom to the top of the stack – basically the entire user-mode address space, and not only the committed (used) area.

This isn't a Wine bug – it's a known limitation of Wine's compatibility layer. TEB is a complex internal structure, and Wine prioritizes compatibility with actual Windows APIs over internal structure accuracy. Most Windows software never directly reads TEB, so this normally isn't a problem.

But crash reporters? They absolutely read TEB.

The Windows minidump epiphany

At this point, we knew what was broken, but we needed to figure out how to fix it.

We started researching how Windows itself generates minidumps (the native MiniDumpWriteDump API). We dug into Wine's implementation of this API and found something interesting:

Wine's minidump code doesn't blindly trust the TEB values either.

Instead, it uses the actual stack pointer (RSP on x64, ESP on x86) from the crash context:

Click to Copy
// Simplified from Wine's dbghelp implementation
DWORD64 stack_pointer = context->Rsp;  // Actual SP at crash time

// Only capture from SP to StackBase
if (stack_pointer >= stack_limit && stack_pointer < stack_base) {
    // Capture from SP, not from StackLimit
    capture_start = stack_pointer - RED_ZONE_SIZE;
    capture_size = stack_base - capture_start;
}

This makes perfect sense: the stack pointer points to currently-used memory. Everything below stack pointer (higher addresses on x86/x64, where stacks grow downward) is the active stack. Everything above stack pointer is either unused or already unwound.

By using stack pointer instead of the TEB's StackLimit, Wine's minidump generation works correctly even when TEB values are wrong.

We could do the same thing in Crashpad.

The Fix: Trust the stack pointer

We implemented a three-layer solution across our entire stack:

1. Crashpad: Smart stack capture mode

We added a new option to Crashpad that switches from TEB-based to SP-based stack capture. When enabled, the crash handler extracts the stack pointer from the saved crash context and calculates a sensible stack range:

Click to Copy
// Get SP from crash context (varies by architecture)
WinVMAddress sp = 0;

#if defined(ARCH_CPU_X86_64)
  sp = crash_context->Rsp;  // x64
#elif defined(ARCH_CPU_X86)
  sp = crash_context->Esp;  // x86
#elif defined(ARCH_CPU_ARM64)
  sp = crash_context->Sp;   // ARM64
#endif

// Verify SP is in valid range
if (sp >= thread_.stack_region_address &&
    sp < thread_.stack_region_address + thread_.stack_region_size) {
}

2. Sentry native: Easy API

We exposed this in Sentry Native's C API, which powers all our game engine integrations:

Click to Copy
sentry_options_t* options = sentry_options_new();
sentry_options_set_crashpad_limit_stack_capture_to_sp(options, 1);
sentry_init(options);

This single API is the foundation that enables compatibility across all engines – Unreal, Unity, Godot, and custom engines.

3. Game engine SDKs: Automatic detection

But manual configuration isn't great developer experience. In our game engine SDKs, we made it automatic by detecting Wine/Proton at runtime. When Wine/Proton is detected, the SDKs automatically:

  • Enable stack capture adjustment

  • Set OS name to SteamOS (+ Version) when detected

  • Add runtime Win/Proton (+ Version) context and tags

With automatic detection, every event includes rich platform context allowing you to filter crashes by platform (native Windows vs Proton vs Wine), track Wine-specific issues separately, understand player platform distribution, and identify whether crashes are game bugs or Wine compatibility issues. Filter to Wine/Proton tag to investigate Wine-specific problems.

Zero configuration - ship your game on any engine, crash reporting works on Wine/Proton.

Beyond Steam Deck: All Linux compatibility layers

While discovered on Steam Deck, this affects any Windows application running on Linux via Wine or compatibility layers—including Proton, Wine Staging, CrossOver, Lutris, and Bottles. This spans gaming platforms (Steam Deck, desktop Linux, macOS via CrossOver, ROG Ally/Legion Go with SteamOS), enterprise use cases (productivity apps, legacy software, dev tools), and CI/CD pipelines.

Great crash reporting shouldn't depend on the platform your players choose or the engine you build with. Whether they're on high-end Windows PCs, Steam Decks running Proton, or Linux desktops using Wine – and whether you're using Unreal, Unity, Godot, or a custom engine – you should get actionable crash reports that help you ship better games.

By understanding how running applications in the compatibility layer differs from native Windows and adapting our approach across our entire SDK stack, we've made Sentry the most reliable crash reporting solution for cross-platform gaming on Linux.

We've already brought this fix to the Unreal Engine SDK and Sentry Native SDK (for custom game engines). It's coming soon for our Unity and Godot SDKs.

Have questions or feedback? Join the conversation in our Discord. New to Sentry? Try Sentry for free.

Listen to the Syntax Podcast

Of course we sponsor a developer podcast. Check it out on your favorite listening platform.

Listen To Syntax
© 2025 • Sentry is a registered Trademark of Functional Software, Inc.