Hacer referencia al contenido de una ubicación de memoria. (modos de direccionamiento x86)

Tengo una ubicación de memoria que contiene un personaje que quiero comparar con otro personaje (y no está en la parte superior de la stack, así que no puedo pop ). ¿Cómo hago referencia al contenido de una ubicación de memoria para poder compararlo?

Básicamente, ¿cómo lo hago sintácticamente?

Para una discusión más extensa de los modos de direccionamiento (16/32 / 64bit), consulte la sección “Optimizing Assembly” de Agner Fog , sección 3.3. Esa guía tiene muchos más detalles que esta respuesta para la reubicación de símbolos o código de posición independiente de 32 bits, entre otras cosas.

Ver también: tabla de syntax de AT & T (GNU) vs. syntax NASM para diferentes modos de direccionamiento , incluyendo saltos / llamadas indirectas.

También vea la colección de enlaces en la parte inferior de esta respuesta.


Sugerencias bienvenidas, esp. en qué partes fueron útiles / interesantes y qué partes no.

x86 (32 y 64 bits) tiene varios modos de direccionamiento para elegir. Son todos de la forma:

 [base_reg + index_reg*scale + displacement] ; or a subset of this [RIP + displacement] ; or RIP-relative: 64bit only. No index reg is allowed 

(donde la escala es 1, 2, 4 u 8, y el desplazamiento es una constante de 32 bits con signo). Todas las otras formas (excepto RIP-relative) son subconjuntos de esto que omiten uno o más componentes . Esto significa que no necesita un index_reg a cero para acceder a [rsi] por ejemplo. En el código fuente asm, no importa en qué orden escriba cosas: [5 + rax + rsp + 15*4 + MY_ASSEMBLER_MACRO*2] funciona bien. (Todas las constantes de matemáticas ocurren en el momento del ensamblaje, lo que resulta en un solo desplazamiento constante).

Todos los registros tienen que ser del mismo tamaño que el modo en el que se encuentra, a menos que use un tamaño de dirección alternativo , requiriendo un byte de prefijo adicional. Los punteros estrechos rara vez son útiles fuera de x32 ABI (ILP32 en modo largo) .

Si desea utilizar al como un índice de matriz, por ejemplo , necesita cero o extender el ancho de puntero. (Tener los bits superiores de rax ya rax cero antes de jugar con los registros de bytes a veces es posible, y es una buena forma de lograr esto).


Cada subconjunto posible del caso general es codificable, excepto los que usan una e/rsp*scale (obviamente inútil en el código “normal” que siempre mantiene un puntero a la memoria de la stack en esp ).

Normalmente, el tamaño de código de las codificaciones es:

  • 1B para modos de registro único (mod / rm (Modo / Registro-o-memoria))
  • 2B para modos de dos registros (mod / rm + SIB (base de índice de escala) byte)
  • el desplazamiento puede ser de 0, 1 o 4 bytes (con extensión de signo a 32 o 64, dependiendo del tamaño de la dirección). Así que los desplazamientos de [-128 to +127] pueden usar la encoding disp8 más compacta, ahorrando 3 bytes frente a disp32 .

excepciones de tamaño de código:

  • [reg*scale] por sí solo solo se puede codificar con un desplazamiento de 32 bits. Los ensambladores inteligentes trabajan en eso codificando lea eax, [rdx*2] como lea eax, [rdx + rdx] , pero ese truco solo funciona para escalar en 2.

  • Es imposible codificar e/rbp o r13 como el registro base sin un byte de desplazamiento, por lo que [ebp] se codifica como [ebp + byte 0] . Las codificaciones de no desplazamiento con ebp como registro base significan que no hay un registro base (por ejemplo, para [disp + reg*scale] ).

  • [e/rsp] requiere un byte SIB incluso si no hay un registro de índice. (si hay o no un desplazamiento). La encoding mod / rm que especificaría [rsp] significa que hay un byte SIB.

Consulte la Tabla 2-5 en el manual de ref de Intel, y la sección que lo rodea, para obtener detalles sobre los casos especiales. (Son los mismos en el modo de 32 y 64 bits. La adición de la encoding relativa al RIP no entraba en conflicto con ninguna otra encoding, incluso sin un prefijo REX).

