2011-07-05 25 views
5

Así que estoy aprendiendo el ensamblado nasm x86_64 en mi mac por diversión. Después de hello world y algo de aritmética básica, intenté copiar un programa de hello world ligeramente más avanzado de this site y modificarlo para intel de 64 bits, pero no puedo deshacerme de este mensaje de error: hello.s:53: error: Mach-O 64-bit format does not support 32-bit absolute addresses. Aquí está el comando que uso para ensamblar y vincular: nasm -f macho64 hello.s && ld -macosx_version_min 10.6 hello.o. Y aquí está la línea correspondiente:Error del ensamblador: Mach-O 64 bit no admite direcciones absolutas de 32 bit

cmp rsi, name+8 

RSI es el registro que estoy usando para mi índice en el bucle, y el nombre es una palabra de cuatro reservado para la entrada del usuario, que es el nombre, que a estas alturas ya ha sido escrito.

Aquí está una parte del código (ver el resto, haga clic en el enlace para ir a la parte inferior, la única diferencia es que yo uso 64 registros bits):

loopAgain: 
mov al, [rsi]   ; al is a 1 byte register 
cmp al, 0x0a   ; if al holds an ascii newline... 
je exitLoop    ; then jump to label exitLoop 

; If al does not hold an ascii newline... 
mov rax, 0x2000004  ; System call write = 4 
mov rdi, 1    ; Write to stdout = 1 
mov rdx, 1    ; Size to write 
syscall 

inc rsi 

cmp rsi, name+8   ; LINE THAT CAUSES ERROR 
jl loopAgain 
+0

¿Cómo se define 'name'? – bdonlan

+0

Una sugerencia: intente escribir el mismo código en C, compilarlo con 'gcc -S' y observar el conjunto para ver cómo lo maneja GCC. – Nemo

+0

@bdonlan: en la sección .bss, tengo 'name: resb 8' – mk12

Respuesta

3

La instrucción cmp no lo hace admite un operando inmediato de 64 bits. Como tal, no puede poner una referencia de dirección inmediata de 64 bits en uno de sus operandos - cargue name+8 en un registro (usando un MOV ordinario), luego compare con ese registro.

Puede ver qué codificaciones de instrucciones están permitidas en el Intel ISA manual (advertencia: gran PDF). Como puede ver en la entrada para CMP, hay codificaciones CMP r/m32, imm32 y CMP r/m64, imm32, que permiten las comparaciones de un archivo de 32 bits inmediato con los registros de 32 bits y de 64 bits, pero no con un CMP r/m64, imm64. Sin embargo, hay una codificación MOV r64, imm64.

Como nasm se bloquea, la falla de MOV rcx, name+8 es simplemente un error en nasm. Informe a los desarrolladores de Nasm (después de asegurarse de que está utilizando la última versión de Nasm, también, verifique que this patch no soluciona el problema). En cualquier caso, sin embargo, una solución sería añadir un símbolo para el final de name:

name: 
    resb 8 
name_end: 

Ahora sólo tiene que utilizar MOV rcx, name_end. Esto tiene la ventaja de no necesitar actualizar los referentes cuando cambia el tamaño de name. Alternativamente, podría usar un ensamblador diferente, como el clang o los ensambladores GNU binutils.

+0

Intenté hacer 'mov rcx, name + 8' y luego' cmp rsi, rcx' pero cuando lo armé con nasm simplemente dice 'Segmentation fault'. Si elimino el +8, se ensambla bien. ¿Por qué está haciendo esto? – mk12

+0

Si hago 'mov rcx, name' seguido de' add rcx, 8' y 'cmp rsi, rcx' funciona bien. Pero ¿por qué no puedo hacer 'mov rcx, name + 8'? – mk12

+0

¿Qué sucede cuando desarmas el resultado de usar nombre + 8? – bdonlan

3

Creo que el problema al que se enfrentan es simple: el formato Mach-O ordena el código reubicable, lo que significa que los datos deben ser accedidos no por una dirección absoluta sino por una dirección relativa. Es decir, el ensamblador no puede resolver name a una constante porque es no una constante, los datos podrían estar en cualquier dirección.

Ahora que sabe que la dirección de los datos es relativa a la dirección de su código, vea si puede dar sentido a la salida de GCC. Por ejemplo,

static unsigned global_var; 
unsigned inc(void) 
{ 
    return ++global_var; 
} 

_inc: 
    mflr r0           ; Save old link register 
    bcl 20,31,"L00000000001$pb"      ; Jump 
"L00000000001$pb": 
    mflr r10           ; Get address of jump 
    mtlr r0           ; Restore old link register 
    addis r2,r10,ha16(_global_var-"L00000000001$pb") ; Add offset to address 
    lwz r3,lo16(_global_var-"L00000000001$pb")(r2) ; Load global_var 
    addi r3,r3,1          ; Increment global_var 
    stw r3,lo16(_global_var-"L00000000001$pb")(r2) ; Store global_var 
    blr            ; Return 

Tenga en cuenta que esto es en PowerPC, porque no conozco el Mach-O ABI para x86-64. En PowerPC, haces un salto, guardando el contador del programa, y ​​luego haces aritmética en el resultado. Creo que algo completamente diferente sucede en x86-64.

(Nota: Si nos fijamos en la producción de montaje de GCC, intente mirarlo con -O2 No me molesto mirando -O0 porque es demasiado detallado y más difícil de entender..)

Mi recomendación? A menos que usted está escribiendo un compilador (y, a veces incluso entonces), escribe sus funciones de montaje de una de dos maneras:

  • pasar todos los punteros necesarios como argumentos de la función, o,
  • Escribir el montaje como en línea ensamblaje dentro de una función C.

Esto generalmente será más portátil también, ya que dependerá menos de ciertos detalles de la ABI. ¡Pero el ABI sigue siendo importante! Si no conoce el ABI y lo sigue, entonces causará errores que son bastante difíciles de detectar. Por ejemplo, hace años hubo un error en el código de ensamblado LibSDL que provocó que memcpy de libc (también ensamblado) copie los datos incorrectos en algunas circunstancias muy específicas.

Cuestiones relacionadas