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 bannedPUSHA
though, it doesn't seem to overlap any new instruction opcodes. Perhaps they are reserved thePUSHA
andPOPA
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 pushedesp
! You need to knowesp
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 registersr8
thrur15
. You also need to save and restore a much larger SSE state,xmm8
thruxmm15
. 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
, andr12
thrur15
. You should only need to save and restorerax
,rcx
,rdx
, andr8
thrur11
. (Note: on Linux or other System V ABI platforms, the compiler will be preservingrbx
,rbp
,r12
-r15
, you can expectrsi
andrdi
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 ofFS
andGS
are set through MSRs0xC0000100
and0xC0000101
. Assuming you won't be usingFS
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
orGS
(even in user mode) will overwrite theFSBASE
orGSBASE
MSR. Since some operating systems useGS
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 loadingGS
in user mode. To solve this problem, there are two MSRs reserved for theGSBASE
register: one active one and one hidden one. In kernel mode, the kernel'sGSBASE
is held in the usualGSBASE
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 hiddenGSBASE
MSR. Since the kernel'sGSBASE
is safely hidden in the other MSR in user mode, the user mode code can't clobber the kernel'sGSBASE
by loading a value intoGS
. When the CPU reenters kernel mode, the context save code will executeSWAPGS
and restore the kernel'sGSBASE
.pusha
is not valid in 64-bit mode because it is redundant. Pushing each register individually is exactly the thing to do.Learn from existing code that does this kind of thing. For example:
SAVE_ARGS_IRQ
): entry_64.SINTR_PUSH
): privregs.hIDT_VEC
): exception.S (similar is vector.S in NetBSD)In fact, "manually pushing" the regs is the only way on AMD64 since
PUSHA
doesn't exist there. AMD64 isn't unique in this aspect - most non-x86 CPUs do require register-by-register saves/restores as well at some point.But if you inspect the referenced sourcecode closely you'll find that not all interrupt handlers require to save/restore the entire register set, so there is room for optimizations.
Hi it might not be the correct way to do it but one can create macros like
and
and eventually add the other r8-15 registers if one needs to