Saltar al contenido

¿Qué es el código de máquina absoluto y reubicable?

Después de de nuestra larga selección de información dimos con la solución este atascamiento que tienen ciertos los lectores. Te regalamos la respuesta y nuestro objetivo es resultarte de gran ayuda.

Solución:

Muchos / la mayoría de los conjuntos de instrucciones tienen direccionamiento relativo de PC, lo que significa que tome la dirección del contador del programa, que está relacionada con la dirección de la instrucción que está ejecutando, y luego agregue un desplazamiento a eso y utilícelo para acceder a la memoria o bifurcación o algo así como ese. Eso sería lo que usted llama reubicable. Porque no importa dónde se encuentre esa instrucción en el espacio de direcciones, a lo que desea saltar es relativo. Mueva todo el bloque de código y datos a alguna otra dirección y todavía estarán relativamente a la misma distancia, por lo que el direccionamiento relativo seguirá funcionando. Si el salto es igual, la siguiente instrucción funciona dondequiera que estén esas tres instrucciones (el salto if, el que se omite y el que sigue al salto).

Absolute usa direcciones absolutas, salte a esta dirección exacta, lea desde esta dirección exacta. Si es igual, bifurca a 0x1000.

El ensamblador no hace esto, el compilador y / o programador lo hace. Generalmente, eventualmente, el código compilado terminará teniendo direccionamiento absoluto, en particular si su código consta de objetos separados que están vinculados entre sí. En el momento de la compilación, el compilador no puede saber dónde terminará el objeto ni es posible saber dónde están las referencias externas o qué tan lejos, por lo que generalmente no puede asumir que estarán lo suficientemente cerca para el direccionamiento relativo de la PC (que generalmente tiene un límite de rango) . Por lo tanto, los compiladores suelen generar un marcador de posición para que el vinculador lo complete con una dirección absoluta. Depende de la operación y el conjunto de instrucciones y algunos otros factores en cuanto a cómo se resuelve este problema de dirección externa. Eventualmente, aunque basado en el tamaño del proyecto, el enlazador terminará con un direccionamiento absoluto. Por lo tanto, el valor no predeterminado suele ser una opción de línea de comando para generar código independiente de la posición; por ejemplo, PIC podría ser algo que su compilador admita. tanto el compilador como el enlazador tienen que hacer un trabajo adicional para que la posición de esos elementos sea independiente. Un programador en lenguaje ensamblador tiene que hacer todo esto por sí mismo, el ensamblador generalmente no se involucra en esto, solo crea el código de máquina para las instrucciones que usted le dice que genere.

novectors.s:

.globl _start
_start:
    b   reset
reset:
    mov sp,#0xD8000000
    bl notmain
    ldr r0,=notmain
    blx r0
hang: b hang

.globl dummy
dummy:
    bx lr

Hola C

extern void dummy ( unsigned int );
int notmain ( void )

    unsigned int ra;
    for(ra=0;ra<1000;ra++) dummy(ra);
    return(0);

memap (la secuencia de comandos del vinculador) MEMORIA ram: ORIGIN = 0xD6000000, LENGTH = 0x4000 SECTIONS .text: (.texto)> ram Makefile

ARMGNU = arm-none-eabi
COPS = -Wall -O2 -nostdlib -nostartfiles -ffreestanding 
all : hello_world.bin
clean :
    rm -f *.o
    rm -f *.bin
    rm -f *.elf
    rm -f *.list

novectors.o : novectors.s
    $(ARMGNU)-as novectors.s -o novectors.o

hello.o : hello.c
    $(ARMGNU)-gcc $(COPS) -c hello.c -o hello.o

hello_world.bin : memmap novectors.o hello.o 
    $(ARMGNU)-ld novectors.o hello.o -T memmap -o hello_world.elf
    $(ARMGNU)-objdump -D hello_world.elf > hello_world.list
    $(ARMGNU)-objcopy hello_world.elf -O binary hello_world.bin 

