13

He implementado PARLANSE, un lenguaje bajo MS Windows que usa pilas de cactus para implementar programas paralelos. Los trozos de pila se asignan por función a y son solo el tamaño correcto para manejar variables locales, push de temperatura de expresión/pops y llamadas a bibliotecas (incluido espacio de pila para que las rutinas de biblioteca funcionen). Tales marcos de pila pueden ser tan pequeños como 32 bytes en la práctica y a menudo lo son.Windows: evite empujar contexto x86 completo en la pila

Todo esto funciona muy bien a menos que el código hace algo estúpido y provoca una trampa de hardware ... y en ese momento aparece de Windows a insistir en empujar todo el contexto máquina x86 "en la pila". Esto es unos 500+ bytes si incluye el FP/MMX/etc. registra, que hace. Naturalmente, una pulsación de 500 bytes en una pila de 32 bytes rompe cosas que no debería. (El hardware empuja unas pocas palabras en una trampa, pero no en todo el contexto).

[EDIT 27/11/2012: Ver this for measured details on the rediculous amount of stack Windows actually pushes]

¿Puedo obtener Windows para almacenar algún lugar del bloque de contexto excepción otra cosa (por ejemplo, a una ubicación específica de un hilo)? Luego, el software podría tomar la excepción presionar sobre la rosca y procesarla sin desbordar mis marcos de pila pequeña .

No creo que esto sea posible, pero pensé que le pediría a un público mucho más grande . ¿Hay una llamada/interfaz estándar del sistema operativo que pueda hacer que esto suceda?

Sería trivial para hacer en el sistema operativo, si podía estafar MS en dejar mi proceso definir opcionalmente una ubicación de almacenamiento contexto, "contextp", que se inicializa para que el comportamiento heredado actual por defecto. volviendo a poner la interrrupt/vector trampa Codee:

hardwareint: push context 
       mov contextp, esp 

... ... con

hardwareint: mov <somereg> contextp 
       test <somereg> 
       jnz $2 
       push context 
       mov contextp, esp 
       jmp $1 
     $2: store context @ somereg 
     $1: equ * 

con los evidentes cambios necesarios para salvar somereg, etc.

[Lo que hago ahora es: verifica el código generado para cada función. Si tiene una posibilidad de generar una trampa (por ejemplo, dividir por cero), o estamos depurando (posible puntero malo deref, etc.), agregue suficiente espacio al marco de la pila para el contexto FP. Los marcos de apilamiento ahora terminan siendo ~~ 500-1000 bytes de tamaño, los programas no pueden recuperarse hasta , lo que a veces es un problema real para las aplicaciones que estamos escribiendo. Así que tenemos una solución viable, pero Complica la depuración]

EDITAR 25 de agosto: Me las he arreglado para conseguir esta historia a un ingeniero interno de Microsoft que tiene la autoridad aparentemente para averiguar quién en realidad podría MS cuidado . Puede haber una débil esperanza para una solución.

EDITAR 14 de septiembre: MS Kernal Group Architect ha escuchado la historia y es comprensivo. Dijo que MS considerará una solución (como la propuesta) pero es poco probable que esté en un paquete de servicio.Puede que tenga que esperar la próxima versión de Windows. (Suspiro ... podría envejecer ...)

EDIT: 13 de septiembre de 2010 (1 año después). Ninguna acción por parte de Microsoft. Mi última pesadilla: ¿tomar una trampa ejecutando un proceso de 32 bits en Windows X64, empujar todo el contexto X64 en la pila antes de que el manejador de interrupciones falsifique empujando un contexto de 32 bits? Eso sería aún más grande (el doble de registros enteros el doble de ancho, el doble de registros SSE (?))?