Para el rendimiento, por lo general no vale la pena gastar una instrucción adicional solo para obtener un código de máquina x86 más pequeño. En las CPU Intel con una memoria caché uop, es más pequeña que L1 I $ y un recurso más valioso. Minimizar el uops de dominio fusionado suele ser más importante.


El tamaño de la dirección de 16 bits no puede usar un byte SIB, por lo que todos los modos de direccionamiento de uno y dos registros están codificados en el byte único mod / rm. reg1 puede ser BX o BP, y reg2 puede ser SI o DI (o puede usar cualquiera de esos 4 registros por su cuenta). Escala no está disponible. El código de 16 bits es obsoleto por muchas razones, incluida esta, y no vale la pena aprender si no es necesario.

Tenga en cuenta que las restricciones de 16 bits se aplican en el código de 32 bits cuando se utiliza el prefijo del tamaño de la dirección, por lo que 16 bits LEA-math es muy restrictivo. Sin embargo, puede evitar esto: lea eax, [edx + ecx*2] establece ax = dx + cx*2 , porque la basura en los bits superiores de los registros fuente no tiene ningún efecto .


Cómo se usan

Esta tabla no coincide exactamente con las codificaciones de hardware de los modos de direccionamiento posibles, ya que estoy distinguiendo entre el uso de una etiqueta (por ejemplo, datos globales o estáticos) y el uso de un pequeño desplazamiento constante. Así que estoy cubriendo los modos de direccionamiento de hardware + soporte del enlazador para los símbolos.

Si tiene una char array[] punteros char array[] en esi ,

  • mov al, esi : inválido, no se ensamblará. Sin corchetes, no es una carga en absoluto. Es un error porque los registros no son del mismo tamaño.

  • mov al, [esi] carga el byte al que apunta.

  • mov al, [esi + ecx] carga la array[ecx] .

  • mov al, [esi + 10] carga la array[10] .

  • mov al, [esi + ecx*8 + 200] carga la array[ecx*8 + 200]

  • mov al, [global_array + 10] carga desde global_array[10] . En el modo de 64 bits, esta puede ser una dirección relativa al RIP. Se recomienda utilizar DEFAULT REL , para generar direcciones relativas a RIP de forma predeterminada en lugar de tener que usar siempre [rel global_array + 10] . No hay forma de usar un registro de índice con una dirección relativa al RIP directamente. El método normal es lea rax, [global_array] mov al, [rax + rcx*8 + 10] o similar.

  • mov al, [global_array + ecx + edx*2 + 10] cargas de global_array[ecx + edx*2 + 10] Obviamente, puede indexar una matriz estática / global con un solo registro. Incluso es posible una matriz 2D utilizando dos registros separados. (Pre-escalado uno con una instrucción adicional, para factores de escala que no sean 2, 4 u 8). Tenga en cuenta que global_array + 10 math se realiza en tiempo de enlace. El archivo de objeto (salida del ensamblador, entrada del enlazador) informa al enlazador del +10 para agregar a la dirección absoluta final, para colocar el desplazamiento de 4 bytes derecho en el ejecutable (salida del enlazador). Es por eso que no puede usar expresiones arbitrarias en constantes de tiempo de enlace que no son constantes de tiempo de ensamblaje (por ejemplo, direcciones de símbolos).

  • mov al, 0ABh No es una carga en absoluto, sino una constante inmediata que se almacenó dentro de la instrucción. (Tenga en cuenta que necesita poner un prefijo 0 para que el ensamblador sepa que es una constante, no un símbolo. Algunos ensambladores también aceptarán 0xAB ). Puede usar un símbolo como la constante inmediata, para obtener una dirección en un registro.

    • NASM: mov esi, global_array ensambla en un mov esi, imm32 que pone la dirección en esi.
    • MASM: mov esi, OFFSET global_array es necesario para hacer lo mismo.
    • MASM: mov esi, global_array ensambla en una carga: mov esi, dword [global_array] .

    En el modo de 64 bits, el direccionamiento de símbolos globales generalmente se realiza con el direccionamiento relativo al RIP, que su ensamblador realizará de manera predeterminada con la directiva DEFAULT REL , o con mov al, [rel global_array + 10] . Ningún registro de índice se puede usar con direcciones relativas a RIP, solo desplazamientos constantes. Todavía puedes hacer un direccionamiento absoluto, e incluso hay una forma especial de mov que puede cargarse desde una dirección absoluta de 64 bits (en lugar de la habitual de 32 bits con signo extendido ). Llamadas de syntax de AT & T que movabs código de movabs (también utilizadas para mov r64, imm64 ) mientras que la syntax Intel / NASM todavía lo llama una forma de mov .

    Utilice lea rsi, [rel global_array] para obtener direcciones relativas a la ruptura en los registros, ya que mov reg, imm codificaría una dirección no relativa en los bytes de la instrucción.

    Tenga en cuenta que OS X carga todo el código en una dirección fuera de los 32 bits bajos, por lo que el direccionamiento absoluto de 32 bits no se puede usar. El código independiente de la posición no es necesario para los ejecutables, pero es posible que también porque el direccionamiento absoluto de 64 bits es menos eficiente que el relativo al RIP. El formato de archivo de objeto macho64 no admite reubicaciones para direcciones absolutas de 32 bits como lo hace Linux ELF. Asegúrese de no utilizar un nombre de etiqueta como una constante de tiempo de comstackción en cualquier lugar, excepto en una dirección efectiva como [global_array + constant] , ya que se puede ensamblar a un modo de direccionamiento relativo de RIP. por ejemplo [global_array + rcx] no está permitido, porque RIP no se puede usar con ningún otro registro, por lo que tendría que ser ensamblado con la dirección absoluta de global_array codificada como el desplazamiento de 32 bits ( que se extenderá a 64b ).


