Sie sind auf Seite 1von 6

Introduction to the x86-64

Duane A. Bailey (Feb. 15, 2017)


In 1971 Intel developed the 4004 chip, the first processor to be fabricated as a single monolithic chip. That
device, along with several memory and coprocessor chips, was the basis for a simple calculator. It had the capability
of manipulating 4-bit values, so we colloquially refer to this as a 4-bit processor. The 4004 was followed by the
8008 (1972, an 8-bit processor), the 8080 (1974, 8-bits), and finally the 8086 processor (1978, 16-bits). Each of these
shared a core of architectural features we see in the Intel processors today. The 8086, also called iAPX86, is the first
processor in the x86 series including the 286, 386, 486, and several variants of the Pentium architecture.
Since the earliest years, Advanced Micro Devices (AMD) developed a competing line of products and, ultimately,
developed the standard for the architecture we now know as the x86-64, an instruction set architecture capable
of manipulating 64 bits at a time. Today, both AMD and Intel boast several lines of x86-64 architectures. This
document describes various features of the x86-64 architecture that are particularly important.

Register Architecture
Programmers of the x86-64 processor can store data in a wide variety of registers: there are 16 64-bit general
purpose registers, 2 64-bit state registers, 4 16-bit segment registers, 14 registers for floating point computation, and
two banks of registers that support optimized execution of instructions. For the most part, we will focus on the 18
64-bit registers that support the core of general purpose integer computation.
The 16 general purpose registers are each 64-bits wide. The unusual naming of these registers is an historic
artifact that reflects their once typical usage. It also makes the registers difficult to remember, and reminds us that
this little technical world is the accretion of thinking by many groups of people over many years.
64-bits [63:0] 32-bits [31:0 ] 16-bits [15:0] 8-bits [15:8] 8-bits [7:0] Comments
%rax %eax %ax %ah %al
%rbx %ebx %bx %bh %bl
%rcx %ecx %cx %ch %cl
%rdx %edx %dx %dh %dl
%rsi %esi %si %sil source index
%rdi %edi %di %dil destination index
%rbp %ebp %bp %bpl frame base pointer
%rsp %esp %sp %spl stack pointer
%r8 %r8d %r8w %r8b
%r9 %r9d %r9w %r9b
%r10 %r10d %r10w %r10b
%r11 %r11d %r11w %r11b
%r12 %r12d %r12w %r12b
%r13 %r13d %r13w %r13b
%r14 %r14d %r14w %r14b
%r15 %r15d %r15w %r15b
(RFLAGS) flags register
%rip instruction pointer
Notice the following:
We will always refer to the registers with a leading %, as above, to distinguish them from other objects that
might be named similarly.
Typically we will use registers in their 64-bit variants. These register names all start with r.
Registers are either byte (8-bit), word (16-bit), long-word (32-bit), or quad-word (64-bit). Unfortunately, in
some settings we refer to 16-bit values as long, and 32-bit values as double.
In early machines, pairs of registers, like %ah (a-high) and %al (a-low), were distinct 8-bit registers that were
sometimes yoked together as a single 16-bit value. Because the need to access the high byte, explicitly, is less
important today, newer registers do not have this pairing.
Though the %si (for source index) and %di (for destination index) registers were once mainly used in string
processing, they are available as general purpose registers, today.

