2009-10-13 11 views
6

Por lo tanto, estoy confundido acerca de cómo funcionan las instrucciones de salto en un sistema operativo. Pensé que la instrucción de salto establecía el valor en el contador del programa del procesador. Pero los programas se pueden ejecutar en varias ubicaciones de la memoria. Veo que en x86, está la instrucción JMP EAX, pero mi código C++ no parece usar esto. He compilado algo de código C++ en VC++:¿Cómo funciona la instrucción de salto en el ensamblaje con múltiples procesos?

int main() 
{ 
    int i = 0; 
    while (i < 10) 
    { 
     ++i; 
     if (i == 7) 
     { 
      i += 1; 
      continue; 
     } 
    } 
} 

Esto se traduce en:

int main() 
    { 
00411370 push  ebp 
00411371 mov   ebp,esp 
00411373 sub   esp,0CCh 
00411379 push  ebx 
0041137A push  esi 
0041137B push  edi 
0041137C lea   edi,[ebp-0CCh] 
00411382 mov   ecx,33h 
00411387 mov   eax,0CCCCCCCCh 
0041138C rep stos dword ptr es:[edi] 
     int i = 0; 
0041138E mov   dword ptr [i],0 
     while (i < 10) 
00411395 cmp   dword ptr [i],0Ah 
00411399 jge   main+47h (4113B7h) 
     { 
      ++i; 
0041139B mov   eax,dword ptr [i] 
0041139E add   eax,1 
004113A1 mov   dword ptr [i],eax 
      if (i == 7) 
004113A4 cmp   dword ptr [i],7 
004113A8 jne   main+45h (4113B5h) 
      { 
       i += 1; 
004113AA mov   eax,dword ptr [i] 
004113AD add   eax,1 
004113B0 mov   dword ptr [i],eax 
       continue; 
004113B3 jmp   main+25h (411395h) 
      } 
     } 
004113B5 jmp   main+25h (411395h) 
    } 
004113B7 xor   eax,eax 
004113B9 pop   edi 
004113BA pop   esi 
004113BB pop   ebx 
004113BC mov   esp,ebp 
004113BE pop   ebp 
004113BF ret    

Así que estoy confundido, para el comando jmp 411395h, implica esto el programa se carga siempre en el mismo lugar en ¿memoria? Porque eso parece ilógico.

+3

Tenga en cuenta que las CPU modernas tienden a ser compatibles con la memoria virtual, lo que significa que cada programa tiene su propio espacio de direcciones. Es decir, el byte en 0x12345678 en un proceso puede ser un punto diferente en la memoria real que el byte en 0x12345678 en otro proceso. –

Respuesta

6

Como escribieron otras personas, hay instrucciones de salto relativo y llamada relativa que esencialmente añaden un valor fijo a eip y por lo tanto no dependen de la ubicación del programa en la memoria; los compiladores prefieren usar estos siempre que sea posible. Puede ver los bytes del código para ver qué instrucciones exactas utilizó su compilador. Sin embargo, asumo que estás preguntando sobre saltos/llamadas a direcciones absolutas.

Cuando el enlazador genera un ejecutable, genera direcciones absolutas suponiendo un particular base address; El enlazador de Microsoft usualmente usa 400000h. Cuando OS carga un archivo ejecutable o un dll, "arregla" todas las direcciones absolutas al agregar la diferencia entre la dirección en la que se cargó realmente el archivo ejecutable y la dirección en la que se basó el vinculador. Todos los formatos ejecutables excepto .com especifican algún tipo de tabla de corrección, que enumera todas las ubicaciones en el ejecutable que tienen que ser parcheadas de esta manera. Por lo tanto, después de que el SO cargue su ejecutable en la memoria en la dirección base, por ejemplo, 1500000h, su salto se verá como jmp 1511395h. Puede verificar esto mirando los bytes de código reales con un depurador.

Los sistemas de Windows anteriores prefirieron cargar ejecutables en la dirección base utilizada por el enlazador; esto creó un riesgo de seguridad, porque un atacante sabría de antemano qué es lo que está en la memoria. Esta es la razón por la cual los sistemas más nuevos usan aleatorización de direcciones base.

+1

Las instrucciones de jmp en 004113B3 y ... B5 deben ser saltos relativos. Podemos decir por las etiquetas de dirección que estas instrucciones jmp están codificadas como instrucciones de dos bytes. Por lo tanto, son instrucciones relativas de jmp. Las instrucciones de jmp de dos bytes hacen que el EIP se vuelva a cargar con EIP +/- 127. El valor de +/- 127 es el segundo byte del código de operación de dos bytes. El primer byte de ese código de operación jmp es EB. Hay versiones de jmp (por ejemplo, direccionamiento no relativo) que comienzan con E9, EA, FF, por lo que es importante darse cuenta de que hay algunos códigos de operación diferentes, todos con el mnemotécnico "jmp" en lenguaje ensamblador. –

2

La mayoría de los chips tienen saltos relativos (relativos a la ubicación actual) y direcciones virtuales.

+0

¿Hay algo malo con la respuesta? Por favor deja un comentario. ¡Gracias! –

3

