Freigeben über


Thread's Stack

Let’s talk about the thread’s stack today. Each thread has its own user mode and kernel mode stack which grows on demand. When a binary is built, linker inserts the default size for the stack into the PE header of binary. These default settings can be overridden by giving different values with /STACK option. Below is a snip of PE header file of a test executable ThreadStack.exe:

0:000> !dh ThreadStack.exe -f

File Type: EXECUTABLE IMAGE

FILE HEADER VALUES

     14C machine (i386)

       5 number of sections

4F8A2827 time date stamp Sat Apr 14 18:45:11 2012 

       0 file pointer to symbol table

       0 number of symbols

      E0 size of optional header

     102 characteristics

            Executable

            32 bit word machine 

By default the size of stack is 1 MB, however full 1MB is not committed at the beginning but only reserved. As the stack grows, more memory is committed and the amount of memory that is committed is size of Stack commit (1000) as shown above.

After reserving the 1 MB of memory for stack, system commits first 2 pages (i.e. 8KB) and assigns a guard attribute to second page. Thread starts executing from the first page and as soon as it hits the guard page, system is notified. System then removes the guard attribute from the second page, commits another page of stack memory and assigns the guard attribute to that new committed page. As the stack grows, every time a new page is committed as soon as thread hits the guard page. The last three pages of stacks are treated a little differently; I will talk about that a little later.

Let’s see the stack status of ThreadStack.exe program when the recursive function has iterated a few times. ThreadStack.exe is a simple recursive program that causes stack overflow:

 /*ThreadStack.cpp*/
int RecurseFunction()

{

       int arr[200];

       //cin>>arr[0];

       RecurseFunction();

       return 1;

}

 int main()

{

       RecurseFunction();

       return 1;

}

0:000> kL

ChildEBP RetAddr 

0024eef8 00f0102e ThreadStack!RecurseFunction

0024f228 00f0102e ThreadStack!RecurseFunction+0xe

0024f558 00f0102e ThreadStack!RecurseFunction+0xe

0024f888 00f0102e ThreadStack!RecurseFunction+0xe

0024fbb8 00f01048 ThreadStack!RecurseFunction+0xe

0024fbc0 00f011d9 ThreadStack!main+0x8

0024fc08 76c81114 ThreadStack!__tmainCRTStartup+0x10b

0024fc14 776bb299 kernel32!BaseThreadInitThunk+0xe

0024fc54 776bb26c ntdll!__RtlUserThreadStart+0x70

0024fc6c 00000000 ntdll!_RtlUserThreadStart+0x1b

 

The thread environment block contains the Thread’s stack information:

0:000> !teb

TEB at 7ffdf000

 

0:000> dt _NT_TIB stackBase StackLimit 7ffdf000

ThreadStack!_NT_TIB

   +0x004 StackBase : 0x00250000 Void

   +0x008 StackLimit : 0x0024e000 Void

 

0:000> r esp

esp=0024eefc

 

The stack base is the base address of first page from where the thread started andb stack limit is the last valid address on the current committed page being used. The snapshot of stack at this point can be represented as below

Let’s check the status and protection attribute of these pages from debugger, as the above picture depicts the first two pages are committed and third page is committed but has a guard attribute set. The fourth page is reserved but not committed.

0:000> !vprot 0x0024f000

BaseAddress: 0024f000

AllocationBase: 00150000

AllocationProtect: 00000004 PAGE_READWRITE

RegionSize: 00001000

State: 00001000 MEM_COMMIT

Protect: 00000004 PAGE_READWRITE

Type: 00020000 MEM_PRIVATE

 

0:000> !vprot 0x0024e000

BaseAddress: 0024e000

AllocationBase: 00150000

AllocationProtect: 00000004 PAGE_READWRITE

RegionSize: 00002000

State: 00001000 MEM_COMMIT

Protect: 00000004 PAGE_READWRITE

Type: 00020000 MEM_PRIVATE

 

0:000> !vprot 0x0024d000

BaseAddress: 0024d000

AllocationBase: 00150000

AllocationProtect: 00000004 PAGE_READWRITE

RegionSize: 00001000

State: 00001000 MEM_COMMIT

Protect: 00000104 PAGE_READWRITE + PAGE_GUARD

Type: 00020000 MEM_PRIVATE

 

0:000> !vprot 0x0024c000

BaseAddress: 0024c000

AllocationBase: 00150000

AllocationProtect: 00000004 PAGE_READWRITE

RegionSize: 00001000

State: 00002000 MEM_RESERVE

Type: 00020000 MEM_PRIVATE

 This way the stack keeps growing but in the last three pages system does things a little differently. When the program access the 3rd page from end as usual system is notified and system removes the Guard attribute from this page, commits the next page BUT this time it doesn’t apply the guard page attribute to the newly committed page(second last page). Also after committing the new page system raises a stack Overflow exception which you can catch if you are running the program into debugger. Let’s see this in our program threadstack.exe.

 0:000> g

(1284.96c): Stack overflow - code c00000fd (first chance)

First chance exceptions are reported before any exception handling.

This exception may be expected and handled.

eax=004720d0 ebx=7ffd9000 ecx=00000001 edx=776a6194 esi=00000000 edi=00000000

eip=00f01029 esp=00152ed0 ebp=001531f8 iopl=0 nv up ei pl nz na po nc

cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202

ThreadStack!RecurseFunction+0x9:

00f01029 e8d7ffffff call ThreadStack!ILT+0(?RecurseFunctionYAHXZ) (00f01005)

0:000> !error c00000fd

Error code: (NTSTATUS) 0xc00000fd (3221225725) - A new guard page for the stack cannot be created.

 Below output shows that no guard attribute is set to second last page:

 0:000> !vprot 0x00151000

BaseAddress: 00151000

AllocationBase: 00150000

AllocationProtect: 00000004 PAGE_READWRITE

RegionSize: 000ff000

State: 00001000 MEM_COMMIT

Protect: 00000004 PAGE_READWRITE

Type: 00020000 MEM_PRIVATE

 

0:000> !vprot 0x00150000

BaseAddress: 00150000

AllocationBase: 00150000

AllocationProtect: 00000004 PAGE_READWRITE

RegionSize: 00001000

State: 00002000 MEM_RESERVE

Type: 00020000 MEM_PRIVATE

 At this point the stacklimit would be the last address in second last page.

 0:000> dt _NT_TIB stackBase StackLimit 7ffdf000

ThreadStack!_NT_TIB

StackBase: 00250000

   StackLimit: 00151000

 

By running the program under debugger we can see that the stack has reached third last page. If you continue ‘g’ the program would run and consume the rest of the space onto the stack. Once it consumes the second last page as well and tries to access the Last page the program would raise an access violation exception since the last page is just reserved and not committed. At this point the program would be terminated. As you can see the thread gets one page less than what was reserved for stack since the last page is never committed.

 0:000> g

(1284.96c): Access violation - code c0000005 (first chance)

First chance exceptions are reported before any exception handling.

This exception may be expected and handled.

eax=004720d0 ebx=7ffd9000 ecx=00000001 edx=776a6194 esi=00000000 edi=00000000

eip=00f01029 esp=00150ef0 ebp=00151218 iopl=0 nv up ei pl nz na pe nc

cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206

ThreadStack!RecurseFunction+0x9:

00f01029 e8d7ffffff call ThreadStack!ILT+0(?RecurseFunctionYAHXZ) (00f01005)