EDITAR: 25 de febrero de 2012: (han pasado 1.5 años ...) Ninguna reacción por parte de Microsoft. Supongo que simplemente no les importa mi tipo de paralelismo. Creo que esto es un perjuicio para la comunidad; el "modelo de big stack" utilizado por MS en circunstancias normales limita la cantidad de cálculos paralelos que uno puede tener en vivo en cualquier instante al comer grandes cantidades de VM. El modelo PARLANSE le permitirá a uno tener una aplicación con un millón de "granos" en vivo en varios estados de funcionamiento/espera; esto realmente ocurre en algunas de nuestras aplicaciones donde un gráfico de 100 millones de nodos se procesa "en paralelo". El esquema PARLANSE puede hacer esto con aproximadamente 1 Gb de RAM, que es bastante manejable. Si lo intentaste con MS 1Mb "grandes cantidades", necesitarías 10^12 bytes de VM solo para el espacio de pila y estoy seguro de que Windows no te permitirá administrar un millón de subprocesos.

EDITAR: 29 de abril de 2014: (han pasado 4 años). Supongo que MS simplemente no lee SO. He hecho suficiente ingeniería en PARLANSE, por lo que solo pagamos el precio de grandes estructuras de pila durante la depuración o cuando hay operaciones de FP en marcha, por lo que hemos logrado encontrar formas muy prácticas de vivir con esto. MS ha seguido decepcionando; la cantidad de cosas que varias versiones de Windows empujan en la pila parece variar considerablemente y por encima y más allá de la necesidad del contexto del hardware. Hay algunos indicios de que parte de esta variabilidad es causada por la permanencia de productos que no son de MS (por ejemplo, antivirus) metiéndose en la cadena de manejo de excepciones; ¿Por qué no pueden hacer eso desde fuera de mi espacio de direcciones? Cualquiera, manejamos todo esto simplemente agregando un gran factor de pendiente para trampas de depuración/depuración, y esperando el inevitable sistema MS en el campo que excede esa cantidad.

+0

Si aplica parche ntdll.dll en la memoria, los cambios solo se verán en el proceso actual (copiar-en-escribir). Supongo que se usa una dirección directa, no el IAT, pero podría sobrescribir los primeros bytes del manejador con un JMP a su propio código y regresar al anillo 3. Windows podría tener cierta seguridad para evitar este tipo de cosa, pero vale la pena intentarlo. – zildjohn01

+0

Ahora, eso es un pensamiento. ¿Estás sugiriendo que el objetivo del IDT está en ntdll.dll y que puedo pisarlo? ¿Cómo averiguo dónde apunta IDT, o es un punto de entrada publicado en ntdll.dll? ¿Dónde puedo obtener más información sobre la estructura de ntdll.dll? Para repetir una frase que acabo de escuchar, "Esto me mantendrá ocupado un tiempo. Gracias"! –

+0

oops ..He usado IDT, quiero decir vector de interrupción o lo que sea que la arquitectura x86 lo llame en estos días. (Tengo los manuales x86, así que esta es una declaración retórica :-) –

Respuesta

0

El manejo de excepciones de Windows se denomina SEH. IIRC puede desactivarlo, pero el tiempo de ejecución del idioma que está utilizando podría no gustarle.

+0

Sé de SEH, y lo configuramos para que apunte a nuestro manejador de trampa de excepción. ¿Cómo uno lo desactiva y dónde va una trampa de hardware? El tiempo de ejecución del lenguaje que estoy usando está completamente bajo mi control. Gran parte del tiempo de ejecución paralelo del lenguaje se implementa en C, pero el software cambia las pilas de la pila de estilo de cactus a una pila "grande" de MS estándar cuando se ejecuta dicho código; También podría cambiar manejadores de excepciones, si resuelve mi problema de desbordamiento de pila. –

+1

Si deshabilita SEH, su aplicación se bloquea en una división por cero. Y si de alguna manera pudiera deshabilitar las excepciones, ¿qué esperaría que hiciera la CPU en una falla triple de división por cero .....? – zildjohn01

+0

No deshabilité SEH, solo lo configuré para señalar a mi controlador. Para cuando mi manejador toma el control, Windows ya ha insertado el marco de pila completo en la pila. –

1