Las ubicaciones de la memoria son relativas al proceso. main está siempre en el mismo lugar en la memoria, relativo al comienzo del programa.

+1

Eso no es del todo cierto: algunos sistemas operativos utilizan la * asignación al azar del diseño del espacio de direcciones * para cargar un programa en una dirección diferente cada vez que se ejecuta para proteger mejor contra amenazas de seguridad. –

+1

@ Adam, no importa dónde esté cargado, el programa ve el mismo espacio de direcciones sin importar lo que haga el sistema operativo. De lo contrario, el caos aseguraría. –

+1

@Byron - exes se pueden cargar en diferentes direcciones. El archivo ejecutable contiene información de reubicación para que el cargador pueda ajustar direcciones absolutas en el exe si no está cargado en su dirección preferida. Con Exes esto no es tan común, es más común con la carga de archivos DLL. – Michael

3

No. En x86 (y otras arquitecturas, también), la mayoría de las instrucciones de salto son relativo a IP: los códigos de máquina binarios para las instrucciones representan un desplazamiento del puntero de instrucción actual. Entonces, no importa en qué dirección virtual se cargue el código, las instrucciones de salto funcionan correctamente.

6

No, aquí hay dos cosas en juego: no especifica un sistema operativo, así que le daré una respuesta general.

La primera es que un archivo ejecutable rara vez se encuentra en el formato final. Como simplificación, la compilación convierte fuente en archivos de objeto y el enlace combina archivos de objeto en un ejecutable.

Pero el ejecutable tiene que ser cargado en la memoria y, en ese momento, puede haber modificaciones aún más. Una de estas modificaciones puede ser corregir referencias de memoria dentro del ejecutable para señalar a la memoria que se ha cargado en diferentes ubicaciones.

Esto se puede lograr con el archivo ejecutable que contiene una lista de direcciones que deben corregirse en tiempo de ejecución.

También existe una desconexión entre la memoria virtual y la memoria física en muchos sistemas operativos modernos.

Cuando se inicia el proceso, obtiene su propio espacio de direcciones (4G para Windows 32 bits, creo) en el que se carga el proceso. Las direcciones dentro de este espacio de direcciones tienen poca relación con sus direcciones reales de memoria física y la traducción entre las dos se realiza mediante una unidad de administración de memoria (MMU).

De hecho, su proceso podría estar volando por todo el espacio de direcciones físicas, ya que está paginado y dentro. Las direcciones virtuales no cambiarán sin embargo.

+0

"no especifica un sistema operativo" ¿Cuántos sistemas operativos ejecuta Visual C++? –

+2

Bueno, dijo "Veo eso en x86" y "Compilé un código C++ en VC++", pero lo tomé solo como un ejemplo ya que (1) no hay etiquetas específicas del sistema operativo; y (2) la pregunta es de naturaleza muy general: "en asamblea", "un sistema operativo". – paxdiablo

3

Los saltos relativos toman la dirección de la instrucción actual de la máquina (llamada puntero de instrucción) y agregan un desplazamiento para calcular la dirección a la que se saltará.

Si nos fijamos en el código

004113B3 jmp   main+25h (411395h) 
004113B5 jmp   main+25h (411395h) 
004113B7 xor   eax,eax 

se le nota que la instrucción JMP es de 2 bytes de longitud (1 byte para JMP, 1 byte para offset), y no es posible almacenar un 4 bytes absoluta dirección.

Los saltos relativos son una funcionalidad básica de las CPU (de lo que sé sobre 65xx, Z80, 8086, 68000) y no están relacionados con funciones avanzadas como memoria virtual, asignación de memoria o aleatorización de espacio de direcciones.

0
int main() 
    { 
00411370 push  ebp 
00411371 mov   ebp,esp 
00411373 sub   esp,0CCh 
00411379 push  ebx 
0041137A push  esi 
0041137B push  edi 
0041137C lea   edi,[ebp-0CCh] 
00411382 mov   ecx,33h 
00411387 mov   eax,0CCCCCCCCh 
0041138C rep stos dword ptr es:[edi] 
     int i = 0,int j=0; 
0041138E mov   dword ptr [i][j],0 
     while (i < 10) 
00411395 cmp   dword ptr [i][j[,0Bh 
00411399 jge   main+47h (4113B7h) 
     { 
      ++i; 
0041139B mov   eax,dword ptr [i][j] 
0041139E add   eax,1 
004113A1 mov   dword ptr [i][j],eax ' 
      if (i == 7) 
004113A4 cmp   dword ptr [i][j],7 
004113A8 jne   main+45h (4113B5h) 
      { 
       i += 1; 
004113AA mov   eax,ebx,dword ptr [i][j] 
004113AD add   eax,1 
004113B0 mov   dword ptr [i][j],ebx 
       continue; 
004113B3 jmp   main+25h (411395h) 
      } 
     } 
004113B5 jmp   main+25h (411395h) 
    } 
004113B7 xor   eax,ebx 
004113B9 pop   edi 
004113BA pop   esi 
004113BB pop   ecx 
004113BC mov   esp,ebp 
004113BE pop   ebp 
004113BF ret 
Cuestiones relacionadas