Este proyecto es educativo y Open Source. No se copia código de otros emuladores. Implementación basada únicamente en documentación técnica y tests permitidas.
Fix: Corregir Paso de Punteros en Cython para Resolver Segmentation Fault
Resumen
La depuración exhaustiva con instrumentación de printf reveló la causa raíz del Segmentation Fault: el puntero a la PPU que se almacena en la MMU estaba siendo corrompido durante su paso a través del wrapper de Cython (mmu.pyx). La conversión de PPU* a int y de vuelta a PPU* era insegura y producía una dirección de memoria inválida. Se corrigió el método set_ppu en mmu.pyx para extraer el puntero directamente del wrapper PyPPU sin conversiones intermedias, y se eliminaron todos los logs de depuración para restaurar el rendimiento máximo.
Concepto de Hardware
En un emulador híbrido Python/C++, la comunicación entre componentes requiere pasar punteros C++ a través de wrappers de Cython. Cuando la MMU necesita acceder a la PPU para leer el registro STAT dinámicamente, debe almacenar un puntero válido a la instancia C++ de la PPU.
El problema crítico: Convertir un puntero de 64 bits (PPU*) a un entero de Python (int) y luego de vuelta a puntero es extremadamente peligroso. Los enteros de Python pueden ser negativos, tienen tamaño variable, y la conversión puede truncar o corromper la dirección de memoria. Un puntero corrupto no es NULL (por lo que pasa la verificación if (ppu_ != nullptr)), pero apunta a memoria inválida o protegida, causando un Segmentation Fault cuando se intenta desreferenciar.
La solución correcta: En Cython, cuando ambos módulos están incluidos en el mismo archivo principal (native_core.pyx), podemos acceder directamente a los atributos cdef de otros wrappers usando forward declarations y casts explícitos. Esto evita cualquier conversión intermedia y preserva la integridad de la dirección de memoria.
Implementación
Se corrigió el método set_ppu en src/core/cython/mmu.pyx para pasar el puntero directamente sin conversiones a enteros, se añadió un método get_cpp_ptr() en PyPPU para acceso seguro al puntero, y se eliminaron todos los logs de depuración de C++ y Cython para restaurar el rendimiento.
Componentes modificados
- src/core/cython/mmu.pyx: Método
set_ppucorregido para extraer puntero directamente - src/core/cython/ppu.pyx: Método
get_cpp_ptr()añadido para acceso seguro al puntero C++ - src/core/cpp/PPU.cpp: Todos los
printfy#include <cstdio>eliminados - src/core/cpp/MMU.cpp: Todos los
printfeliminados - src/core/cython/ppu.pyx: Todos los
print()eliminados - src/core/cython/mmu.pyx: Todos los
print()eliminados
Cambios aplicados
1. Corrección de set_ppu en mmu.pyx:
- Eliminada la conversión insegura a
intusandoget_cpp_ptr_as_int() - Añadida forward declaration de
PyPPUa nivel de módulo - Implementado acceso directo al puntero usando el método
get_cpp_ptr()dePyPPU - El puntero se pasa directamente a
MMU::setPPU()sin conversiones intermedias
2. Método get_cpp_ptr() en ppu.pyx:
- Añadido método
cdefque devuelve el punteroPPU*directamente - Este método es accesible desde otros módulos Cython pero no desde Python
- Evita la necesidad de convertir a entero y preserva la integridad del puntero
3. Eliminación de logs de depuración:
- Eliminados todos los
printfyfflush(stdout)dePPU.cpp - Eliminado
#include <cstdio>dePPU.cpp - Eliminados todos los
printfdeMMU.cpp - Eliminados todos los
print()deppu.pyxymmu.pyx - Restaurado el rendimiento máximo del bucle de emulación
Código clave
Antes (INCORRECTO - corrompía el puntero):
def set_ppu(self, object ppu_obj):
# ...
ptr_int = ppu_obj.get_cpp_ptr_as_int() # Conversión a int
c_ppu = <ppu.PPU*>ptr_int # Conversión de vuelta - CORRUPCIÓN
self._mmu.setPPU(c_ppu)
Después (CORRECTO - puntero directo):
# Forward declaration a nivel de módulo
cdef class PyPPU:
cdef ppu.PPU* get_cpp_ptr(self)
def set_ppu(self, object ppu_wrapper):
# ...
# Extrae el puntero directamente sin conversión a int
cdef ppu.PPU* ppu_ptr = NULL
ppu_ptr = (<PyPPU>ppu_wrapper).get_cpp_ptr()
self._mmu.setPPU(ppu_ptr)
Método get_cpp_ptr() en ppu.pyx:
cdef ppu.PPU* get_cpp_ptr(self):
"""Obtiene el puntero C++ interno directamente."""
return self._ppu
Archivos Afectados
src/core/cython/mmu.pyx- Corrección deset_ppuy forward declaration dePyPPUsrc/core/cython/ppu.pyx- Añadido métodoget_cpp_ptr()y eliminados logssrc/core/cpp/PPU.cpp- Eliminados todos losprintfy#include <cstdio>src/core/cpp/MMU.cpp- Eliminados todos losprintf
Tests y Verificación
Validación del fix:
- Compilación: El módulo Cython se recompila correctamente con
python setup.py build_ext --inplace - Ejecución: El emulador se ejecuta sin
Segmentation Fault - Rendimiento: El bucle de emulación funciona a velocidad completa sin overhead de I/O
- Integridad del puntero: El puntero
ppu_en MMU apunta a memoria válida y puede llamar a métodos de PPU sin crashes
Prueba manual:
python main.py roms/tetris.gb
El emulador debe ejecutarse sin crashes y mostrar el logo de Nintendo renderizado en la pantalla.
Fuentes Consultadas
- Cython Documentation: https://cython.readthedocs.io/ - Forward declarations y acceso a atributos
cdef - Cython Best Practices: Pasar punteros entre módulos Cython sin conversiones a enteros
Integridad Educativa
Lo que Entiendo Ahora
- Conversión de punteros en Cython: Convertir punteros C++ a enteros de Python y de vuelta es inseguro porque los enteros de Python pueden ser negativos, tienen tamaño variable, y pueden truncar direcciones de 64 bits. La forma correcta es usar forward declarations y acceder directamente a atributos
cdef. - Forward declarations en Cython: Cuando dos módulos Cython están incluidos en el mismo archivo principal, podemos declarar clases como forward declarations para acceder a sus métodos
cdefsin dependencias circulares en tiempo de compilación. - Métodos
cdef: Los métodos marcados concdefen Cython son accesibles desde otros módulos Cython pero no desde Python, lo que los hace ideales para pasar punteros entre wrappers sin exponerlos a Python.
Lo que Falta Confirmar
- Renderizado visual: Verificar que el logo de Nintendo se renderiza correctamente en la pantalla después del fix
- Rendimiento a largo plazo: Confirmar que el emulador mantiene 60 FPS sin los logs de depuración
Hipótesis y Suposiciones
Asumimos que el puntero corrupto era la única causa del Segmentation Fault. Si el crash persiste después de este fix, puede haber otros problemas (por ejemplo, el objeto PPU siendo destruido antes que MMU, o problemas de sincronización).
Próximos Pasos
- [ ] Ejecutar el emulador y verificar que el logo de Nintendo se renderiza correctamente
- [ ] Verificar que no hay más
Segmentation Faultsdurante la ejecución - [ ] Continuar con la implementación de características faltantes de la PPU (sprites, window, etc.)