1
The x86-64 Instruction Set Architecture (ISA)
Class Instruction Behavior Description CC?
Arithmetic
addq src,dst dst src + dst addition (also addl, addw, addb) yes
subq src,dst dst dst - src subtraction yes
negq dst dst 0 - dst negate (change sign) yes
incq dst dst dst + 1 increment yes
decq dst dst dst - 1 decrement yes
imulq src %rdx:%rax src * %rdx:%rax signed multiply yes
mulq src %rdx:%rax src * %rdx:%rax unsigned multiply yes
idivq src %rax %rdx:%rax / src signed divide and yes
%rdx %rdx:%rax mod src signed remainder
divq src %rax %rdx:%rax / src unsigned divide and yes
%rdx %rax:%rdx mod src unsigned remainder
Bitwise logic
notq dst dst notdst bit complement no
andq src,dst dst src and dst bitwise and yes
orq src,dst dst src or dst bitwise or yes
xorq src,dst dst src xor dst bitwise exclusive or yes
Shifting
salq n,dst dst dst << n arithmetic shift left yes
shlq n,dst dst dst << n logical shift left (same as salq) yes
sarq n,dst dst dst >> n arithmetic shift right yes
shrq n,dst dst dst >> n logical shift right yes
Data movement
movq src,dst dst src move (copy) value no
xchgq a,b a b exchange register values no
push src *(--%rsp) src push value on stack no
pop dst dst (*%rsp)++ pop value from stack no
leaq src,dst dst &src load effective address no
Comparison
cmpq a,b flags b - a compare (sets N, Z, V) yes
testq a,b flags b and a test bits (sets N, Z) yes
Branching
call addr *(--%rsp) %rip
%rip addr call procedure no
ret %rip (*%rsp)++ return from procedure no
jmp addr %rip addr jump to address no
Conditional
jCC addr %rip addr jump, if condition: no
jz addr %rip addr zero no
je addr %rip addr equal (same as jz) no
jnz addr %rip addr not zero no
jne addr %rip addr not equal (same as jnz) no
jl addr %rip addr less no
jle addr %rip addr less or equal no
jge addr %rip addr greater or equal no
jg addr %rip addr greater no
cmovCC src,dst dst src move if condition no
setCC dst dst boolean condition copy condition test (1 or 0) no
Miscellaneous
nop do nothing no

2
Note the following:
Most instructions have equivalent 32-bit (e.g. addl), 16-bit (e.g. addw), or 8-bit (e.g. addb) equivalents. The
assembler can often guess the operand size by looking at the register specifications involved. For example, the
following are equivalent:

addw %ax,%bx
add %ax,%bx

Nonetheless, adding the size specifier on the opcode helps to verify the size of the operation is interpreted
correctly.
The assembler will not attempt to deduce the size of the operation if either of the operands is in memory. A
suffix (q, l, w, or b) must be specified.
For all instructions (other than sign-extend operations), if the destination is 16-bits or 8-bits, only the desired
bits are set. If the destination register is 32-bits, the result is zero-extended to 64 bits. This subtle point can
lead to very confusing errors in logic.
If yes appears in the right-most column of the previous table, the instruction sets or clears one or more of the
condition code bits in the status register. These may include, ZF (one if and only if the result is 0), SF (one if
and only if the results most significant (sign) bit is set), OF (one if and only if there was overflow), and CF
(one if there was a carry out). If no, then the condition codes are not modified by the instruction.

Addressing Modes
Immediate. In this mode, typically written as a constant with a dollar prefix, the value of the constant is used as
the operand value.

movq $42,%rax # move immediate value 42 into the %rax register

Assemblers will typically allow decimal (e.g. $42), octal (with leading 0, as in $052), hexadecimal (with leading
0x, as in 0x2A), or character (with just a leading apostrophe (), as in *). Since the data has no address, this
mode is not legal in any instruction that would require an effective address in that operand. The size of the
signed value is determined by the size of the instruction involved.
Register direct. In this mode, a register name is specified. The value in the register is used as the operand value.
Because the value was found in a register (not in memory) it has no effective address.

xorl %eax,%eax # clear all 64 bits of register %rax

Register indirect. In this mode, the effective address of the operand is found in a register. The value is found in
memory at that address. This instruction uses address register indirect, with register %rax:

movq $42,(%rax)

Register indirect with displacement. In this mode, the register is a pointer to the base of a data structure, and
a signed constant displacement is added in to determine the effective address of the data. The value is found
in memory at the effective address. This mode is frequently used in stack-relative instructions:

movq %rbx,8(%rsp) # save the bx register in local stack frame

The register indirect mode is, essentially, a zero-displacement case of this mode.
Absolute. Absolute addressing is typically used in kernel and other systems programming, when code must access
procedures at a compile-time specific address, or at an address computed into a register or memory location:

call *0x80 # call routine at address 0x80


call *%rax # call routine at address in register %rax
call *8(%rsp) # call routine at address in location near top of stack