Si Windows usa hardware x86 para implementar su código de trampa, necesita el anillo 0 de acceso (a través del controlador o API) para cambiar qué puerta se usa para las trampas.

El concepto x86 de puntos de compuerta uno de:

  • una dirección de interrupción (segmento de código + puntero de desplazamiento) que se llama, mientras que todo el contexto del registro, incluyendo la dirección de retorno, se empuja en la pila actual (= corriente esp) o
  • un descriptor de tareas, que cambia a otra tarea (se puede considerar como subproceso compatible con hardware). En su lugar, todos los datos relevantes se envían a la pila (esp) de esa tarea.

Por supuesto que quiere este último. Me gustaría ver cómo lo implementó Wine, que podría ser más efectivo que preguntar a Google.

Supongo que desafortunadamente necesita implementar un controlador para que funcione en x86, y de acuerdo con Wikipedia es imposible para los controladores cambiarlo en la plataforma IA64. La segunda mejor opción podría ser intercalar espacio en sus pilas, para que siempre se ajuste el contexto de una trampa.

+0

Puedo mirar Wine, pero no estoy seguro de lo que aprenderé con respecto a Windows. Primero, Wine corre bajo Linux; no hay una razón específica para creer que sus llamadas al sistema operativo se pueden usar para Windows. En segundo lugar, no hay ninguna razón específica para creer que Windows me permitirá tomar el control de la puerta de interrupción de hardware o el descriptor de tareas. (Pero, pueden ocurrir milagros, iré a buscar ... ¿me estás diciendo que puedo obtener acceso a través de una API MS estándar? ¿Cuál de ellos? ¿O estás sugiriendo que construya un controlador y haga trampa?) –

+0

tu suposición de que el el contexto completo que se envía a un manejador de int es incorrecto. Lo único que se garantiza que se encuentra en la pila es: errorCode (opcional), eip, selector de segmentos de código, eflags, esp y selector de segmento de pila (en este orden). No puede cambiar este comportamiento porque está cableado en la CPU – newgre

+0

Correcto, el hardware tiene que presionar * algún * contexto. Y esta cantidad modesta está bien, y siempre puedo incluir eso en el relleno requerido para mis marcos de pila. Hay instrucciones de la máquina para almacenar el contexto FP; cuidadosamente hecho, se puede almacenar en cualquier memoria intermedia lo suficientemente grande, incluso en la pila. Pero el hardware no está empujando el contexto FP en mi pila. * Windows * parece estar haciéndolo. Desde mi punto de vista, no importa si el hardware o Windows lo hacen, si se empuja y mi marco de pila es pequeño. Lo que importa es si puedo hacer que Windows no presione el contexto FP. –

4

Básicamente, tendría que volver a implementar muchos controladores de interrupción, es decir, engancharse en el Descriptor de interruptores Tabla (IDT). El problema es que también necesitaría volver a implementar un kernelmoder -> usermode callback (para SEH, esta devolución de llamada reside en ntdll.dll y se llama KiuserExceptionDispatcher, esto activa toda la lógica de SEH). El punto es que el resto del sistema depende de que SEH funcione de la manera en que lo hace ahora, y su solución rompería las cosas porque lo estaba haciendo en todo el sistema. Tal vez podría verificar en qué proceso se encuentra en el momento de la interrupción. Sin embargo, el concepto general es propenso a errores y afecta muy gravemente la estabilidad del sistema.
Estas son en realidad técnicas tipo rootkit.

Editar:
Algunos detalles más: la razón por la que tendría que volver a implementar el manejador de interrupciones es decir, que las excepciones (por ejemplo, dividir por cero) son esencialmente interrupciones de software y los que siempre pasan por el IDT. Cuando se lanza la excepción, el kernel recopila el contexto y envía la excepción a la modalidad de usuario (a través del KiUserExceptionDispatcher antes mencionado en ntdll). Debería interferir en este punto y, por lo tanto, también debería proporcionar un mecanismo para volver al modo de usuario. (Hay una función en ntdll que se usa como punto de entrada desde el modo kernel; no recuerdo el nombre pero es algo con KiUserACP .....)

