Reverse basic OS-specpacific (1)

Source: Internet
Author: User
Tags windows x64 windows x86

Reverse basic OS-specpacific (1)

Chapter 64 methods for passing Parameters

64.1 cdcel

This method of passing parameters is popular in C/C ++.

As shown in the following code snippet, the caller places parameters in the stack in reverse order: the last parameter, the second to the last parameter, and the first parameter. The caller must also restore the stack pointer (ESP) to the initial state after the function returns.

Listing64.1: cdecl

push arg3push arg2push arg1call functionadd esp, 12 ; returns ESP
64.2 stdcall

This call method is similar to cdecl. Except that the caller must use the RET x command to replace the RET command to set the ESP pointer to the initialization state, where x = arguments number * sizeof (int ). The caller does not need to adjust the stack pointer (ESP ).

Listing 64.2: stdcall

push arg3push arg2push arg1call functionfunction:... do something ...ret 12

This call method is everywhere in the win32 standard library, but win64 does not use this call method (for details, see the win64 section below ).

For example, We Can slightly modify the sample code 8.1 On page 91 to add a _ stdcall modifier.

int __stdcall f2 (int a, int b, int c){    return a*b+c;};

The compiled result is almost the same as 8.2, but you can see that it is returned through RET 12 rather than RET. At the same time, the caller has not adjusted the stack pointer (ESP ).

Therefore, it is easy to use the RETN n command to export the number of function parameters (n divided by 4 ).

Listing 64.3: MSVC 2010

_a$ = 8 ; size = 4_b$ = 12 ; size = 4_c$ = 16 ; size = 4_f2@12 PROC    push ebp    mov ebp, esp    mov eax, DWORD PTR _a$[ebp]    imul eax, DWORD PTR _b$[ebp]    add eax, DWORD PTR _c$[ebp]    pop ebp    ret 12 ; 0000000cH_f2@12 ENDP; ...    push 3    push 2    push 1    call _f2@12    push eax    push OFFSET $SG81369    call _printf    add esp, 8
64.2.1 variable parameter functions

Printf () series functions are probably the only series of functions with variable parameters in C/C ++, with the help of these functions, it is easy to clarify the important differences between the cdecl and stdcall call methods. Let's assume that the compiler knows the number of parameters that call the printf () function. In any case, when we call printf (), it already exists in the compiled MSVCRT. in DLL (we are talking about Windows), there is no information about how many parameters are passed, and the rest is to get the number of parameters through its format string. Therefore, if the printf () function is a stdcall call method function, it must calculate the number of parameters through the format string for restoring the stack pointer (ESP). This is a very dangerous situation, a programmer's mistake can cause the program to crash. Therefore, the cdecl call method for such functions is far more suitable than the stdcall call method.

64.3 fastcall

This is a method to pass some parameters through registers and other parameters through stacks. Its execution efficiency is better in some old cases than in cdecl/stdcall (because of the pressure on small stacks ). However, using this call method in modern CPUs may not necessarily achieve better performance.

Fastcall is not standardized, so the implementations of different compilers can be different. This is a well-known warning: if you have two DLL, the first of which calls the second DLL function, they are compiled by different compilers using the fastcall call method, there will be unpredictable consequences.

Both MSVC and GCC compilers pass the first and second parameters through ECX and EDX, and pass other parameters through the stack. The stack pointer must be restored to the initial state by the caller (similar to stdcall ).

Listing 64.4: fastcall

push arg3mov edx, arg2mov ecx, arg1call functionfunction:.. do something ..ret 4

For example, we can slightly modify the sample code of 8.1 to add a _ fastcall modifier.

int __fastcall f3 (int a, int b, int c){    return a*b+c;};

The compiled result is as follows:

Listing 64.5: Optimizing MSVC 2010/Ob0

