X86 assembly language: Wikis

  

Note: Many of our articles have direct quotes from sources you can cite, within the Wikipedia article! This article doesn't yet, but we're working on it! See more info or our list of citable articles.

Encyclopedia

(Redirected to x86 assembly language article)

From Wikipedia, the free encyclopedia

x86 assembly language is the family of backwards-compatible assembly languages for the x86 class of processors, which includes Intel's Pentium series and AMD's Athlon series. Like all assembly languages, it uses short mnemonics to represent the fundamental operations that the CPU in a computer can perform. Compilers often produce assembly code as an intermediate step when translating a high level program into machine code. Regarded as a programming language, assembly coding is machine specific and low level, indeed the lowest level possible, directly addressing hardware, also known as bare metal. Assembly languages are therefore mainly used for detailed or time critical applications such as bootloaders, operating system kernels, and device drivers, as well as for real time or small embedded systems, although it is perfectly possible to write application software in an appropriate dialect (determined by choice of assembler) of assembly language with an assembler which compiles for the relevant target architecture.

Contents

History

The Intel 8088 and 8086 CPUs were 16-bit CPUs that first had an instruction set that is now commonly referred to as x86, they were an evolution of the previous generation of 8-bit CPUs such as the 8080, inheriting many characteristics and instructions, extended for the 16-bit era. The 8088 and 8086 both used a 20-bit address bus and 16-bit internal registers but whilst the 8086 had a 16-bit data bus, the 8088, intended as a low cost option for embedded applications, had an 8-bit data bus. The x86 assembly language covers the many different versions of CPUs that followed, from Intel; the 80188, 80186, 80286, 80386, 80486, Pentium as well as non-Intel CPUs from AMD and Cyrix such as the 5x86 and K6 processors. The term x86 applies to any CPU which can run the original assembly language (usually it will run at least some of the extensions too).

The modern x86 instruction set is a superset of 8086 instructions and a series of extensions to this instruction set that began with the Intel 8008 microprocessor, nearly full binary backward compatibility exists between the Intel 8086 chip through to the more modern Pentium 4, Intel Core, Core i7, AMD Athlon 64, Opteron, etc. processors, although certain exceptions do exist. In practice it is typical to use instructions which will execute on anything later than an Intel 80386 (or fully compatible clone) processor or else anything later than an Intel Pentium (or compatible clone) processor but in recent years various operating systems and application software have begun to require more modern processors or at least support for later specific extensions to the instruction set (e.g. MMX, 3DNow!, SSE/SSE2/SSE3).

Mnemonics and opcodes

Each x86 assembly instruction is represented by a mnemonic which, often combined with one or more operands, translates to one or more bytes called an opcode; the NOP instruction translates to 0x90, for instance and the HLT instruction translates to 0xF4. There are potential opcodes with no documented mnemonic which different processors may interpret differently, making a program using them behave inconsistently or even generate an exception on some processors. These opcodes often turn up in code writing competitions as a way to make the code smaller, faster, more elegant or just show off the author's prowess.

Syntax

x86 assembly language has two main syntax branches: Intel syntax, originally used for documentation of the x86 platform, and AT&T syntax.[1] Intel syntax is dominant in the Windows world. In the Unix/Linux world, both are used because GCC only supported AT&T-syntax in former times. Here is a summary of the main differences between Intel syntax and AT&T syntax:

