Brazil's biggest MU Online portal — since 2003
Tutorial Advanced Client

How to debug a MU client crash (dump analysis)

Turn that mysterious Main.exe crash into a root cause using memory dumps, WinDbg, symbols and stack trace reading, with a repeatable workflow to find the guilty module and fix it.

BR Bruno · Updated on Jul 14, 2025 · ⏱ 25 min read
Quick answer

A player opens a ticket: "the game closes by itself when I enter Lorencia." With no information, that turns into guesswork — swap the driver, reinstall, try again, and the problem comes back. The professional way to solve it is to turn the crash into an analyzable artifact: a memory dump. With the r

A player opens a ticket: "the game closes by itself when I enter Lorencia." With no information, that turns into guesswork — swap the driver, reinstall, try again, and the problem comes back. The professional way to solve it is to turn the crash into an analyzable artifact: a memory dump. With the right dump, configured symbols and a methodical read of the stack, you go from "closes by itself" to "null-pointer access inside d3d9.dll called from the particle render in build 1.04g." This tutorial builds that workflow from start to finish, repeatable for any Main.exe crash. All offsets, module names and paths appear as examples and vary by season/client.

Prerequisites

This guide assumes a client that already runs on most machines and crashes in specific situations. If you're still building the server base, first see how to set up a MU Online server. Debugging a crash makes sense when the client already works in the common case and fails in the exception.

RequirementDetail (example — varies by season/client)
WinDbgFrom the Windows SDK (classic WinDbg or WinDbg Preview)
Microsoft symbolsLocal cache + public srv
Main.exe PDBFrom the exact build, if you compile the client
Process HackerTo generate a dump on demand
Healthy reference machineWhere the client runs without crashing
The crash dumpMinidump from the affected machine

The item that separates fast analysis from suffering is the correct PDB. Without it, you read the stack in offsets; with it, in function names. If you have the client's source, archive the PDB of every released version alongside the binary — days later, an old dump without the right PDB is nearly useless for the client's own frames.

Stage 1 — Capture the dump

No dump, no analysis. There are three ways, in order of practicality for production.

Configure Windows Error Reporting to capture every Main.exe crash into a local folder. This catches the crash on the spot, without relying on the player reproducing it with a tool open.

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\Main.exe]
"DumpFolder"=hex(2):43,00,3a,00,5c,00,43,00,72,00,61,00,73,00,68,00,00,00
"DumpType"=dword:00000001
"DumpCount"=dword:0000000a

DumpType=1 generates a minidump; 2 would generate a full dump. The dump appears in the configured folder as soon as Main.exe closes on an exception. Distribute this tweak in your launcher to collect dumps from players who agree to send them.

Step 2 — On-demand dump with Process Hacker

When the crash is a "freeze" before closing, or when you want the state at the right moment: open Process Hacker, find Main.exe, right-click → Create dump file. It generates a manual minidump of the live process. Useful for hangs (not crashes), when the process doesn't die on its own.

Step 3 — MiniDumpWriteDump in the client itself

If you compile the client, install an unhandled-exception handler that calls MiniDumpWriteDump. That way the client generates its own dump with extra context (version, current map, player) written alongside a log. This is the richest option, because you control what gets captured.

// Sketch — install early, before the rest of init
LONG WINAPI MyHandler(EXCEPTION_POINTERS* ep) {
    HANDLE h = CreateFile(L"C:\\Crash\\main_crash.dmp", GENERIC_WRITE, 0,
                          nullptr, CREATE_ALWAYS, 0, nullptr);
    MINIDUMP_EXCEPTION_INFORMATION mei{ GetCurrentThreadId(), ep, FALSE };
    MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), h,
                      MiniDumpNormal, &mei, nullptr, nullptr);
    return EXCEPTION_EXECUTE_HANDLER;
}
// SetUnhandledExceptionFilter(MyHandler);

Stage 2 — Prepare WinDbg

Badly configured symbols turn a readable stack into offset soup. Configure it before opening any dump.

Step 4 — Symbol path

In WinDbg, set the local cache plus Microsoft's public server and reload:

.sympath srv*C:\Symbols*https://msdl.microsoft.com/download/symbols
.reload /f

This resolves ntdll, kernel32, d3d9, user32 and the like. For Main.exe's own frames, add the folder with the build's PDB:

.sympath+ C:\MU\pdb\1.04g
.reload /f Main.exe

Step 5 — Open the dump

File → Open Crash Dump, select the .dmp. WinDbg loads the modules and stops at the exception context. The first thing to run is the automatic analysis.

Stage 3 — Automatic analysis and reading the stack

Step 6 — !analyze -v

!analyze -v

This command does the heavy lifting up front: it identifies the exception code, the address that faulted, the likely guilty module and prints the stack of the thread that crashed. Read these fields carefully:

FieldWhat it tells you
EXCEPTION_CODEType of error (e.g. c0000005 = access violation)
FAULTING_IPInstruction/address where it blew up (e.g. Main+0x3f21a)
MODULE_NAMEModule flagged as guilty
STACK_TEXTThe call stack up to the crash
PROCESS_NAMEShould be Main.exe

c0000005 (access violation) is by far the most common: the client tried to read/write at an invalid address — almost always a null pointer or an already-freed one. If the faulting address is close to zero (e.g. 0x00000008), it's a null pointer with a struct-field offset: someone accessed object->field with object == null.

Step 7 — Read the stack manually

k

k prints the current thread's stack. Read it bottom to top (oldest at the bottom, the crash at the top). You want to answer: is the fault the client's, a third-party DLL's or the system's?

# illustrative example — varies by season/client
00 d3d9!CDevice::DrawPrimitive+0x2a     <- top: where it blew up
01 Main+0x3f21a                         <- client code calling the render
02 Main+0x41008
03 Main+0x51c30
04 kernel32!BaseThreadInitThunk         <- base: thread start

