La sintassi AT&T (usata da GNU Assembler / GAS) differisce dalla Intel per ordine degli operandi, prefissi e suffissi.
| Elemento | AT&T | Intel (confronto) |
|---|---|---|
| Ordine operandi | src, dst | dst, src |
| Registri | Prefisso % → %rax | Nessun prefisso → rax |
| Immediati | Prefisso $ → $42 | Nessun prefisso → 42 |
| Suffisso dimensione | b(8) w(16) l(32) q(64) | Keyword: BYTE PTR, ecc. |
| Indirizzamento | disp(base,index,scale) | [base+index*scale+disp] |
| Commenti | # commento oppure /* ... */ | ; commento |
movb $0x41, %al # 8 bit (byte) movw $0x1234, %ax # 16 bit (word) movl $0xDEAD, %eax # 32 bit (long/doubleword) movq $0xCAFE, %rax # 64 bit (quadword)
movq $42, %rax # Immediato movq %rbx, %rax # Registro-registro movq (%rbx), %rax # Indiretto: mem[RBX] movq 8(%rbx), %rax # Base + displacement: mem[RBX+8] movq (%rbx,%rcx,4), %rax # Base + index*scale: mem[RBX+RCX*4] movq 16(%rbx,%rcx,8), %rax # Completo: mem[RBX+RCX*8+16] movq var(%rip), %rax # RIP-relative (PIC, x86_64)
.section .text # Sezione codice .section .data # Dati inizializzati .section .bss # Dati non inizializzati .section .rodata # Dati read-only .global _start # Rendi simbolo visibile al linker .extern printf # Importa simbolo esterno # Dichiarazione dati .byte 0xFF # 1 byte .word 0x1234 # 2 byte (16 bit) .long 0xDEADBEEF # 4 byte (32 bit) .quad 0xCAFEBABE # 8 byte (64 bit) .ascii "hello" # Stringa (no terminatore) .asciz "hello" # Stringa con \0 .space 64 # Riserva 64 byte (zero-filled) .skip 64, 0xFF # Riserva 64 byte riempiti con 0xFF .string "hello" # Alias per .asciz (stringa con \0) .align 16 # Allinea a 16 byte .p2align 4 # Allinea a 2^4 = 16 byte .equ BUF_SIZE, 1024 # Costante simbolica .set LIMIT, 256 # Alias di .equ (riassegnabile con .set) .comm buffer, 4096, 16 # Alloca 4096 byte in BSS, allineati a 16 .lcomm local_buf, 256 # Come .comm ma simbolo locale (non esportato) .type my_func, @function # Tipo simbolo ELF (function/object) .size my_func, . - my_func # Dimensione simbolo ELF .include "macros.inc" # Include file sorgente # Macro .macro PUSH_CALLEE_SAVED pushq %rbx pushq %r12 pushq %r13 .endm # Macro con parametri .macro SYSCALL_1 nr, arg1 movq $\nr, %rax movq \arg1, %rdi syscall .endm # Ripetizione .rept 8 nop # Ripete 8 volte .endr # Assembly condizionale .ifdef DEBUG # codice di debug .endif .if BUF_SIZE > 512 # codice condizionale .else # alternativa .endif
Le due sintassi rappresentano le stesse istruzioni macchina. GCC e GAS usano AT&T per default; NASM e la documentazione Intel usano la sintassi Intel. GAS accetta anche Intel con .intel_syntax noprefix.
AT&T (GAS)
movq $42, %rax movq %rbx, %rax movq (%rbx), %rax movq 8(%rbx), %rax movq (%rbx,%rcx,4), %rax leaq msg(%rip), %rdi addq $1, %rax cmpq $0, %rax pushq %rbp call func
Intel (NASM)
mov rax, 42 mov rax, rbx mov rax, [rbx] mov rax, [rbx+8] mov rax, [rbx+rcx*4] lea rdi, [rel msg] add rax, 1 cmp rax, 0 push rbp call func
AT&T (GAS)
# Byte da memoria movb (%rsi), %al # Long da indirizzo assoluto movl var, %eax # Store word in memoria movw %ax, 4(%rdi) # Indirizzamento completo movl -8(%rbp,%rsi,4), %eax
Intel (NASM)
; Byte da memoria mov al, BYTE [rsi] ; Long da indirizzo assoluto mov eax, DWORD [var] ; Store word in memoria mov WORD [rdi+4], ax ; Indirizzamento completo mov eax, DWORD [rbp+rsi*4-8]
AT&T (GAS) — as + ld
.section .rodata msg: .asciz "Hello!\n" .equ LEN, 7 .section .text .global _start _start: movq $1, %rax movq $1, %rdi leaq msg(%rip), %rsi movq $LEN, %rdx syscall movq $60, %rax xorq %rdi, %rdi syscall
Intel (NASM) — nasm + ld
section .rodata msg: db "Hello!", 0x0A LEN equ 7 section .text global _start _start: mov rax, 1 mov rdi, 1 lea rsi, [rel msg] mov rdx, LEN syscall mov rax, 60 xor rdi, rdi syscall
| Aspetto | AT&T (GAS) | Intel (NASM) |
|---|---|---|
| Ordine operandi | src, dst | dst, src |
| Prefisso registri | %rax | rax |
| Prefisso immediati | $42 | 42 |
| Suffisso dimensione | Sulla istruzione: movl | Sull'operando: DWORD PTR |
| Indirizzamento | disp(%base,%idx,scale) | [base+idx*scale+disp] |
| Commenti | # ... o /* ... */ | ; ... |
| Stringhe | .asciz "str" | db "str", 0 |
| Costanti | .equ NAME, val | NAME equ val |
| Sezioni | .section .text | section .text |
| Assembler | GNU AS (gas) | NASM, YASM, FASM |
| Usato da | GCC, Clang, GDB, objdump -d | Documentazione Intel/AMD, NASM |
.intel_syntax noprefix all'inizio del file. Per tornare ad AT&T: .att_syntax prefix. In GCC inline asm: asm(".intel_syntax noprefix\n\t" ... ".att_syntax prefix"); Con objdump: objdump -d -M intel per disassemblare in sintassi Intel.In x86_64 ogni registro generale ha 4 sotto-registri accessibili. I registri R8–R15 sono nuovi in x86_64.
| 64-bit | 32-bit | 16-bit | 8-bit alto | 8-bit basso | Uso tipico |
|---|---|---|---|---|---|
%rax | %eax | %ax | %ah | %al | Accumulatore, valore di ritorno |
%rbx | %ebx | %bx | %bh | %bl | Base (callee-saved) |
%rcx | %ecx | %cx | %ch | %cl | Contatore, 4° arg |
%rdx | %edx | %dx | %dh | %dl | Dati, 3° arg |
%rsi | %esi | %si | — | %sil | Source index, 2° arg |
%rdi | %edi | %di | — | %dil | Dest index, 1° arg |
%rbp | %ebp | %bp | — | %bpl | Frame pointer (callee-saved) |
%rsp | %esp | %sp | — | %spl | Stack pointer |
%r8 | %r8d | %r8w | — | %r8b | 5° arg |
%r9 | %r9d | %r9w | — | %r9b | 6° arg |
%r10 | %r10d | %r10w | — | %r10b | Temporaneo (caller-saved) |
%r11 | %r11d | %r11w | — | %r11b | Temporaneo (caller-saved) |
%r12 | %r12d | %r12w | — | %r12b | Callee-saved |
%r13 | %r13d | %r13w | — | %r13b | Callee-saved |
%r14 | %r14d | %r14w | — | %r14b | Callee-saved |
%r15 | %r15d | %r15w | — | %r15b | Callee-saved |
| Registro | Descrizione |
|---|---|
%rip | Instruction Pointer (prossima istruzione da eseguire) |
%rflags | Registro dei flag: CF, ZF, SF, OF, PF, AF, DF, IF, TF |
%cs, %ds, %ss, %es, %fs, %gs | Registri di segmento (FS/GS usati per TLS in Linux) |
| Flag | Bit | Significato |
|---|---|---|
| CF (Carry) | 0 | Riporto/prestito in operazioni unsigned |
| ZF (Zero) | 6 | Risultato = 0 |
| SF (Sign) | 7 | Bit di segno del risultato |
| OF (Overflow) | 11 | Overflow in operazioni signed |
| PF (Parity) | 2 | Parità del byte basso |
| DF (Direction) | 10 | Direzione per istruzioni stringa (0=avanti, 1=indietro) |
movl $1, %eax) azzera automaticamente i 32 bit superiori del corrispondente registro a 64 bit. Scrivere a 8 o 16 bit NON azzera i bit superiori.Layout tipico dello spazio di indirizzamento di un processo Linux in x86_64 (user space, 48-bit virtual).
| Direttiva GAS | Dimensione | Suffisso istruzione | Esempio |
|---|---|---|---|
.byte | 1 byte (8 bit) | b | .byte 0xFF, 42, 'A' |
.word | 2 byte (16 bit) | w | .word 0x1234 |
.long | 4 byte (32 bit) | l | .long 1000000 |
.quad | 8 byte (64 bit) | q | .quad 0xDEADCAFE |
.float | 4 byte (IEEE 754) | s (scalar single) | .float 3.14 |
.double | 8 byte (IEEE 754) | d (scalar double) | .double 2.718281828 |
.section .data array: .long 10, 20, 30, 40, 50 # Array di 5 interi a 32 bit array_len: .equ ARRAY_LEN, (array_len - array) / 4 .section .text # Accesso: array[i] = base + i * sizeof(element) leaq array(%rip), %rdi # RDI = &array[0] movl $2, %esi # indice i = 2 movl (%rdi,%rsi,4), %eax # EAX = array[2] = 30
.section .rodata msg: .asciz "Hello, World!\n" # Stringa null-terminated msg_len: .equ MSG_LEN, msg_len - msg - 1 # Stringa con lunghezza calcolata a compile-time (no \0) msg2: .ascii "Ciao" .equ MSG2_LEN, . - msg2 # '.' = posizione corrente
# ─── MOV base ─── movq $0, %rax # Carica immediato movq %rbx, %rax # Copia registro movq (%rbx), %rax # Load da memoria movq %rax, (%rbx) # Store in memoria # ─── Estensione ─── movzbq %al, %rax # Zero-extend byte → quad movzwl %ax, %eax # Zero-extend word → long movsbq %al, %rax # Sign-extend byte → quad movswl %ax, %eax # Sign-extend word → long movslq %eax, %rax # Sign-extend long → quad (cltq) cltq # EAX sign-extend → RAX (alias di movslq %eax,%rax) cqto # RAX sign-extend → RDX:RAX (per idivq) # ─── LEA (Load Effective Address) ─── leaq 8(%rbx,%rcx,4), %rax # RAX = RBX + RCX*4 + 8 (no memory access!) # ─── Scambio ─── xchgq %rax, %rbx # Scambia RAX e RBX (atomico se con memoria) # ─── Conditional move (evita branch) ─── cmpq %rbx, %rax cmovgq %rbx, %rax # RAX = RBX se RAX > RBX (signed) cmoveq %rcx, %rax # RAX = RCX se ZF=1 (equal)
# ─── Somma / Sottrazione ─── addq $10, %rax # RAX += 10 addq %rbx, %rax # RAX += RBX subq %rbx, %rax # RAX -= RBX incq %rax # RAX++ (non modifica CF) decq %rax # RAX-- (non modifica CF) negq %rax # RAX = -RAX (complemento a 2) adcq $0, %rax # RAX += 0 + CF (add with carry) sbbq %rbx, %rax # RAX -= RBX + CF (subtract with borrow) # ─── Moltiplicazione ─── imulq %rbx, %rax # RAX = RAX * RBX (signed, 64-bit result) imulq $12, %rbx, %rax # RAX = RBX * 12 (3 operandi) mulq %rbx # RDX:RAX = RAX * RBX (unsigned, 128-bit result) # ─── Divisione ─── cqto # Sign-extend RAX → RDX:RAX (preparazione per idivq) idivq %rbx # RAX = RDX:RAX / RBX, RDX = RDX:RAX % RBX (signed) xorq %rdx, %rdx # Zero-extend per divisione unsigned divq %rbx # RAX = RDX:RAX / RBX, RDX = remainder (unsigned) # ─── Confronto ─── cmpq %rbx, %rax # Calcola RAX - RBX, setta flag (risultato scartato) testq %rax, %rax # AND logico, setta flag (test se zero)
# ─── Logiche ─── andq $0xFF, %rax # Maschera: mantieni solo byte basso orq $0x20, %rax # Setta bit (es: uppercase → lowercase) xorq %rax, %rax # Azzera registro (modo efficiente) notq %rax # Complemento a 1 (inverte tutti i bit) # ─── Shift ─── shlq $3, %rax # Shift left 3 (= RAX * 8) shrq $1, %rax # Shift right logico (unsigned / 2) sarq $1, %rax # Shift right aritmetico (signed / 2, preserva segno) shlq %cl, %rax # Shift di CL posizioni (solo CL per shift variabile!) # ─── Rotazione ─── rolq $4, %rax # Ruota a sinistra di 4 rorq $4, %rax # Ruota a destra di 4 # ─── Bit test/set ─── btq $5, %rax # Testa bit 5 di RAX → CF btsq $5, %rax # Test e Set bit 5 btrq $5, %rax # Test e Reset bit 5 bsfq %rax, %rbx # Bit Scan Forward: RBX = indice primo bit=1 (da LSB) bsrq %rax, %rbx # Bit Scan Reverse: RBX = indice primo bit=1 (da MSB) bswapq %rax # Byte swap: inverte l'ordine dei byte (endian swap)
Operano su blocchi di memoria. Usano RSI (sorgente), RDI (destinazione), RCX (contatore). Il flag DF controlla la direzione (0=avanti, 1=indietro).
| Istruzione | Operazione | Registri coinvolti |
|---|---|---|
movsb/w/l/q | Copia mem[RSI] → mem[RDI], avanza RSI e RDI | RSI, RDI |
stosb/w/l/q | Scrive AL/AX/EAX/RAX → mem[RDI], avanza RDI | RAX, RDI |
lodsb/w/l/q | Carica mem[RSI] → AL/AX/EAX/RAX, avanza RSI | RAX, RSI |
cmpsb/w/l/q | Confronta mem[RSI] vs mem[RDI], setta flag | RSI, RDI |
scasb/w/l/q | Confronta AL/AX/EAX/RAX vs mem[RDI], setta flag | RAX, RDI |
| Prefisso | Condizione loop | Uso tipico |
|---|---|---|
rep | Ripete RCX volte (decrementa RCX ogni iterazione) | movsb, stosb, lodsb |
repe / repz | Ripete finché RCX > 0 AND ZF=1 | cmpsb (confronto stringhe uguali) |
repne / repnz | Ripete finché RCX > 0 AND ZF=0 | scasb (ricerca carattere) |
# ─── memcpy: copia RCX byte da RSI a RDI ─── cld # Direzione avanti (DF=0) movq $256, %rcx # Numero di byte leaq src(%rip), %rsi leaq dst(%rip), %rdi rep movsb # Copia byte per byte # ─── Ottimizzato: copia a quadword (8 byte alla volta) ─── movq $32, %rcx # 32 * 8 = 256 byte rep movsq # Copia 8 byte alla volta # ─── memset: riempi RCX byte con AL ─── cld movb $0, %al # Valore di riempimento movq $1024, %rcx leaq buffer(%rip), %rdi rep stosb # Azzeramento buffer # ─── strlen: cerca \0 in stringa ─── cld xorb %al, %al # Cerca byte 0 movq $-1, %rcx # Contatore massimo leaq str(%rip), %rdi repne scasb # Scansiona finché trova \0 notq %rcx decq %rcx # RCX = lunghezza stringa # ─── strcmp: confronta due stringhe ─── cld movq $100, %rcx # Max byte da confrontare leaq str1(%rip), %rsi leaq str2(%rip), %rdi repe cmpsb # Confronta finché uguali # ZF=1 se tutte uguali; se diversi, i byte differenti sono a -1(%rsi) e -1(%rdi)
cld # Clear Direction Flag: RSI/RDI incrementano (avanti) std # Set Direction Flag: RSI/RDI decrementano (indietro)
std, ripristina sempre con cld prima di ret o call.| Istruzione | Condizione | Flag | Uso |
|---|---|---|---|
je / jz | Equal / Zero | ZF=1 | a == b |
jne / jnz | Not equal | ZF=0 | a != b |
jg / jnle | Greater (signed) | ZF=0 & SF=OF | a > b |
jge / jnl | Greater or equal | SF=OF | a >= b |
jl / jnge | Less (signed) | SF≠OF | a < b |
jle / jng | Less or equal | ZF=1 | SF≠OF | a <= b |
ja / jnbe | Above (unsigned) | CF=0 & ZF=0 | a > b (unsigned) |
jae / jnb | Above or equal | CF=0 | a >= b (unsigned) |
jb / jnae | Below (unsigned) | CF=1 | a < b (unsigned) |
jbe / jna | Below or equal | CF=1 | ZF=1 | a <= b (unsigned) |
js | Sign (negativo) | SF=1 | risultato < 0 |
jo | Overflow | OF=1 | overflow signed |
jc | Carry | CF=1 | carry/borrow |
Imposta un registro a 8 bit a 1 o 0 in base alla condizione (stesse condizioni dei salti).
cmpq %rbx, %rax sete %al # AL = 1 se RAX == RBX, 0 altrimenti setne %al # AL = 1 se RAX != RBX setg %al # AL = 1 se RAX > RBX (signed) setge %al # AL = 1 se RAX >= RBX (signed) setl %al # AL = 1 se RAX < RBX (signed) setle %al # AL = 1 se RAX <= RBX (signed) seta %al # AL = 1 se RAX > RBX (unsigned) setb %al # AL = 1 se RAX < RBX (unsigned) # Tipico pattern: converti flag in intero a 32 bit cmpq %rsi, %rdi setg %al # AL = (RDI > RSI) ? 1 : 0 movzbl %al, %eax # Zero-extend a 32 bit (azzera anche i 32 bit alti)
# if (RAX > RBX) { ... } else { ... } cmpq %rbx, %rax jle .Lelse # --- blocco if --- jmp .Lendif .Lelse: # --- blocco else --- .Lendif:
# for (int i = 0; i < 10; i++) { ... } xorl %ecx, %ecx # i = 0 .Lloop: cmpl $10, %ecx jge .Lend # --- corpo del loop --- incl %ecx # i++ jmp .Lloop .Lend: # Loop con LOOP (decrementa RCX, salta se RCX != 0) movq $100, %rcx .Lloop2: # --- corpo --- loop .Lloop2 # RCX--, salta se RCX ≠ 0
loop è lenta sulle CPU moderne. Preferire dec/jnz o cmp/jne.# ─── Operazioni base ─── pushq %rax # RSP -= 8; mem[RSP] = RAX popq %rax # RAX = mem[RSP]; RSP += 8 pushq $42 # Push immediato pushfq # Push RFLAGS sullo stack popfq # Pop RFLAGS dallo stack # ─── Chiamata e ritorno ─── call my_func # Push return addr, jump to my_func ret # Pop return addr, jump to it # ─── Prologo / Epilogo funzione ─── my_func: pushq %rbp # Salva vecchio frame pointer movq %rsp, %rbp # Nuovo frame pointer subq $32, %rsp # Alloca 32 byte per variabili locali # --- corpo funzione --- # Variabili locali: -8(%rbp), -16(%rbp), ... # Argomenti (se passati via stack): 16(%rbp), 24(%rbp), ... movq %rbp, %rsp # Dealloca locali popq %rbp # Ripristina vecchio frame pointer ret # ─── Forme compatte: enter / leave ─── enter $32, $0 # Equivale a: push %rbp; mov %rsp,%rbp; sub $32,%rsp leave # Equivale a: mov %rbp,%rsp; pop %rbp # NOTA: enter è lenta sulle CPU moderne, preferire push/mov/sub espliciti # leave è veloce e usata normalmente da GCC # ─── Prologo/Epilogo senza frame pointer (con -fomit-frame-pointer) ─── fast_func: subq $24, %rsp # Alloca (mantieni allineamento a 16!) # --- corpo --- addq $24, %rsp ret
call, RSP è disallineato di 8. Una funzione che chiama altre funzioni deve riallineare RSP a 16 byte prima della prossima call.| Registro | Uso |
|---|---|
%rax | Numero syscall |
%rdi | Argomento 1 |
%rsi | Argomento 2 |
%rdx | Argomento 3 |
%r10 | Argomento 4 |
%r8 | Argomento 5 |
%r9 | Argomento 6 |
%rax | Valore di ritorno (negativo = -errno) |
%rcx, %r11 | Distrutti dalla syscall |
| Registro | Uso |
|---|---|
%eax | Numero syscall |
%ebx | Argomento 1 |
%ecx | Argomento 2 |
%edx | Argomento 3 |
%esi | Argomento 4 |
%edi | Argomento 5 |
%ebp | Argomento 6 |
| Syscall | RAX (x86_64) | EAX (x86) | Firma |
|---|---|---|---|
| read | 0 | 3 | read(fd, buf, count) |
| write | 1 | 4 | write(fd, buf, count) |
| open | 2 | 5 | open(path, flags, mode) |
| close | 3 | 6 | close(fd) |
| mmap | 9 | 90 | mmap(addr, len, prot, flags, fd, off) |
| munmap | 11 | 91 | munmap(addr, len) |
| brk | 12 | 45 | brk(addr) |
| exit | 60 | 1 | exit(status) |
| fork | 57 | 2 | fork() |
| execve | 59 | 11 | execve(path, argv, envp) |
| kill | 62 | 37 | kill(pid, sig) |
| rt_sigaction | 13 | 174 | rt_sigaction(sig, act, oldact, sigsetsize) |
.section .rodata msg: .asciz "Hello, World!\n" .section .text .global _start _start: # write(1, msg, 14) movq $1, %rax # syscall: write movq $1, %rdi # fd: stdout leaq msg(%rip), %rsi # buf: &msg movq $14, %rdx # count: 14 syscall # exit(0) movq $60, %rax # syscall: exit xorq %rdi, %rdi # status: 0 syscall
.section .rodata msg: .asciz "Hello, World!\n" .section .text .global _start _start: # write(1, msg, 14) movl $4, %eax # syscall: write movl $1, %ebx # fd: stdout leal msg, %ecx # buf: &msg movl $14, %edx # count int $0x80 # exit(0) movl $1, %eax # syscall: exit xorl %ebx, %ebx # status: 0 int $0x80
Per installare un signal handler in puro assembly si usa la syscall rt_sigaction (nr. 13 su x86_64).
# struct sigaction (semplificato, 152 byte su x86_64): # offset 0: sa_handler / sa_sigaction (8 byte, puntatore) # offset 8: sa_flags (8 byte) # offset 16: sa_restorer (8 byte, puntatore) # offset 24: sa_mask (128 byte, sigset_t) .equ SA_RESTORER, 0x04000000 .equ SIGINT, 2 .equ SIG_SIZE, 8 # sizeof(sigset_t) per il kernel = 8
.section .data caught_msg: .asciz "Caught SIGINT!\n" .equ CAUGHT_LEN, 15 .section .bss .align 8 sa: .space 152 # struct sigaction .section .text .global _start # ─── Signal handler ─── sigint_handler: # write(1, caught_msg, CAUGHT_LEN) movq $1, %rax movq $1, %rdi leaq caught_msg(%rip), %rsi movq $CAUGHT_LEN, %rdx syscall ret # ─── Restorer (richiesto dal kernel) ─── restorer: movq $15, %rax # rt_sigreturn syscall _start: # Popola struct sigaction leaq sa(%rip), %r12 leaq sigint_handler(%rip), %rax movq %rax, (%r12) # sa_handler movq $SA_RESTORER, 8(%r12) # sa_flags leaq restorer(%rip), %rax movq %rax, 16(%r12) # sa_restorer # rt_sigaction(SIGINT, &sa, NULL, sizeof(sigset_t)) movq $13, %rax # rt_sigaction movq $SIGINT, %rdi movq %r12, %rsi # act xorq %rdx, %rdx # oldact = NULL movq $SIG_SIZE, %r10 syscall # Loop infinito (aspetta segnale) .Lwait: pause # Hint CPU: spin-wait jmp .Lwait
Workflow minimale senza libreria C. Entry point: _start. Nessuna libc, solo syscall dirette.
# Assembla $ as --64 -o program.o program.s # Linka (statico, no libc) $ ld -o program program.o # Oppure in un singolo passo con gcc (no stdlib) $ gcc -nostdlib -no-pie -o program program.s
# Assembla $ as --32 -o program.o program.s # Linka $ ld -m elf_i386 -o program program.o # Con gcc $ gcc -m32 -nostdlib -no-pie -o program program.s
.section .text .global _start _start: # Il tuo codice qui... # IMPORTANTE: termina sempre con exit syscall! # Se ret da _start → SIGSEGV (non c'è return address). movq $60, %rax xorq %rdi, %rdi syscall
_start NON è una funzione: lo stack contiene argc, argv, envp — non un return address. Non usare ret!_start: # All'entry, lo stack contiene: # (%rsp) = argc # 8(%rsp) = argv[0] (path del programma) # 16(%rsp) = argv[1] # ... = argv[n] # NULL # envp[0], envp[1], ..., NULL popq %rdi # RDI = argc movq %rsp, %rsi # RSI = &argv[0]
Linkaggio con la libreria C standard. Entry point: main (la libc fornisce _start).
# Compila e linka con libc $ gcc -o program program.s # 32-bit $ gcc -m32 -o program program.s # Con debug symbols $ gcc -g -o program program.s # Statico (include libc nel binario) $ gcc -static -o program program.s
.section .rodata fmt_out: .asciz "Risultato: %d\n" fmt_in: .asciz "%d" .section .bss num: .space 4 .section .text .global main main: pushq %rbp movq %rsp, %rbp # scanf("%d", &num) leaq fmt_in(%rip), %rdi leaq num(%rip), %rsi xorl %eax, %eax # AL = 0 (nessun arg XMM per variadic) call scanf@PLT # printf("Risultato: %d\n", num * 2) movl num(%rip), %esi addl %esi, %esi # ESI = num * 2 leaq fmt_out(%rip), %rdi xorl %eax, %eax # AL = 0 call printf@PLT # return 0 xorl %eax, %eax popq %rbp ret
AL deve contenere il numero di argomenti passati in registri XMM (0 se nessun float). Usare @PLT per il linkaggio dinamico (PIE).La ABI System V AMD64 è lo standard su Linux, BSD, macOS per x86_64.
| Argomento | Registro |
|---|---|
| 1° | %rdi |
| 2° | %rsi |
| 3° | %rdx |
| 4° | %rcx |
| 5° | %r8 |
| 6° | %r9 |
| 7°+ | Stack (push da destra a sinistra) |
| Argomento | Registro |
|---|---|
| 1°–8° float/double | %xmm0 – %xmm7 |
| 9°+ | Stack |
| Tipo | Registro |
|---|---|
| Intero / puntatore | %rax (e %rdx per valori a 128 bit) |
| Float / double | %xmm0 (e %xmm1) |
Caller-saved (il chiamante deve salvarli se ne ha bisogno dopo la call):
%rax %rcx %rdx %rsi %rdi
%r8 %r9 %r10 %r11
Callee-saved (la funzione chiamata deve ripristinarli):
%rbx %rbp %r12 %r13 %r14 %r15
# Chiama func(a, b, c, d, e, f, g, h) con g e h su stack pushq $8 # h (8° arg, pushato per primo) pushq $7 # g (7° arg) movq $1, %rdi # a movq $2, %rsi # b movq $3, %rdx # c movq $4, %rcx # d movq $5, %r8 # e movq $6, %r9 # f xorl %eax, %eax # AL = 0 (no XMM args) call func addq $16, %rsp # Pulisci stack (2 * 8 byte)
I 128 byte sotto %rsp sono la "red zone": le funzioni foglia (che non chiamano altre funzioni) possono usarli senza modificare RSP. Le funzioni che chiamano altre funzioni NON devono fare affidamento su questa zona.
Registri XMM0–XMM15 (128 bit ciascuno). Usati per float (32-bit), double (64-bit) e operazioni SIMD.
# ─── Single-precision (float, 32 bit) ─── movss fval(%rip), %xmm0 # Load float addss %xmm1, %xmm0 # xmm0 += xmm1 subss %xmm1, %xmm0 # xmm0 -= xmm1 mulss %xmm1, %xmm0 # xmm0 *= xmm1 divss %xmm1, %xmm0 # xmm0 /= xmm1 sqrtss %xmm1, %xmm0 # xmm0 = sqrt(xmm1) # ─── Double-precision (double, 64 bit) ─── movsd dval(%rip), %xmm0 # Load double addsd %xmm1, %xmm0 # Addizione double mulsd %xmm1, %xmm0 # Moltiplicazione double divsd %xmm1, %xmm0 # Divisione double ucomisd %xmm1, %xmm0 # Confronto (setta EFLAGS) # ─── Conversioni ─── cvtsi2sdq %rax, %xmm0 # int64 → double cvtsd2siq %xmm0, %rax # double → int64 (arrotonda secondo MXCSR) cvttsd2siq %xmm0, %rax # double → int64 (tronca verso zero) cvttss2sil %xmm0, %eax # float → int32 (tronca verso zero) cvtss2sd %xmm0, %xmm0 # float → double cvtsd2ss %xmm0, %xmm0 # double → float
# ─── 4 float in parallelo (packed single) ─── movaps (%rdi), %xmm0 # Load 4 float allineati (16 byte) addps %xmm1, %xmm0 # xmm0[0..3] += xmm1[0..3] mulps %xmm1, %xmm0 # 4 moltiplicazioni parallele movaps %xmm0, (%rdi) # Store risultato # ─── 2 double in parallelo (packed double) ─── movapd (%rdi), %xmm0 # Load 2 double allineati addpd %xmm1, %xmm0 # 2 addizioni parallele movapd %xmm0, (%rdi) # Store # ─── Non-allineato (più lento, ma senza vincoli) ─── movups (%rdi), %xmm0 # Load non-allineato (single) movupd (%rdi), %xmm0 # Load non-allineato (double)
movaps/movapd richiedono dati allineati a 16 byte. Accesso non allineato = SIGSEGV. Usare .align 16 prima dei dati..section .data .align 16 arr: .double 1.1, 2.2, 3.3, 4.4 .equ ARR_LEN, 4 .section .text .global main main: pushq %rbp movq %rsp, %rbp xorpd %xmm0, %xmm0 # sum = 0.0 leaq arr(%rip), %rdi movl $ARR_LEN, %ecx .Lsum: addsd (%rdi), %xmm0 # sum += arr[i] addq $8, %rdi # next double decl %ecx jnz .Lsum # xmm0 contiene la somma (11.0) xorl %eax, %eax popq %rbp ret
asm volatile ( "istruzioni assembly" /* template (AT&T syntax) */ : "=constraint"(output) /* output operands */ : "constraint"(input) /* input operands */ : "clobber_list" /* registri sporcati */ );
| Constraint | Significato |
|---|---|
"r" | Qualsiasi registro general purpose |
"a" | %rax / %eax / %ax / %al |
"b" | %rbx / %ebx |
"c" | %rcx / %ecx |
"d" | %rdx / %edx |
"S" | %rsi / %esi |
"D" | %rdi / %edi |
"m" | Operando in memoria |
"i" | Immediato intero |
"x" | Registro SSE (XMM) |
"=r" | Output: registro |
"+r" | Input+Output: stesso registro |
"0" | Matching constraint: usa lo stesso registro dell'operando 0 |
/* Semplice: leggi il TSC (Time Stamp Counter) */ static inline unsigned long rdtsc(void) { unsigned hi, lo; asm volatile ("rdtsc" : "=a"(lo), "=d"(hi)); return ((unsigned long)hi << 32) | lo; } /* Addizione con flag detection */ int add_check_overflow(int a, int b, int *overflow) { int result; asm ( "addl %2, %0\n\t" "seto %b1" : "=r"(result), "=q"(*overflow) : "r"(b), "0"(a) : "cc" ); return result; } /* Syscall wrapper (write) */ long my_write(int fd, const void *buf, unsigned long count) { long ret; asm volatile ( "syscall" : "=a"(ret) : "a"((long)1), "D"(fd), "S"(buf), "d"(count) : "rcx", "r11", "memory" ); return ret; } /* Atomic compare-and-swap */ static inline int cas(int *ptr, int old, int new) { int prev; asm volatile ( "lock cmpxchgl %2, %1" : "=a"(prev), "+m"(*ptr) : "r"(new), "0"(old) : "memory", "cc" ); return prev == old; }
volatile impedisce al compilatore di eliminare o riordinare il blocco asm. Usalo sempre per I/O, syscall e operazioni con side-effect. Il clobber "memory" indica al compilatore che l'asm potrebbe leggere/scrivere memoria arbitraria. Il clobber "cc" indica che i flag sono modificati.# LEA non accede alla memoria: calcola solo l'indirizzo leaq (%rax,%rax,2), %rax # RAX = RAX * 3 leaq (%rax,%rax,4), %rax # RAX = RAX * 5 leaq (,%rax,8), %rax # RAX = RAX * 8 leaq 1(%rax,%rax,2), %rax # RAX = RAX * 3 + 1 leaq (%rdi,%rsi), %rax # RAX = RDI + RSI (somma senza toccare flag) # Moltiplicazione per costanti non-potenza-di-2: # RAX * 7 = RAX * 8 - RAX leaq (,%rax,8), %rdx subq %rax, %rdx # RDX = RAX * 7
xorl %eax, %eax # Modo ottimale: 2 byte, azzera anche i 32 bit alti # Rompe la dipendenza dal valore precedente di RAX # Riconosciuto dalla CPU come idioma di azzeramento # NON usare: movq $0, %rax # 7 byte, più lento, NON rompe la dipendenza
# ─── Loop base (1 iterazione per ciclo) ─── .Lloop: addl (%rdi), %eax addq $4, %rdi decl %ecx jnz .Lloop # ─── Unrolled x4 (4 iterazioni per ciclo) ─── .Lloop4: addl (%rdi), %eax addl 4(%rdi), %eax addl 8(%rdi), %eax addl 12(%rdi), %eax addq $16, %rdi subl $4, %ecx jnz .Lloop4 # (gestire il resto con un loop separato o duff's device)
# Stack: allineare a 16 byte prima di CALL andq $-16, %rsp # Forza allineamento RSP a 16 # Dati: allineare per SIMD .align 16 vec_data: .float 1.0, 2.0, 3.0, 4.0 # Allineamento loop target (riduce penalita' di branch) .p2align 4 # Allinea a 2^4 = 16 byte .Lhot_loop: # ...
# if (a > b) max = a; else max = b; # Versione branch-free: cmpl %esi, %edi # confronta a, b movl %esi, %eax # eax = b (default) cmovgl %edi, %eax # se a > b: eax = a
prefetcht0 64(%rdi) # Prefetch in L1 cache prefetcht1 128(%rdi) # Prefetch in L2 cache prefetchnta 256(%rdi) # Prefetch non-temporal (non inquina cache) pause # Hint per spin-wait loop (riduce consumo CPU) nop # No operation (padding) lfence # Load fence (serializza load) sfence # Store fence (serializza store) mfence # Full memory fence
Il prefisso lock rende atomica un'operazione read-modify-write su memoria. Essenziale per codice multithread. Senza lock, un'altra CPU può intervenire tra la lettura e la scrittura.
# ─── Incremento / Decremento atomico ─── lock incq (%rdi) # atomic: mem[RDI]++ lock decq (%rdi) # atomic: mem[RDI]-- # ─── Add / Sub atomico ─── lock addq $1, (%rdi) # atomic: mem[RDI] += 1 lock subq $1, (%rdi) # atomic: mem[RDI] -= 1 # ─── Operazioni logiche atomiche ─── lock orq $0x01, (%rdi) # atomic: mem[RDI] |= 0x01 (set bit) lock andq $~0x01, (%rdi) # atomic: mem[RDI] &= ~0x01 (clear bit) lock xorq $0xFF, (%rdi) # atomic: mem[RDI] ^= 0xFF (toggle bits)
# lock xaddq %rax, (%rdi) # Atomicamente: temp = mem[RDI]; mem[RDI] += RAX; RAX = temp # Utile per: fetch-and-add (contatori atomici) movq $1, %rax lock xaddq %rax, (%rdi) # RAX = vecchio valore, mem[RDI] += 1
# lock cmpxchgq %rcx, (%rdi) # Atomicamente: # se mem[RDI] == RAX: mem[RDI] = RCX, ZF=1 (successo) # altrimenti: RAX = mem[RDI], ZF=0 (fallimento) # ─── Tipico pattern CAS con retry ─── .Lretry: movq (%rdi), %rax # Leggi valore attuale (expected) leaq 1(%rax), %rcx # Calcola nuovo valore (desired) lock cmpxchgq %rcx, (%rdi) # Prova a scambiare jnz .Lretry # Ritenta se fallito # ─── CMPXCHG16B: CAS su 128 bit (richiede RBX:RCX e RDX:RAX) ─── lock cmpxchg16b (%rdi) # Confronta RDX:RAX con mem[RDI] (128 bit) # Se uguale: mem[RDI] = RCX:RBX # Altrimenti: RDX:RAX = mem[RDI]
# xchg con operando in memoria ha lock implicito (non serve lock prefix) xchgq %rax, (%rdi) # Atomicamente: swap RAX e mem[RDI] # ─── Spinlock con xchg ─── .Lacquire: movq $1, %rax xchgq %rax, (%rdi) # Prova ad acquisire (set lock=1) testq %rax, %rax # Era già 1 (locked)? jnz .Lspin ret # Lock acquisito! .Lspin: pause # Hint CPU: spin-wait cmpq $0, (%rdi) # Test-and-test-and-set (riduce traffico bus) jne .Lspin jmp .Lacquire .Lrelease: movq $0, (%rdi) # Rilascio: store normale (sufficiente su x86) ret
lock btsq $5, (%rdi) # Atomic test-and-set bit 5 → CF = vecchio bit lock btrq $5, (%rdi) # Atomic test-and-reset bit 5 lock btcq $5, (%rdi) # Atomic test-and-complement bit 5
lock aggiunge una full barrier. mfence è necessario solo in casi rari (es. pattern store-load). xchg con operando memoria è sempre atomico, anche senza lock.# switch(n) con jump table (compilato da GCC per switch densi) # Assume: n già in %edi, valori 0..3 .section .rodata .align 8 .Ljumptable: .quad .Lcase0 .quad .Lcase1 .quad .Lcase2 .quad .Lcase3 .section .text cmpl $3, %edi ja .Ldefault # n > 3 → default leaq .Ljumptable(%rip), %rax movslq %edi, %rdi # Estendi indice a 64 bit jmpq *(%rax,%rdi,8) # Salto indiretto: jumptable[n] .Lcase0: # ... codice caso 0 ... jmp .Lend_switch .Lcase1: # ... codice caso 1 ... jmp .Lend_switch .Lcase2: # ... codice caso 2 ... jmp .Lend_switch .Lcase3: # ... codice caso 3 ... jmp .Lend_switch .Ldefault: # ... codice default ... .Lend_switch:
# struct Point { long x; long y; long z; }; # Offset: x=0, y=8, z=16 (sizeof = 24) .equ POINT_X, 0 .equ POINT_Y, 8 .equ POINT_Z, 16 .equ SIZEOF_POINT, 24 # Accesso: RDI = puntatore a struct Point movq POINT_X(%rdi), %rax # rax = p->x movq POINT_Y(%rdi), %rbx # rbx = p->y addq %rbx, %rax # rax = p->x + p->y movq %rax, POINT_Z(%rdi) # p->z = rax # Iterare su un array di struct: # for (int i = 0; i < n; i++) arr[i].z = arr[i].x + arr[i].y; xorl %ecx, %ecx .Lstruct_loop: cmpl %esi, %ecx # i < n ? jge .Lstruct_end movq POINT_X(%rdi), %rax addq POINT_Y(%rdi), %rax movq %rax, POINT_Z(%rdi) addq $SIZEOF_POINT, %rdi # Avanza al prossimo elemento incl %ecx jmp .Lstruct_loop .Lstruct_end:
# Chiamata tramite puntatore a funzione movq func_ptr(%rip), %rax # Carica puntatore a funzione movq $42, %rdi # Primo argomento call *%rax # Chiamata indiretta # Vtable (array di function pointer, es. C++ virtual dispatch) # RDI = puntatore a oggetto, primo campo = puntatore a vtable movq (%rdi), %rax # RAX = vtable ptr call *16(%rax) # Chiama il 3° metodo virtuale (offset 16)
# do { ... } while (--n); # Forma più efficiente di un while: evita un salto iniziale .Ldo: # --- corpo del loop --- decl %ecx jnz .Ldo # Ripete finché ECX != 0
# ─── min(a, b) senza branch ─── # EDI = a, ESI = b cmpl %esi, %edi cmovgl %esi, %edi # EDI = min(a, b) # ─── abs(a) senza branch ─── # EDI = a movl %edi, %eax sarl $31, %eax # EAX = 0 se a ≥ 0, -1 se a < 0 xorl %eax, %edi # Complementa se negativo subl %eax, %edi # +1 se era negativo → EDI = abs(a) # ─── sign(a): -1, 0, +1 senza branch ─── movl %edi, %eax sarl $31, %eax # EAX = -1 se negativo, 0 altrimenti testl %edi, %edi movl $1, %edx cmovgl %edx, %eax # EAX = 1 se positivo # ─── clamp(x, lo, hi) ─── # EDI = x, ESI = lo, EDX = hi cmpl %esi, %edi cmovll %esi, %edi # x = max(x, lo) cmpl %edx, %edi cmovgl %edx, %edi # x = min(x, hi)
# ─── Test se x è potenza di 2 ─── # x & (x - 1) == 0 && x != 0 leaq -1(%rdi), %rax # RAX = x - 1 testq %rdi, %rax # x & (x - 1) setz %al # AL = 1 se potenza di 2 (o zero) # ─── Allinea x al prossimo multiplo di align (potenza di 2) ─── # aligned = (x + align - 1) & ~(align - 1) leaq 15(%rdi), %rax # RAX = x + 15 (align=16) andq $-16, %rax # RAX = aligned to 16 # ─── popcount (conta bit a 1) ─── (richiede POPCNT, SSE4.2) popcntq %rdi, %rax # RAX = numero di bit a 1 in RDI # ─── lzcnt / tzcnt (leading/trailing zero count) ─── (ABM/BMI1) lzcntq %rdi, %rax # RAX = zeri iniziali (da MSB) tzcntq %rdi, %rax # RAX = zeri finali (da LSB)
GDB è lo strumento essenziale per il debugging di programmi assembly su Linux. Compilare sempre con -g per i debug symbols.
# Compila con debug symbols $ as -g --64 -o prog.o prog.s && ld -o prog prog.o $ gcc -g -no-pie -o prog prog.s # Avvia GDB $ gdb ./prog $ gdb -tui ./prog # Con interfaccia TUI (mostra sorgente) # Comandi base (gdb) run # Esegui il programma (gdb) run arg1 arg2 # Esegui con argomenti (gdb) quit # Esci (gdb) start # Esegui e fermati a main/_start
(gdb) break _start # Breakpoint su label (gdb) break *0x401000 # Breakpoint su indirizzo (gdb) break *_start+20 # Offset da label (gdb) info break # Lista breakpoint (gdb) delete 1 # Rimuovi breakpoint #1 (gdb) disable 2 # Disabilita breakpoint #2 # Watchpoint: ferma quando un valore cambia (gdb) watch *0x601000 # Watch su indirizzo di memoria (gdb) watch $rax # Watch su registro (gdb) rwatch *0x601000 # Read watchpoint (ferma alla lettura)
(gdb) si # Step Instruction: esegui 1 istruzione (entra nelle call) (gdb) ni # Next Instruction: esegui 1 istruzione (scavalca le call) (gdb) si 5 # Esegui 5 istruzioni (gdb) continue # Continua fino al prossimo breakpoint (gdb) finish # Esegui fino al ret della funzione corrente (gdb) until *_start+40 # Esegui fino all'indirizzo specificato
(gdb) info registers # Mostra tutti i registri general purpose (gdb) info all-registers # Mostra TUTTI i registri (inclusi XMM, flag, segmento) (gdb) print $rax # Stampa valore di RAX (decimale) (gdb) print/x $rax # Stampa in esadecimale (gdb) print/t $rax # Stampa in binario (gdb) print/d $eax # Stampa come signed decimal (gdb) print/u $eax # Stampa come unsigned decimal (gdb) set $rax = 42 # Modifica un registro (gdb) print $eflags # Mostra flag: [ CF ZF SF OF ... ]
# Formato: x/NFU indirizzo # N = numero di unità # F = formato: x(hex) d(dec) u(unsigned) t(binary) s(string) i(instruction) c(char) # U = unità: b(byte) h(halfword/2B) w(word/4B) g(giant/8B) (gdb) x/10xb $rsp # 10 byte in hex dallo stack (gdb) x/4xg $rsp # 4 quadword (64-bit) in hex dallo stack (gdb) x/s 0x402000 # Stringa all'indirizzo (gdb) x/10i $rip # Disassembla 10 istruzioni da RIP (gdb) x/10i _start # Disassembla da label (gdb) x/20xb &msg # 20 byte dalla label msg # Modifica memoria (gdb) set *(long*)0x601000 = 42 # Scrivi valore in memoria
(gdb) disassemble _start # Disassembla funzione/label (gdb) disassemble /r _start # Con byte della macchina (raw hex) (gdb) set disassembly-flavor att # Sintassi AT&T (default) (gdb) set disassembly-flavor intel # Sintassi Intel # TUI (Text User Interface) (gdb) layout asm # Mostra assembly nel pannello superiore (gdb) layout regs # Mostra registri + assembly (gdb) layout split # Mostra sorgente + assembly (gdb) tui reg float # Mostra registri float/XMM (gdb) focus asm # Sposta il focus sul pannello asm
(gdb) backtrace # Mostra call stack (bt) (gdb) info frame # Info sullo stack frame corrente (gdb) info stack # Alias per backtrace (gdb) x/8xg $rsp # Ispeziona le prime 8 quadword sullo stack
# objdump: disassembla un binario $ objdump -d prog # Disassembla sezioni eseguibili $ objdump -d -M intel prog # In sintassi Intel $ objdump -d -M intel -S prog # Intermezza con sorgente C (-g richiesto) # readelf: ispeziona struttura ELF $ readelf -h prog # Header ELF $ readelf -S prog # Tabella delle sezioni $ readelf -s prog # Tabella dei simboli $ readelf -l prog # Program headers (segmenti) # strace: traccia syscall in tempo reale $ strace ./prog # Mostra ogni syscall $ strace -e trace=write ./prog # Filtra solo write # nm: lista simboli $ nm prog # Mostra simboli e indirizzi
gdb -tui con layout regs offre la migliore esperienza di debugging per assembly. I registri si evidenziano in tempo reale quando cambiano valore. Usare si (step instruction) per avanzare istruzione per istruzione.