Assembly by Joseph
Assembly by Joseph
#assembly
Tabla de contenido
Acerca de 1
Observaciones 2
Examples 2
Introducción 2
Codigo de maquina 4
Introducción 12
Examples 12
Examples 15
Interfaz de usuario 15
Subrtx.asm 15
Generic.asm 17
Makefile 18
Capítulo 4: Interrupciones 20
Observaciones 20
Examples 20
Capítulo 5: La pila 21
Observaciones 21
Examples 21
Observaciones 23
Examples 23
x86 registros 25
x64 registros 26
Creditos 28
Acerca de
You can share this PDF with anyone you feel could benefit from it, downloaded the latest version
from: assembly-language
It is an unofficial and free Assembly Language ebook created for educational purposes. All the
content is extracted from Stack Overflow Documentation, which is written by many hardworking
individuals at Stack Overflow. It is neither affiliated with Stack Overflow nor official Assembly
Language.
The content is released under Creative Commons BY-SA, and the list of contributors to each
chapter are provided in the credits section at the end of this book. Images may be copyright of
their respective owners unless otherwise specified. All trademarks and registered trademarks are
the property of their respective company owners.
Use the content presented in this book at your own risk; it is not guaranteed to be correct nor
accurate, please send your feedback and corrections to [email protected]
https://riptutorial.com/es/home 1
Capítulo 1: Empezando 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:
Examples
Introducción
EJEMPLO:
mov eax, 4
cmp eax, 5
je point
https://riptutorial.com/es/home 2
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.
https://riptutorial.com/es/home 3
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.
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).
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 .
https://riptutorial.com/es/home 4
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
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.
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.
section .data
msg db "Hello world!",10 ; 10 is the ASCII code for a new line (LF)
section .text
global _start
https://riptutorial.com/es/home 5
_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.
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:
.intel_syntax noprefix
.data
.align 16
hello_msg:
.asciz "Hello, World!"
.text
.global _main
_main:
push rbp
https://riptutorial.com/es/home 6
mov rbp, rsp
Montar:
Notas:
https://riptutorial.com/es/home 7
Paso 2 : haga clic con el botón derecho en la solución del proyecto y seleccione Crear
dependencias -> Crear personalizaciones .
https://riptutorial.com/es/home 8
Paso 4 : Presiona el botón "ok" .
https://riptutorial.com/es/home 9
.386
.model small
.code
public main
main proc
ret
main endp
end main
Paso 6 : ¡Compila!
https://riptutorial.com/es/home 10
Lea Empezando con el lenguaje ensamblador en línea:
https://riptutorial.com/es/assembly/topic/1358/empezando-con-el-lenguaje-ensamblador
https://riptutorial.com/es/home 11
Capítulo 2: Control de flujo
Introducción
Cada pieza de software no trivial necesita estructuras de control de flujo para desviar el flujo del
programa según las condiciones. El ensamblaje, siendo el lenguaje de programación de nivel más
bajo, proporciona solo primitivas para estructuras de control. Normalmente, las operaciones de la
máquina afectan a los indicadores en la CPU, y las ramas / saltos condicionales implementan el
control de flujo. En el ensamblaje, todas las estructuras de control de nivel superior deben
construirse a partir de dichas primitivas.
Examples
Trivial IF-THEN-ELSE en m68k Assembly
Las instrucciones que afectan a qué banderas y qué ramas condicionales (que también podrían
estar basadas en combinaciones específicas de banderas ) están disponibles, dependen en gran
medida de la CPU elegida y deben consultarse en los manuales.
El Z80 tiene una instrucción específica para implementar conteos en bucle: DJNZ significa "registro
B decremento y salta si no es cero". Entonces, B es el registro de elección para implementar
bucles en este procesador. FOR ... NEXT debe implementarse "hacia atrás", porque el registro
cuenta atrás hasta cero. Otras CPU (como la 8086, esta CPU usa el registro CX como contador
de bucle) podrían tener registros e instrucciones de contador de bucle específicos similares,
algunas otras CPU permiten comandos de bucle con registros arbitrarios (m68k tiene una
instrucción DBRA que funciona con cualquier registro de datos).
https://riptutorial.com/es/home 12
;
; Pseudo code
; C = A : A = 0 : FOR B = Factor 2 DOWNTO 0 : A = A + C : NEXT B
mul:
LD C,A ; Save Factor 1 in C register
XOR A ; Clear accumulator
mLoop:
ADD A,C ; Add Factor 1 to accumulator
DJNZ mLoop ; Do this Factor 2 times
RET ; return to caller
section .data
msg_eq db 'Equal', 10
len_eq equ $ - msg_eq
syscall
syscall
jmp exit
_greater:
; Whatever code here
https://riptutorial.com/es/home 13
syscall
jmp exit
section .data
msg db 'Hello, world!', 0xA
len equ $ - msg
section .text
global _main
_main:
mov rax, 0 ; This will be the current number
mov rcx, 10 ; This will be the last number
_loop:
cmp rax, rcx
jl .loopbody ; Jump to .loopbody if rax < rcx
jge _exit ; Jump to _exit if rax ≥ rcx
.loopbody:
push rax ; Store the rax value for later use
syscall
inc rax ; Add 1 to rax. This is required since the loop must have an ending.
https://riptutorial.com/es/home 14
Capítulo 3: Ejemplos de Linux elf64 que no
usan glibc
Examples
Interfaz de usuario
Me atrevería a decir que el 80% del procesamiento que se lleva a cabo en los sistemas
informáticos modernos no requiere la interacción del usuario, como el código del kernel para
Linux, OSX y Windows. Para aquellos que lo hacen, hay dos aspectos fundamentales que son la
interactividad a través del teclado ( dispositivos señaladores ) y la consola. Este y otros ejemplos
de mi serie están orientados alrededor de la consola basada en texto (emulación VT100) y el
teclado.
En sí mismo, este ejemplo es muy simple, pero es un elemento esencial para la creación de
algoritmos más complejos.
Subrtx.asm
STDIN equ 0
STDOUT equ 1
SYS_READ equ 0
SYS_WRITE equ 1
section .text
Como esto está destinado exclusivamente para el teclado, la probabilidad de errores es casi nula.
Me imagino que la mayoría de las veces, el programa podrá contemplar el tamaño del búfer para
evitar el desbordamiento del búfer, pero eso no está garantizado debido a la indirección.
; =============================================================================
; Accept canonical input from operator for a maximum of EDX bytes and replace
; terminating CR with NULL.
https://riptutorial.com/es/home 15
xor eax, eax ; RAX = SYS_READ
mov edi, eax ; RDI = STDIN
syscall
pop rdi
pop rcx
ret
; =============================================================================
; Determine length, including terminating character EOS. Result may include
; VT100 escape sequences.
; NOTE: Probably should check direction flag here, but I always set and
; reset DF in the process that is using it.
ret
; =============================================================================
https://riptutorial.com/es/home 16
; Display an ASCIIZ string on console that may have embedded VT100 sequences.
; NOTE: This procedure is intended for console, but in the event STDOUT is
; redirected by some means, EAX may return error code from syscall.
ret
Generic.asm
global _start
SYS_EXIT equ 60
ESC equ 27
BSize equ 96
section .rodata
Prompt: db ESC, '[2J' ; VT100 clear screen
db ESC, '[4;11H' ; " Position cursor to line 4 column 11
https://riptutorial.com/es/home 17
db 'ASCII -> INT64 (binary, octal, hexidecimal, decimal), '
db 'Packed & Unpacked BCD and floating point conversions'
db 10, 10, 0, 9, 9, 9, '=> ', 0
db 10, 9, 'Bye'
db ESC, '[0m' ; VT100 Reset console
db 10, 10, 0
section .text
_start: pop rdi
mov rsi, rsp
and rsp, byte 0xf0 ; Align stack on 16 byte boundary.
call main
mov rdi, rax ; Copy return code into ARG0
Makefile
OBJECTS = Subrtx.o Generic.o
Generic : $(OBJECTS)
ld -oGeneric $(OBJECTS)
readelf -WS Generic
Generic.o : Generic.asm
nasm -g -felf64 Generic.asm
Subrtx.o : Subrtx.asm
nasm -g -felf64 Subrtx.asm
clean:
rm -f $(OBJECTS) Generic
https://riptutorial.com/es/home 18
Lea Ejemplos de Linux elf64 que no usan glibc en línea:
https://riptutorial.com/es/assembly/topic/7059/ejemplos-de-linux-elf64-que-no-usan-glibc
https://riptutorial.com/es/home 19
Capítulo 4: Interrupciones
Observaciones
¿Por qué necesitamos interrupciones?
Las interrupciones son activadas por el software ( INT 80h) o el hardware (pulsación de tecla), se
comportan como una llamada (saltan a una ubicación específica, ejecutan el código y vuelven a
saltar).
Examples
Trabajando con interrupciones en la Z80:
El Z80 no tiene mesa de interrupción como procesadores modernos. Las Interrupciones todas
ejecutan el mismo código. En el modo de interrupción 1, ejecutan el código en una ubicación
inmutable específica. En el modo de interrupción 2, ejecutan el código del registro de puntero al
que apunto. El Z80 tiene un temporizador que dispara la Interrupción todos ~ 0.007s.
EI ;enables Interrupts
DI ;disables Interrupts
IM 1 ;sets the Normal Interrupt Mode
https://riptutorial.com/es/home 20
Capítulo 5: La pila
Observaciones
La pila de computadoras es como una pila de libros. PUSH agrega uno a la parte superior y POP
quita lo máximo. Al igual que en la vida real, la pila no puede ser infinita, por lo que tiene el
tamaño máximo. La pila se puede usar para ordenar algoritmos, para manejar una mayor
cantidad de datos o para valores seguros de registros mientras se realiza otra operación.
Examples
Zilog Z80 Stack
El registro sp se utiliza como puntero de pila , apuntando al último valor almacenado en pila
("parte superior" de pila). Entonces EX (sp),hl intercambiará el valor de hl con el valor en la parte
superior de la pila.
Para sp = $4844 con los valores 1 , 2 , 3 almacenados en la pila (los 3 se colocan en la pila como
último valor, por lo que están en la parte superior de la misma), la memoria se verá así:
LD hl,$0506
EX (sp),hl ; $0003 into hl, "06 05" bytes at $4844
POP bc ; like: LD c,(sp); INC sp; LD b,(sp); INC sp
; so bc is now $0506, and sp is $4846
XOR a ; a = 0, sets zero and parity flags
PUSH af ; like: DEC sp; LD (sp),a; DEC sp; LD (sp),f
; so at $4844 is $0044 (44 = z+p flags), sp is $4844
CALL $8000 ; sp is $4842, with address of next ins at top of stack
; pc = $8000 (jumping to sub-routine)
; after RET will return here, the sp will be $4844 again
LD (L1+1),sp ; stores current sp into LD sp,nn instruction (self modification)
DEC sp ; sp is $4843
L1 LD sp,$1234 ; restores sp to $4844 ($1234 was modified)
POP de ; de = $0044, sp = $4846
POP ix ; ix = $0002, sp = $4848
https://riptutorial.com/es/home 21
...
...
ORG $8000
RET ; LD pc,(sp); INC sp; INC sp
; jumps to address at top of stack, "returning" to caller
Resumen : PUSH almacenará el valor en la parte superior de la pila, POP obtendrá el valor de la
parte superior de la pila, es una cola LIFO (último en entrar, primero en salir). CALL es igual que JP
, pero también empuja la dirección de la siguiente instrucción después de CALL en la parte superior
de la pila. RET es similar a JP también, sacando la dirección de la pila y saltando a ella.
Advertencia : cuando las interrupciones están habilitadas, el sp debe ser válido durante la señal
de interrupción, con suficiente espacio libre reservado para la rutina del manejador de
interrupciones, ya que la señal de interrupción almacenará la dirección de retorno ( pc real) antes
de llamar a la rutina del manejador, que puede almacenar más datos en apilar también. Cualquier
valor por delante de sp puede ser modificado así "inesperadamente", si ocurre una interrupción.
Truco avanzado : en Z80 con PUSH tomó 11 ciclos de reloj (11t) y POP tomó 10t, el POP / PUSH
desenrollado a través de todos los registros, incluyendo EXX para las variantes de sombra, fue la
forma más rápida de copiar bloques de memoria, incluso más rápido que el LDI desenrollado.
Pero tuvo que programar la copia entre las señales de interrupción para evitar la corrupción de la
memoria. También el PUSH enrollado fue la forma más rápida de llenar la memoria con un valor
particular en el ZX Spectrum (nuevamente, con el riesgo de corrupción por Interrupción, si no se
cronometra correctamente, o se realiza bajo DI ).
https://riptutorial.com/es/home 22
Capítulo 6: Registros
Observaciones
¿Qué son los registros?
El procesador puede funcionar con valores numéricos (números), pero estos deben almacenarse
en algún lugar primero. Los datos se almacenan principalmente en la memoria, o dentro del
código de operación de la instrucción (que también se almacena generalmente en la memoria), o
en la memoria especial en chip colocada directamente en el procesador, que se denomina
registro .
Para trabajar con valor en el registro, no es necesario que lo dirija por dirección, pero se utilizan
"nombres" mnemónicos especiales, como por ejemplo ax en x86, A en Z80 o r0 en ARM.
Algunos procesadores están construidos de una manera, donde casi todos los registros son
iguales y se pueden usar para todos los propósitos (a menudo el grupo de procesadores RISC),
otros tienen especialización distinta, cuando solo algunos registros se pueden usar para
aritmética ( "acumulador" en CPU tempranas) ) y otros registros para direccionamiento de
memoria solamente, etc.
Esta construcción que utiliza la memoria directamente en el chip del procesador tiene una gran
implicación en el rendimiento, ya que agregar dos números de los registros que lo guardan para
registrarse se realiza generalmente en el menor tiempo posible por ese procesador (Ejemplo en el
procesador ARM: ADD r2,r0,r1 establece r2 en (r0 + r1) valor, en ciclo de procesador único).
Por el contrario, cuando uno de los operandos hace referencia a una ubicación de memoria, el
procesador puede detenerse durante algún tiempo, esperando que llegue el valor del chip de
memoria (en x86, esto puede ir desde cero, esperar los valores en caché L0 hasta cientos de Los
ciclos de la CPU cuando el valor no está en ningún caché y se debe leer directamente desde el
chip de memoria DRAM).
El tamaño nativo del registro en bits se usa a menudo para agrupar procesadores, como Z80 es
"procesador de 8 bits" y 80386 es "procesador de 32 bits" , aunque esa agrupación rara vez es
clara. Por ejemplo, Z80 opera también con pares de registros, formando un valor nativo de 16
bits, y la CPU de 32 bits 80686 tiene instrucciones MMX para trabajar de forma nativa con
registros de 64 bits.
Examples
https://riptutorial.com/es/home 23
Zilog Z80 se registra
La mayoría de los registros de 8 bits se pueden usar también en pares como registros de 16 bits:
AF , BC , DE y HL .
SP es el puntero de pila , que marca la parte inferior de la memoria de pila (utilizada por las
instrucciones PUSH / POP / CALL / RET ).
PC es un contador de programa , que apunta a la instrucción actualmente ejecutada.
I es el registro de interrupción , que proporciona un byte alto de la dirección de la tabla de
vectores para el modo de interrupción de IM 2 .
R es el registro de actualización , se incrementa cada vez que la CPU busca un código de
operación (o prefijo de código de operación).
Algunas instrucciones no oficiales existen en algunos procesadores Z80 para manipular partes de
IYH:IYL de IX como IXH:IXL e IY como IYH:IYL .
Las combinaciones correctas de posibles operandos de origen y destino están limitadas (por
ejemplo, LD H,(a16) no existe).
https://riptutorial.com/es/home 24
PUSH DE ; 16b value DE pushed to stack
CALL a16 ; while primarily used for execution branching
; it also stores next instruction address into stack
x86 registros
En el mundo de 32 bits, los registros de propósito general se dividen en tres clases generales: los
registros de propósito general de 16 bits, los registros de propósito general extendidos de 32 bits
y las mitades de registro de 8 bits. Estas tres clases no representan tres conjuntos de registros
completamente distintos. Los registros de 16 bits y 8 bits son en realidad nombres de regiones
dentro de los registros de 32 bits. El crecimiento de los registros en la familia de CPU x86 se ha
producido al extender los registros existentes en las CPU más antiguas
Hay ocho registros de propósito general de 16 bits: AX, BX, CX, DX, BP, SI, DI y SP; y puede
colocar cualquier valor en ellos que pueda expresarse en 16 bits o menos.
Cuando Intel expandió la arquitectura x86 a 32 bits en 1986, duplicó el tamaño de los ocho
registros y les dio nuevos nombres prefijando una E delante de cada nombre de registro, dando
como resultado EAX, EBX, ECX, EDX, EBP, ESI, EDI, y ESP.
Con x86_64 vino otra duplicación del tamaño del registro, así como la adición de algunos nuevos
registros. Estos registros tienen 64 bits de ancho y se denominan (barra diagonal utilizada para
mostrar el nombre del registro alternativo): RAX / r0, RBX / r3, RCX / r1, RDX / r2, RBP / r5, RSI /
r6, RDI / r7, RSP / r4 , R8, R9, R10, R11, R12, R13, R14, R15.
Si bien los registros de propósito general se pueden usar técnicamente para cualquier cosa, cada
registro también tiene un propósito alternativo / principal:
https://riptutorial.com/es/home 25
Registro Nombre completo Descripción
x64 registros
Propósito general
Nota
Los sufijos utilizados para direccionar los bits más bajos de los nuevos registros representan:
https://riptutorial.com/es/home 26
• Byte B, 8 bits;
• W palabra, 16 bits;
• D doble palabra, 32 bits.
https://riptutorial.com/es/home 27
Creditos
S.
Capítulos Contributors
No
Ejemplos de Linux
3 elf64 que no usan Shift_Left
glibc
4 Interrupciones Jonas W.
https://riptutorial.com/es/home 28