Todos y cada uno de estos modos de direccionamiento se pueden usar con LEA para hacer cálculos enteros con un bonus de no afectar a los indicadores , independientemente de si se trata de una dirección válida. [esi*4 + 10] usualmente solo es útil con LEA (a menos que el desplazamiento sea un símbolo, en lugar de una pequeña constante). En el código máquina, no hay encoding para el registro escalado solo, por lo que [esi*4] tiene que ensamblarse a [esi*4 + 0] , con 4 bytes de ceros para un desplazamiento de 32 bits. A menudo todavía vale la pena copiar + cambiar en una instrucción en lugar de una mov + shl más corta, porque generalmente el rendimiento de uop es más un cuello de botella que un tamaño de código, especialmente en las CPU con una memoria caché descodificada.


Puede especificar anulaciones de segmento como mov al, fs:[esi] . Una anulación de segmento simplemente agrega un prefijo-byte delante de la encoding habitual. Todo lo demás permanece igual, con la misma syntax.

Incluso puede usar anulaciones de segmento con direccionamiento relativo de RIP. El direccionamiento absoluto de 32 bits requiere un byte más para codificar que el relativo a RIP, por lo que mov eax, fs:[0] se puede codificar de manera más eficiente usando un desplazamiento relativo que produce una dirección absoluta conocida. es decir, elija rel32 para RIP + rel32 = 0. YASM lo hará con mov ecx, [fs: rel 0] , pero NASM siempre usa direcciones absolutas disp32, ignorando el especificador rel . No he probado MASM o gas.


Si el tamaño del operando es ambiguo (por ejemplo, en una instrucción con un operando inmediato y de memoria), use byte / word / dword / qword / xmmword / ymmword para especificar:

 mov dword [rsi + 10], 0xAB ; NASM mov dword ptr [rsi + 10], 0xAB ; MASM and GNU .intex_syntax noprefix movl $0xAB, 10(%rsi) # GNU(AT&T): operand size from insn suffix 

Consulte los documentos de yasm para direcciones efectivas de syntax NASM y / o la sección de entrada de wikipedia x86 sobre modos de direccionamiento . La página wiki dice lo que está permitido en el modo de 16 bits. Aquí hay otra “hoja de trampa” para modos de direccionamiento de 32 bits .

También hay una guía más detallada para abordar modos, para 16 bits . 16 bits todavía tiene los mismos modos de direccionamiento que 32 bits, por lo que si encuentra que los modos de direccionamiento son confusos, léalo de todos modos

También vea la página de la wiki x86 para enlaces.

    Intereting Posts