X86-64: Difference between revisions

607 bytes removed ,  28 days ago
m
Fix lint errors
[unchecked revision][unchecked revision]
m (Section hierarchy denoted incorrectly)
m (Fix lint errors)
 
(26 intermediate revisions by 20 users not shown)
Line 1:
{{DISPLAYTITLE:x86-64}}
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.
 
Line 5 ⟶ 6:
===Long Mode===
 
Long mode extends general registers to 64 bits (RAX, RBX, RIP, RSP, RFLAGS, etc), and adds aneight additional 8 integer registers (R8, R9, ..., R15) plus 8eight 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.
 
Long mode does not support hardware task switching or virtual 8086 tasks. 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). Using paging has become mandatory, and segmentation has been stripped down for performance reasons.
Line 15 ⟶ 16:
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 [[MSR|Model Specific Register (MSR)]]s, 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'''.
 
=== Further information ===
 
:''This feature overview is incomplete. Please see the [[http://en.wikipedia.org/wiki/X86-64 Wikipedia article on x86-64]] for more information.''
 
==Setting up==
Line 25 ⟶ 26:
===How do I detect if the CPU is 64 bits ?===
 
After calling CPUID with EAX=0x80000001, all AMD64 compliant processors have the longmode-capable-bit turned on in the extended feature flags (bit 29) in EDX. There are also other bits required by long mode; you can check them out in the CPUID docs in the [http://support.amd.com/us/Processor_TechDocs/24594.pdf AMD general purpose instruction reference] (Link dead, the original author probably meant "AMD64 Architecture Programmer’s Manual Volume 3: General Purpose and System Instructions", found here: http://developer.amd.com/resources/developer-guides-manuals/)
 
===How do I enable Long Mode ?===
Line 32 ⟶ 33:
* Disable paging
* Set the PAE enable bit in CR4
* Load CR3 with the physical address of the PML4 (Level 4 Page Map)
* Enable long mode by setting the EFER.LME flag (bit 8) in MSR 0xC0000080 (aka EFER)
* Enable paging
 
''Reference: [https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-vol-3a-part-1-manual.pdf Intel 64 and IA-32 Architectures Software Developer's Manual], Section 9.8.5''
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.
 
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 32-bit value) 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 32-bit value) 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 ?===
Line 163 ⟶ 166:
|}
 
=== Text Segment Types ===
== Linking a 64-bit ELF64 kernel against a 32-bit ELF32 bootstrap (for use with Multiboot) ==
 
Another thing that you must keep in mind, that although the address space (and with it all the pointers) are 64 bit wide, the generated code in the text segment is most likely not. That's because by default gcc compiles to the "mov" instruction which has only 32 bit immediate. This means 64 bit programs are limited to 2G, just as 32 bit mode programs.
{{Disputed|Talk:X86-64}}
 
If you have ever seen an error message like this:
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.
<syntaxhighlight lang="bash">
relocation truncated to fit: R_X86_64_32 against symbol
</syntaxhighlight>
then your code hit this barrier. For Assembly, you must use the "movabs" instruction instead of "mov", and for gcc you need to select a different text segment model with the "-mcmodel" argument.
 
{| {{wikitable}}
<source lang="bash">
! Flag
 
! Text Segment Addressing
# I assume that you've already compiled and linked your 64-bit kernel
|-
 
| -mcmodel=small
 
| The program and its symbols must be linked in the lower 2 GB of the address space (this is the default model)
# your 64-bit kernel
|-
kernel64="build/kernel64.bin"
| -mcmodel=large
 
| This model makes no assumptions about addresses and sizes of sections.
# its base address -- make sure it doesn't overlap with the kernel32 sections!
|-
kernel64_baseaddress="0x00140000"
| -mcmodel=medium
 
| The program is linked in the lower 2 GB of the address space. Small symbols are also placed there. Symbols with sizes larger than -mlarge-data-threshold are put into large data or bss sections and can be located above 2GB.
# 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"
| -mcmodel=kernel
 
| The kernel runs in the negative 2 GB of the address space. This model has to be used for Linux kernel code.
 
|}
kernel64export32_ldscript=`mktemp`
It worth noting that code models are different for architectures, as they are tied with the instruction encoding. For example, AArch64 has a "-mcmodel=tiny" too, which allows 1M addressing, unknown to x86_64. And for AArch64 "-mcmodel=small" has a 4G limitation, not 2G as for the x86_64.
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
 
</source>
 
== See Also ==
Line 254 ⟶ 198:
* [[EM64T|Intel EM64T]]
* [[Creating a 64-bit kernel]]
* [[BOOTBOOT|BOOTBOOT bootloader]]
* [[Limine|Limine bootloader]]
* [[X86-64 Instruction Encoding]]
* [[User:Stephanvanschaik/Setting_Up_Long_Mode|Setting up long mode]]
 
=== Wikipedia ===
Line 265 ⟶ 211:
* [http://www.x86-64.org/documentation.html x86-64 ABI and assembly guide]
* [http://downloads.openwatcom.org/ftp/devel/docs/elf-64-gen.pdf ELF-64 Object File Format (direct PDF link)]
* [http://stackoverflow.com/questions/1753602/registers-for-x86-64-processors StackOverflow x86_64 register assignment]
 
[[Category:X86 CPU]]
[[Category:X86-64]]
[[Category:Operating Modes]]