Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ATL Under the Hood Part 4

0.00/5 (No votes)
4 Jun 2002 1  
Contininuing the ATL Under the Hood series to explain the inner workings of ATL

Introduction

Till now we haven't discuss anything about assembly language. But we can't avoid it so long if we really want to know what is going on under the hood of ATL. Because ATL use some low level technique as well as some inline assembly language to make it as small and as fast as possible. I assume that reader already have basic knowledge of assembly language so I will only concentrate on my topic and not try to write another tutorial of assembly language. If you don't know enough assembly language then I recommends taking a look at Matt Pietrek's Article "Under The Hood" in Feb 1998 issue of Microsoft System Journal, it gives you enough information about the assembly language.

To start out tour take a look at this simple program

Program 55

void fun(int, int) {
}

int main() {
	fun(5, 10);
	return 0;
}
Now compile it on command line with command line compiler cl.exe. Compile it with -FAs switch. E.g. if this program name is prog55 then compile it this way

Cl -FAs prog55.cpp

This will generate a file with the same name but .asm extension contains the assembly language code of the following program. Now take a look at generated output file. Lets discuss the calling of function first. The assembly code to call this function is something like this.

	push	10				; 0000000aH

	push	5
	call	?fun@@YAXHH@Z			; fun

The parameters of function are pushed on the stack from right to left order and then call function. But the name of function is little bit different then our given function name. This is because C++ compiler decorates the name of function to perform function overloading. Let's change a program little bit and overload the function to take a look at the behavior of the code.

Program 56
void fun(int, int) {
}

void fun(int, int, int) {
}

int main() {
	fun(5, 10);
	fun(5, 10, 15);
	return 0;
}

Now the assembly languages of calling both of the functions are something like this

	push	10					; 0000000aH

	push	5
	call	?fun@@YAXHH@Z				; fun


	push	15					; 0000000fH

	push	10					; 0000000aH

	push	5
	call	?fun@@YAXHHH@Z				; fun

Take a look at the name of the function, we write both function with the same name but compiler decorates these functions itself to do function overloading.

If you don't want to decorate the function name then you can use extern "C" with function. Let's see little bit change in the program.

Program 57
extern "C" void fun(int, int) {
}

int main() {
	fun(5, 10);
	return 0;
}

The assembly language code of this function is

	push	10					; 0000000aH

	push	5
	call	_fun

This means that now you can't overload the function with C linkage. Take a look at the following program

Program 58
extern "C" void fun(int, int) {
}

extern "C" void fun(int, int, int) {
}

int main() {
	fun(5, 10);
	return 0;
}

This program give compilation error because function overloading is not supported in C language and you are going to make the two function with the same name and tells the compiler to not decorate its name i.e. use C language linkage not C++ linkage.

Now take a look what code compiler is generated for our do noting function. Here is the code which compiler generate for our function.

	push	ebp
	mov	ebp, esp
	pop	ebp
	ret	0

Before go into further detail take a look at the last statement of the function i.e. ret 0. Why it is 0? Or can it be other than 0? As we have seen all the parameters which we pass to the function are in fact pushed into the stack. What will be the effect on register when you or compiler pushes something on stack? Take a look at the following simple program to see the behavior of this. I use the printf rather than cout to avoid the overhead of cout.

Program 59
#include <cstdio>


int g_iTemp;

int main() {

	fun(5, 10);

	_asm mov g_iTemp, esp
	printf("Before push %d\n", g_iTemp);

	_asm push eax
	_asm mov g_iTemp, esp
	printf("After push %d\n", g_iTemp);
	_asm pop eax

	return 0;
}

The output of this program is

Before push 1244980
After push 1244976

This program displays the value of ESP register before and after push some value into the stack. This clearly shows that when you push something into the stack then it grows downward in the memory.

Now there is a question, who is goring to restore the stack pointer when we pass parameter into the function, the function itself or the caller of that function? In fact both cases are possible and this is the difference between standard calling convention and c calling convention. Take a look at the very next statement after calling the function.

	push	10					; 0000000aH

	push	5
	call	_fun
	add	esp, 8

Here two parameters are passed in the function, so the stack pointer is subtract 8 bytes after pushing two values into the stack. Now in this program it is the responsibility of the caller of the function to set the stack pointer. This is called C Calling convention. In this calling convention you can pass variable no of argument, because caller knows how many parameter is being passed to the function, so it can set the stack pointer itself.

However if standard calling convention is selected then it is the responsibility of the callee to clear the stack. So in this case variable not of argument can't be passed in the function, because there is no way to know the function that how much parameter is passed, so it cal set the stack pointer appropriately.

Take a look at the following program to see the behavior of standard calling convention.

Program 60
extern "C" void _stdcall fun(int, int) {
}

int main() {

	fun(5, 10);
	return 0;
}

Now take a look at the calling of function.

	push	10					; 0000000aH

	push	5
	call	_fun@8

Here @ with the function name shows that this is standard calling convention and 8 show the no of bytes pushed into the stack. So no of argument can be calculated by dividing this no by 4.

Here is the code of our do nothing function

	push	ebp
	mov	ebp, esp
	pop	ebp
	ret	8

This function set the stack pointer itself with the help of "ret 8" instruction before leaving it.

Now explore the code which compiler generate for us. Compiler inserts this code to make stack frame so it can access the parameter and local variable in standard way. Stack frame is a memory area reserved for the function to store the information about the parameter, local variable and return address. Stack frame is always created when new function is called and destroys when function returns. On 8086 architecture EBP register is used to store the address of stack frame, sometimes called stack pointer.

So compiler first save the address of previous stack frame and then create new stack frame by using the value of ESP. And before return the function the value of old stack frame is preserved.

Now take a look what is in the stack frame. Stack frame have all the parameter at +ve side of EBP and all the local variable at -ve side of EBP.

So the return address of function is store at EBP and the value of previous Stack frame is store at EBP + 4. Now take a look at the example, which have two parameter and three local variables.

Program 61
extern "C" void fun(int a, int b) {
	int x = a;
	int y = b;
	int z = x + y;
	return;
}

int main() {
	fun(5, 10);
	return 0;
}

And now take a look at the compiler generated code of the function.

	push	ebp
	mov	ebp, esp
	sub	esp, 12					; 0000000cH


	; int x = a;

	mov	eax, DWORD PTR _a$[ebp]
	mov	DWORD PTR _x$[ebp], eax

	; int y = b;

	mov	ecx, DWORD PTR _b$[ebp]
	mov	DWORD PTR _y$[ebp], ecx

	; int z = x + y;

	mov	edx, DWORD PTR _x$[ebp]
	add	edx, DWORD PTR _y$[ebp]
	mov	DWORD PTR _z$[ebp], edx

	mov	esp, ebp
	pop	ebp
	ret	0

Now what is _x, _y etc. It is define just above the function definition something like this

_a$ = 8
_b$ = 12
_x$ = -4
_y$ = -8
_z$ = -12

Means you can read this code something like this

	; int x = a;

	mov	eax, DWORD PTR [ebp + 8]
	mov	DWORD PTR [ebp - 4], eax

	; int y = b;

	mov	ecx, DWORD PTR [ebp + 12]
	mov	DWORD PTR [ebp - 8], ecx

	; int z = x + y;

	mov	edx, DWORD PTR [ebp - 4]
	add	edx, DWORD PTR [ebp - 8]
	mov	DWORD PTR [ebp - 12], edx

Means the address of parameters a and b are EBP + 8 and EBP + 12 respectively. And the value of x, y and z are store at memory location EBP - 4, EBP - 8, EBP - 12 respectively.

After armed with this knowledge lets play a game with the parameter of the functions. Let's take a look at this simple program.

Program 62
#include <cstdio>


extern "C" int fun(int a, int b) {
	return a + b;
}

int main() {

	printf("%d\n", fun(4, 5));
	return 0;
}

The output of this program is expected. Out put of this program is "9". Now change a program little bit.

Program 63
#include <cstdio>


extern "C" int fun(int a, int b) {
	_asm mov dword ptr[ebp+12], 15
	_asm mov dword ptr[ebp+8], 14
	return a + b;
}

int main() {

	printf("%d\n", fun(4, 5));
	return 0;
}

The output of this program is "29". We know the address of parameter and in this program we change the value of parameter. And when we add those variables then new values i.e. 15 and 14 are added.

VC has naked attributed for function. If you specify any function to naked then it won't generate prolog and epilog code for that function. Now what is prolog and epilog code? Prolog is an English word mean "Opening", yes it is a name of programming language too, which is used in AI, but there is no relation between that programming language and prolog code generated by the compiler. This is a code which compiler automatically inserted in the opening of the function calling to set the stack frame. Take a look at assembly language code generated by program 61. In the beginning of the function compiler automatically insert the following code to set the stack frame.

	push	ebp
	mov	ebp, esp
	sub	esp, 12					; 0000000cH

This code is called prolog code. And in the same way the code inserted at the end of function is called Epilog code. In the same program the Epilog code generated by the compiler is

	mov	esp, ebp
	pop	ebp
	ret	0

Now take a look at the function with naked attribute

Program 64
extern "C" void _declspec(naked) fun() {
	_asm ret
}

int main() {

	fun();
	return 0;
}

The code of the function fun, which is generated by the compiler, is something like this.

	_asm ret

Means there is no prolog and epilog code in this function. In fact, there are rules of naked function, i.e. you can't declare automatic variable in naked function, because for this compiler have to generate the code for you and in naked function compiler wont generate any code for you. In fact you have to write the ret statement yourself otherwise program will be crash. You even can't write return statement in the naked function. Why? Because when you return something from the function, then compiler puts its value in eax register. So it means compiler have to generate the code for your return statement. Let's take a look at this simple program to understand the working of return value from the function.

Program 64
#include <cstdio>


extern "C" int sum(int a, int b) {
	return a + b;
}

int main() {

	int iRetVal;
	sum(3, 7);
	_asm mov iRetVal, eax
	printf("%d\n", iRetVal);
	return 0;
}

The output of this program is "10". Here we haven't directly use the return value of the function, instead of this we copy the value of eax in the variable just after calling the function.

Now write our whole function naked with prolog and epilog code which return the value of two variables after return it.

Program 65
#include <cstdio>


extern "C" int _declspec(naked) sum(int a, int b) {

	// prolog code

	_asm push ebp
	_asm mov ebp, esp

	// code for add two variables and return

	_asm mov eax, dword ptr [ebp + 8]
	_asm add eax, dword ptr [ebp + 12]
	
	// epilog code

	_asm pop ebp
	_asm ret
}

int main() {

	int iRetVal;
	sum(3, 7);
	_asm mov iRetVal, eax
	printf("%d\n", iRetVal);
	return 0;
}

The output of this program is "10" i.e. the sum of two parameter 3 and 7.

This attributed is used in ATLBASE.H file to implement the member of _QIThunk structure. This is structure is used to debug reference counting the ATL program when _ATL_DEBUG_INTERFACES are defined.

I hope to explore some other mysterious of ATL in next article.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here