_c$ = 8         ; size = 4@f3@12 PROC; _a$ = ecx; _b$ = edx    mov eax, ecx    imul eax, edx    add eax, DWORD PTR _c$[esp-4]    ret 4@f3@12 ENDP; ...    mov edx, 2    push 3    lea ecx, DWORD PTR [edx-1]    call @f3@12    push eax    push OFFSET $SG81390    call _printf    add esp, 8

We can see that the caller uses the ret n command to adjust the stack pointer (ESP ). This means that we can infer the number of parameters through this command.

64.3.1 GCC regparm

This is an optimization of the fastcall call method. Use the-mregparm compilation option to set how many parameters are passed through registers (up to 3 ). Therefore, the EAX, EDX, and ECX registers will be used.

Of course, if you specify that the number of parameters passed through the Register is smaller than three, these three registers are not used.

The caller needs to restore the stack pointer to the initial state.

For relevant examples, see (19.1.1 ).

64.3.2 Watcom/OpenWatcom Compiler

Here, it becomes a "register call Convention". The first four parameters are passed through EAX, EDX, EBX, and ECX. Other parameters are passed through the stack. You can add underscores (_) on the function name to differentiate the call conventions.

64.4 thiscall

This is a member function call Convention for passing the this pointer in C ++.

In MSVC, The this pointer is passed through the ECX register.

In GCC, the this pointer is passed through the first parameter. Therefore, it is obvious that an additional parameter is added to all member functions.

For more information, see (51.1.1 ).

64.5 x86-64 64.5.1 Windows x64

The method for passing function parameters in Win64 is similar to the fastcall call convention. The first four parameters are passed through the RCX, RDX, R8, and R9 registers, and other parameters are passed through the stack. The caller must also reserve 32 bytes or 4 64-bit space so that the caller can save the first four parameters. Short functions may directly use the values passed through registers, but more likely they will be used after they are saved.

The caller must also be responsible for restoring the stack pointer.