Here the top is in d3d9, but the one who called with a bad argument was Main+0x3f21a. That points to the client's render/graphics. If the top were in a strange module (e.g. overlay_x.dll or an antivirus), suspicion would shift to injected third-party software.

Step 8 — List modules and find the intruder

lm

lm lists all loaded modules. Compare with those of a healthy machine. Modules that appear only in the problematic dump — recording overlays, injectors, antivirus hooks — are strong suspects, especially if they show up in the stack.

Stage 4 — Triage by crash pattern

With the stack and modules in hand, classify. Most MU client crashes fall into a few patterns.

Pattern in the stackLikely causeFix direction
Top in d3d9/d3d8 called by the clientNull graphics resource, missing texture/effectCheck for a missing asset; update the GPU driver
Top in Main.exe with c0000005 near zeroNull pointer in the client codeLocate the offset in the PDB; fix it in the source
Third-party module at the topOverlay/AV/injectorRemove the software; test without it
Stack overflow (c00000fd)Infinite recursionReview the client's loop/callback
Heap corruptionWrite past a bufferFull dump + gflags/PageHeap
Crash only when loading a mapCorrupt asset of that mapRepatch the files of that map

Step 9 — From offset to line (with the PDB)

If the crash is in Main.exe and you have the PDB, resolve the address:

ln Main+0x3f21a

ln shows the nearest symbol — the function name and offset. With source, you reach the line. Without a PDB, Main+0x3f21a is still useful: it is stable for that build, so repeated crashes at the same offset confirm the same bug, and you can compare reports from several players.

Stage 5 — Reproduce and confirm

Analysis without reproduction is a hypothesis. Try to reproduce it on the reference machine following the player's scenario (same map, same action). If it reproduces, generate your own dump and check whether it lands on the same offset/module. A match confirms the cause; a mismatch indicates an environmental factor (driver, overlay, the player's local DLL).

For intermittent heap crashes, turn on PageHeap for Main.exe with gflags before reproducing — it makes the corruption blow up at the moment of the invalid write, not later, letting the stack point to the real culprit.

Common errors and fixes

Symptom in the analysisCauseFix
Stack with only offsets, no namesPDB/symbols missingConfigure .sympath; archive the PDB per build
!analyze -v blames a system moduleThe real fault is with the callerRead k and find the client frame that called it
Empty or truncated dumpInsufficient minidumpGenerate a full dump for the case
Crash won't reproduce on your machineThe player's environmental factorCompare lm; suspect an overlay/driver/AV
Offset changes with each reportDifferent builds among playersStandardize the client version via a launcher
WinDbg won't open the dumpDump of a different architecture (x86/x64)Use the WinDbg matching the architecture
Symbols won't downloadFirewall blocks msdlAllow access or pre-populate the local cache

Best practices to avoid debugging blind

Debugging starts before the crash. Standardize the client version with a launcher so all dumps come from the same build; archive the PDB of every released version; embed an exception handler that writes a dump + context (map, version, player); and keep a healthy reference machine for module comparison. With that, every "closes by itself" ticket arrives already with a dump and context, and analysis becomes routine instead of archaeology.

Launch checklist

  • WER configured to capture Main.exe dumps
  • Exception handler with MiniDumpWriteDump in the client (if you compile it)
  • Context (version, map, player) written alongside the dump
  • PDB of each build archived alongside the binary
  • WinDbg with .sympath for local cache + MS server
  • Healthy reference machine available for comparison
  • !analyze -vklmln workflow documented for the team
  • Client version standardized via a launcher
  • Procedure for the player to send the dump safely defined
  • Crashes cataloged by offset/module to detect recurrence
  • PageHeap/gflags available to hunt heap corruption
  • Fix validated by reproducing at the same offset before closing the case

Frequently asked questions

What's the difference between a minidump and a full dump?

A minidump is small (a few MB) and contains thread stacks, registers and loaded modules, enough for most crash analyses. A full dump includes the entire process memory, is huge (can exceed 1 GB) and is only worth it when you need to inspect the heap, buffers and object states that the minidump doesn't keep. Always start with the minidump.

Do I need the Main.exe source code to analyze the dump?

It helps a lot, but it's not required. Without source and without symbols (PDB), you still see the stack in terms of modules and offsets (e.g. Main.exe+0x3F21A), identify whether the crash is in the client, a third-party DLL or the system, and recognize patterns like a null-pointer access. With symbols you gain function names; with source, the exact line.

Where does Windows save crash dumps?

It depends on the configuration. WER (Windows Error Reporting) usually writes to %LOCALAPPDATA%\\CrashDumps when enabled via the registry. You can also generate one on demand with Process Hacker/Task Manager (Create dump file) or programmatically via MiniDumpWriteDump in the client itself. Configuring WER to capture Main.exe automatically is the most practical option in production.

The crash only happens on some players' machines, now what?

A machine-specific crash is almost always environmental: an old GPU driver, an injected third-party DLL (overlay, antivirus), missing DirectX/redistributables, or the wrong compatibility mode. Ask the player for the dump, compare the loaded modules with those of a healthy machine and look for the strange module in the stack. Often the fix is to update a driver or remove an overlay.

How do I configure symbols in WinDbg?

Point the symbol path to Microsoft's server plus a local cache folder, for example with .sympath and .reload. That resolves the names of system functions (ntdll, kernel32, d3d9). For Main.exe itself, you need the PDB matching that build; without it, the client frames show up only as offsets. Keep the PDBs of every version you release.

BR
Events, maps & items editor

Bruno specializes in MU Online events, maps, bosses and item economy. He documents every detail based on real gameplay.

Keep reading

Related articles