Cómo depurar un crash del cliente de MU (análisis de dump)
Transforma ese crash misterioso del Main.exe en causa raíz usando dumps de memoria, WinDbg, símbolos y lectura de stack trace, con un flujo repetible para encontrar el módulo culpable y corregir.
Un jugador abre un ticket: "el juego se cierra solo cuando entro en Lorencia". Sin información, eso se vuelve adivinanza — cambias el driver, reinstalas, intentas de nuevo, y el problema vuelve. La forma profesional de resolverlo es transformar el crash en un artefacto analizable: un dump de memoria
Un jugador abre un ticket: "el juego se cierra solo cuando entro en Lorencia". Sin información, eso se vuelve adivinanza — cambias el driver, reinstalas, intentas de nuevo, y el problema vuelve. La forma profesional de resolverlo es transformar el crash en un artefacto analizable: un dump de memoria. Con el dump correcto, símbolos configurados y una lectura metódica de la pila, pasas de "se cierra solo" a "acceso a puntero nulo dentro de d3d9.dll llamado desde el render de partículas en la build 1.04g". Este tutorial arma ese flujo de principio a fin, repetible para cualquier crash del Main.exe. Todos los offsets, nombres de módulo y rutas aparecen como ejemplo y varían por season/cliente.
Requisitos previos
Esta guía asume un cliente que ya corre en la mayoría de las máquinas y crashea en situaciones específicas. Si aún estás montando la base del servidor, mira antes cómo crear un servidor de MU Online. Depurar un crash tiene sentido cuando el cliente ya funciona en el caso común y falla en la excepción.
| Requisito | Detalle (ejemplo — varía por season/cliente) |
|---|---|
| WinDbg | Del Windows SDK (WinDbg clásico o WinDbg Preview) |
| Símbolos de Microsoft | Caché local + srv público |
| PDB del Main.exe | De la build exacta, si compilas el cliente |
| Process Hacker | Para generar el dump bajo demanda |
| Máquina sana de referencia | Donde el cliente corre sin crashear |
| El dump del crash | Minidump de la máquina afectada |
El elemento que separa un análisis rápido del sufrimiento es el PDB correcto. Sin él, lees la pila en offsets; con él, en nombres de función. Si tienes el código del cliente, archiva el PDB de cada versión lanzada junto al binario — días después, un dump antiguo sin el PDB correcto es casi inútil para los frames del propio cliente.
Etapa 1 — Capturar el dump
Sin dump no hay análisis. Hay tres formas, en orden de practicidad para producción.
Paso 1 — WER automático (recomendado)
Configura el Windows Error Reporting para capturar todo crash del Main.exe en una carpeta local. Esto atrapa el crash en el acto, sin depender de que el jugador lo reproduzca con una herramienta abierta.
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 genera un minidump; 2 generaría un full dump. El dump aparece en la carpeta configurada apenas el Main.exe se cierra por excepción. Distribuye este ajuste en tu launcher para recolectar dumps de los jugadores que acepten enviarlos.
Paso 2 — Dump bajo demanda con Process Hacker
Cuando el crash es un "cuelgue" antes de cerrarse, o cuando quieres el estado en el momento justo: abre Process Hacker, encuentra Main.exe, clic derecho → Create dump file. Genera un minidump manual del proceso vivo. Útil para hangs (no crashes), cuando el proceso no muere solo.
Paso 3 — MiniDumpWriteDump en el propio cliente
Si compilas el cliente, instala un handler de excepción no manejada que llame a MiniDumpWriteDump. Así el cliente genera su propio dump con contexto extra (versión, mapa actual, jugador) grabado junto en un log. Es el más rico, porque controlas qué capturar.
// Esbozo — instalar temprano, antes del resto del init
LONG WINAPI MeuHandler(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(MeuHandler);
Etapa 2 — Preparar el WinDbg
Los símbolos mal configurados hacen que una pila legible se vuelva una sopa de offsets. Configúralos antes de abrir cualquier dump.
Paso 4 — Ruta de símbolos
En WinDbg, define el caché local más el servidor público de Microsoft y recarga:
.sympath srv*C:\Symbols*https://msdl.microsoft.com/download/symbols
.reload /f
Esto resuelve ntdll, kernel32, d3d9, user32 y similares. Para los frames del propio Main.exe, agrega la carpeta con el PDB de la build:
.sympath+ C:\MU\pdb\1.04g
.reload /f Main.exe
Paso 5 — Abrir el dump
File → Open Crash Dump, selecciona el .dmp. WinDbg carga los módulos y se detiene en el contexto de la excepción. Lo primero a ejecutar es el análisis automático.
Etapa 3 — Análisis automático y lectura de la pila
Paso 6 — !analyze -v
!analyze -v
Este comando hace el trabajo pesado inicial: identifica el código de la excepción, la dirección que falló, el módulo culpable probable e imprime la pila del thread que crasheó. Lee con atención estos campos:
| Campo | Qué dice |
|---|---|
EXCEPTION_CODE | Tipo del error (ej.: c0000005 = access violation) |
FAULTING_IP | Instrucción/dirección donde estalló (ej.: Main+0x3f21a) |
MODULE_NAME | Módulo señalado como culpable |
STACK_TEXT | La pila de llamadas hasta el crash |
PROCESS_NAME | Debe ser Main.exe |
c0000005 (access violation) es de lejos el más común: el cliente intentó leer/escribir en una dirección inválida — casi siempre un puntero nulo o ya liberado. Si la dirección que falló está cerca de cero (ej.: 0x00000008), es un puntero nulo con offset de campo de struct: alguien accedió a objeto->campo con objeto == null.
Paso 7 — Leer la pila manualmente
k
k imprime la pila del thread actual. Léela de abajo hacia arriba (lo más antiguo abajo, el crash arriba). Quieres responder: ¿la culpa es del cliente, de una DLL de terceros o del sistema?
# ejemplo ilustrativo — varía por season/cliente
00 d3d9!CDevice::DrawPrimitive+0x2a <- arriba: donde estalló
01 Main+0x3f21a <- código del cliente llamando al render
02 Main+0x41008
03 Main+0x51c30
04 kernel32!BaseThreadInitThunk <- base: inicio del thread
Aquí la parte de arriba está en d3d9, pero quien llamó con un argumento malo fue Main+0x3f21a. Esto apunta al render/gráfico del cliente. Si la parte de arriba estuviera en un módulo extraño (ej.: overlay_x.dll o un antivirus), la sospecha cambiaría hacia software de terceros inyectado.
Paso 8 — Listar módulos y encontrar al intruso
lm
lm lista todos los módulos cargados. Compáralos con los de una máquina sana. Los módulos que solo aparecen en el dump problemático — overlays de grabación, inyectores, hooks de antivirus — son fuertes sospechosos, especialmente si aparecen en la pila.
Etapa 4 — Triaje por patrón de crash
Con la pila y los módulos en mano, clasifica. La mayoría de los crashes de cliente de MU cae en pocos patrones.
| Patrón en la pila | Causa probable | Dirección de la corrección |
|---|---|---|
Arriba en d3d9/d3d8 llamado por el cliente | Recurso gráfico nulo, textura/efecto ausente | Revisar asset faltante; actualizar driver GPU |
Arriba en Main.exe con c0000005 cerca de cero | Puntero nulo en el código del cliente | Localizar el offset en el PDB; corregir en la fuente |
| Módulo de terceros arriba | Overlay/AV/inyector | Quitar el software; probar sin él |
Stack overflow (c00000fd) | Recursión infinita | Revisar loop/callback del cliente |
Heap corruption | Escritura fuera de buffer | Full dump + gflags/PageHeap |
| Crash solo al cargar un mapa | Asset corrupto de ese mapa | Repatch de los archivos de ese mapa |
Paso 9 — Del offset a la línea (con PDB)
Si el crash está en el Main.exe y tienes el PDB, resuelve la dirección:
ln Main+0x3f21a
ln muestra el símbolo más cercano — nombre de la función y desplazamiento. Con la fuente, llegas a la línea. Sin PDB, Main+0x3f21a aún es útil: es estable para esa build, así que crashes repetidos en el mismo offset confirman el mismo bug, y puedes comparar reportes de varios jugadores.
Etapa 5 — Reproducir y confirmar
Un análisis sin reproducción es una hipótesis. Intenta reproducirlo en la máquina de referencia siguiendo el escenario del jugador (mismo mapa, misma acción). Si lo reproduces, genera un dump tuyo y comprueba si cae en el mismo offset/módulo. Que coincida confirma la causa; que no coincida indica un factor de entorno (driver, overlay, DLL local del jugador).
Para crashes de heap intermitentes, activa el PageHeap para el Main.exe con gflags antes de reproducir — hace que la corrupción estalle en el acto de la escritura inválida, no después, dejando que la pila apunte al verdadero culpable.
Errores comunes y soluciones
| Síntoma en el análisis | Causa | Solución |
|---|---|---|
| Pila solo con offsets, sin nombres | PDB/símbolos ausentes | Configurar .sympath; archivar el PDB por build |
!analyze -v señala un módulo del sistema | La culpa real es de quien lo llamó | Leer k y encontrar el frame del cliente que lo llamó |
| Dump vacío o truncado | Minidump insuficiente | Generar un full dump para el caso |
| El crash no se reproduce en tu máquina | Factor de entorno del jugador | Comparar lm; sospechar de overlay/driver/AV |
| El offset cambia en cada reporte | Builds diferentes entre jugadores | Estandarizar la versión del cliente vía launcher |
| WinDbg no abre el dump | Dump de arquitectura diferente (x86/x64) | Usar el WinDbg correspondiente a la arquitectura |
| Los símbolos no descargan | El firewall bloquea el msdl | Liberar el acceso o pre-poblar el caché local |
Buenas prácticas para no depurar a ciegas
La depuración empieza antes del crash. Estandariza la versión del cliente con un launcher para que todos los dumps sean de la misma build; archiva el PDB de cada versión lanzada; incrusta un handler de excepción que grabe dump + contexto (mapa, versión, jugador); y mantén una máquina de referencia sana para comparar módulos. Con eso, cada ticket "se cierra solo" llega ya con dump y contexto, y el análisis se vuelve rutina en vez de arqueología.
Lista de verificación de lanzamiento
- WER configurado para capturar dumps del
Main.exe - Handler de excepción con
MiniDumpWriteDumpen el cliente (si compilas) - Contexto (versión, mapa, jugador) grabado junto al dump
- PDB de cada build archivado junto al binario
- WinDbg con
.sympathpara caché local + servidor MS - Máquina de referencia sana disponible para comparación
- Flujo
!analyze -v→k→lm→lndocumentado para el equipo - Versión del cliente estandarizada vía launcher
- Procedimiento para que el jugador envíe el dump con seguridad definido
- Crashes catalogados por offset/módulo para detectar recurrencia
- PageHeap/gflags disponible para cazar corrupción de heap
- Corrección validada por reproducción en el mismo offset antes de cerrar el caso
Preguntas frecuentes
¿Cuál es la diferencia entre minidump y full dump?
El minidump es pequeño (pocos MB) y contiene pilas de threads, registros y módulos cargados, suficiente para la mayoría de los análisis de crash. El full dump incluye toda la memoria del proceso, es enorme (puede pasar de 1 GB) y solo vale la pena cuando necesitas inspeccionar heap, buffers y estados de objetos que el minidump no guarda. Empieza siempre por el minidump.
¿Necesito el código fuente del Main.exe para analizar el dump?
Ayuda mucho, pero no es obligatorio. Sin fuente y sin símbolos (PDB), aún ves la pila en términos de módulos y offsets (ej.: Main.exe+0x3F21A), identificas si el crash es en el cliente, en una DLL de terceros o en el sistema, y reconoces patrones como acceso a puntero nulo. Con símbolos ganas nombres de función; con fuente, la línea exacta.
¿Dónde guarda Windows los dumps de crash?
Depende de la configuración. El WER (Windows Error Reporting) suele grabar en %LOCALAPPDATA%\\CrashDumps cuando está habilitado por registro. También puedes generarlo bajo demanda con Process Hacker/Administrador de Tareas (Create dump file) o programáticamente vía MiniDumpWriteDump en el propio cliente. Configurar el WER para capturar el Main.exe automáticamente es lo más práctico en producción.
El crash solo ocurre en la máquina de algunos jugadores, ¿y ahora?
Un crash específico de máquina casi siempre es de entorno: driver de GPU antiguo, DLL de terceros inyectada (overlay, antivirus), DirectX/redistributables faltantes, o modo de compatibilidad equivocado. Pídele el dump al jugador, compara los módulos cargados con los de una máquina sana y busca el módulo extraño en la pila. Muchas veces la corrección es actualizar el driver o quitar un overlay.
¿Cómo configurar los símbolos en WinDbg?
Apunta la ruta de símbolos al servidor de Microsoft más una carpeta local de caché, por ejemplo con .sympath y .reload. Esto resuelve los nombres de las funciones del sistema (ntdll, kernel32, d3d9). Para el propio Main.exe, necesitas el PDB correspondiente a esa build; sin él, los frames del cliente aparecen solo como offsets. Guarda los PDB de cada versión que lances.