Related
So, I have a task that I need to convert from string for example string "542215" to int 542215 or with any other ASCII symbols. I am kindof new to assembly programming so, I have almost no clue to what I am doing with it, but I wrote my experimental code.
int main(int argc, char** argv)
{
int Letters;
char* argv1 = argv[1];
if (argc < 2) {
printf("Nepateiktas parametras*/\n");
return(0);
}
__asm {
push eax
push ebx
push ecx
xor ecx, ecx
mov eax, argv1
NextChar:
movzx eax, byte ptr [esi + ecx]
test eax, eax
jz Done
push ecx
sub eax, 48
push eax
call PrintIt
pop ecx
inc ecx
jmp NextChar
Done:
pop ecx
pop ebx
pop eax
PrintIt:
mov[Letters], eax
pop ecx
pop ebx
pop eax
};
printf("Count of letters in string %s is %d\n", argv[1], Letters);
return(0);
}
and I am getting error that "Run-time check failure #0 - The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.
This error basicly gives me 0 ideas what is going on, or what even ESP is, so any help would be appreciated with my code.
you don't need to push/pop registers around your whole asm statement; MSVC already notices which registers you modify.
You're popping the registers twice at the end, when execution falls through from Done: to PrintIt. Or even worse, on strings of non-zero length, you're leaving the asm statement via a path of execution that does 3 pushes at the start, push ecx, push eax, call PrintInt, and then 3 pops. So one of your register-restores is actually reloading the return address pushed by call, and leaving 3 pushed values on the stack.
If you don't know that ESP is the stack pointer, one of x86's 8 general-purpose integer registers, you need to continue reading an intro to basic x86 asm before using stack instructions like push and call.
Also something about the fact that labels aren't the same thing as functions, and how execution continues to the next instruction.
And BTW, int->string will involve a multiply by 10. See NASM Assembly convert input to integer? for the standard algorithm and an asm implementation.
Hi heads up this is a homework. I'm given an assembly generated by MSVC 32-bit Release with optimizations on, and I'm supposed to decode it back into C++. I've included the top of the function to the line I'm having problems with. The comments are mine, which I'm wrote while trying to understand this.
Note: Code is supposedly generated from C++. Not traditional ASM.
Note 2: There is one area of undefined behavior in the code.
Here are the lines I'm stuck with
TheFunction: ; TheFunction(int* a, int s);
0F2D4670 push ebp ; Push/clear/save ebp
0F2D4671 mov ebp,esp ; ebp now points to top of stack
0F2D4673 push ecx ; Push/clear/save ecx
0F2D4674 push ebx ; Push/clear/save ebs
0F2D4675 push esi ; Push/clear/save esi
0F2D4676 mov ebx,edx ; ebx = int s
0F2D4678 mov esi,1 ; esi = 1
0F2D467D push edi ; calling convention ; Push/clear/save edi
0F2D467E mov edi,dword ptr [a (0F2D95E8h)] ; edi = a[0]
0F2D4684 cmp ebx,esi ; if(s < 1)
0F2D4686 jl SomeFunction+3Ch (0F2D46ACh) ; Jump to return
0F2D4688 nop dword ptr [eax+eax] ; !! <-- No op involving dereferencing? What does this do?
0F2D4690 mov eax,dword ptr [edi+esi*4-4] ; !! <-- edi is *a, while esi is 1. There is no address
here!
..... More code but I've figured these out ....
I've more or less got the gist of the function. Its a function that takes a pointer to an int, with an underlying array, and a size. It then goes through each element in the array from last to first, adding to each subsequent one and printing it out. However, I still haven't got the details down and need help
Two questions, both at the end of the code snippet. What does no op on a dereference pointer do, and am I reading the last line in that its attempting to dereference something not in memory?
The nop dword ptr [eax+eax] instruciton does nothing. It doesn't even access the memory location given by the operand. It literally performs no operation.
It's just there so the next instruction is aligned to a 16-byte boundary. You'll notice that next instruction address is 0F2D4690 which ends with 0 which means it's 16-byte aligned. This can improve the performance of loops. Somewhere there will be an instruction that jumps back to 0F2D4690 as part of a loop. This particular form of a NOP instruction is used because it encodes a single NOP instruction in 8 bytes.
There is no corresponding C++ code for this instruction. You shouldn't try to represent it in your C++ code, just ignore it.
Also note that your comment for mov edi,dword ptr [a (0F2D95E8h)] is incorrect. Instead of being edi = a[0] it's simply edi = a. The variable a isn't a parameter at all, instead it's a global (or file level static) variable located at memory location 0F2D95E8h. This instruction just loads the value from memory.
I'm currently having some unexplained register issues which I can't figure my guess is I'm moving registers improperly from one register to another one.
I'm trying to get the value of EDX into a test.myEDX which mostly always puts the wrong value of EDX into test.myEDX, but part of that EDX value seems right which is very strange some kind of Hi/Lo Bits issues I assume.
Let me explain what I'm trying to do.
First I hook a location in memory which contains some arbitrary assembly code.
I hook it by placing a hardware breakpoint at that location then I can see all the register values at that location in time.
Now I made a simple program that everytime the cpu goes into that breakpointed location I put the EDX value into a struct in C++.
As far as i am aware nothing should modify the EDX register while I am putting it into the struct unless putting values into the struct modifies the EDX which I tested isn't the case, I tested by moving the EDX first into EAX then passing EAX into struct which should have the same value as EDX and it's still wrong maybe EAX is used as well when putting data into the struct? I didn't go further then that in testing all registers to find which one isn't used, doubt that's even the problem anyways.
Another thing to take into consideration to actually do any operations like putting EDX into the struct I have to make in C++ the current EIP go into my naked function, I know from previously doing this stuff that naked functions don't modify any registers at all, they are simply used as a way to extend the current asm code at that EIP into much larger asm code without any trash that C++ would add when going into subroutines.
I also use PUSHAD and PUSHFD to restore previously set register values when I finish dumping EDX into struct it's all in a safe environment (as long as I don't use PUSH/POP's incorrectly of course) before I call POPFD then POPAD
I barely know any ASM but from looking at many examples I never see registers moved like this. (which obviously wouldn't make any sense to duplicate a register, but even the same register moved into 2 different addresses one after the one I haven't seen that).
MOV EBX, EDX
MOV ECX, EDX
But in fact I see something like this (which I thought was my problem why it wasn't working), (it wasn't, I am still clueless what I am doing wrong)
MOV EBX, EDX
MOV EAX, EDX //Theory: in order to move EDX a second time into ECX, I must not move EDX directly into ECX.
MOV ECX, EAX
I don't see any difference except now EAX is lost, but I still figured I have to re-route every multiple moving of the same register to multiple locations by moving it over and over before actually moving to the original location, silly but that's what I thought you had to do I still think this is the proper way to this job.
Either way this still hasn't helped me I still get wrong values.
I think either those option's AL BL registers screw up EDX or maybe TEST or JE messes it up I can't really tell, I pasted this code into OllyDBG and seems like nothing modified EDX mystery why EDX is wrong, Maybe the address updates it's value too slow? since it's based on RAM speed which doesn't sync up with CPU speed (all stupid theories of course).
Anyways that's about everything I can explain here is the code.
struct TestStruct {
int myEDX;
int mySetEDX;
} test;
extern bool optionOne = false;
extern bool optionTwo = false;
DWORD gotoDumpBackAddress = 0x40012345;
void __declspec( naked ) dump(void) {
__asm {
PUSHAD
PUSHFD
XOR EAX, EAX //zero EAX, used below as AL (optionOne)
XOR EBX, EBX //zero EBX, used below as BL (optionTwo)
MOV AL, optionOne //optionOne set
MOV BL, optionTwo //optionTwo set
TEST EAX, EAX //Tests if optionOne equals == 0, then je will be equal.
je gotoOptionOne //Jumps if optionOne equals 0.
TEST EBX, EBX //Tests if optionTwo equals == 0, then je will be equal.
je gotoOptionTwo //Jumps if optionTwo equals 0.
gotoOptionOne:
//This the default case (improper value in EDX..., I could just use address at [ESI+0x2] which is old EDX, which is risky since it's delayed (outdated)
MOV DWORD PTR DS:[ESI+0x2], EDX //(normal operation)
MOV test.myEDX, EDX //Stores freshest EDX to test.myEDX (wrong EDX value)
JMP finish //Clear temporary used registers and go back to next asm code
//Special case. (works mostly properly)
//Thing is EDX gets updated very frequently, Causes no side-effect only because
//[ESI+0x2] gets updated in a different location as well a bit later to renew the value.
//So it's not even noticeable, but when I run this at it's peak speeds, you start to see the flickering effect, which isn't normal, if I run peak speeds without hook.
//I eliminated the problem that the hook could cause the flicker effect since after
//I call a empty naked Hook with just return address to same location and disable hook
//Then re-enable hook and repeat step above (no flicker occurs).
gotoOptionTwo:
//MOV DWORD PTR DS:[ESI+0x2], EDX //(normal operation), omitted
MOV EAX, DWORD PTR DS:[ESI+0x2] //Old EDX, but atleast it's correct.
MOV test.myEDX, EAX //Stores old EDX into struct test.myEDX
MOV EAX, test.mySetEDX //Replace old EDX with what I wish it should be.
MOV DWORD PTR DS:[ESI+0x2], EAX //nySetEDX into what EDX should of did.
JMP finish //Clear temporary used registers and go back to next asm code
finish:
POPFD
POPAD
JMP gotoDumpBackAddress //return to starting location before dump + 1.
}
}
EDIT: Okay I haven't explained how I test this code out.
I'm not going to explain how I do the Hardware breakpoint, I don't want this method to be public on the internet, for my own security reasons in the future.
But it works by calling another DLL which communicates with a system driver.
But this should explain how I test it.
I run in a different thread in this DLL Injection.
void diffThread() {
while(1) {
if(GetAsyncKeyState(VK_NUMPAD0)) {
optionOne != optionOne;
Sleep(1000);
}
if(GetAsyncKeyState(VK_NUMPAD1)) {
optionTwo != optionTwo;
Sleep(1000);
}
Sleep(10);
}
}
bool __stdcall DllMain(HINSTANCE hInst,DWORD uReason,void* lpReserved)
{
if(uReason == DLL_PROCESS_ATTACH)
{
CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)&diffThread,0 ,NULL,NULL);
}
else if(uReason == DLL_PROCESS_DETACH)
{
ExitThread(0);
}
return true;
}
Okay honestly this is wrong on so many levels, I mean the code itself.
What you are trying to do is to force your way into C territory telling the compiler to back off and you do your own thing by handwriting the code. This may come handy sometimes but you have to keep in mind that you also loose some C features especially things that are done for you automatically and as far as I know the Microsoft compile just doesnt like you messing around in inline assmebly ( don't get me wrong the MASM compiler is great but it just doesn't play along well with the C compiler )
// doing this is not the best since the compiler may
// do some nasty stuff like aligning the struct that
// you have to take watch out for in asm
struct TestStruct {
int myEDX;
int mySetEDX;
} test;
extern bool optionOne = false;
extern bool optionTwo = false;
DWORD gotoDumpBackAddress = 0x40012345; // static memory address? this should fail like instantly, I dont really know what this is supposed to be
void __declspec( naked ) dump(void) {
__asm {
PUSHAD // you shouldn't normally push all registers only the ones you actually used!
PUSHFD
XOR EAX, EAX // this is how you zero out but honestly you can use EAX's low and high parts as 2 16bit regsiters instead of using 2 32bits for 2 bits of data
XOR EBX, EBX
MOV AL, optionOne //optionOne set
MOV BL, optionTwo //optionTwo set
TEST EAX, EAX // This check is meaning less because if gotoOptionTwo is false you move on regardless of gotoOpeionOne's value
je gotoOptionOne
TEST EBX, EBX // This test should always be true isn't it?
je gotoOptionTwo
gotoOptionOne:
// Assuming that ESI is coming from the system as you said that there is a
// breakpoint that invokes this code, do you even have access to that part of the memory?
MOV DWORD PTR DS:[ESI+0x2], EDX
MOV test.myEDX, EDX // this is just a C idom 'struct' this doesnt really exist in ASM
JMP finish
gotoOptionTwo:
MOV EAX, DWORD PTR DS:[ESI+0x2]
MOV test.myEDX, EAX
MOV EAX, test.mySetEDX
MOV DWORD PTR DS:[ESI+0x2], EAX
JMP finish
finish:
POPFD
POPAD
JMP gotoDumpBackAddress //return to starting location before dump + 1.
}
}
So to answer your question,
I think the biggest problem here is this line
MOV test.myEDX, EDX
Generally in ASM there is no such thing as a scope, like you're telling it to get you a value of myEDX from test which means get the pointer to the value test and then get the value by the pointer. It may work but it's up to the C compiler to help you out.
So normally you would need to get a pointer and use that to move the data like
LEA ecx,test // get the address of test
MOV DWORD PTR[ecx],edx // use that address to move the data into the struct
Now you are telling it to put that data into the struct, however this makes some assumptions, like here we know that the first element is the one we want to put the data into ( myEDX ) and we assume that a DWORD is the same as an int in C ( on Windows it usually is ) but again assumptions are bad!
Some things that I noticed in your code:
First of all, I don't think that you really need to do the pushad and pushfd You are only using one register really, because ebx wouldn't even be needed, and eax is not considered by the compiler to be preserved anyway.
XOR EAX, EAX //zero EAX, used below as AL (optionOne)
MOV AL, optionOne //optionOne set
TEST EAX, EAX //Tests if optionOne equals == 0, then je will be equal.
je gotoOptionOne //Jumps if optionOne equals 0.
MOV AL, optionTwo //optionTwo set
TEST EAX, EAX //Tests if optionTwo equals == 0, then je will be equal.
je gotoOptionTwo //Jumps if optionTwo equals 0.
Your assumption
MOV EBX, EDX
MOV EAX, EDX //Theory: in order to move EDX a second time into ECX, I must not move EDX directly into ECX.
MOV ECX, EAX
is simply wrong. You can move the same register as often as you like, there is no limitation. The only thing that MIGHT be of consideration is when you are looking for maximum performance because of the instruction scheduling, but this is an entirely different issue and doesn't apply here anyway.
So simply doing
MOV EBX, EDX
MOV ECX, EDX
would be fine.
You are using ESI but it is nowhere initialized. Maybe this is correct, as I don't really understand what your code is supposed to do.
The you are using the variable test here
MOV test.myEDX, EAX
MOV EAX, test.mySetEDX
but this variable isn't even declared, so where should the compiler know it's address from? And for using it as an offset then you would have to use a register as the base address.
This is my programming assignment. I need to find out the largest among the array of integers using a method written in 8086 programming language. This is my attempt :
#include <iostream.h>
#include <conio.h>
int returnLargest(int a[])
{
int max;
asm mov si,offset a
for(int i=0;i<6;i++) //Assuming six numbers in the array...Can be set to a variable 'n' later
{
asm mov ax,[si]
asm mov max,ax
asm inc si
cout<<max<<"\n"; //Just to see what is there in the memory location
}
asm mov si,offset a
asm mov cx,0000h
asm mov dx, [si]
asm mov cx,06h
skip: asm mov si,offset a
asm mov bx,[si]
asm mov max,bx
asm inc si
abc: asm mov bx,max
asm cmp [si],bx
asm jl ok
asm mov bx,[si]
asm mov max,bx
ok: asm loop abc
asm mov ax,max
return max;
}
void main()
{
clrscr();
int n;
int a[]={1,2,3,4,5,6};
n=returnLargest(a);
cout<<n; //Prints the largest
getch();
}
The expected answer is
1
2
3
4
5
6
6. But what I get is this :
Here I sit down and think... Is'nt it the value at the index i of array actually stored in the memory? Because atleast we were taught that if a[i] is 12(say) then ith memory location has the number 12 written inside it.
Or if the value is'nt stored at the memory location, How do I write into the memory location so as to accomplish the desired task?
Also I request you all to link some material on net/paperback so as to brush-up on these concepts.
EDIT :
The same code in assembly works just fine...
data segment
a db 01h,02h,03h,04h,05h,06h,'$'
max db ?
data ends
code segment
start:
assume cs:code,ds:data
mov ax,data
mov ds,ax
mov si,offset a
mov cx,0000h
back: mov dl,byte ptr [si]
cmp dl,'$'
je skip
inc cx
inc si
jmp back
skip: mov si,offset a
mov bl,byte ptr[si]
mov max,bl
inc si
abc: mov bl,max
cmp [si],bl
jl ok
mov bl,[si]
mov max,bl
ok: loop abc
mov al,max
int 03h
code ends
end start
mov si,offset a is incorrect. When you have a function parameter declared as int a[], the function actually receives a pointer. Since you want the pointer value (a) rather than its address (&a in C, offset a in assembly), use mov si, a.
Additionally, inc si doesn't seem right - you need to increase si by sizeof(int) for each element.
Edit:
You are mixing C++ code (for loop, cout) with your assembly. The C++ code is likely to use the same registers, which would cause conflicts. You should avoid doing this.
You also need to find out which registers your function is allowed to change according to the calling convention used. If you use any registers which aren't allowed to change, you need to push them at the beginning and pop them at the end.
You will have to make sure your compiler doesnt use your registers. Best way would be to write the entire function in assembly and implement a desired calling convention (c-call or stdcall - whatever). Then call that function from C/C++.
However if you know you will use only one compiler and how it works you shouldnt have any problems by inlining assembler, but it's really a pitfall.
I got the following simple C++ code:
#include <stdio.h>
int main(void)
{
::printf("\nHello,debugger!\n");
}
And from WinDbg, I got the following disassembly code:
SimpleDemo!main:
01111380 55 push ebp
01111381 8bec mov ebp,esp
01111383 81ecc0000000 sub esp,0C0h
01111389 53 push ebx
0111138a 56 push esi
0111138b 57 push edi
0111138c 8dbd40ffffff lea edi,[ebp-0C0h]
01111392 b930000000 mov ecx,30h
01111397 b8cccccccc mov eax,0CCCCCCCCh
0111139c f3ab rep stos dword ptr es:[edi]
0111139e 8bf4 mov esi,esp
011113a0 683c571101 push offset SimpleDemo!`string' (0111573c)
011113a5 ff15b0821101 call dword ptr [SimpleDemo!_imp__printf (011182b0)]
011113ab 83c404 add esp,4
011113ae 3bf4 cmp esi,esp
011113b0 e877fdffff call SimpleDemo!ILT+295(__RTC_CheckEsp) (0111112c)
011113b5 33c0 xor eax,eax
011113b7 5f pop edi
011113b8 5e pop esi
011113b9 5b pop ebx
011113ba 81c4c0000000 add esp,0C0h
011113c0 3bec cmp ebp,esp
011113c2 e865fdffff call SimpleDemo!ILT+295(__RTC_CheckEsp) (0111112c)
011113c7 8be5 mov esp,ebp
011113c9 5d pop ebp
011113ca c3 ret
I have some difficulties to fully understand it. What is the SimpleDemo!ILT things doing here?
What's the point of the instruction comparing ebp and esp at 011113c0?
Since I don't have any local variables in main() function, why there's still a sub esp,0C0h at the loacation of 01111383?
Many thanks.
Update 1
Though I still don't know what ILT means, but the __RTC_CheckESP is for runtime checks. These code can be elimiated by placing the following pragma before the main() function.
#pragma runtime_checks( "su", off )
Reference:
http://msdn.microsoft.com/en-us/library/8wtf2dfz.aspx
http://msdn.microsoft.com/en-us/library/6kasb93x.aspx
Update 2
The sub esp,0C0h instruction allocate another 0C0h bytes extra space on the stack. Then EAX is filled with 0xCCCCCCCC, this is 4 bytes, since ECX=30h, 4*30h=0C0h, so the instruction rep stos dword ptr es:[edi] fill exactly the extra spaces with 0xCC. But what is this extra space on stack for? Is this some kind of safe belt? Also I notice that if I turn off the runtime check as Update 1 shows, there's still such extra space on stack, though much smaller. And this space is not filled with 0xCC.
The assembly code without runtime check is like below:
SimpleDemo!main:
00231250 55 push ebp
00231251 8bec mov ebp,esp
00231253 83ec40 sub esp,40h <-- Still extra space allocated from stack, but smaller
00231256 53 push ebx
00231257 56 push esi
00231258 57 push edi
00231259 683c472300 push offset SimpleDemo!`string' (0023473c)
0023125e ff1538722300 call dword ptr [SimpleDemo!_imp__printf (00237238)]
00231264 83c404 add esp,4
00231267 33c0 xor eax,eax
00231269 5f pop edi
0023126a 5e pop esi
0023126b 5b pop ebx
0023126c 8be5 mov esp,ebp
0023126e 5d pop ebp
0023126f c3 ret
Most of the instructions are part of MSVC runtime checking, enabled by default for debug builds. Just calling printf and returning 0 in an optimized build takes much less code. (Godbolt compiler explorer). Other compilers (like GCC and clang) don't do as much stuff like stack-pointer comparison after calls, or poisoning stack memory with a recognizable 0xCC pattern to detect use-uninitialized, so their debug builds are like MSVC debug mode without its extra runtime checks.
I've annotated the assembler, hopefully that will help you a bit. Lines starting 'd' are debug code lines, lines starting 'r' are run time check code lines. I've also put in what I think a debug with no runtime checks version and release version would look like.
; The ebp register is used to access local variables that are stored on the stack,
; this is known as a stack frame. Before we start doing anything, we need to save
; the stack frame of the calling function so it can be restored when we finish.
push ebp
; These two instructions create our stack frame, in this case, 192 bytes
; This space, although not used in this case, is useful for edit-and-continue. If you
; break the program and add code which requires a local variable, the space is
; available for it. This is much simpler than trying to relocate stack variables,
; especially if you have pointers to stack variables.
mov ebp,esp
d sub esp,0C0h
; C/C++ functions shouldn't alter these three registers in 32-bit calling conventions,
; so save them. These are stored below our stack frame (the stack moves down in memory)
r push ebx
r push esi
r push edi
; This puts the address of the stack frame bottom (lowest address) into edi...
d lea edi,[ebp-0C0h]
; ...and then fill the stack frame with the uninitialised data value (ecx = number of
; dwords, eax = value to store)
d mov ecx,30h
d mov eax,0CCCCCCCCh
d rep stos dword ptr es:[edi]
; Stack checking code: the stack pointer is stored in esi
r mov esi,esp
; This is the first parameter to printf. Parameters are pushed onto the stack
; in reverse order (i.e. last parameter pushed first) before calling the function.
push offset SimpleDemo!`string'
; This is the call to printf. Note the call is indirect, the target address is
; specified in the memory address SimpleDemo!_imp__printf, which is filled in when
; the executable is loaded into RAM.
call dword ptr [SimpleDemo!_imp__printf]
; In C/C++, the caller is responsible for removing the parameters. This is because
; the caller is the only code that knows how many parameters were put on the stack
; (thanks to the '...' parameter type)
add esp,4
; More stack checking code - this sets the zero flag if the stack pointer is pointing
; where we expect it to be pointing.
r cmp esi,esp
; ILT - Import Lookup Table? This is a statically linked function which throws an
; exception/error if the zero flag is cleared (i.e. the stack pointer is pointing
; somewhere unexpected)
r call SimpleDemo!ILT+295(__RTC_CheckEsp))
; The return value is stored in eax by convention
xor eax,eax
; Restore the values we shouldn't have altered
r pop edi
r pop esi
r pop ebx
; Destroy the stack frame
r add esp,0C0h
; More stack checking code - this sets the zero flag if the stack pointer is pointing
; where we expect it to be pointing.
r cmp ebp,esp
; see above
r call SimpleDemo!ILT+295(__RTC_CheckEsp)
; This is the usual way to destroy the stack frame, but here it's not really necessary
; since ebp==esp
mov esp,ebp
; Restore the caller's stack frame
pop ebp
; And exit
ret
; Debug only, no runtime checks
push ebp
mov ebp,esp
d sub esp,0C0h
d lea edi,[ebp-0C0h]
d mov ecx,30h
d mov eax,0CCCCCCCCh
d rep stos dword ptr es:[edi]
push offset SimpleDemo!`string'
call dword ptr [SimpleDemo!_imp__printf]
add esp,4
xor eax,eax
mov esp,ebp
pop ebp
ret
; Release mode (The optimiser is clever enough to drop the frame pointer setup with no VLAs or other complications)
push offset SimpleDemo!`string'
call dword ptr [SimpleDemo!_imp__printf]
add esp,4
xor eax,eax
ret
Number one your code's main() is improperly formed. It doesn't return the int you promised it would return. Correcting this defect, we get:
#include
int main(int argc, char *argv[])
{
::printf("\nHello,debugger!\n");
return 0;
}
Additionally, any more, it is very strange to see #include <stdio.h> in a C++ program. I believe you want #include <cstdio>
In all cases, space must be made on the stack for arguments and for return values. main()'s return value requires stack space. main()s context to be saved during the call to printf() requires stack space. printf()'s arguments require stack space. printf()'s return value requires stack space. That's what the 0c0h byte stack frame is doing.
The first thing that happens is the incoming bas pointer is copied to the top of the stack. Then the new stack pointer is copied into the base pointer. We'll be checking later to be sure that the stack winds up back where it started from (because you have runtime checking turned on). Then we build the (0C0h bytes long) stack frame to hold our context and printf()'s arguments during the call to printf(). We jump to printf(). When we get back, we hop over the return value which you didn't check in your code (the only thing left on its frame) and make sure the stack after the call is in the same place it was before the call. We pop our context back off the stack. We then check that the final stack pointer matches the value we saved way up at the front. Then we pop the prior value of the base pointer off the very top of the stack and return.
That is code that is inserted by the compiler when you build with runtime checking (/RTC). Disable those options and it should be clearer. /GZ could also be causing this depending on your VS version.
For the record, I suspect that ILT means "Incremental Linking Thunk".
The way incremental linking (and Edit&Continue) works is the following: the linker adds a layer of indirection for every call via thunks which are grouped at the beginning of executable, and adds a huge reserved space after them. This way, when you're relinking the updated executable it can just put any new/changed code into the reserved area and patch only the affected thunks, without changing the rest of the code.
The 40 bytes is the worst case stack allocation for any called or subsequently called function. This is explained in glorious detail here.
What is this space reserved on the top of the stack for? First, space is created for any local variables. In this case, FunctionWith6Params() has two. However, those two local variables only account for 0x10 bytes. What’s the deal with the rest of the space created on the top of the stack?
On the x64 platform, when code prepares the stack for calling another function, it does not use push instructions to put the parameters on the stack as is commonly the case in x86 code. Instead, the stack pointer typically remains fixed for a particular function. The compiler looks at all of the functions the code in the current function calls, it finds the one with the maximum number of parameters, and then creates enough space on the stack to accommodate those parameters. In this example, FunctionWith6Params() calls printf() passing it 8 parameters. Since that is the called function with the maximum number of parameters, the compiler creates 8 slots on the stack. The top four slots on the stack will then be the home space used by any functions FunctionWith6Params() calls.