system V abi question

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
ITchimp
Member
Member
Posts: 134
Joined: Sat Aug 18, 2018 8:44 pm

system V abi question

Post 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..
nullplan
Member
Member
Posts: 1790
Joined: Wed Aug 30, 2017 8:24 am

Re: system V abi question

Post 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.
Carpe diem!
ITchimp
Member
Member
Posts: 134
Joined: Sat Aug 18, 2018 8:44 pm

Re: system V abi question

Post 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);
} 
Octocontrabass
Member
Member
Posts: 5572
Joined: Mon Mar 25, 2013 7:01 pm

Re: system V abi question

Post 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.
ITchimp
Member
Member
Posts: 134
Joined: Sat Aug 18, 2018 8:44 pm

Re: system V abi question

Post by ITchimp »

by telling compile, are you referring to the clobber list?
User avatar
iansjack
Member
Member
Posts: 4703
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: system V abi question

Post 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?
ITchimp
Member
Member
Posts: 134
Joined: Sat Aug 18, 2018 8:44 pm

Re: system V abi question

Post 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!!!!!!!
User avatar
iansjack
Member
Member
Posts: 4703
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: system V abi question

Post 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.
User avatar
Octacone
Member
Member
Posts: 1138
Joined: Fri Aug 07, 2015 6:13 am

Re: system V abi question

Post 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:
OS: Basic OS
About: 32 Bit Monolithic Kernel Written in C++ and Assembly, Custom FAT 32 Bootloader
User avatar
bellezzasolo
Member
Member
Posts: 110
Joined: Sun Feb 20, 2011 2:01 pm

Re: system V abi question

Post 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.
Whoever said you can't do OS development on Windows?
https://github.com/ChaiSoft/ChaiOS
Post Reply