Buscar..


Observaciones

Esta sección proporciona una descripción general de qué es x86 y por qué un desarrollador puede querer usarlo.

También debe mencionar cualquier tema grande dentro de x86, y vincular a los temas relacionados. Dado que la Documentación para x86 es nueva, es posible que deba crear versiones iniciales de esos temas relacionados.

lenguaje ensamblador x86

La familia de lenguajes de ensamblaje x86 representa décadas de avances en la arquitectura original Intel 8086. Además de haber varios dialectos diferentes basados ​​en el ensamblador utilizado, se han agregado instrucciones, registros y otras características adicionales del procesador a lo largo de los años, mientras que siguen siendo compatibles con el ensamblaje de 16 bits utilizado en la década de 1980.

El primer paso para trabajar con el ensamblaje x86 es determinar cuál es el objetivo. Si está buscando escribir código dentro de un sistema operativo, por ejemplo, también querrá determinar si elegirá usar un ensamblador independiente o funciones integradas de ensamblaje en línea de un lenguaje de nivel superior como C. Si Si desea codificar el código completo sin un sistema operativo, simplemente necesita instalar el ensamblador de su elección y comprender cómo crear un código binario que se puede convertir en memoria flash, imagen de arranque o cargar de otra manera en la memoria. Ubicación apropiada para comenzar la ejecución.

Un ensamblador muy popular que está bien soportado en varias plataformas es NASM (Netwide Assembler), que se puede obtener en http://nasm.us/ . En el sitio NASM, puede proceder a descargar la última versión de lanzamiento para su plataforma.

Windows

Tanto las versiones de 32 bits como las de 64 bits de NASM están disponibles para Windows. NASM viene con un instalador conveniente que se puede usar en su host de Windows para instalar el ensamblador automáticamente.

Linux

Bien puede ser que NASM ya esté instalado en su versión de Linux. Para verificar, ejecute:

nasm -v

Si no se encuentra el comando, deberá realizar una instalación. A menos que esté haciendo algo que requiera funciones de NASM de vanguardia, la mejor ruta es utilizar su herramienta de administración de paquetes incorporada para su distribución de Linux para instalar NASM. Por ejemplo, en sistemas derivados de Debian como Ubuntu y otros, ejecute lo siguiente desde un indicador de comando:

sudo apt-get install nasm

Para los sistemas basados ​​en RPM, puede probar:

sudo yum install nasm

Mac OS X

Las versiones recientes de OS X (incluidas Yosemite y El Capitán) vienen con una versión anterior de NASM preinstalada. Por ejemplo, El Capitán tiene instalada la versión 0.98.40. Si bien esto probablemente funcionará para casi todos los propósitos normales, en realidad es bastante antiguo. En este momento, se lanza la versión 2.11 de NASM y la versión 2.12 tiene varios candidatos disponibles.

Puede obtener el código fuente de NASM desde el enlace anterior, pero a menos que tenga una necesidad específica para instalar desde la fuente, es mucho más sencillo descargar el paquete binario del directorio de la versión de OS X y descomprimirlo.

Una vez descomprimido, se recomienda encarecidamente que no sobrescriba la versión de NASM instalada en el sistema. En su lugar, puede instalarlo en / usr / local:

 $ sudo su
 <user's password entered to become root>
 # cd /usr/local/bin
 # cp <path/to/unzipped/nasm/files/nasm> ./
 # exit

En este punto, NASM está en /usr/local/bin , pero no está en su ruta. Ahora debes agregar la siguiente línea al final de tu perfil:

 $ echo 'export PATH=/usr/local/bin:$PATH' >> ~/.bash_profile

Esto añadirá /usr/local/bin a su ruta. La ejecución de nasm -v en el símbolo del sistema ahora debería mostrar la versión correcta y más nueva.

Linux x86 ejemplo de Hello World

Este es un programa básico de Hello World en el ensamblaje de NASM para Linux x86 de 32 bits, que utiliza llamadas del sistema directamente (sin ninguna función libc). Es mucho para asimilar, pero con el tiempo será comprensible. Las líneas que comienzan con un punto y coma ( ; ) son comentarios.

Si aún no conoce la programación de sistemas Unix de bajo nivel, es posible que desee escribir funciones en asm y llamarlas desde programas C o C ++. Entonces puede preocuparse por aprender a manejar registros y memoria, sin aprender también la API de llamada de sistema POSIX y la ABI para usarla.


Esto hace dos llamadas al sistema: write(2) y _exit(2) (no la exit(3) envoltorio libc que vacía los buffers de stdio y así sucesivamente). (Técnicamente, _exit() llama a sys_exit_group, no a sys_exit, pero eso solo importa en un proceso de múltiples subprocesos ). Consulte también syscalls(2) para obtener información sobre las llamadas al sistema en general, y la diferencia entre hacerlas directamente en lugar de usar libc. Funciones de envoltura.

En resumen, las llamadas al sistema se realizan colocando los argumentos en los registros apropiados y el número de llamada del sistema en eax , luego ejecutando una instrucción int 0x80 . Consulte también ¿Cuáles son los valores de retorno de las llamadas del sistema en ensamblaje? para obtener una explicación más detallada de cómo se documenta la interfaz syscall de asm con la mayor parte de la sintaxis C.

