x86 Function call conventions

x86 Function call conventions

Another cybersecurity post. Focused on binary (dis)assembly.

This time it’s about how different compilers turn high-level programming language code into low-level assembly code, in regards to function calling.

As a sample, I’ll use a pseudocode call like test(a,b,c,d) to demonstrate how it would look like in regards to different conventions.

Keep in mind push is a pseudoinstruction that subtracts 4 from the stack pointer (ESP), and then writes the value onto the memory ESP points to. pop is the opposite: reads from the address ESP points to, and then adds 4.

cdecl

It’s used by many C compilers.

In Linux, the GNU Compiler Collection (GCC) sets the cdecl de facto standard.

Conditions

The registers ST0 through ST7 are empty when calling a function.

ST1 through ST7 are empty upon exiting a function, and if no floating point is returned, ST0 is empty too.

Arguments and return values

Arguments are passed on the stack, right-to-left (C style).

For returns, integers and memory addresses are in EAX, whereas floating points will be in the ST0 x87 register.

Clean-up

Registers EAX, ECX and EDX are caller-saved, and the rest are callee-saved.

The stack pointer (ESP) is cleaned by the caller.

Pseudocode assembly

push  d
push  c
push  b
push  a
call  test
add   esp, 16

The result will be in EAX, and floating point values in ST*.

syscall

Similar to cdecl.

It’s the convention for the 32 bit OS/2 API.

Arguments and return values

Arguments are passed on the stack, right-to-left (C style). The size of the parameter list in doublewords is passed in AL.

Return values are in EAX.

Clean-up

ECX, and EDX are cleared upon exiting the function call.

The stack pointer (ESP) is cleaned by the caller.

Pseudocode assembly

mov   4, al
push  d
push  c
push  b
push  a
call  test
add   esp, 16
mov   0, al

The result will be in EAX, and floating point values in ST*.

Microsoft fastcall

This convention is better than the previous two in terms of performance, hence its “fast” prefix.

However, the fastcall convention is not standarised, and varies a lot from compiler to compiler, so I’ll focus in the Microsoft implementation. In this case, GCC’s __msfastcall is similar.

Arguments and return values

First two arguments are passed left-to-right into ECX and EDX. The remaining arguments are pushed onto the stack from right to left.

Microsoft compilers behave differently for IA64 and AMD64 architectures, because of additional registers. They simply use their own x64 calling convention, regardless of what was specified to the compiler.

Clean-up

The callee is responsible for cleaning the stack, as well as the ECX and EDX registers.

Pseudocode assembly

mov   a, ecx
mov   b, edx
push  d
push  c
call  test

The result will be in EAX, and floating point values in ST*.