Looking for assembly Answers? Try Ask4KnowledgeBase
Looking for assembly Keywords? Try Ask4Keywords

Assembly LanguageEmpezando con el lenguaje ensamblador


Observaciones

Ensamblaje es un nombre general que se usa para muchas formas de código de máquina legibles por humanos. Naturalmente, difiere mucho entre diferentes CPU (unidad central de procesamiento), pero también en una sola CPU pueden existir varios dialectos de ensamblaje incompatibles, cada uno compilado por un ensamblador diferente, en el código de máquina idéntico definido por el creador de la CPU.

Si desea hacer una pregunta sobre su propio problema de ensamblaje, siempre indique qué HW y qué ensamblador está utilizando, de lo contrario será difícil responder a su pregunta en detalle.

El Ensamblaje de Aprendizaje de una sola CPU en particular ayudará a aprender lo básico sobre diferentes CPU, pero cada arquitectura de HW puede tener diferencias considerables en los detalles, por lo que aprender ASM para una nueva plataforma puede estar cerca de aprenderlo desde cero.

Campo de golf:

Wikilibro de la Asamblea X86

Introducción

El lenguaje ensamblador es una forma humana legible de lenguaje de máquina o código de máquina que es la secuencia real de bits y bytes en los que opera la lógica del procesador. En general, es más fácil para los humanos leer y programar en mnemotécnicas que en binario, octal o hexadecimal, por lo que los humanos suelen escribir código en lenguaje ensamblador y luego usar uno o más programas para convertirlo al formato de lenguaje de máquina que entiende el procesador.

EJEMPLO:

mov eax, 4
cmp eax, 5
je point
 

Un ensamblador es un programa que lee el programa de lenguaje ensamblador, lo analiza y produce el lenguaje de máquina correspondiente. Es importante comprender que, a diferencia de un lenguaje como C ++ que es un lenguaje único definido en un documento estándar, existen muchos lenguajes ensambladores diferentes. Cada arquitectura de procesador, ARM, MIPS, x86, etc. tiene un código de máquina diferente y, por lo tanto, un lenguaje ensamblador diferente. Además, a veces hay varios lenguajes de ensamblaje diferentes para la misma arquitectura de procesador. En particular, la familia de procesadores x86 tiene dos formatos populares que a menudo se denominan sintaxis de gas ( gas es el nombre del ejecutable para el ensamblador GNU) y la sintaxis Intel (denominada así por el autor de la familia de procesadores x86). Son diferentes pero equivalentes, ya que uno puede escribir cualquier programa dado en cualquier sintaxis.

En general, el inventor del procesador documenta el procesador y su código de máquina y crea un lenguaje ensamblador. Es común que ese lenguaje ensamblador en particular sea el único utilizado, pero a diferencia de los escritores de compiladores que intentan ajustarse a un estándar de lenguaje, el lenguaje ensamblador definido por el inventor del procesador es usualmente la versión que usan las personas que escriben ensambladores. .

Hay dos tipos generales de procesadores:

  • CISC (Computadora de conjunto de instrucciones complejas): tiene muchas instrucciones de lenguaje de máquina diferentes ya menudo complejas

  • RISC (Computadoras de conjunto de instrucciones reducidas): por el contrario, tiene menos instrucciones y más simples

Para un programador en lenguaje ensamblador, la diferencia es que un procesador CISC puede tener muchas instrucciones para aprender, pero a menudo hay instrucciones adecuadas para una tarea en particular, mientras que los procesadores RISC tienen menos instrucciones y más simples, pero cualquier operación dada puede requerir el programador en lenguaje ensamblador. Escribir más instrucciones para hacer lo mismo.

Los compiladores de otros lenguajes de programación a veces producen el ensamblador primero, que luego se compila en un código de máquina llamando a un ensamblador. Por ejemplo, gcc utiliza su propio ensamblador de gas en la etapa final de compilación. El código de máquina producido a menudo se almacena en archivos de objetos , que pueden vincularse a ejecutables mediante el programa enlazador.

Una "cadena de herramientas" completa a menudo consiste en un compilador, ensamblador y enlazador. Entonces se puede usar ese ensamblador y enlazador directamente para escribir programas en lenguaje ensamblador. En el mundo GNU, el paquete binutils contiene el ensamblador y el enlazador y las herramientas relacionadas; aquellos que están interesados ​​únicamente en la programación en lenguaje ensamblador no necesitan gcc u otros paquetes de compilación.

