Click here to Skip to main content
15,860,861 members
Articles / Desktop Programming / ATL
Article

ATL Under the Hood Part 4

Rate me:
Please Sign up or sign in to vote.
4.89/5 (29 votes)
4 Jun 2002CPOL8 min read 121.7K   101   13
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.

ASM
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

ASM
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

ASM
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.

ASM
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.

ASM
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.

ASM
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

ASM
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.

ASM
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

ASM
; 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.

ASM
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

ASM
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
_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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader American Institute for Research
United States United States
Working as a Team leader in American Institute for Research

Comments and Discussions

 
Generalgood job Pin
fancycow31-May-12 17:04
fancycow31-May-12 17:04 
GeneralHelp please [modified] Pin
Mizan Rahman19-Nov-09 1:55
Mizan Rahman19-Nov-09 1:55 
Generalreally useful Pin
Arsineh Boodaghian27-Apr-09 5:31
Arsineh Boodaghian27-Apr-09 5:31 
QuestionQuestions about _QIThunk Pin
Bill Tian7-May-07 18:50
Bill Tian7-May-07 18:50 
GeneralVery neat Pin
Billiebub4-Apr-07 6:26
Billiebub4-Apr-07 6:26 
GeneralHmm Pin
Polity4h25-Feb-07 7:42
Polity4h25-Feb-07 7:42 
QuestionStack description - mistake Pin
domel91121-Nov-05 2:44
domel91121-Nov-05 2:44 
Generalyou should draw stack! Pin
GoodSword4-Nov-03 15:54
sussGoodSword4-Nov-03 15:54 
Generali am a faithful reader of your articles Pin
alidiedie26-Feb-03 15:53
alidiedie26-Feb-03 15:53 
QuestionATL? Pin
henchook29-Sep-02 23:14
henchook29-Sep-02 23:14 
AnswerRe: ATL? Pin
Zeeshan Amjad18-Oct-02 11:18
Zeeshan Amjad18-Oct-02 11:18 
GeneralRe: ATL? Pin
Jelly Fish24-Dec-02 16:39
Jelly Fish24-Dec-02 16:39 
GeneralRe: ATL? Pin
Anonymous14-Nov-04 23:08
Anonymous14-Nov-04 23:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.