Llamada a una funcion en un esquema de memoria segmentada

Iniciado por Usuario887, 14 Abril 2020, 18:32 PM

0 Miembros y 1 Visitante están viendo este tema.

Usuario887

Hola.

Soy nuevo en este foro y me gustaria hacer una pregunta.
He estado intentando desde hace una semana esto y sinceramente me frustra no hallar la manera. Lo que estoy intentando es hacer una llamada a una funcion que esta fuera del segmento por defecto (en modo real, es decir, real-mode tipo 8086), es decir, lejana (o far), desde el lenguaje de programacion C y no logro hacerlo. He utilizado ensamblador integrado (lo cual el compilador codifica como una llamada near, y me refiero a instrucciones tipo call es:[bx], por ejemplo), una locura de conversiones de tipos de punteros a funciones y hasta he intentado hacer el programa en su totalidad en emsablador como resultado de mi frustracion pero he terminado peor al haberlo "terminado" sin encontrar un ensamblador para 16 bits. ¿a alguien se le ocurre alguna solucion a mi problema?

posdata: Si alguien sabe de un ensablador para 16 bits (tipo TASM preferiblemente), se lo agradeceria.

Muchas gracias de antemano.

@XSStringManolo

#1
No te sirve emular real mode? Prueba con DOSBox
https://www.dosbox.com/information.php

Igual si pasas el binario y la dirección de la función alguien te lo puede rescribir en C para sistemas modernos.

fary

Por curiosidad... que compilador de C te genera un .COM para 16 bits? ( Asumo que generas un .COM, no lo sé)

Con Flat  Assembler puede generar código para 16 bits.

https://flatassembler.net/


Incluso puede que tu respuesta la encuentres aquí.

https://board.flatassembler.net/topic.php?t=11055

to do a 16 bits far call, there are two solutions.

imediate far pointer
Code:

use16
call 0:function
   


indirect far pointer
Code:

use16
call far[farpointer]
...
farpointer dd 0:function
   



i recommend the second solution because it lets you manage far pointers as datas.


Espero haberte ayudado.

saludos.
Un byte a la izquierda.

Usuario887

#3
En respuesta a @XSStringManolo,

Si, de hecho precisamente es lo que hago para probar el funcionamiento. Sin embargo me refiero mas bien a la codificacion de las instrucciones.
Pense en buscar la referencia de Intel de la instruccion CALL pero tendria que hacer el trabajo del compilador y honestamente me parece demasiado tedioso.
Para introducir mejor mi problema, hare un ejemplo:

Para hacer una llamada a una funcion del segmento CS, el mnemonico (si me permites un anglicismo) CALL puede expresarse simplemente:

Citar
CALL mylabel

En cambio para hacer una llamada fuera del segmento en real-mode seria expresado:

Citar
CALL ES:lo_que_sea

En donde lo_que_sea hace referencia un registro, direccion de memoria, ... (vamos, Intel).

Mi problema es que la codificacion de esta instruccion en C por parte de un compilador, de manera estandar, es:

Citar
CALL 0xXXXX
(notar que seguimos en el contexto de 16 bits)

Ahora mi pregunta es: tomando en cuenta esto, ¿como se expresa una llamada fuera del segmento?

Por ejemplo, si en un sistema DOS, ya que lo mencionas, tengo un programa con la funcion getivt (BYTE, WORD*, WORD*) la cual almacena en sus ultimos argumentos el segmento de la entrada del slot del vector de interrupcion (del sistema MS-DOS) y el offset respectivo, especificado por su primer argumento, y tengo el siguiente codigo:


WORD segmentselector, offset;
void __far (*interrupt21h)(); /* __far para precisar una funcion fuera del segmento por defecto. Sin embargo incluso asi el compiador parece ignorar esta especificacion */
getivt (0x21, &segmentselector, &offset);

/* aqui comienza mi confusion */


la pregunta precisa seria: ¿como almaceno segmentselector y offset en el puntero, para realizar la llamada far?




Cita de: fary en 14 Abril 2020, 20:26 PM
Por curiosidad... que compilador de C te genera un .COM para 16 bits? ( Asumo que generas un .COM, no lo sé)

Open Watcom. Un proyecto para con un compilador bastante interesante.
http://wiki.openwatcom.org/index.php/Main_Page

