X86-64

From OSDev.wiki
Revision as of 16:07, 17 April 2011 by osdev>Madanra (corrected dead link)
Jump to navigation Jump to search

This article discusses x86-64 CPUs (AMD64 and Intel's equivalent EM64T implementation). IA-64 (Itanium) is really a different beast and not addressed here.


Features

What does Long Mode offer?

Long mode extends general registers to 64 bits (RAX, RBX, RIP, RSP, RFLAGS, etc), and adds an additional 8 integer registers (R8, R9, ..., R15) plus 8 more SSE registers (XMM8 to XMM15) to the CPU. Linear addresses are extended to 64 bit (however, a given CPU may implement less than this) and the physical address space is extended to 52 bits (a given CPU may implement less than this). In essence long mode adds another mode to the CPU:

  • Real mode
  • Legacy mode (32 bit protected mode)
  • Long mode (64 bit protected mode)
  • System Management mode

Long mode does not support hardware task switching or virtual 8086 tasks, and most of the segment register details are ignored (a flat memory model is required). In long mode the current CS determines if the code currently running is 64 bit code (true long mode) or 32 bit code (compatibility mode), or even 16-bit protected mode code (still in compatibility mode).

The first 64 bit CPUs from both Intel and AMD support 40 bit physical addresses and 48 bit linear addresses.

Segmentation in Long Mode

Segmentation in long mode functions with a flat model with the exception of two registers: FS and GS. Setting the base address for these two segment registers is possible via two specific MSRs, FS.base (C000_0100h)and GS.base (C000_0101h).

Additionally there is a long mode specific instruction called SWAPGS, which swaps the contents of GS.base and another MSR called KernelGSBase (C000_0102h). This instruction is particularly useful for preserving kernel information for a specific logical processor core across context switches. Note: This is an exchange operation.

Lastly it also important to note that any attempt to load a selector value into the FS or GS registers in long mode will automatically set their base addresses to zero due to the previously mentioned semantics of long mode segmentation.

Setting up

How do I detect if the CPU is 64 bits ?

You can find that out by checking CPUID. All AMD64 compliant processors have the longmode-capable-bit turned on in the extended feature flags (bit 29) in EDX, after calling CPUID with EAX=0x80000001. There are also other bits required by long mode, but you can see those yourself in CPUID at AMD general purpose instruction reference

How do I enable Long Mode ?

The steps for enabling long mode are:

   * Have paging disabled
   * Set the PAE enable bit in CR4
   * Load CR3 with the physical address of the PML4
   * Enable long mode by setting the EFER.LME flag in MSR 0xC0000080
   * Enable paging

Now the CPU will be in compatibility mode, and instructions are still 32-bit. To enter long mode, the D/B bit (bit 22, 2nd dword) of the GDT code segment must be clear (as it would be for a 16-bit code segment), and the L bit (bit 21, 2nd dword) of the GDT code segment must be set. Once that is done, the CPU is in 64-bit long mode.

Are there restrictions on 32-bit code running in Legacy Mode ?

x86-64 processors can operate in a legacy mode, they still start in real mode and protected mode is still available (along with the associated v8086 mode). This means an x86 operating system, even DOS, will still run just fine. The only difference is that physical addresses can be up to 52 bits (or as many bits as implemented by the CPU) when PAE is used.

However, there is nothing like Virtual 8086 Mode once in long/compatibility mode.

Entering Long Mode directly

Protected mode must be entered before activating long mode. A minimal protected-mode environment must be established to allow long-mode initialization to take place. This environment must include the following:

  • A protected-mode IDT for vectoring interrupts and exceptions to the appropriate handlers while in protected mode.
  • The protected-mode interrupt and exception handlers referenced by the IDT.
  • Gate descriptors for each handler must be loaded in the IDT.
--AMD64 docs, volume 2, section 14.4 (Enabling Protected Mode), 24593 Rev. 3.10 February 2005

That being said, we have a thread where Brendan shows how you can enable 64-bit long mode with no 32-bit IDT and no 32-bit segments ... Be assured, however, that any paging-related exception that occurs in long mode before you enable 64-bit IDT will cause the processor to reset due to a triple fault ... There is also an example of this implemented in a bootloader.

64bit Environment Models

The following discusses issues related to data types of high level languages. So, it's not relevant for people developing only in assembly.

There are three 64bit programming models you need to consider: LP64, ILP64, LLP64. Each model has its own pitfalls. The I/L/P stand for Int, Long, Pointer, respectively; the 64 is the number of bits in each.

This LP64 means Longs and Pointers are 64bits wide. LL is a special case and means long-long...

Linking a 64-bit ELF64 kernel against a 32-bit ELF32 bootstrap (for use with Multiboot)

You can link a 64-bit ELF64 kernel against a 32-bit ELF32 bootstrap using the following shell script. Alternatively you can also write a small assembly stub for your 64-bit ELF64 kernel that includes 32-bit code.

# I assume that you've already compiled and linked your 64-bit kernel


# your 64-bit kernel
kernel64="build/kernel64.bin"

# its base address -- make sure it doesn't overlap with the kernel32 sections!
kernel64_baseaddress="0x00140000"

# the symbols from it you want available in your 32-bit loader
kernel64export32_symbols="k_GDT k_GDTptr k_PML4 k_PML3 k_PML2 k_PML1_first8M kinit64 k_multibootdata k64_sbss k64_ebss k_pages k_npages"


kernel64export32_ldscript=`mktemp`
kernel64_section=`mktemp`


# generate .kernel64 section's data
readelf -SW "$kernel64" | python -c "`cat <<EOF
import re, sys
regex=r"\[\s*\d+\]\s*(?!NULL)(\S+)\s+(PROGBITS|NOBITS)\s+([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s+([0-9a-fA-F]+)"
baseaddress=$kernel64_baseaddress
k64f = open("$kernel64", "r")
f = open("$kernel64_section", "w")
for line in sys.stdin:
	r = re.search(regex, line)
	if not r:
		continue
	section, stype, LMA, offset, size = r.groups()
	LMA, offset, size = map(lambda s: int(s, 16), (LMA, offset, size))
	if LMA < baseaddress:
		raise ValueError("section ('%s' offset=0x%08x size=0x%08x ) at address < 0x%x" % (section, offset, size, baseaddress))
	k64f.seek(offset)
	if stype == "PROGBITS":
		f.seek(LMA-baseaddress)
		f.write(k64f.read(size))
f.close()
EOF`" || fail

# export wanted symbols from kernel64
readelf -sW "$kernel64" | python -c "`cat <<EOF
import re, sys
regex=r"\s*\:\s+([0-9a-fA-F]+)\s+\d+\s+\S+\s+\S+\s+\S+\s+\S+\s+(\S+)"
syms=filter(len,"$kernel64export32_symbols".split(" "))
f = open("$kernel64export32_ldscript", "w")
for line in sys.stdin:
	r = re.search(regex, line)
	if not r:
		continue
	val, sym = r.groups()
	if not sym in syms:
		continue
	val = int(val, 16)
	if val > 0xffffffff:
		raise ValueError("symbol value must be below 0xffffffff limit")
	f.write('"%s" = %s;\n' % (sym, hex(val)))
f.close()
EOF`" || fail


# now we have to build the 32-bit loader

# your actual lines may vary, change to your needs
gcc -m32 -Wall -Wextra -nostdlib -nostartfiles -nodefaultlibs -Isrc -O5 -fno-strict-aliasing -c \
    src/start64/start64.c -o build/start64/start64.o
nasm -f elf32 \
    src/start64/loader32.asm -o build/start64/loader32.o

# add the contents of $kernel64_section as a section in one of the 32-bit object files
objcopy --add-section      .kernel64="$kernel64_section"  \
        --set-section-flag .kernel64=alloc,data,load,contents \
        build/start64/loader32.o || fail

# link the previous two files, taking care to include (-T) the autogenerated syms file
ld -melf_i386 -T src/start64/linker.ld -T "$kernel64export32_ldscript" -o kernel.bin \
   build/start64/start64.o build/start64/loader32.o || fail


Data Types

This table lists the breakdown of sizes in the various programming models.

Datatype LP64 ILP64 LLP64 ILP32 LP32
char 8 8 8 8 8
short 16 16 16 16 16
_int 32 -- 32 -- --
int 32 64 32 32 16
long 64 64 32 32 32
long long -- -- 64 -- --
pointer 64 64 64 32 32

Models used by 64bit OSs

The following table lists what some current 64bit OS have as a programming model.

OS Mode
Windows XP X64 LLP64
Linux LP64
FreeBSD/OpenBSD LP64
Solaris LP64
DEC OSF/1 Alpha LP64
SGI Irix LP64
HP UX 11 LP64

See Also

Articles

Wikipedia

External Links