Los microcontroladores pequeños a menudo se programan únicamente en lenguaje ensamblador o en una combinación de lenguaje ensamblador y uno o más lenguajes de nivel superior como C o C ++. Esto se hace porque a menudo se pueden usar los aspectos particulares de la arquitectura del conjunto de instrucciones para que dichos dispositivos escriban códigos más compactos y eficientes de lo que sería posible en un lenguaje de nivel superior y tales dispositivos a menudo tienen memoria y registros limitados. Muchos microprocesadores se utilizan en sistemas integrados que son dispositivos distintos de las computadoras de propósito general que tienen un microprocesador en su interior. Ejemplos de tales sistemas integrados son televisores, hornos de microondas y la unidad de control del motor de un automóvil moderno. Muchos de estos dispositivos no tienen teclado o pantalla, por lo que un programador generalmente escribe el programa en una computadora de propósito general, ejecuta un ensamblador cruzado (llamado así porque este tipo de ensamblador produce código para un tipo de procesador diferente al de aquel en el que se ejecuta). ) y / o un compilador cruzado y un enlazador cruzado para producir código de máquina.

Existen muchos proveedores para tales herramientas, que son tan variados como los procesadores para los que producen código. Muchos, pero no todos los procesadores también tienen una solución de código abierto como GNU, sdcc, llvm u otros.

Ejecutando ensamblaje x86 en Visual Studio 2015

Paso 1 : Crear un proyecto vacío a través de Archivo -> Nuevo proyecto .

introduzca la descripción de la imagen aquí

Paso 2 : haga clic con el botón derecho en la solución del proyecto y seleccione Crear dependencias -> Crear personalizaciones .

Paso 3 : Marque la casilla de verificación ".masm" .

introduzca la descripción de la imagen aquí

Paso 4 : Presiona el botón "ok" .

Paso 5 : Crea tu archivo de ensamblaje y escribe esto:

.386
    .model small
        .code

            public main
                main proc

                    ; Ends program normally

                    ret
                main endp
            end main
 

Paso 6 : ¡Compila!

introduzca la descripción de la imagen aquí

Hola mundo para Linux x86_64 (Intel 64 bit)

section .data
    msg db "Hello world!",10      ; 10 is the ASCII code for a new line (LF)

section .text
    global _start

_start:
    mov rax, 1
    mov rdi, 1
    mov rsi, msg
    mov rdx, 13
    syscall
    
    mov rax, 60
    mov rdi, 0
    syscall
 

Si desea ejecutar este programa, primero necesita el Ensamblador nasm , nasm , porque este código usa su sintaxis. Luego use los siguientes comandos (asumiendo que el código está en el archivo helloworld.asm ). Son necesarios para ensamblar, enlazar y ejecutar, respectivamente.

  • nasm -felf64 helloworld.asm
  • ld helloworld.o -o helloworld
  • ./helloworld

El código hace uso de sys_write syscall de Linux. Aquí puede ver una lista de todas las llamadas al sistema para la arquitectura x86_64. Cuando también tiene en cuenta las páginas del manual de escritura y salida , puede traducir el programa anterior en uno que hace lo mismo y es mucho más legible:

#include <unistd.h>

#define STDOUT 1

int main()
{
    write(STDOUT, "Hello world!\n", 13);
    _exit(0);
}
 

Aquí solo se necesitan dos comandos para compilar y vincular (el primero) y ejecutar:

  • gcc helloworld_c.c -o helloworld_c .
  • ./helloworld_c

Hola mundo para OS X (x86_64, gas de sintaxis de Intel)

.intel_syntax noprefix

.data

.align 16
hello_msg:
    .asciz "Hello, World!"

.text

.global _main
_main:
    push rbp
    mov rbp, rsp

    lea rdi, [rip+hello_msg]
    call _puts

    xor rax, rax
    leave
    ret
 

Montar:

clang main.s -o hello
./hello
 

Notas:

Codigo de maquina

El código de máquina es un término para los datos en un formato de máquina nativo particular, que son procesados ​​directamente por la máquina, generalmente por el procesador llamado CPU (Unidad central de procesamiento).

La arquitectura de computadora común (arquitectura de von Neumann ) consiste en un procesador de propósito general (CPU), memoria de propósito general, que almacena tanto el programa (ROM / RAM) como los datos procesados ​​y los dispositivos de entrada y salida (dispositivos de E / S).