Cita de: fary en 14 Abril 2020, 20:26 PM

Con Flat  Assembler puede generar código para 16 bits.

https://flatassembler.net/


Incluso puede que tu respuesta la encuentres aquí.

https://board.flatassembler.net/topic.php?t=11055

En verdad parece precisamente lo que buscaba. Muchas gracias por tu ayuda.
Saludos.

RayR

#4
Cita de: marax en 14 Abril 2020, 21:00 PM
la pregunta precisa seria: ¿como almaceno segmentselector y offset en el puntero, para realizar la llamada far?

No sé si entendí bien lo que quieres, pero si ya tienes el puntero far y necesitas el offset y el segmento, lo puedes hacer así:

unsigned int segmento = (unsigned int)((unsigned long)puntero_far >> 16);
unsigned int offset = (unsigned int)((unsigned long)puntero_far & 0xFFFF);


Si lo quieres es lo opuesto:

// Editado: la funcion que deseas llamar no recibe parametros:

//puntero_far = (void far (*)(int))(((unsigned long)segmento << 16) + offset);
puntero_far = (void far (*)())(((unsigned long)segmento << 16) + offset);


Como los tamaños de los tipos varían de un compilador a otro, puede que necesites cambiar long por algún otro tipo que ocupe 32 bits, y poner __far en lugar de far.

En cuanto a tu pregunta original, C no tiene concepto de punteros near, far, modelos de memoria, ni nada por el estilo, por lo que todo esto depende totalmente del compilador. Lo más cercano a Open Watcom que usé es el Watcom C normal, pero hace mucho de eso, y además no sé qué tanto difieran, pero el código que pones tendría que funcionar. En general, usar la palabra "far" (o __far, o cualquier otro equivalente en el compilador que se use) ya debería bastar para llamar a estas funciones. No sé cuál sea el problema aquí, pero yo vería si el Open Watcom no hace algo "raro" al usar __far. Lo mejor en estos casos es checar el ensamblador generado por el compilador, ya sea mediante algún flag, si lo tiene, o desensamblando el ejecutable.

Usuario887

Cita de: RayR en 15 Abril 2020, 00:59 AM
No sé si entendí bien lo que quieres, pero si ya tienes el puntero far y necesitas el offset y el segmento, lo puedes hacer así:

unsigned int segmento = (unsigned int)((unsigned long)puntero_far >> 16);
unsigned int offset = (unsigned int)((unsigned long)puntero_far & 0xFFFF);


Lo intente, sin embargo parece que el compilador continua ignorando la diferencia entre un puntero comun y uno far. Creo que deberia tomar el consejo de analizar el codigo ejecutable resultante para comprender mejor como trata el compilador de Open Watcom este tipo de declaraciones. Es una lastima no encontrar mucha referencia sobre esta forma de referencia a memoria en internet, o al menos eso me parece.
Por cierto, si me permites la curiosidad ¿por que en vez de hacer una rotacion de 16 bits (puntero_far >> 16) no multiplicar simplemente el segmento por 0x10?

Cita de: RayR en 15 Abril 2020, 00:59 AM

Si lo quieres es lo opuesto:

puntero_far = (void far (*)(int))(((unsigned long)segmento << 16) + offset);

Como los tamaños de los tipos varían de un compilador a otro, puede que necesites cambiar long por algún otro tipo que ocupe 32 bits, y poner __far en lugar de far.

En cuanto a tu pregunta original, C no tiene concepto de punteros near, far, modelos de memoria, ni nada por el estilo, por lo que todo esto depende totalmente del compilador. Lo más cercano a Open Watcom que usé es el Watcom C normal, pero hace mucho de eso, y además no sé qué tanto difieran, pero el código que pones tendría que funcionar. En general, usar la palabra "far" (o __far, o cualquier otro equivalente en el compilador que se use) ya debería bastar para llamar a estas funciones. No sé cuál sea el problema aquí, pero yo vería si el Open Watcom no hace algo "raro" al usar __far. Lo mejor en estos casos es checar el ensamblador generado por el compilador, ya sea mediante algún flag, si lo tiene, o desensamblando el ejecutable.

Encontre una referencia de Wikipedia con esta manera de tratar este tipo de puntero sin embargo sigo pensando que debe haber algo mal con la forma de tratarlos por parte de este compilador, espero no sea una supersticion, jajaja.

Saludos, gracias por tu ayuda.

RayR

A diferencia de los punteros near, los far son más simples: los 2 bytes más significativos contienen el segmento, y los 2 menos significativos el desplazamiento. Las operaciones que hago en el código que puse son simplemente extraer los el conjunto de dos bytes que interesen, para obtener sus componentes; o bien, juntarlos en un único número de 4 bytes, para generar el puntero far.

Cita de: marax en 17 Abril 2020, 13:28 PM
Por cierto, si me permites la curiosidad ¿por que en vez de hacer una rotacion de 16 bits (puntero_far >> 16) no multiplicar simplemente el segmento por 0x10?

Bueno, en realidad no estoy haciendo una rotación (de hecho, C no proporciona operadores para eso) sino un simple desplazamiento a la derecha. Con esto, como decía arriba, obtengo el valor los 2 bytes más significativos. Multiplicar por 0x10 daría un resultado completamente diferente. La operación a nivel de bits equivalente a esa muliplicación sería (puntero_far << 4). La operación aritmética equivalente al desplazamiento que hago sería dividir entre 0x10000 (65536). De cualquier forma, las operaciones a nivel de bits, además de que suelen ser más eficientes que las aritméticas, en estos casos me parece que hacen más explícito lo que se quiere hacer.

Pasé por alto un mensaje anterior. ¿Esás generando un archivo COM? Porque si es así, a eso se debe el error. Los COM no pueden hacer llamadas far, dado que están limitados a un sólo segmento. Aunque sí que hay forma de acceder a datos en otros segmentos (teniendo que calcularlos manualmente, y directamente en ensamblador, no en C), no es así con las llamadas far. Lo normal sería que el linker diera error, diciendo que el archivo contiene referencias reubicables (relocatable), pero es posible que algunos simplemente conviertan las llamadas far a near, o directamente ignoren el problema y las dejen far, lo cual ocasionará que el programa falle durante la ejecución.

Usuario887

Cita de: RayR en 17 Abril 2020, 17:46 PM
A diferencia de los punteros near, los far son más simples: los 2 bytes más significativos contienen el segmento, y los 2 menos significativos el desplazamiento. Las operaciones que hago en el código que puse son simplemente extraer los el conjunto de dos bytes que interesen, para obtener sus componentes; o bien, juntarlos en un único número de 4 bytes, para generar el puntero far.

Esto lo sabia, sin embargo digamos que no dispongo aun del conocimiento intuitivo para no cometer ciertos errores. Sinceramente llevo apenas un mes utilizando tanto C como ensamblador para esta forma de programacion (me refiero a la interaccion directa con los mecanismos del procesador).

Cita de: RayR en 17 Abril 2020, 17:46 PM
Bueno, en realidad no estoy haciendo una rotación (de hecho, C no proporciona operadores para eso) sino un simple desplazamiento a la derecha. Con esto, como decía arriba, obtengo el valor los 2 bytes más significativos. Multiplicar por 0x10 daría un resultado completamente diferente. La operación a nivel de bits equivalente a esa muliplicación sería (puntero_far << 4). La operación aritmética equivalente al desplazamiento que hago sería dividir entre 0x10000 (65536). De cualquier forma, las operaciones a nivel de bits, además de que suelen ser más eficientes que las aritméticas, en estos casos me parece que hacen más explícito lo que se quiere hacer.

Me has generado y resuelto una duda en el mismo instante. No estaba seguro de si el operador de desplazamiento era precisamente de desplazamiento y no ademas de rotacion (circular). Digamosle una confusion de terminos inadvertida.  :silbar:
Ademas he cometido un error idealizando la direccion efectiva como una combinacion de dos bytes y no de dos words, por eso mi confusion con el desplazamiento de los 16 bits: si la direccion estuviese compuesta de 8 bits para el selector de segmento y 8 bits para el desplazamiento (como la idealizaba), entonces hacer un desplazamiento de 16 bits no tuviese sentido. Mi error.

Cita de: RayR en 17 Abril 2020, 17:46 PM
Pasé por alto un mensaje anterior. ¿Esás generando un archivo COM? Porque si es así, a eso se debe el error. Los COM no pueden hacer llamadas far, dado que están limitados a un sólo segmento. Aunque sí que hay forma de acceder a datos en otros segmentos (teniendo que calcularlos manualmente, y directamente en ensamblador, no en C), no es así con las llamadas far. Lo normal sería que el linker diera error, diciendo que el archivo contiene referencias reubicables (relocatable), pero es posible que algunos simplemente conviertan las llamadas far a near, o directamente ignoren el problema y las dejen far, lo cual ocasionará que el programa falle durante la ejecución.

Si, el archivo es un COM. Esto no lo sabia. El codigo que hago es para una maquina con DOS, y crei que el sistema ejecutaba las instrucciones como si codigo del kernel se tratase. Aunque en ausencia de modos de ejecucion no comprendo como es posible. ¿sabes como funciona esto precisamente, o en donde podria encontrar una referencia acerca de ello?
Ademas, si no es molestia, ¿existe entonces una forma de ejecutable que el sistema si permita tales cosas? espero no parezca tonta la pregunta y delante solo tenga como respuesta controladores.

RayR

#8
Pasa que los COM están muy limitados y una de las pocas razones para usarlos era su simpleza, que era lo que quise decir arriba, cuando comenté que los punteros far eran más simples que los near (en realidad me quería referir a los COM y los EXE, pero entre que borraba y cambiaba mi mensaje, se me quedó mezclado). Obviamente, tanto como más simples no, es sólo que incluyen el segmento y no va implícito CS, como en los far. Como sea, lo que sucede con los COM es que al ejecutarlos, se cargan en memoria tal cual están en el disco, todo en un sólo segmento, y sin las reubicaciones que tienen lugar con los EXE. Esto hacía que, entre otras cosas, fuera mucho más rápido cargar los COM, pero con desventajas, como el tener sólo un segmento. Sin embargo, no es tan simple, ya que, como había comentado antes, sí que hay formas de acceder a datos más allá, pues nada impide que cargues valores en DS ó ES, por ejemplo, pero obviamente no se puede modificar directamente a CS.

Cita de: marax en 18 Abril 2020, 21:12 PMSi, el archivo es un COM. Esto no lo sabia. El codigo que hago es para una maquina con DOS, y crei que el sistema ejecutaba las instrucciones como si codigo del kernel se tratase. Aunque en ausencia de modos de ejecucion no comprendo como es posible. ¿sabes como funciona esto precisamente, o en donde podria encontrar una referencia acerca de ello?
Ademas, si no es molestia, ¿existe entonces una forma de ejecutable que el sistema si permita tales cosas? espero no parezca tonta la pregunta y delante solo tenga como respuesta controladores.

Hace ya mucho que no programo nada en 16 bits y puede que se me escape algún detalle o me confunda en algún punto, pero en esencia así es como funciona:

Como bien intuyes, no es que el sistema operativo impida a estos archivos hacer llamadas far durante la ejecución, sino que, como normalmente éstas implican código reubicable, los linkers se quejan y no permiten generar archivos COM. Podrías intentar forzar esto usando un "truco" muy común, que es meter tú mismo los opcodes de la operación. Por ejemplo, en la parte de tu código donde quieras la llamada, usas un DB con el valor del opcode para la llamada far deseada, e inmediatamente después un par de DW con los valores de segmento y desplazamiento, si los sabes.

Dicho todo lo anterior, y respondiendo a tu última pregunta, los EXE no tienen ninguna de estas limitaciones, así que te recomiendo que no te compliques con los COM. De esta manera, puedes hacerlo desde C, y en este caso, el compilador sí que debería hacer lo que pides. E independientemente del compilador que uses, si permite generar COM, obviamente debe poder hacer EXE de 16 bits.




Edito: Cuando escribí el mensaje, tal como puse, no estaba seguro de estar recordando bien, y como me quedó la duda, el sábado me puse a revisar códigos viejos que tenía con esa información. Aunque lo dicho arriba en general es más o menos válido, van algunas puntualizaciones.

Lo que mencioné sobre meter opcodes manualmente, yo lo llegué a usar para saltarme limitaciones de los ensambladores que no admitían llamadas en modo inmediato, pero en general, no lo necesitarías. El asunto de los linkers quejándose del código reubicable (y el hecho de que un compilador de C difícilmente generaría COM con llamadas far válidas) sigue siendo correcto; sin embargo, trabajando directamente en ensamblador, sí podrías hacer las llamadas far sin problemas, siempre que no quieras usar nombres de procedimientos definidos en otros segmentos (lo que ocasionaría los errores de reubicación), sino usando directamente el segmento y desplazamiento.

