Page 1 of 1

system V abi question

Posted: Sat Aug 29, 2020 12:14 am
by ITchimp
"Functions preserve the registers ebx, esi, edi, ebp, and esp; while eax, ecx, edx are scratch registers. The return value is stored in the eax register, or if it is a 64-bit value, then the higher 32-bits go in edx. Functions push ebp such that the caller-return-eip is 4 bytes above it, and set ebp to the address of the saved ebp. This allows iterating through the existing stack frames. This can be eliminated by specifying the -fomit-frame-pointer GCC option."
I still have a problem understanding this paragraph: what does it really mean that eax ecx and edx are scratch registers? If the caller make uses of those registers, does that mean the compiler will push those register onto the stack and pop them back after calling the
callee? By the same token, functions preserve registers ebx, esi, edi ebp, esp... does that mean if callee modifies ebx or ebi, the caller 's behavior may alter, or misbehave..

Re: system V abi question

Posted: Sat Aug 29, 2020 1:15 am
by nullplan
It means that an external function is free to change EAX, ECX, and EDX without backing them up and restoring them. For the other five registers, it is not allowed to do that.
ITchimp wrote:does that mean if callee modifies ebx or ebi, the caller 's behavior may alter, or misbehave..
Yes, that is what it means. The caller can only rely on EBX, ESI, EDI, EBP, and ESP having the same value on return as at the call, and the callee can only change EAX, ECX, and EDX, and if it needs to use the others, it must back them up and restore them. If you do anything else, you fail to act according to the ABI, and your program may misbehave.

Re: system V abi question

Posted: Sat Aug 29, 2020 1:53 am
by ITchimp
I tried the following code.... and it does not appear that the eax, edx and ecx are restored to their original values in caller function...
what I witnessed is that the whatever values that I assigned to eax, edx, and ecx, completely overwrite the value in the caller...
that is not the advertised behavior to me....

could that be that inline assembly is exception to the rule... and does not apply to the abi behavior

Code: Select all

void test_cdecl(){
        // put values in rax, rcx, rdx
        asm(" mov $0xff, %eax;"
            " mov $0xfe, %ecx;"
            " mov $0xfd, %edx;"
            );
        return;
}

void main(){
  int eax, ecx, edx,edi;
  // put some random value in registers eax edx, ecx
  // call test_cdecl 
  
  asm( "mov  $0xee, %eax;"
       "mov  $0xdd, %edx;"
       "mov  $0xcc, %ecx;"
       );

  test_cdecl();

  // get value of eax edx and ecx and verify they
  // are the same as before calling test_cdecl
  //
  asm (" mov %%eax, %0;"
       " mov %%edx, %1;"
       " mov %%ecx, %2;"
       :"=m"(eax),"=m"(edx),"=m"(ecx)::);

  printf(" eax is %x \n", eax);
  printf(" edx is %x \n", edx);
  printf(" ecx is %x \n", ecx);
} 

Re: system V abi question

Posted: Sat Aug 29, 2020 2:07 am
by Octocontrabass
In inline assembly, all registers belong to the compiler, and if you change any of them without telling the compiler that you're going to do that, the results are undefined behavior.

Since your inline assembly changes EAX, ECX, and EDX without telling the compiler, your results are undefined behavior.

Re: system V abi question

Posted: Sat Aug 29, 2020 2:49 am
by ITchimp
by telling compile, are you referring to the clobber list?

Re: system V abi question

Posted: Sat Aug 29, 2020 3:08 am
by iansjack
The ABI doesn't say that those registers are preserved in function calls (it says the opposite), so why do you expect that to happen?

Re: system V abi question

Posted: Sat Aug 29, 2020 4:08 am
by ITchimp
ian, you know I am your apprentice... even if you don't reciprocate, can you just cut out out the crap and give me the straight answer!!!!!!!

Re: system V abi question

Posted: Sat Aug 29, 2020 4:18 am
by iansjack
I don't see how I can give a straighter answer than that.

The ABI does not guarantee that scratch registers will be, or should be, preserved by function calls.

Re: system V abi question

Posted: Sat Aug 29, 2020 7:33 am
by Octacone
ITchimp wrote:ian, you know I am your apprentice... even if you don't reciprocate, can you just cut out out the crap and give me the straight answer!!!!!!!
Some politeness wouldn’t hurt. :wink:

Re: system V abi question

Posted: Sun Aug 30, 2020 4:49 am
by bellezzasolo

Code: Select all

%macro INTERRUPT_HANDLER 2
global x64_interrupt_handler_%2
x64_interrupt_handler_%2:
cld			;Safe, IRET restores flags
%if %1 == 0
push 0xDEADBEEF			;Dummy error code
%endif
;Stack frame
push rbp
mov rbp, rsp
SAVE_VOLATILE_REGISTERS
;Per CPU information
call swap_gs_ifpriv
call save_fpu_interrupt

;Now we pass the stack interrupt stack and vector
mov rcx, %2
mov rdx, rbp

sub rsp, 32
call x64_interrupt_dispatcher
add rsp, 32

call restore_fpu_interrupt
call swap_gs_ifpriv


RESTORE_VOLATILE_REGISTERS
pop rbp
add rsp, 8		;Get rid of error code
iretq
%endmacro

%macro SAVE_VOLATILE_REGISTERS 0
push rax
call save_interrupt_registers
%endmacro

save_interrupt_registers:
pop rax
push rcx
push rdx
push r8
push r9
push r10
push r11
jmp rax
That's my interrupt handling code.

Firstly, you have to remember that interrupts are, well, interrupts, so the ABI doesn't count for squat. Furthermore, you don't want to make your kernel dependent on a particular user mode ABI, beyond what you specify for syscalls. So you can say "kernel scratches these registers, these registers are preserved, these are parameters. User mode has to do the relevant translations (it likely will have a syscall wrapper anyway, so that's pretty easy).

However, once my interrupt handler calls into the main, C++, kernel (x64_interrupt_dispatcher), then we have to be concerned about the ABI.

My kernel is compiled in Visual C++, so uses the Microsoft x64 ABI. It's worth noting that the MS ABI is supported by GCC, so this actually means that the kernel is more portable to different compilers! We also avoid that annoying thing called the red zone.

Anyway, what is the ABI?
Integer arguments are passed in RCX, RDX, R8, and R9.
Floating point arguments are really irrelevant here, but XMM0, XMM1, XMM2, and XMM3.

Now, volatile, or scratch registers, are both these, and:
RAX, R10, R11
XMM4, XMM5

Upper portions of YMM and ZMM registers, as well as ZMM16-31, are also volatile. This is only relevant if you use AVX(-512) instructions in the kernel.

It's worth noting that RAX is the return value.

Now, how does this correlate with my interrupt handler? Notice that save_interrupt_registers saves all integer volatile registers, except for RAX, which is used as a scratch register (because the function modifies the stack frame of the caller, which is in a way, a custom ABI).

Hence the helper macro SAVE_VOLATILE_REGISTERS, which preserves rax before calling the function.

Finally, the sub rsp,32 before calling into the main kernel?
That's the parameter shadow space. The x64 ABI specifies that this area of the stack is reserved for the function's register parameters.