+0

Sí, eso es bastante radical. No estoy seguro de querer arreglar el sistema operativo. –

+0

Sí, pero no hay otra manera de lograr lo que desea, porque todo el proceso de manejo de excepciones se desencadena desde el modo kernel. – SDD

+0

Esperaba que MS fuera lo suficientemente inteligente como para entender el tipo de problema que estoy teniendo (después de todo, ¿no están sentando las bases para el futuro en Windows :-), de modo que todo lo que tenía que hacer era usar la API adecuada? Suena a No Tal Suerte. –

1

Me quedé sin espacio en el cuadro de comentarios. ..

De todos modos no estoy seguro de dónde apunta el vector, estaba basando el comentario de la respuesta de SDD y la mención de "KiUserExceptionDispatcher" ... excepto al realizar una búsqueda (http://www.nynaeve.net/?p=201) parece que en este punto podría ser demasiado tarde

SIDT se puede ejecutar en el anillo 3 ... esto revelará el contenido de la tabla de interrupción, y usted puede cargar el segmento y leer al menos el contenido de la tabla. Con un poco de suerte, puede leer la entrada de (por ejemplo) vector 0/dividir por cero y leer el contenido del controlador.

En este punto intentaría hacer coincidir los bytes hexadecimales para que coincida con el código con un archivo de sistema, pero puede haber una mejor manera de determinar a qué archivo pertenece (no es necesariamente una DLL, podría ser win32k .sys, o podría generarse dinámicamente, quién sabe). No sé si hay una manera de volcar el diseño de la memoria física del modo de usuario.

Si falla todo lo demás, puede configurar un depurador en modo kernel o emular Windows (Bochs), donde puede ver directamente las tablas de interrupción y el diseño de la memoria. Luego puede rastrear hasta el punto en que se empuja el CONTEXTO, y buscar la oportunidad de obtener control antes de que eso suceda.

+1

I * really * * really * no quiero parchar el código kernal. Solo quiero que MS me permita preguntar para poner el contexto en un búfer que proporciono, en lugar de meterlo en la garganta de mi pila actual. –

3

Considere desacoplar el parámetro/pila local del real. Use otro registro (por ejemplo, EBP) como el puntero de pila efectivo, deje la pila basada en ESP de la forma en que Windows lo quiere.

Ya no puede usar PUSH/POP. Tendría que usar el combo SUB/MOV/MOV/MOV en lugar de PUSH. Pero bueno, supera el parcheo del sistema operativo.

+0

Sí, eso funcionaría técnicamente. Seguro cede mucho en densidad de código. El esquema que tengo funciona, al precio de hacer que los marcos de pila sean demasiado grandes cuando hay operaciones de coma flotante, y/o cuando el programa podría atrapar una referencia de memoria ilegal y quiero proporcionar una buena traza inversa. Actualmente compilamos en dos modos: a) modo de producción, con marcos de pila mínimos (a veces tan pequeños como 32 bytes), pero no capacidad de recuperación de una trampa de máquina que no sea "programa murió @xxx", yb) modo de depuración, que agrega una cantidad atroz (1500 bytes) a cada marco de pila, lo que proporciona suficiente deslastre para MS. –

+0

Pensé que estabas fuera para optimizar la velocidad a expensas de la memoria. –

+0

Limitar el conjunto de instrucciones que utiliza (especialmente las instrucciones básicas, altamente optimizadas, como push y pop) mediante simulación con instrucciones múltiples para reemplazar su efecto, no le dará velocidad. Tienes razón, en realidad no me importa la densidad del código, ya que creo que los procesadores son asombrosamente buenos en la búsqueda de instrucciones. Pero el compromiso que hemos logrado significa que no sacrificamos la capacidad de usar ninguna parte del conjunto de instrucciones; solo significa que somos incompatibles con la administración de stack MS irreflexiva. (He ofrecido una solución * muy * simple en mi pregunta, pero dudo que MS lo haga alguna vez). –

Cuestiones relacionadas