La principal ventaja de esta arquitectura es la relativa simplicidad y universalidad de cada uno de los componentes, en comparación con las máquinas informáticas anteriores (con el programa de cableado en la construcción de la máquina), o las arquitecturas competidoras (por ejemplo, la arquitectura de Harvard que separa la memoria del programa de la memoria de datos). Desventaja es un poco peor el rendimiento general. A largo plazo, la universalidad permitió un uso flexible, que generalmente superaba el costo de rendimiento.

¿Cómo se relaciona esto con el código de máquina?

El programa y los datos se almacenan en estas computadoras como números, en la memoria. No hay una forma genuina de diferenciar el código de los datos, por lo que los sistemas operativos y los operadores de la máquina le dan sugerencias a la CPU, en la que el punto de entrada de la memoria inicia el programa, después de cargar todos los números en la memoria. Luego, la CPU lee la instrucción (número) almacenada en el punto de entrada y la procesa de manera rigurosa, leyendo secuencialmente los siguientes números como instrucciones adicionales, a menos que el propio programa le indique a la CPU que continúe con la ejecución en otro lugar.

Por ejemplo, dos números de 8 bits (8 bits agrupados son iguales a 1 byte, es un número entero sin signo dentro del rango de 0-255): 60 201 , cuando se ejecuta como código en la CPU Zilog Z80 se procesará como dos instrucciones: INC a (incrementando el valor en el registro a por uno) y RET (regresando de la sub-rutina, apuntando a la CPU para ejecutar instrucciones desde diferentes partes de la memoria).

Para definir este programa, un humano puede ingresar esos números mediante algún editor de memoria / archivo, por ejemplo en hex-editor como dos bytes: 3C C9 (números decimales 60 y 201 escritos en codificación de base 16). Eso sería la programación en código de máquina .

Para facilitar la tarea de la programación de la CPU para los humanos, se crearon programas de ensamblador , capaces de leer un archivo de texto que contiene algo como:

subroutineIncrementA:
    INC   a
    RET

dataValueDefinedInAssemblerSource:
    DB    60          ; define byte with value 60 right after the ret
 

La secuencia de números hexadecimales de byte de salida 3C C9 3C envolvió con números adicionales opcionales específicos para la plataforma de destino: marcando qué parte de dicho binario es un código ejecutable, donde está el punto de entrada para el programa (la primera instrucción de la misma), qué partes están codificadas Datos (no ejecutables), etc.

Observe cómo el programador especificó el último byte con el valor 60 como "datos", pero desde la perspectiva de la CPU no difiere en modo alguno de INC a byte. Depende del programa en ejecución navegar correctamente en la CPU sobre los bytes preparados como instrucciones y procesar los bytes de datos solo como datos para las instrucciones.

Dicha salida generalmente se almacena en un archivo en el dispositivo de almacenamiento, que se carga más tarde por el sistema operativo ( Sistema operativo: un código de máquina que ya se está ejecutando en la computadora, lo que ayuda a manipular la computadora ) en la memoria antes de ejecutarla y, finalmente, apunta la CPU en la memoria. Punto de entrada del programa.

La CPU puede procesar y ejecutar solo el código de la máquina, pero cualquier contenido de la memoria, incluso uno aleatorio, puede procesarse como tal, aunque el resultado puede ser aleatorio, desde el " fallo " detectado y manejado por el SO hasta el borrado accidental de los datos de I / O dispositivos, o daños en equipos sensibles conectados a la computadora (no es un caso común para las computadoras en el hogar :)).

El proceso similar es seguido por muchos otros lenguajes de programación de alto nivel, compilando la fuente (forma de programa de texto legible por humanos) en números, ya sea representando el código de máquina (instrucciones nativas de la CPU), o en el caso de lenguajes híbridos interpretados en algunos código de máquina virtual específico del idioma, que se descodifica en código de máquina nativo durante la ejecución por intérprete o máquina virtual.

Algunos compiladores usan el Ensamblador como etapa intermedia de compilación, primero traducen la fuente a la forma Ensamblador, luego ejecutan la herramienta del ensamblador para obtener el código final de la máquina (ejemplo GCC: ejecute gcc -S helloworld.c para obtener una versión del ensamblador del programa C helloworld.c ).