De todas maneras, generar EXE de 16 bits sería lo más recomendable, ya que de esa manera lo podrías hacer desde C.

Usuario887

#9
Cita de: RayR en 19 Abril 2020, 00:19 AM
Pasa que los COM están muy limitados y una de las pocas razones para usarlos era su simpleza, que era lo que quise decir arriba, cuando comenté que los punteros far eran más simples que los near (en realidad me quería referir a los COM y los EXE, pero entre que borraba y cambiaba mi mensaje, se me quedó mezclado). Obviamente, tanto como más simples no, es sólo que incluyen el segmento y no va implícito CS, como en los far. Como sea, lo que sucede con los COM es que al ejecutarlos, se cargan en memoria tal cual están en el disco, todo en un sólo segmento, y sin las reubicaciones que tienen lugar con los EXE. Esto hacía que, entre otras cosas, fuera mucho más rápido cargar los COM, pero con desventajas, como el tener sólo un segmento. Sin embargo, no es tan simple, ya que, como había comentado antes, sí que hay formas de acceder a datos más allá, pues nada impide que cargues valores en DS ó ES, por ejemplo, pero obviamente no se puede modificar directamente a CS.

Sin embargo como comentas en seguida es posible mientras no se trate de codigo reubicable. La parte de mi programa que ejecuta esta llamada es una llamada a un ISR sin la utilizacion de la instruccion INT. Es decir, especificando de manera explicita el segmento y el desplazamiento para con el ISR. Al tener problemas con este tipo de llamadas y la codificacion de llamadas far, se me ocurrio lo siguiente:


;pushf en el caso de una llamada a ISR
push cs
push delta
push es ;segmento para con la llamada
push bx ;desplazamiento para con la misma
retf ;ultimamente resulta en lo mismo que un CALL far
delta:


Me di cuenta de que a pesar de los problemas con la instruccion CALL tipo far, los ensambladores codifican perfectamente este pequeño algoritmo (que incluye precisamente un retorno de igualmente tipo far, y funciona de maravilla).

Cita de: RayR en 19 Abril 2020, 00:19 AM
Hace ya mucho que no programo nada en 16 bits y puede que se me escape algún detalle o me confunda en algún punto, pero en esencia así es como funciona:

Como bien intuyes, no es que el sistema operativo impida a estos archivos hacer llamadas far durante la ejecución, sino que, como normalmente éstas implican código reubicable, los linkers se quejan y no permiten generar archivos COM. Podrías intentar forzar esto usando un "truco" muy común, que es meter tú mismo los opcodes de la operación. Por ejemplo, en la parte de tu código donde quieras la llamada, usas un DB con el valor del opcode para la llamada far deseada, e inmediatamente después un par de DW con los valores de segmento y desplazamiento, si los sabes.

Dicho todo lo anterior, y respondiendo a tu última pregunta, los EXE no tienen ninguna de estas limitaciones, así que te recomiendo que no te compliques con los COM. De esta manera, puedes hacerlo desde C, y en este caso, el compilador sí que debería hacer lo que pides. E independientemente del compilador que uses, si permite generar COM, obviamente debe poder hacer EXE de 16 bits.

Lo intente de esta manera y me di cuenta de que la raiz de mi problema no yace en la llamada far sino en el codigo. Igualmente gracias por ayudarme a aclarar tantas cuestiones sobre estas llamadas, el esfuerzo de ninguna equivocacion se pierde.

El programa que estoy intentando hacer es simplemente un TSR que engancha (hook) un ISR de la BIOS a una parte del mismo. Todo funciona perfectamente hasta que retoco (tweak) el slot del ISR (el cual es la interrupcion general del teclado en la BIOS, es decir, la 09h). Desde ese momento el sistema parece dejar de reaccionar. Este es el codigo al que se "hookea" el ISR:


pushf
call far dwoldISR
iret


En dwoldISR esta almacenada la direccion original de la rutina de servicio.

En verdad no se cual es el problema realmente. Igual sera una cuestion de que siga intentandolo.
Me parecio de mala educacion hablarte del problema sin la raiz del mismo.

Aprecio tu ayuda y tu tiempo.
Saludos.