Los números de llamada syscall para el ABI de 32 bits están en /usr/include/i386-linux-gnu/asm/unistd_32.h (el mismo contenido en /usr/include/x86_64-linux-gnu/asm/unistd_32.h ).

#include <sys/syscall.h> finalmente incluirá el archivo correcto, por lo que podría ejecutar echo '#include <sys/syscall.h>' | gcc -E - -dM | less para ver las definiciones de macros (consulte esta respuesta para obtener más información sobre cómo encontrar constantes para asm en encabezados C )


section .text             ; Executable code goes in the .text section
global _start             ; The linker looks for this symbol to set the process entry point, so execution start here
;;;a name followed by a colon defines a symbol.  The global _start directive modifies it so it's a global symbol, not just one that we can CALL or JMP to from inside the asm.
;;; note that _start isn't really a "function".  You can't return from it, and the kernel passes argc, argv, and env differently than main() would expect.
 _start:
    ;;; write(1, msg, len);
    ; Start by moving the arguments into registers, where the kernel will look for them
    mov     edx,len       ; 3rd arg goes in edx: buffer length
    mov     ecx,msg       ; 2nd arg goes in ecx: pointer to the buffer
    ;Set output to stdout (goes to your terminal, or wherever you redirect or pipe)
    mov     ebx,1         ; 1st arg goes in ebx: Unix file descriptor. 1 = stdout, which is normally connected to the terminal.

    mov     eax,4         ; system call number (from SYS_write / __NR_write from unistd_32.h).
    int     0x80          ; generate an interrupt, activating the kernel's system-call handling code.  64-bit code uses a different instruction, different registers, and different call numbers.
    ;; eax = return value, all other registers unchanged.

    ;;;Second, exit the process.  There's nothing to return to, so we can't use a ret instruction (like we could if this was main() or any function with a caller)
    ;;; If we don't exit, execution continues into whatever bytes are next in the memory page,
    ;;; typically leading to a segmentation fault because the padding 00 00 decodes to  add [eax],al.

    ;;; _exit(0);
    xor     ebx,ebx       ; first arg = exit status = 0.  (will be truncated to 8 bits).  Zeroing registers is a special case on x86, and mov ebx,0 would be less efficient.
                      ;; leaving out the zeroing of ebx would mean we exit(1), i.e. with an error status, since ebx still holds 1 from earlier.
    mov     eax,1         ; put __NR_exit into eax
    int     0x80          ;Execute the Linux function

section     .rodata       ; Section for read-only constants

             ;; msg is a label, and in this context doesn't need to be msg:.  It could be on a separate line.
             ;; db = Data Bytes: assemble some literal bytes into the output file.
msg     db  'Hello, world!',0xa     ; ASCII string constant plus a newline (0x10)

             ;;  No terminating zero byte is needed, because we're using write(), which takes a buffer + length instead of an implicit-length string.
             ;; To make this a C string that we could pass to puts or strlen, we'd need a terminating 0 byte. (e.g. "...", 0x10, 0)

len     equ $ - msg       ; Define an assemble-time constant (not stored by itself in the output file, but will appear as an immediate operand in insns that use it)
                          ; Calculate len = string length.  subtract the address of the start
                          ; of the string from the current position ($)
  ;; equivalently, we could have put a str_end: label after the string and done   len equ str_end - str

En Linux, puedes guardar este archivo como Hello.asm y construir un ejecutable de 32 bits con estos comandos:

nasm -felf32 Hello.asm                  # assemble as 32-bit code.  Add -Worphan-labels -g -Fdwarf  for debug symbols and warnings
gcc -nostdlib -m32 Hello.o -o Hello     # link without CRT startup code or libc, making a static binary

Vea esta respuesta para obtener más detalles sobre cómo compilar ensamblados en ejecutables Linux de 32 o 64 bits estáticos o enlazados dinámicamente, para la sintaxis NASM / YASM o la sintaxis GNU AT&T con GNU as directivas. (Punto clave: asegúrese de usar -m32 o equivalente al -m32 código de 32 bits en un host de 64 bits, o tendrá problemas confusos en tiempo de ejecución).

Puede rastrear su ejecución con strace para ver las llamadas del sistema que hace:

$ strace ./Hello 
execve("./Hello", ["./Hello"], [/* 72 vars */]) = 0
[ Process PID=4019 runs in 32 bit mode. ]
write(1, "Hello, world!\n", 14Hello, world!
)         = 14
_exit(0)                                = ?
+++ exited with 0 +++

La traza en stderr y la salida regular en stdout van al terminal aquí, por lo que interfieren en la línea con la llamada del sistema de write . Redirigir o rastrear a un archivo si te importa. Observe cómo esto nos permite ver fácilmente los valores de retorno de syscall sin tener que agregar código para imprimirlos, y es incluso más fácil que usar un depurador regular (como gdb) para esto.

La versión x86-64 de este programa sería extremadamente similar, pasando los mismos argumentos a las mismas llamadas del sistema, solo en diferentes registros. Y usando la instrucción syscall lugar de int 0x80 .



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow