x86 assembly language is a family of low-level programming languages that are used to produce object code for the x86 class of processors. Previous evolutions of this family of languages provide backward compatibility with CPUs dating back to the Intel 8008 microprocessor, introduced in April 1972. As assembly languages, they are closely tied to the architecture's machine code instructions, allowing for precise control over hardware.
In x86 assembly languages, mnemonics are used to represent fundamental CPU instructions, making the code more human-readable compared to raw machine code. Each machine code instruction is an opcode which, in assembly, is replaced with a mnemonic. Each mnemonic corresponds to a basic operation performed by the processor, such as arithmetic calculations, data movement, or control flow decisions. Assembly languages are most commonly used in applications where performance and efficiency are critical. This includes real-time embedded systems, operating-system kernels, and device drivers, all of which may require direct manipulation of hardware resources.
Additionally, compilers for high-level programming languages sometimes generate assembly code as an intermediate step during the compilation process. This allows for optimization at the assembly level before producing the final machine code that the processor executes.
Mnemonics and opcodes
Each instruction in the x86 assembly language is represented by a mnemonic which often combines with one or more operands to translate into one or more bytes known as an opcode. For example, the NOP instruction translates to the opcode 0x90, and the HLT instruction translates to 0xF4. There are potential opcodes without documented mnemonics, which different processors may interpret differently. Using such opcodes can cause a program to behave inconsistently or even generate exceptions on some processors.
Syntax x86 assembly language has two primary syntax branches: Intel syntax and AT&T syntax. Intel syntax is dominant in the DOS and Windows environments, while AT&T syntax is dominant in Unix-like systems, as Unix was originally developed at AT&T Bell Labs. Below is a summary of the main differences between Intel syntax and AT&T syntax:
AT&T Intel Parameter order movl $5, %eax Source before the destination. mov eax, 5 Destination before source. Parameter size addl $0x24, %esp movslq %ecx, %rax paddd %xmm1, %xmm2 Mnemonics are suffixed with a letter indicating the size of the operands: q for qword (64 bits), l for long (dword, 32 bits), w for word (16 bits), and b for byte (8 bits). add esp, 24h movsxd rax, ecx paddd xmm2, xmm1 Derived from the name of the register that is used (e.g. rax, eax, ax, al imply q, l, w, b, respectively). If the source or destination is not a register, the parameter size cannot be inferred and must be declared explicitly.
sub byte ptr [esi], 20h
Width-based names may still appear in instructions when they define a different operation. MOVSXD refers to sign extension with dword input, unlike MOVSX. SIMD registers have width-named instructions that determine how to split up the register. AT&T tends to keep the names unchanged, so PADDD is not renamed to "paddl". Sigils Immediate values prefixed with a "$", registers prefixed with a "%". The assembler automatically detects the type of symbols; i.e., whether they are registers, constants or something else. Effective addresses movl offset(%ebx, %ecx, 4), %eax General syntax of displacement(base, index, scale). mov eax, [ebx + ecx*4 + offset] Arithmetic expressions in square brackets; additionally, size keywords like byte, word, or dword have to be used if the size cannot be determined from the operands.
Many x86 assemblers use Intel syntax, including FASM, MASM, NASM, TASM, and YASM. The GNU Assembler, which originally used AT&T syntax, has supported both syntaxes since version 2.10 via the .intel_syntax directive. A quirk in the AT&T syntax for x86 is that x87 floating-point operands are reversed, an inherited bug from the original AT&T assembler.
The AT&T syntax is nearly universal across other architectures (retaining the same operand order for the instruction); it was originally designed for PDP-11 assembly and was inherited onto Unix-like systems. In contrast, the Intel syntax is specific to the x86 architecture and is the one used in the x86 platform's official documentation. The Intel 8080, which predates the x86 architecture, also uses the "destination-first" order for instruction.
Reserved words In most x86 assembly languages, the reserved words consist of two parts: mnemonics that translate to opcodes, and directives (or "pseudo-ops") that access features in the assembler program beyond the simple translation of opcodes. For a list of the former part, see x86 instruction listings. The latter part is highly assembler-dependent, with no such thing as a standard among Intel-syntax assemblers. AT&T-syntax assemblers share a common way of naming directives (all directives starts with a dot, like .ascii), and a number of basic directives such as .ascii and .string are broadly supported.
Registers
x86 processors feature a set of registers that serve as storage for binary data and addresses during program execution. These registers are categorized into general-purpose registers, segment registers, the instruction pointer, the FLAGS register, and various extension registers introduced in later processor models. Each register has specific functions in addition to their general capabilities:
General-purpose registers These registers have conventional roles, but usage is not strictly enforced. Programs are generally free to use them for other purposes. AX (Accumulator register): Primarily used in arithmetic, logic, and data transfer operations. It is favored by instructions that perform multiplication and division, and by string load and store operations. Immediate ALU operations and exchanges with AX can be encoded more compactly. BX (Base register): Base pointer for memory access. It can hold the base address of data structures and is useful in indexed addressing modes. It is used with . CX (Count register): Serves as a counter in loop, string, and shift/rotate instructions. Iterative operations often use CX to determine the number of times a loop or operation should execute. DX (Data register): Used in conjunction with AX for multiplication and division operations that produce results larger than 16 bits. It also holds I/O port addresses for and instructions. SP (Stack pointer): Points to the top of stack in memory. It is automatically updated during and operations. BP (Base Pointer): Points to the top of the call stack. It is primarily used to access function parameters and local variables within the call stack. SI (Source Index): Used as a pointer to the source in string and memory array operations. Instructions like (move string) use SI to read data from memory. Like BX, it can be used for indexing. It can be added to BP or BX for double indexing. DI (Destination Index): Serves as a pointer to the destination in string and memory array operations. It works alongside SI in instructions that copy or compare data, writing results to memory. Like BX, it can be used for indexing. It can be added to BP or BX for double indexing. Along with the general registers there are additionally the: Instruction Pointer (IP): Holds the offset address of the next instruction to be executed within the code segment (CS). It points to the first byte of the next instruction. While the IP register cannot be read directly by programmers, its value changes through control flow instructions such as jumps, calls, and interrupts, which alter the flow of execution. FLAGS register: Contains a set of status, control, and system flags that reflect the outcome of operations and control the processor's operations. Segment registers (CS, DS, ES, SS): Determines where a 64k segment starts (FS and GS in were added to 80386 and later) Extra extension registers (MMX, 3DNow!, SSE, etc.) (Pentium & later only).
The x86 registers can be used by most instructions. For example, in Intel syntax: mov ax, 1234h ; copies the value 1234hex (4660d) into register AX mov bx, ax ; copies the value of the AX register into the BX register
Segmented addressing
The x86 architecture in real and virtual 8086 mode uses a process known as segmentation to address memory, not the flat memory model used in many other environments. Segmentation involves composing a memory address from two parts, a segment and an offset; the segment points to the beginning of a 64 KiB (64×210) group of addresses and the offset determines how far from this beginning address the desired address is. In segmented addressing, two registers are required for a complete memory address. One to hold the segment, the other to hold the offset. In order to translate back into a flat address, the segment value is shifted four bits left (equivalent to multiplication by 24 or 16) then added to the offset to form the full address, which allows breaking the 64k barrier through clever choice of addresses, though it makes programming considerably more complex.
In real mode/protected only, for example, if DS contains the hexadecimal number and DX contains the number they would together point to the memory address . Therefore, the CPU can address up to 1,048,576 bytes (1 MiB) in real mode. By combining segment and offset values we find a 20-bit address.
The original IBM PC restricted programs to 640 KB but an expanded memory specification was used to implement a bank switching scheme that fell out of use when later operating systems, such as Windows, used the larger address ranges of newer processors and implemented their own virtual memory schemes.
Protected mode, starting with the Intel 80286, was utilized by OS/2. Several shortcomings, such as the inability to access the BIOS and the inability to switch back to real mode without resetting the processor, prevented widespread usage. The 80286 was also still limited to addressing memory in 16-bit segments, meaning only 216 bytes (64 kilobytes) could be accessed at a time. To access the extended functionality of the 80286, the operating system would set the processor into protected mode, enabling 24-bit addressing and thus 224 bytes of memory (16 megabytes).
In protected mode, the segment selector can be broken down into three parts: a 13-bit index, a Table Indicator bit that determines whether the entry is in the GDT or LDT and a 2-bit Requested Privilege Level; see x86 memory segmentation.
When referring to an address with a segment and an offset the notation of segment:offset is used, so in the above example the flat address 0xEB5CE can be written as 0xDEAD:0xCAFE or as a segment and offset register pair; DS:DX.
There are some special combinations of segment registers and general registers that point to important addresses: CS:IP (CS is Code Segment, IP is Instruction Pointer) points to the address where the processor will fetch the next byte of code. SS:SP (SS is Stack Segment, SP is Stack Pointer) points to the address of the top of the stack, i.e. the most recently pushed byte. SS:BP (SS is Stack Segment, BP is Stack Frame Pointer) points to the address of the top of the stack frame, i.e. the base of the data area in the call stack for the currently active subprogram. DS:SI (DS is Data Segment, SI is Source Index) is often used to point to string data that is about to be copied to ES:DI. ES:DI (ES is Extra Segment, DI is Destination Index) is typically used to point to the destination for a string copy, as mentioned above.