hello_world.list (las partes que nos importan)

Disassembly of section .text:

d6000000 <_start>:
d6000000:   eaffffff    b   d6000004 

d6000004 :
d6000004:   e3a0d336    mov sp, #-671088640 ; 0xd8000000
d6000008:   eb000004    bl  d6000020 
d600000c:   e59f0008    ldr r0, [pc, #8]    ; d600001c 
d6000010:   e12fff30    blx r0

d6000014 :
d6000014:   eafffffe    b   d6000014 

d6000018 :
d6000018:   e12fff1e    bx  lr
d600001c:   d6000020    strle   r0, [r0], -r0, lsr #32

d6000020 :
d6000020:   e92d4010    push    r4, lr
d6000024:   e3a04000    mov r4, #0
d6000028:   e1a00004    mov r0, r4
d600002c:   e2844001    add r4, r4, #1
d6000030:   ebfffff8    bl  d6000018 
d6000034:   e3540ffa    cmp r4, #1000   ; 0x3e8
d6000038:   1afffffa    bne d6000028 
d600003c:   e3a00000    mov r0, #0
d6000040:   e8bd4010    pop r4, lr
d6000044:   e12fff1e    bx  lr

Lo que muestro aquí es una mezcla de instrucciones independientes de la posición e instrucciones dependientes de la posición.

estas dos instrucciones, por ejemplo, son un atajo para obligar al ensamblador a agregar una ubicación de memoria de estilo .word que el vinculador debe completar por nosotros.

ldr r0,=notmain
blx r0

0xD600001c es esa ubicación.

    d600000c:   e59f0008    ldr r0, [pc, #8]    ; d600001c 
    d6000010:   e12fff30    blx r0
...
    d600001c:   d6000020    strle   r0, [r0], -r0, lsr #32

y se completa con la dirección 0xD6000020, que es una dirección absoluta, por lo que para que ese código funcione, la función notmain debe estar en la dirección 0xD6000020, no es reubicable. pero esta parte del ejemplo también demuestra algún código independiente de la posición, el

ldr r0, [pc, #8]

¿Es el direccionamiento relativo de la PC? Estaba hablando de la forma en que funciona este conjunto de instrucciones en el momento de la ejecución. La PC está dos instrucciones por delante o, básicamente, en este caso, si la instrucción está en 0xD600000c en la memoria, entonces la PC será 0xD6000014 cuando se ejecute y luego agregue. 8 a eso como dice la instrucción y obtienes 0xD600001C. Pero si movimos exactamente esa misma instrucción de código de máquina a la dirección 0x1000 Y movemos todos los binarios circundantes allí, incluido lo que está leyendo (el 0xD6000020). básicamente haz esto:

    1000:   e59f0008    ldr r0, [pc, #8]    
    1004:   e12fff30    blx r0
...
    1010:   d6000020    

Y esas instrucciones, ese código de máquina seguirá funcionando, no es necesario volver a ensamblarlo ni volver a vincularlo. el código 0xD6000020 sitll tiene que estar en esa dirección fija bit que el ldr pc y blx no lo hacen.

Aunque el desensamblador los muestra con direcciones basadas en 0xd6 ..., bl y bne también son relativos a la PC, lo cual puede averiguar consultando la documentación del conjunto de instrucciones.

d6000030:   ebfffff8    bl  d6000018 
d6000034:   e3540ffa    cmp r4, #1000   ; 0x3e8
d6000038:   1afffffa    bne d6000028 

0xD6000030 tendría una PC de 0xD6000038 cuando se ejecuta y 0xD6000038-0xD6000018 = 0x20 que son 8 instrucciones. Y un complemento negativo de 8 en dos es 0xFFF..FFFF8, puede ver que la mayor parte de ese código de máquina ebfffff8 es ffff8, que es el signo extendido y agregado al contador del programa para decir básicamente instrucciones de rama hacia atrás 8. Lo mismo ocurre con ffffa en 1afffffa, lo que significa que si no es igual, bifurque hacia atrás 6 instrucciones. Recuerde que este conjunto de instrucciones (brazo) asume que el PC está dos instrucciones adelante, de modo que atrás 6 significa adelante dos y luego atrás 6 o efectivamente atrás 4.

Si quita el

d600000c:   e59f0008    ldr r0, [pc, #8]    ; d600001c 
d6000010:   e12fff30    blx r0

Entonces todo este programa termina siendo independiente de la posición, por accidente si se quiere (yo sabía que sucedería), pero no porque le dije a las herramientas que hicieran eso, sino simplemente porque hice todo cerca y no usé ningún direccionamiento absoluto.

por último, cuando dice "dondequiera que el vinculador encuentre espacio para ellos", si observa en mi secuencia de comandos del vinculador, le digo al vinculador que ponga todo a partir de 0xD6000000, no especifiqué ningún nombre de archivo o función, por lo que si no se indica lo contrario, este vinculador coloca los elementos en el orden en que se especifican en la línea de comando. el código hello.c es el segundo, así que después de que el vinculador haya colocado el código novectors.s, el lugar donde el vinculador tenía espacio es justo después de eso, el código hello.c comienza en 0xD6000020.

Y una manera fácil de ver qué es independiente de la posición y qué no sin tener que investigar cada instrucción sería cambiar el script del enlazador para poner el código en alguna otra dirección.

MEMORY

    ram : ORIGIN = 0x1000, LENGTH = 0x4000

SECTIONS

    .text :  *(.text*)  > ram

y ver qué código de máquina cambia, si lo hay, y qué no.

00001000 <_start>:
    1000:   eaffffff    b   1004 

00001004 :
    1004:   e3a0d336    mov sp, #-671088640 ; 0xd8000000
    1008:   eb000004    bl  1020 
    100c:   e59f0008    ldr r0, [pc, #8]    ; 101c 
    1010:   e12fff30    blx r0

00001014 :
    1014:   eafffffe    b   1014 

00001018 :
    1018:   e12fff1e    bx  lr
    101c:   00001020    andeq   r1, r0, r0, lsr #32

00001020 :
    1020:   e92d4010    push    r4, lr
    1024:   e3a04000    mov r4, #0
    1028:   e1a00004    mov r0, r4
    102c:   e2844001    add r4, r4, #1
    1030:   ebfffff8    bl  1018 
    1034:   e3540ffa    cmp r4, #1000   ; 0x3e8
    1038:   1afffffa    bne 1028 
    103c:   e3a00000    mov r0, #0
    1040:   e8bd4010    pop r4, lr
    1044:   e12fff1e    bx  lr

No estoy seguro de que la respuesta aceptada sea necesariamente correcta aquí. Existe una diferencia fundamental entre el código reubicable y lo que se considera un código independiente de la posición.

Ahora he estado codificando ensamblados durante mucho tiempo y en muchas arquitecturas diferentes y siempre he pensado que el código de máquina viene en tres sabores específicos: -

  • Código de posición independiente
  • Código reubicable
  • Código absoluto

Primero discutamos independiente de la posición código. Este es un código que cuando se ensambla tiene todas sus instrucciones relativas entre sí. Entonces, las ramas, por ejemplo, especifican un desplazamiento del Puntero de instrucción actual (o Contador de programa, como quiera llamarlo). El código que es independiente de la posición constará de un solo segmento de código y sus datos también estarán contenidos dentro de este segmento (o sección). Hay excepciones a la incrustación de datos dentro del mismo segmento, pero estos son beneficios que generalmente le transmite el sistema operativo o el cargador.

Es un tipo de código muy útil porque significa que el sistema operativo no necesita realizar ninguna operación de poscarga en él para poder comenzar a ejecutar. Simplemente se ejecutará en cualquier lugar que esté cargado en la memoria. Por supuesto, este tipo de código también tiene sus problemas, a saber, cosas como no poder segregar código y datos que podrían ser adecuados para diferentes tipos de memoria y limitaciones de tamaño antes de que los parientes comiencen a moverse fuera del rango, etc., por nombrar solo algunos.

Código reubicable es bastante parecido al código independiente de la posición en muchos aspectos, pero tiene una diferencia muy sutil. Como sugiere su nombre, este tipo de código es reubicable, ya que el código se puede cargar en cualquier lugar de la memoria, pero por lo general se ha reubicado o reparado antes de que sea ejecutable. De hecho, algunas arquitecturas que usan este tipo de código incorporan cosas como secciones "reloc" con el mismo propósito de arreglar las partes reubicables del código. La desventaja de este tipo de código es que una vez que se reubica y se arregla, casi se vuelve de naturaleza absoluta y se fija en su dirección.

Lo que le da al código reubicable su mayor ventaja y la razón por la que es el código más frecuente es que permite que el código se divida fácilmente en secciones. Cada sección se puede cargar en cualquier lugar de la memoria para adaptarse a sus requisitos y luego, durante la reubicación, cualquier código que haga referencia a otra sección se puede arreglar con una tabla de reubicación y, por lo tanto, las secciones se pueden unir muy bien. El código en sí mismo suele ser relativo (como con la arquitectura x86), pero no es necesario que lo sea, ya que cualquier cosa que pueda estar fuera de rango puede ensamblarse como una instrucción reubicable de manera que consista en un desplazamiento agregado a su dirección de carga. También significa que las limitaciones impuestas por el direccionamiento relativo ya no son un problema.

El último tipo de código es Código absoluto. Este código que está ensamblado para funcionar en una dirección específica y solamente funcionan cuando se cargan en esa dirección específica. Las instrucciones de bifurcación y salto contienen todas una dirección fija exacta (absoluta). Es un tipo de código que generalmente se encuentra en los sistemas integrados mediante el cual se puede garantizar que un fragmento de código se cargará en esa dirección específica, ya que es lo único que se carga allí. En una computadora moderna, dicho código absoluto no funcionaría porque el código debe cargarse donde haya memoria libre y nunca hay garantía de que un cierto rango de memoria esté disponible. Sin embargo, el código absoluto tiene sus ventajas, principalmente porque generalmente es el de ejecución más rápida, pero esto puede depender de la plataforma.

Todo lo que realmente contenga una dirección dentro del código tiene una dirección absoluta. Los programas que no contienen direcciones dentro del código (todo se hace con direcciones relativas) se pueden ejecutar desde cualquier dirección.

El ensamblador no hace esto, el programador lo hace. He hecho un poco de esto en el pasado, para cosas pequeñas suele ser fácil, una vez que vas más allá del rango de un salto relativo, se vuelve bastante doloroso. IIRC, los únicos dos enfoques son deslizar saltos relativos entre rutinas o agregar un desplazamiento conocido a la dirección actual, presionarlo y luego regresar. En los viejos tiempos había un tercer enfoque para calcularlo y escribirlo en el código, pero eso ya no es aceptable. Ha pasado lo suficiente como para no jurar que no hay otros enfoques.

IIRC la única forma de "llamar" a algo sin direcciones absolutas es presionar la dirección a la que desea volver, calcular la dirección, presionarla y regresar.

Tenga en cuenta que, en la práctica, suele utilizar un enfoque híbrido. El ensamblador y el enlazador almacenan la información necesaria para realizar los ajustes, cuando el programa se carga en la memoria, se modifica para ejecutarse en cualquier dirección en la que se cargó. La imagen real en la memoria es, por tanto, absoluta, pero el archivo en el disco funciona como si fuera relativo, pero sin todos los dolores de cabeza que normalmente presenta. (Tenga en cuenta que se utiliza el mismo enfoque con todos los lenguajes de nivel superior que realmente producen código.)

Valoraciones y comentarios

Si aceptas, puedes dejar una sección acerca de qué le añadirías a este tutorial.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *