As of x86, function execution result is usually returned 1 in the EAX register. If it is byte type or character (char) —then in the lowest register EAX part— AL. If function returns float number, the FPU register ST(0) is to be used instead. In ARM, result is usually returned in the R0 register.
By the way, what if returning value of the main() function will be declared not as int but as void?
so-called startup-code is calling main() roughly as:
push envp push argv push argc call main push eax call exit
In other words:
exit(main(argc,argv,envp));
If you declare main() as void and nothing will be returned explicitly (by return statement), then something random, that was stored in the EAX register at the moment of the main() finish, will come into the sole exit() function argument. Most likely, there will be a random value, leaved from your function execution. So, exit code of program will be pseudorandom.
I can illustrate this fact. Please notice, the main() function has void type:
#include <stdio.h>
void main() {
printf ("Hello, world!\n");
};
Let‘s compile it in Linux.
GCC 4.8.1 replaced printf() to puts() (we saw this before: 2.3.3), but that‘s OK, sinceputs() returns number of char-acters printed, just like printf(). Please notice that EAX is not zeroed before main() finish. This means, EAX value at the main() finish will contain what puts() leaved there.
Listing 8.1: GCC 4.8.1 .LC0:
.string "Hello, world!"
main:
push ebp
mov ebp, esp and esp, -16 sub esp, 16
mov DWORD PTR [esp], OFFSET FLAT:.LC0
call puts
1See also: MSDN: Return Values (C++): http://msdn.microsoft.com/en-us/library/7572ztz4.aspx
61
CHAPTER 8. ONE MORE WORD ABOUT RESULTS RETURNING.
Let‘s back to the fact the returning value is leaved in theEAX register. That is why old C compilers cannot create functions capable of returning something not fitting in one register (usually type int) but if one needs it, one should return information via pointers passed in function arguments. Now it is possible, to return, let‘s say, whole structure, but still it is not very pop-ular. If function must return a large structure, caller must allocate it and pass pointer to it via first argument, transparently for programmer. That is almost the same as to pass pointer in first argument manually, but compiler hide this.
Small example:
Macro name for internal variable passing pointer to structure is $T3853 here.
This example can be rewritten using C99 language extensions:
struct s {
62
CHAPTER 8. ONE MORE WORD ABOUT RESULTS RETURNING.
int a;
int b;
int c;
};
struct s get_some_values (int a) {
return (struct s){.a=a+1, .b=a+2, .c=a+3};
};
Listing 8.3: GCC 4.8.1 _get_some_values proc near
ptr_to_struct = dword ptr 4
a = dword ptr 8
mov edx, [esp+a]
mov eax, [esp+ptr_to_struct]
lea ecx, [edx+1]
mov [eax], ecx lea ecx, [edx+2]
add edx, 3 mov [eax+4], ecx mov [eax+8], edx retn
_get_some_values endp
As we may see, the function is just filling fields in the structure, allocated by caller function. So there are no performance drawbacks.
63
CHAPTER 9. POINTERS
Chapter 9
Pointers
Pointers are o_en used to return values from function (recall scanf() case (6)). For example, when function should return two values.
9.1 Global variables example
#include <stdio.h>
void f1 (int x, int y, int *sum, int *product) {
*sum=x+y;
*product=x*y;
};
int sum, product;
void main() {
f1(123, 456, &sum, &product);
printf ("sum=%d, product=%d\n", sum, product);
};
This compiling into:
Listing 9.1: Optimizing MSVC 2010 (/Ox /Ob0) COMM _product:DWORD
COMM _sum:DWORD
$SG2803 DB ‘sum=%d, product=%d‘, 0aH, 00H
_x$ = 8 ; size = 4
_y$ = 12 ; size = 4
_sum$ = 16 ; size = 4
_product$ = 20 ; size = 4
_f1 PROC
mov ecx, DWORD PTR _y$[esp-4]
mov eax, DWORD PTR _x$[esp-4]
lea edx, DWORD PTR [eax+ecx]
imul eax, ecx
mov ecx, DWORD PTR _product$[esp-4]
push esi
mov esi, DWORD PTR _sum$[esp]
mov DWORD PTR [esi], edx mov DWORD PTR [ecx], eax
pop esi
ret 0
_f1 ENDP
64
9.1. GLOBAL VARIABLES EXAMPLE CHAPTER 9. POINTERS
_main PROC
push OFFSET _product push OFFSET _sum
push 456 ; 000001c8H
push 123 ; 0000007bH
call _f1
mov eax, DWORD PTR _product mov ecx, DWORD PTR _sum
push eax
push ecx
push OFFSET $SG2803
call DWORD PTR __imp__printf
add esp, 28 ; 0000001cH
xor eax, eax
ret 0
_main ENDP
Let‘s see this in OllyDbg: fig. 9.1. At first, global variables addresses are passed intof1(). We can click―Follow in dump‖ on the stack element, and we will see a place in data segment allocated for two variables. These variables are cleared, because non-initialized data (BSS1) are cleared before execution begin. They are residing in data segment, we can be sure it is so, by pressing Alt-M and seeing memory map: fig. 9.5.
Let‘s trace (F7) until execution off1()fig. 9.2. Two values are seen in the stack 456 (0x1C8) and 123 (0x7B), and two global variables addresses as well.
Let‘s trace until the end off1(). At the window at le_ we see how calculation results are appeared in the gloval variables fig. 9.3.
Now values of global variables are loaded into registers for passing into printf(): fig. 9.4.
Figure 9.1: OllyDbg: global variables addresses are passing into f1()
1Block Started by Symbol
65
9.1. GLOBAL VARIABLES EXAMPLE CHAPTER 9. POINTERS
Figure 9.2: OllyDbg: f1()is started
Figure 9.3: OllyDbg: f1()finishes
66
9.2. LOCAL VARIABLES EXAMPLE CHAPTER 9. POINTERS
Figure 9.4: OllyDbg: global variables addresses are passed into printf()
Figure 9.5: OllyDbg: memory map
9.2 Local variables example
Let‘s rework our example slightly:
Listing 9.2: now variables are local void main()
{
int sum, product; // now variables are here
f1(123, 456, &sum, &product);
printf ("sum=%d, product=%d\n", sum, product);
};
f1()function code will not changed. Only main() code will:
Listing 9.3: Optimizing MSVC 2010 (/Ox /Ob0)
_product$ = -8 ; size = 4
_sum$ = -4 ; size = 4
_main PROC
; Line 10
sub esp, 8
; Line 13
67
9.2. LOCAL VARIABLES EXAMPLE CHAPTER 9. POINTERS lea eax, DWORD PTR _product$[esp+8]
push eax
lea ecx, DWORD PTR _sum$[esp+12]
push ecx
push 456 ; 000001c8H
push 123 ; 0000007bH
call _f1
; Line 14
mov edx, DWORD PTR _product$[esp+24]
mov eax, DWORD PTR _sum$[esp+24]
push edx
push eax
push OFFSET $SG2803
call DWORD PTR __imp__printf
; Line 15
xor eax, eax
add esp, 36 ; 00000024H
ret 0
Let‘s again take a look into OllyDbg. Local variable addresses in the stack are0x35FCF4 and 0x35FCF8. We see how these are pushed into the stack: fig. 9.6.
f1()is started. Random garbage are at 0x35FCF4 and 0x35FCF8 so far fig. 9.7.
f1()finished. There are 0xDB18 and 0x243 now at 0x35FCF4 and 0x35FCF8 addresses, these values are f1()function result.
Figure 9.6: OllyDbg: addresses of local variables are pushed into the stack
68
9.3. CONCLUSION CHAPTER 9. POINTERS
Figure 9.7: OllyDbg: f1()starting
Figure 9.8: OllyDbg: f1()finished
9.3 Conclusion
f1()can return results to any place in memory, located anywhere. This is essence and usefulness of pointers.
By the way, C++ references works just in the same way. Read more about them: (29.3).
69
CHAPTER 10. CONDITIONAL JUMPS