How to save the registers on x86_64 for an interrupt service routine?

Mr. Shickadance picture Mr. Shickadance · Jul 27, 2011 · Viewed 24.3k times · Source

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.

Answer

doug65536 picture doug65536 · Jan 23, 2013

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.