I am looking at some old code from a school project, and in trying to compile it on my laptop I ran into some problems. It was originally written for an old 32 bit version of gcc. Anyway I was trying to convert some of the assembly over to 64 bit compatible code and hit a few snags.
Here is the original code:
pusha
pushl %ds
pushl %es
pushl %fs
pushl %gs
pushl %ss
pusha
is not valid in 64 bit mode. So what would be the proper way to do this in x86_64 assembly while in 64 bit mode?
There has got to be a reason why pusha
is not valid in 64 bit mode, so I have a feeling manually pushing all the registers may not be a good idea.
AMD needed some room to add new opcodes for REX
prefixes and some other new instructions when they developed the 64-bit x86 extensions. They changed the meaning of some of the opcodes to those new instructions.
Several of the instructions were simply short-forms of existing instructions or were otherwise not necessary. PUSHA
was one of the victims. It's not clear why they banned PUSHA
though, it doesn't seem to overlap any new instruction opcodes. Perhaps they are reserved the PUSHA
and POPA
opcodes for future use, since they are completely redundant and won't be any faster and won't occur frequently enough in code to matter.
The order of PUSHA
was the order of the instruction encoding: eax
, ecx
, edx
, ebx
, esp
, ebp
, esi
, edi
. Note that it redundantly pushed esp
! You need to know esp
to find the data it pushed!
If you are converting code from 64-bit the PUSHA
code is no good anyway, you need to update it to push the new registers r8
thru r15
. You also need to save and restore a much larger SSE state, xmm8
thru xmm15
. Assuming you are going to clobber them.
If the interrupt handler code is simply a stub that forwards to C code, you don't need to save all of the registers. You can assume that the C compiler will generate code that will be preserving rbx
, rbp
, rsi
, rdi
, and r12
thru r15
. You should only need to save and restore rax
, rcx
, rdx
, and r8
thru r11
. (Note: on Linux or other System V ABI platforms, the compiler will be preserving rbx
, rbp
, r12
-r15
, you can expect rsi
and rdi
clobbered).
The segment registers hold no value in long mode (if the interrupted thread is running in 32-bit compatibility mode you must preserve the segment registers, thanks ughoavgfhw). Actually, they got rid of most of the segmentation in long mode, but FS
is still reserved for operating systems to use as a base address for thread local data. The register value itself doesn't matter, the base of FS
and GS
are set through MSRs 0xC0000100
and 0xC0000101
. Assuming you won't be using FS
you don't need to worry about it, just remember that any thread local data accessed by the C code could be using any random thread's TLS. Be careful of that because C runtime libraries use TLS for some functionality (example: strtok typically uses TLS).
Loading a value into FS
or GS
(even in user mode) will overwrite the FSBASE
or GSBASE
MSR. Since some operating systems use GS
as "processor local" storage (they need a way to have a pointer to a structure for each CPU), they need to keep it somewhere that won't get clobbered by loading GS
in user mode. To solve this problem, there are two MSRs reserved for the GSBASE
register: one active one and one hidden one. In kernel mode, the kernel's GSBASE
is held in the usual GSBASE
MSR and the user mode base is in the other (hidden) GSBASE
MSR. When context switching from kernel mode to a user mode context, and when saving a user mode context and entering kernel mode, the context switch code must execute the SWAPGS instruction, which swaps the values of the visible and hidden GSBASE
MSR. Since the kernel's GSBASE
is safely hidden in the other MSR in user mode, the user mode code can't clobber the kernel's GSBASE
by loading a value into GS
. When the CPU reenters kernel mode, the context save code will execute SWAPGS
and restore the kernel's GSBASE
.