This call Convention is also used for DLL on Windows x86-64 bit systems (instead of Win32's stdcall ).

Example

#include <stdio.h>void f1(int a, int b, int c, int d, int e, int f, int g){    printf ("%d %d %d %d %d %d %d\n", a, b, c, d, e, f, g);};int main(){    f1(1,2,3,4,5,6,7);};

Listing 64.6: MSVC 2012/0 B

$SG2937 DB '%d %d %d %d %d %d %d', 0aH, 00Hmain PROC    sub rsp, 72                     ; 00000048H    mov DWORD PTR [rsp+48], 7    mov DWORD PTR [rsp+40], 6    mov DWORD PTR [rsp+32], 5    mov r9d, 4    mov r8d, 3    mov edx, 2    mov ecx, 1    call f1    xor eax, eax    add rsp, 72                     ; 00000048H    ret 0main ENDPa$ = 80b$ = 88c$ = 96d$ = 104e$ = 112f$ = 120g$ = 128f1 PROC$LN3:    mov DWORD PTR [rsp+32], r9d    mov DWORD PTR [rsp+24], r8d    mov DWORD PTR [rsp+16], edx    mov DWORD PTR [rsp+8], ecx    sub rsp, 72                     ; 00000048H    mov eax, DWORD PTR g$[rsp]    mov DWORD PTR [rsp+56], eax    mov eax, DWORD PTR f$[rsp]    mov DWORD PTR [rsp+48], eax    mov eax, DWORD PTR e$[rsp]    mov DWORD PTR [rsp+40], eax    mov eax, DWORD PTR d$[rsp]    mov DWORD PTR [rsp+32], eax    mov r9d, DWORD PTR c$[rsp]    mov r8d, DWORD PTR b$[rsp]    mov edx, DWORD PTR a$[rsp]    lea rcx, OFFSET FLAT:$SG2937    call printf    add rsp, 72                     ; 00000048H    ret 0    f1 ENDP

Here we can clearly see how these seven parameters are passed: four parameters are passed through registers, and the other three are passed through stacks. The disassembly code of f1 () Stores parameters in the "Reserved" stack space from the very beginning. The purpose of doing so is that the compiler cannot ensure that there are enough registers available, if this is not done, the four registers will be occupied by parameters until function execution ends. Finally, it is the caller's responsibility to reserve stack space.

Listing 64.7: Optimizing MSVC 2012/0 B

$SG2777 DB '%d %d %d %d %d %d %d', 0aH, 00Ha$ = 80b$ = 88c$ = 96d$ = 104e$ = 112f$ = 120g$ = 128f1 PROC$LN3:    sub rsp, 72                     ; 00000048H    mov eax, DWORD PTR g$[rsp]    mov DWORD PTR [rsp+56], eax    mov eax, DWORD PTR f$[rsp]    mov DWORD PTR [rsp+48], eax    mov eax, DWORD PTR e$[rsp]    mov DWORD PTR [rsp+40], eax    mov DWORD PTR [rsp+32], r9d    mov r9d, r8d    mov r8d, edx    mov edx, ecx    lea rcx, OFFSET FLAT:$SG2777    call printf    add rsp, 72                     ; 00000048H    ret 0f1 ENDPmain PROC    sub rsp, 72                     ; 00000048H    mov edx, 2    mov DWORD PTR [rsp+48], 7    mov DWORD PTR [rsp+40], 6    lea r9d, QWORD PTR [rdx+2]    lea r8d, QWORD PTR [rdx+1]    lea ecx, QWORD PTR [rdx-1]    mov DWORD PTR [rsp+32], 5    call f1    xor eax, eax    add rsp, 72                     ; 00000048H    ret 0main ENDP

If we use the compile optimization switch to compile the above example, its disassembly code is almost the same, but the reserved stack space will not be used, because the reserved stack space is not required here.

In addition, we can see how MSVC 2012 uses the LEA command to optimize the code (A.6.2 ).

I am not sure whether it is worth doing so.

For more examples, see (74.1)

Transfer of this pointer (C/C ++)

This pointer is passed through RCX, and the first parameter of the member function is passed through RDX. For more examples, see (51.1.1 ).

64.5.2 Linux x64

Linux x86-64 delivers parameters in almost the same way as Windows. However, we use six registers instead of four registers to transmit parameters (RDI, RSI, RDX, RCX, R8, R9). In addition, there is no reserved stack space. Although, you can save the register value to the stack if it needs/wants.

Listing 64.8: Optimizing GCC 4.7.3

.LC0:    .string "%d %d %d %d %d %d %d\n"f1:    sub rsp, 40    mov eax, DWORD PTR [rsp+48]    mov DWORD PTR [rsp+8], r9d    mov r9d, ecx    mov DWORD PTR [rsp], r8d    mov ecx, esi    mov r8d, edx    mov esi, OFFSET FLAT:.LC0    mov edx, edi    mov edi, 1    mov DWORD PTR [rsp+16], eax    xor eax, eax    call __printf_chk    add rsp, 40    retmain:    sub rsp, 24    mov r9d, 6    mov r8d, 5    mov DWORD PTR [rsp], 7    mov ecx, 4    mov edx, 3    mov esi, 2    mov edi, 1    call f1    add rsp, 24    ret

Note: The value here is written to the 32-bit register (EAX...) instead of the entire 64-bit register (RAX ...). This is because 32-bit is automatically cleared when the data is written to the 32-bit register. It is said that this is to facilitate code porting to x86-64.

64.6 return float and double values

Except Win64, other float and double values are returned through the ST (0) Register in FPU. In Win64, float and double values are returned through the XMM0 register.

64.7 modify parameters

Sometimes C/C ++ programmers (although not just these people) may ask what happens if they happen to have modified the parameters? The answer is very simple. These parameters are stored in the stack. When you modify the parameters, you modify the content in the stack, callers do not use the called functions after they exit (at least not in my practice ).

#include <stdio.h>void f(int a, int b){    a=a+b;    printf ("%d\n", a);};

Listing 64.9: MSVC 2012

_a$ = 8     ; size = 4_b$ = 12    ; size = 4_f PROC    push ebp    mov ebp, esp    mov eax, DWORD PTR _a$[ebp]    add eax, DWORD PTR _b$[ebp]    mov DWORD PTR _a$[ebp], eax    mov ecx, DWORD PTR _a$[ebp]    push ecx    push OFFSET $SG2938 ; '%d', 0aH    call _printf    add esp, 8    pop ebp    ret 0_f END

Yes. You can modify the parameters as needed. Of course, it is not a reference (references) (51.3) of C ++, and if you do not modify the data pointed to by the pointer. Modifying parameters does not affect the current function.

Theoretically, after the caller's function returns, the caller can obtain, modify, and use it. If it is directly written in assembly language. However, C/C ++ does not provide any access to them.

64.8 Use Pointer function parameters

... More interestingly, it is possible to take a pointer to a function parameter in a program and pass it to another function.

#include <stdio.h>// located in some other filevoid modify_a (int *a);void f (int a){    modify_a (&a);    printf ("%d\n", a);};

It is hard to understand if it is implemented until we see its disassembly code:

Listing 64.10: Optimizing MSVC 2010

$SG2796 DB '%d', 0aH, 00H_a$ = 8_f PROC    lea eax, DWORD PTR _a$[esp-4]   ; just get the address of value in local stack    push eax                        ; and pass it to modify_a()    call _modify_a    mov ecx, DWORD PTR _a$[esp]     ; reload it from the local stack    push ecx                        ; and pass it to printf()    push OFFSET $SG2796             ; '%d'    call _printf    add esp, 12    ret 0_f ENDP

The address of a passed to another function in the stack space. The function modifies the pointer to the value and then calls printf () to print the modified value.

Careful readers may ask, how do I pass function pointer parameters using the register parameter passing call conventions?

This is a situation where the shadow space is used. The input parameter value is first copied from the register to the Shadow space in the local stack, and then the address is passed to other functions.

Listing 64.11: Optimizing MSVC 2012x64

$SG2994 DB '%d', 0aH, 00Ha$ = 48f PROC    mov DWORD PTR [rsp+8], ecx      ; save input value in Shadow Space    sub rsp, 40    lea rcx, QWORD PTR a$[rsp]      ; get address of value and pass it to modify_a()    call modify_a    mov edx, DWORD PTR a$[rsp]      ; reload value from Shadow Space and pass it to printf()    lea rcx, OFFSET FLAT:$SG2994    ; '%d'    call printf    add rsp, 40    ret 0f ENDP

GCC also stores the passed parameters in the local stack space:

Listing 64.12: Optimizing GCC 4.9.1 x64

.LC0:.string "%d\n"f:    sub rsp, 24    mov DWORD PTR [rsp+12], edi     ; store input value to the local stack    lea rdi, [rsp+12]               ; take an address of the value and pass it to modify_a()    call modify_a    mov edx, DWORD PTR [rsp+12]     ; reload value from the local stack and pass it to printf()    mov esi, OFFSET FLAT:.LC0       ; '%d'    mov edi, 1    xor eax, eax    call __printf_chk    add rsp, 24    ret

The GCC of ARM64 does the same thing, but this space is called a register reserve:

f:    stp x29, x30, [sp, -32]!    add x29, sp, 0      ; setup FP    add x1, x29, 32     ; calculate address of variable in Register Save Area    str w0, [x1,-4]!    ; store input value there    mov x0, x1          ; pass address of variable to the modify_a()    bl modify_a    ldr w1, [x29,28]    ; load value from the variable and pass it to printf()    adrp x0, .LC0 ; '%d'    add x0, x0, :lo12:.LC0    bl printf ; call printf()    ldp x29, x30, [sp], 32    ret.LC0:    .string "%d\n"

By the way, the use of a shadow-like space is also mentioned here (46.1.2 ).

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.