Attribute AT&T Intel
Parameter order source comes before the destination destination before source (follows that of many program statements ("a=5" is "mov eax, 5")
Parameter Size mnemonics are suffixed with a letter indicating the size of the operands (e.g. "q" for qword, "l" for dword, "w" for word, and "b" for byte)[1] derived from the name of the register that is used (e.g. eax, ax, al)
Immediate value sigils prefixed with a "$", and registers must be prefixed with a "%"[1] The assembler automatically detects the type of symbols - if they are registers, constants or something else.
Effective addresses general syntax DISP(BASE,INDEX,SCALE)

Example: movl mem_location(%ebx,%ecx,4), %eax

use variables, and need to be in square brackets; additionally, size keywords like 'byte', 'word' or 'dword' have to be used.[1]

Example: mov eax, dword [ebx + ecx*4 + mem_location]

Most x86 assemblers use Intel syntax including MASM, TASM, NASM, FASM and YASM. GAS has supported both syntaxes since version 2.10 via the .intel_syntax directive.[1][2][3]

Registers

x86 processors have a collection of registers available to be used as stores for binary data. Collectively the data and address registers are called the general registers. Each register has a special purpose in addition to what they can all do.

  • AX multiply/divide
  • BX index register for MOVE
  • CX count register for string operations
  • DX port address for IN and OUT

Along with the general registers there are additionally the:

  • IP instruction pointer
  • FLAGS
  • segment registers (CS, DS, ES, FS, GS, SS) which enable where a 64k segment starts
  • extra extension registers (MMX, 3DNow!, SSE, etc).

The IP register points to where in the program the processor is currently executing its code. The IP register cannot be accessed by the programmer directly.

The x86 registers can be used by using the MOV instructions. For example:

   mov ax, 1234h
   mov bx, ax

copies the value 1234h (4660d) into register ax and then copies the value of the ax register into the bx register. (Intel syntax)

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 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 allowed breaking the 64k barrier through clever choice of addresses, addressing up to 1 MiB of memory while using 16-bit rather than 32-bit registers, though it made programming considerably more complex.

In real mode only, for example, if DS contains the hexadecimal number 0xDEAD and DX contains the number 0xCAFE they would together point to the memory address 0xDEAD * 0x10 + 0xCAFE = 0xEB5CE. In real mode the CPU can address up to 1,048,576 bytes (1 MiB). This applies to 20-bit address space. By combining segment and offset values we find a 20-bit address.

The original IBM PC restricted programs to 640 KiB 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.[4] The 80286 was also still limited to addressing memory in 16-bit segments, meaning only 216 bytes (64 kibibytes) 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.
  • 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.

The Intel 80386 featured three operating modes: real mode, protected mode and virtual mode. The protected mode which debuted in the 80286 was extended to allow the 80386 to address up to 4 GB of memory, the all new virtual 8086 mode (VM86) made it possible to run one or more real mode programs in a protected environment which largely emulated real mode, though some programs were not compatible (typically as a result of memory addressing tricks or using unspecified op-codes).

The 32-bit flat memory model of the 80386's extended protected mode may be the most important feature change for the x86 processor family until AMD released x86-64 in 2003, as it helped drive large scale adoption of Windows 3.1 (which relied on protected mode) since Windows could now run many applications at once, including DOS applications, by using virtual memory and simple multitasking.

Execution modes

The x86 processors support four modes of operation for x86 code, Real Mode, Protected Mode, Virtual 86 Mode and System Management Mode, in which some instructions are available and others are not. A 16-bit subset of instructions are available in real mode (all x86 processors), 16-bit protected mode (80286 onwards), V86 mode (80386 and later) and SMM (Some Intel i386SL, i486 and later). In 32-bit protected mode (Intel 80386 onwards), 32-bit instructions (including later extensions) are also available. The instruction set is similar in each mode but memory addressing and word size vary, requiring different programming strategies.

The modes in which x86 code can be executed in are:

Switching modes

The processor enters real mode immediately after power on, so an operating system kernel, or other program, must explicitly switch to another mode if it wishes to run in anything but real mode. Switching modes is accomplished by modifying certain bits of the processor's control registers although some preparation is required beforehand, in many cases and some post switch cleanup may be required.

Instruction types

In general, the features of the modern x86 instruction set are:

  • A compact encoding
    • Variable length and alignment independent (encoded as little endian, as is all data in the x86 architecture)
    • Mainly one-address and two-address instructions, that is to say, the first operand is also the destination.
    • Memory operands as both source and destination are supported (frequently used to read/write stack elements addressed using small immediate offsets).
    • Both general and implicit register usage; although all seven (counting ebp) general registers can be freely used as accumulators or for addressing, most of them are also implicitly used by certain (more or less) special instructions; affected registers must therefore be temporarily preserved (normally stacked), if active during such instruction sequences.
  • Produces conditional flags implicitly through most integer ALU instructions.
  • Supports various addressing modes including immediate, offset, and scaled index but not PC-relative, except jumps (introduced as an improvement in the x86-64 architecture).
  • Includes floating point to a stack of registers.
  • Contains special support for atomic instructions (XCHG, CMPXCHG(8B), XADD, and integer instructions which combine with the LOCK prefix)
  • SIMD instructions (instructions which perform parallel simultaneous single instructions on many operands encoded in adjacent cells of wider registers).

Stack instructions

The x86 architecture has hardware support for an execution stack mechanism. Instructions such as push, pop, call and ret are used with the properly set up stack to pass parameters, to allocate space for local data, and to save and restore call-return points. The ret size instruction is very useful for implementing space efficient (and fast) calling conventions where the callee is responsible for reclaiming stack space occupied by parameters.

When setting up a stack frame to hold local data of a recursive procedure there are several choices; the high level enter instruction takes a procedure-nesting-depth argument as well as a local size argument, and may be faster than more explicit manipulation of the registers (such as push bp, mov bp,sp, sub sp,size) but it is generally not used. Whether it is faster depends on the particular x86 implementation (i.e. processor) as well as the calling convention and code intended to run on multiple processors will usually run faster on most targets without it.

The full range of addressing modes (including immediate and base+offset) even for instructions such as push and pop, makes direct usage of the stack for integer, floating point and address data simple, as well as keeping the ABI specifications and mechanisms relatively simple compared to some RISC architectures (require more explicit call stack details).

Integer ALU instructions

x86 assembly has the standard mathematical operations, add, sub, mul, with idiv; the logical operators and, or, xor, neg; bitshift arithmetic and logical, sal/sar, shl/shr; rotate with and without carry, rcl/rcr, rol/ror, a complement of BCD arithmetic instructions, aaa, aad, daa and others.

Floating point instructions

x86 assembly language includes instructions for a stack-based floating point unit. They include addition, subtraction, negation, multiplication, division, remainder, square roots, integer truncation, fraction truncation, and scale by power of two. The operations also include conversion instructions which can load or store a value from memory in any of the following formats: Binary coded decimal, 32-bit integer, 64-bit integer, 32-bit floating point, 64-bit floating point or 80-bit floating point (upon loading, the value is converted to the currently used floating point mode). x86 also includes a number of transcendental functions including sine, cosine, tangent, arctangent, exponentiation with the base 2 and logarithms to bases 2, 10, or e.

The stack register to stack register format of the instructions is usually F(OP) st, st(*) or F(OP) st(*), st. Where st is equivalent to st(0), and st(*) is one of the 8 stack registers (st(0), st(1), ..., st(7)) Like the integers, the first operand is both the first source operand and the destination operand. FSUBR and FDIVR should be singled out as first swapping the source operands before performing the subtraction or division. The addition, subtraction, multiplication, division, store and comparison instructions include instruction modes that will pop the top of the stack after their operation is complete. So for example FADDP st(1), st performs the calculation st(1) = st(1) + st(0), then removes st(0) from the top of stack, thus making what was the result in st(1) the top of the stack in st(0).

SIMD instructions

Modern x86 CPUs contain SIMD instructions, which largely perform the same operation in parallel on many values encoded in a wide SIMD register. Various instruction technologies support different operations on different register sets, but taken as complete whole (from MMX to SSE4.2) they include general computations on integer or floating point arithmetic (addition, subtraction, multiplication, shift, minimization, maximization, comparison, division or square root). So for example, PADDW MM0, MM1 performs 4 parallel 16-bit (indicated by the W) integer adds (indicated by the PADD) of mm0 values to mm1 and stores the result in mm0. SSE also includes a floating point mode in which only the very first value of the registers is actually modified (expanded in SSE2). Some other unusual instructions have been added including a sum of absolute differences (used for motion estimation in video compression, such as is done in MPEG) and a 16-bit multiply accumulation instruction (useful for software-based alpha-blending and digital filtering). SSE (since SSE3) and 3DNow! extensions include addition and subtraction instructions for treating paired floating point values like complex numbers.

These instruction sets also include numerous fixed sub-word instructions for shuffling, inserting and extracting the values around within the registers. In addition there are instructions for moving data between the integer registers and XMM (used in SSE)/FPU (used in MMX) registers.

Data manipulation instructions

The x86 processor also includes complex addressing modes for addressing memory with an immediate offset, a register, a register with an offset, a scaled register with or without an offset, and a register with an optional offset and another scaled register. So for example, one can encode mov eax, [Table + ebx + esi*4] as a single instruction which loads 32 bits of data from the address computed as (Table + ebx + esi * 4) offset from the DS selector, and stores it to the eax register. In general the x86 processor can load and use memory matched to the size of any register it is operating on. (The SIMD instructions also include half-load instructions.)

The x86 instruction set includes string load, store and move instructions (LODS, STOS, and MOVS) which perform each operation to a specified size (B for 8-bit byte, W for 16-bit word, D for 32-bit double word) then increments/decrements (depending on DF, direction flag) the implicit address register (SI for LODS, DI for STOS, and both for MOVS). For the load and store, the implicit target/source register is in the AL, AX or EAX register (depending on size). The implicit segment used is DS for LODS, ES for STOS and both for MOVS.

The stack is implemented with an implicitly decrementing (push) and incrementing (pop) stack pointer. In 16-bit mode, this implicit stack pointer is addressed as SS:[SP], in 32-bit mode it is SS:[ESP], and in 64-bit mode it is [RSP]. The stack pointer actually points to the last value that was stored, under the assumption that its size will match the operating mode of the processor (i.e., 16, 32, or 64 bits) to match the default width of the PUSH/POP/CALL/RET instructions. Also included are the instructions ENTER and LEAVE which reserve and remove data from the top of the stack while setting up a stack frame pointer in BP/EBP/RBP. However, direct setting, or addition and subtraction to the SP/ESP/RSP register is also supported, so the ENTER/LEAVE instructions are generally unnecessary. Other instructions for manipulating the stack include PUSHF/POPF for storing and retrieving the (E)FLAGS register. The PUSHA/POPA instructions will store and retrieve the entire integer register state to and from the stack.

Values for a SIMD load or store are assumed to be packed in adjacent positions for the SIMD register and will align them in sequential little-endian order. Some SSE load and store instructions require 16-byte alignment to function properly. The SIMD instruction sets also include "prefetch" instructions which perform the load but do not target any register, used for cache loading. The SSE instruction sets also include non-temporal store instructions which will perform stores straight to memory without performing a cache allocate if the destination is not already cached (otherwise it will behave like a regular store.)

Most generic integer and floating point (but no SIMD) instructions can use one parameter as a complex address as the second source parameter. Integer instructions can also accept one memory parameter as a destination operand.

Program flow

The x86 assembly has an unconditional jump operation, jmp, which can take an immediate address, a register or an indirect address as a parameter (note that most RISC processors only support a link register or short immediate displacement for jumping).

Also supported are several conditional jumps, including je (jump on equality), jne (jump on inequality), jg (jump on greater than, signed), jl (jump on less than, signed), ja (jump on above/greater than, unsigned), jb (jump on below/less than, unsigned). These conditional operations are based on the state of specific bits in the (E)FLAGS register. Many arithmetic and logic operations set, clear or complement these flags depending on their result. The comparison cmp (compare) and test instructions set the flags as if they had performed a subtraction or a bitwise AND operation, respectively, without altering the values of the operands. There are also instructions such as clc (clear carry flag) and cmc (complement carry flag) which work on the flags directly. Floating point comparisons are performed via FCOM or FICOM instructions which eventually have to be converted to integer flags.

Each jump operation has three different forms, depending on the size of the operand. A short jump uses an 8-bit signed operand, which is a relative offset from the current instruction. A near jump is similar to a short jump but uses a 16-bit signed operand (in real or protected mode) or a 32-bit signed operand (in 32-bit protected mode only). A far jump is one that uses the full segment base:offset value as an absolute address. There are also indirect and indexed forms of each of these.

In addition to the simple jump operations, there are the call (call a subroutine) and ret (return from subroutine) instructions. Before transferring control to the subroutine, call pushes the segment offset address of the instruction following the call onto the stack; ret pops this value off the stack, and jumps to it, effectively returning the flow of control to that part of the program. In the case of a far call, the segment base is pushed following the offset; far ret pops the offset and then the segment base to return.

There are also two similar instructions, int (interrupt), which saves the current (E)FLAGS register value on the stack, then performs a far call, except that instead of an address, it uses an interrupt vector, an index into a table of interrupt handler addresses. Typically, the interrupt handler saves all other CPU registers it uses, unless they are used to return the result of an operation to the calling program (in software called interrupts). The matching return from interrupt instruction is iret, which restores the flags after returning. Soft Interrupts of the type described above are used by some operating systems for system calls, and can also be used in debugging hard interrupt handlers. Hard interrupts are triggered by external hardware events, and must preserve all register values as the state of the currently executing program is unknown. In Protected Mode, interrupts may be set up by the OS to trigger a task switch, which will automatically save all registers of the active task.

Examples

"Hello world!" program for DOS in MASM style assembler

    .MODEL Small
    .STACK 100h
    .DATA
   msg db 'Hello, world!$'
    .CODE
  start:
  mov ah, 09h
  lea dx, msg
  int 21h
  mov ax,4C00h
  int 21h
  end start

Using the flags register

Flags are heavily used in the x86 architecture, for comparisons. A comparison is made between two data and the CPU set the relevant flag or flags, then conditional jump instructions can be used to check for expected flags and branch to code that should run in that case, e.g.:

    cmp eax, ebx
    jne do_something

Flags are also used in the x86 architecture to turn on and off certain features or execution modes. For example, to disable the processing of interrupts you can use the command:

    cli

The flags register can also be directly accessed. The low 8 bits of the flag register can be loaded into AH using the LAHF instruction. The entire flags register can also be moved on and off the stack using the instructions PUSHF, POPF, INT (including INTO) and IRET.

Using the instruction pointer register

The instruction pointer is ip or in 32-bit mode, eip. The eip register points to the memory address which the processor will next attempt to execute; it cannot be directly accessed but a sequence like the following can be written to put the address of next_line into (e)ax:

    call next_line
next_line:
    pop eax

This works even in position-independent code because call takes an (e)ip-relative immediate operand. Writing to (e)ip is simple:

    jmp eax

See also

References

External links

Manuals








Got something to say? Make a comment.
Your name
Your email address
Message