3
General Indirect. The x86-64 processor supports indirect addressing with combinations of base, index, and dis-
placement. The general form is: d(%rb,%ri,s) where
%rb is any register to be used as the base register , a pointer that points (roughly) to the base of the data
structure.
d is the displacement to from the base pointer to the address of the base of the data structure. In most
C-style programs, where this mode is used for array indexing, the displacement is zero.
s is the scale, typically the size of the elements of the array being index. Legal values are 1, 2, 4, and
8. When elements are odd sizes, the scale is usually 1 and the index register (see below) is explicitly
multiplied by the size of each element.
%ri is a any register to be used as a index register . When elements are sized 1, 2, 4, or 8, the index register
is the index used to find the appropriate element. For odd sized elements, the index is pre-computed to
be the offset from the base pointer to the particular element.
The effective address is computed as d+%rb+s*%ri. The value is found in memory at that location. This is a
typical use:

incl 0(%rax,%rbx,4) # increment the %rbx-th double word element of array at %rax

The base and then possibly the index register may be optionally omitted:

incl 0(,%rbx,4) # increment the %rbx-th double word memory location


incl 0x100(,1) # increment the double word found at 0x100 (note one comma missing)
incl 0x100 # same as above

4
Argument Passing
The main motivation for adding a stack (whose top is pointed to by %rsp) to a processor is to provide a last-in,
first-out structure for storing information specific to nested procedures. For example, parameters to functions were,
for many years, always passed on the stack. Because the stack resides in memoryrelatively distant from the
processoreven fast and simple procedures found themselves bogged down by the pushing and popping of stack
values.
Since the x86-64 architecture is (relatively) register-rich, modern Unix procedure calling convention on the x86-64
demands that the first few parameters be placed within specific registers. In addition, some registers, if they are to
be used, must be saved before, and restored after use. These registers are referred to as callee saved. The following
table identifies these register features and locations.

Register Parameter Save Convention Comments


%rax varags float count (typ. 0) return value
%rbx callee saved
%rcx parameter 4
%rdx parameter 3
%rsi parameter 2
%rdi parameter 1
%rbp callee saved
%rsp stack pointer
%r8 parameter 5
%r9 parameter 6
%r10 reserved, used for linking
%r11 reserved, used for Pascal-like languages
%r12 callee saved
%r13 callee saved
%r14 callee saved
%r15 callee saved

Note the following:

When more than 6 parameters are passed, the remaining parameters are passed on the stack.
For procedures with variable numbers of arguments (printf, scanf), you must pass, in %rax, the number of
parameters that are passed in floating point registers. This happens infrequently, so it is important to clear
%rax before calling these procedures.
When calling a procedure, you can assume that callee-saved registers will remain untouched through the call.
You must, of course, save these registers before, and restore these registers after you use them.
The six registers used for passing parameters may be used without saving, but their values may not persist
through the call.

Assembler Directives
Commands that control the interpretation of the assembly source code are called assembler directives. These com-
mands determine where code and data are placed, how their initialized, and how symbols are defined and exported.
We first describe the directives, and then see how they might appear in an example.

5
Directive Interpretation
.section .rodata The assembler commands that follow specify read-only data.
.section .data The assembler commands the follow specify writeable data.
.data Same as .section .data.
.section .text The assembler commands that follow generate instructions (code or text)
to be executed.
.text Same as .section .text.
.globl <name> When assembly is complete, the symbol name is to be added to a list of
global symbols visible to other modules. By default, symbols are not global.
Note spelling.
.type <name>,<kind> Tell the debugger that symbol name is either a function (kind is @function)
or a data object (kind is @object).
.size <name>, <bytecount> Tell the debugger that the symbol name occupies bytecount bytes. It does
not reserve those bytes; use .asciz, .long, etc. to reserve space in the
current section for data. The size of routine r can be computed with
.size r, .-r
placed after the ret instruction.
.ascii <string> Assemble the ASCII characters (including escaped values) into the current
section. No assumption is made about zero termination.
Compare with .asciz
.asciz <string> Assemble the ASCII characters (including escape values) into the current
section, followed by a C-style terminating zero-character.
Compare with .ascii
.string <string> Synonym for .asciz <string>.
.quad <quad-value-list> Reserves and initializes one or more comma separated quad-word values
in the current section (typically .data). Typical use pointer variables or
arrays.
.long <double-word-value-list> Reserves and initializes double-word values.
.word <word-value-list> Reserves and initializes word values.
.byte <byte-value-list> Reserves and initializes byte values.
.align <size> Ensure the offset from the beginning of the segment is a multiple of <size>.
Typically this is accomplished by padding the section with zeros.

Das könnte Ihnen auch gefallen