¿Por qué al intentar entrar al kernel de NT 3.1 mi máquina 486DX4 se reinicia?
(retrocomputing.stackexchange.com)- En Windows NT 3.1, al hacer un break-in con Ctrl-C usando
i386kd, en vez de aparecer el promptkd>el sistema objetivo 486DX4 se reiniciaba; la causa era un problema de compatibilidad con 486 mejorados KiSaveProcessorControlStateyKiRestoreProcessorControlStatede NTOSKRNL.EXE usan una comparación de word al comprobar el tipo de CPU, por lo que identifican erróneamente a un 486 con soporte de CPUID como si fuera un Pentium o superior- Como
CpuTypeyCpuIDde KPRCB se leen juntos, el número de modelo de un 486 con soporte de CPUID se interpreta como 256 mayor, y eso termina provocando un acceso al registro CR4, que no existe en un 486 - Con el 486DX-33 original y con un Am486DX4-NV8T write-through sin SMM, el kernel debugging funcionaba normalmente, lo que acotó el problema a los 486 mejorados que ofrecen la instrucción CPUID
- Si se parchean las dos ubicaciones de
cmp ds:word_FFDFF138, 5dentro de NTOSKRNL.EXE para usar byte compare, el problema se puede corregir tanto en NT 3.1 Advanced Server original como en NT 3.1 SP3
Entorno de reproducción y síntoma
- Se instaló Windows NT 3.1 en una Compaq ProSignia 3080 y luego se intentó hacer kernel debugging
- Este sistema realmente ejecutó Windows NT 3.1 en operación y parece ser una de las máquinas a las que Windows NT apuntaba explícitamente
- La RAM se amplió a 128 MB y el Intel 486DX-33 en socket fue reemplazado por un AMD enhanced 486DX4-SV8B
- Ese CPU soporta caché write-back y SMM, y está montado en un socket adaptador de voltaje
- Se dejó para después el soporte del BIOS para 486DX4 y se configuró el CPU con multiplicador 2x mediante jumpers
- Sin soporte del chipset para L1 write-back y configurado a 2x, se esperaba compatibilidad de software con un Intel 80486DX2-66
- El Intel 80486DX2-66 es una opción soportada por este sistema
- La instalación de Windows NT 3.1 en sí se completó sin problemas
- El CD de Windows NT 3.1 incluye símbolos de depuración completos, así que se intentó depurar el kernel
- El objetivo era investigar por qué NetDDE dejaba errores en el event log
- También había un problema donde el sistema se caía con cierta tarjeta Ethernet EISA, y seguía existiendo la posibilidad de una falla de hardware
- En lugar del
kdontkdde los kits de desarrollo recientes de Windows 10, había que usari386kdincluido con Windows NT 3.1 para que la configuración de kernel debugging coincidiera - Al intentar un break-in con Ctrl-C desde
i386kd, la máquina objetivo se reiniciaba en vez de mostrar el promptkd>
Cosas descartadas como causa
- Se confirmó que la memoria funcionaba bien
- No era corrupción de archivos del sistema
- No estaba activo ningún watchdog de hardware que reiniciara el sistema cuando el kernel se detenía para depuración
- El adaptador USB-serial del host se comunicaba correctamente
- Aunque parecía ser un dispositivo PL2301 falsificado, no estaba enviando por error un comando de “reboot system” al depurador
- El protocolo KD sí incluye un comando real de reboot system
- Tampoco estaba relacionado con opciones de administración remota o alertas de la motherboard
Causa real: incompatibilidad entre 486 mejorados y el kernel de NT 3.1
- El kernel de Windows NT 3.1 no es compatible con procesadores 486 mejorados
- Más concretamente, el problema aparece en procesadores 486 que ofrecen la instrucción
CPUID - El kernel debugging funciona correctamente en los siguientes CPU
- El 486DX-33 original instalado
- Un Am486DX4-NV8T write-through de un core antiguo no mejorado y sin SMM
- Si el objetivo es solo probar el kernel debugging de NT 3.1, conviene usar un CPU compatible de base con Windows NT 3.1
- Si se quiere corregir NT en sí, hay que parchear el bug de detección del tipo de CPU en NTOSKRNL.EXE
Ruta de código del kernel donde ocurre el problema
- La causa directa de la incompatibilidad es un bug en
KiSaveProcessorControlState- Su función correspondiente,
KiRestoreProcessorControlState, tiene un bug similar
- Su función correspondiente,
KiSaveProcessorControlStatese llama desde tres lugares dentro de NTOSKRNL.EXE- Cuando una excepción se refleja hacia el kernel debugger mediante
KdpTrap- En el break-in con Ctrl-C, ocurre una breakpoint exception en la función de polling de break-in dentro de la interrupción del tick del temporizador
- Cuando se llama a
KeBugCheckEx, es decir, en una situación de “pantalla azul” - Cuando se llama a
KiSaveProcessorState- Si el análisis de flujo de control de NTOSKRNL.EXE en IDA es correcto, esta función no está exportada ni es llamada desde dentro de NTOSKRNL, así que en la práctica parece no ocurrir
- Cuando una excepción se refleja hacia el kernel debugger mediante
KiSaveProcessorControlStateguarda el estado de control del procesador en una estructuraCONTEXTextendida- Guarda CR0, CR2 y CR3
- En Pentium o superior, también guarda CR4
- Guarda los registros de depuración DR0~DR3, DR6 y DR7
- También guarda configuraciones globales de modo protegido como la dirección del GDT, la dirección del IDT, el selector TSS activo y el selector LDT
Error al interpretar campos de KPRCB
- Para determinar el tipo de procesador se usan valores del KPRCB
- KPRCB forma parte del KPCR
- El KPRCB del bootstrap processor o de un sistema monoprocesador está en la dirección virtual
FFDFF120, y ese método la tiene hardcodeada
- Los campos relacionados con KPRCB en NT 3.1, documentados por Geoff Chappell, son los siguientes
+018 CHAR CpuType+019 CHAR CpuID+01A UShort CpuStep
- Estos campos se inicializan en
KiSetProcessorType- En procesadores 486, el byte del offset 18 se establece en 4
- En procesadores Pentium, se establece en 5
- En Pentium Pro y Pentium II/III, se establece en 6
- El byte del offset 19 es un flag booleano que indica si el procesador soporta CPUID y si este se comporta de forma “razonable”
- La instrucción de comparación problemática lee un word en la dirección
FFDFF138, no un byteFFDFF138está a 18h bytes del inicio de KPRCB- Por lo tanto, no solo interpreta
CpuType, sino también el byte siguiente,CpuID, como parte del número de modelo
- Los procesadores con soporte de CPUID son tratados como si tuvieran un número de modelo 256 mayor que el real
- Un 80-4-86 con soporte de CPUID es tratado en Windows NT 3.1 como un 80-260-86
- Como 260 es mucho mayor que 5, que es el umbral de Pentium, el kernel concluye que ese procesador debería tener CR4
- Los 486 mejorados no tienen CR4, por lo que el problema aparece en la ruta del break-in
Método de parcheo
- Una vez confirmado el bug, corregirlo es simple
- En NTOSKRNL.EXE, la instrucción
cmp ds:word_FFDFF138, 5aparece solo dos vecesKiSaveProcessorControlStateKiRestoreProcessorControlState
- En ambas ubicaciones hay que cambiar la comparación de word por una comparación de byte
- Con un editor hexadecimal, se parchea dos veces la siguiente secuencia de bytes
- Original:
66 83 3D 38 F1 DF FF 05 - Modificada:
90 80 3D 38 F1 DF FF 05
- Original:
- Esta corrección aplica tanto al NTOSKRNL.EXE de la distribución original de NT 3.1 Advanced Server como a NT 3.1 SP3
1 comentarios
Comentarios de Hacker News
Hace tiempo hice un chequeo de puertos para verificar si un servicio seguía activo, y terminé tumbando media empresa con solo abrir algunos puertos TCP en las máquinas
Eran tiempos absurdos
Hemos avanzado muchísimo
https://en.wikipedia.org/wiki/Ping_of_death
Algunas máquinas también tenían una vulnerabilidad de SMB sin parchear durante años. Ya existía Metasploit en esa época, así que con unos cuantos comandos podías inyectar VNC en la mayoría de los hosts Windows de la red local. Hoy en día, al menos, la velocidad de parcheo es enorme
Ah, la madriguera de conejo del retrocomputing. Inofensivamente separada de las consecuencias del mundo real, pero aun así muy satisfactoria
Es una auténtica trampa de miel para geeks. Al final terminé haciendo clic en el botón para expandir “ver 6 comentarios más”
Tal vez hoy sí pueda trabajar un poco
¿Pero una 486? Me trae demasiados recuerdos de pantallazos azules y de esperas interminables mientras hacía swapping al disco porque no podía con la memoria. Creo que todavía es demasiado pronto para mí
“La solución es obvia” es totalmente cierto
Probablemente siguió leyendo el desensamblado del código de trap hasta encontrar el problema. No creo que pudiera usar QEMU u otros medios de depuración
Hay que leer también el método que quedó enterrado en un comentario oculto cerca del final
No es que oculte los comentarios con menos votos, ni los más recientes ni los más antiguos. Se siente aleatorio e innecesario
Si el problema es el espacio, creo que sería mejor poner paginación en vez de ocultarlos
Cosas como los adaptadores slotket que se usaban por compatibilidad en placas viejas de Abit, Asus y quizá MSI realmente tenían capacidad de overclocking
La clave era la velocidad de la RAM y hasta qué reloj podía aguantar la motherboard en la configuración del multiplicador. Eran los días relativamente tempranos del overclocking, con Celeron y Pentium II
En algún momento, por compatibilidad, puse un Celeron 600 en un adaptador y luego en un adaptador Slot II a Socket 370, con una configuración como [1], y funcionaba bien a 1.2GHz. Creo que incluso lo subí a 1.4GHz en Windows ME, y al final terminé quemando ese CPU
[1]http://krick.3feetunder.com/370mod/
La respuesta a la pregunta fue realmente excelente
El tema era bueno y la respuesta también fue muy buena
Es genial que la persona que hizo la pregunta también haya publicado la respuesta
Me gusta esto. Me alegra mucho que la gente se interese por el retrocomputing
Documentar este tipo de problemas y soluciones para todos es algo bueno
Sabía que esto de CPUID era una mala idea
¿Por qué la pregunta y la respuesta tenían la misma marca de tiempo?
Probablemente pensó que publicar esta información en el formato de preguntas y respuestas de SO duraría más que una entrada de blog que nadie encontraría
¿Está mal que haya sabido de qué se trataba sin abrirlo?
En fin, tengo que ir a gritarles a los chicos de hoy que se salgan de mi césped
Solo contábamos con nuestro ingenio y, si hacía falta, con algunos expertos a los que podíamos llamar por teléfono fijo. No existía esa maravillosa Internet llena de respuestas de utilidad variable