Example
This is an example that tries to demonstrate the effect of Visual Studio's option /Gs
.
gs.c
gs.c
is a simple
C program with a function (
func
) that allocates a certain amount of bytes on the
stack.
This amount is specified by the
preprocessor macro BUFSIZE
. When the compiler is invoked, a value is assigned to this macro with the
/D
option.
#include <windows.h>
void func() {
char buf[BUFSIZE];
//
// Trying to write a byte at the end of the buf.
//
// If the size of buf is larger than the stack's guard page,
// the OS (Windows) has no chance to realize that it had to
// grow the commited size of the stack - thus, the instructino
// will fail…
// … except if /Gs inserts a call to chkstk in the beginning of
// the function which will cause the necessary exception.
//
buf[BUFSIZE] = 0;
MessageBoxA(NULL, "OK" , "", 0);
}
int start() {
func();
ExitProcess(0);
}
Makefile
The makefile creates three
exes from this source.
The first exe, gsOK.exe
allocates a relatively small amount on the stack so that the stack does not grow beyond the stack's guard page (actually, it does not even grow into the guard page).
So, this exe runs ok when run.
The second exe,
gsFail.exe
, grows the stack by 200000 bytes: the end of
buf
is below the guard page and the OS didn't have a chance to commit more memory to the stack. Thus, when
func
tries to write a byte to the end of
buf
, it is outside of the
process's committed memory which causes an exception to be raised.
The third exe, gsOk_2.exe
allocates the same amount on the stack. But this time, the /Gs
option instructs the compiler to insert a call to chkstk
(which is defined in chkstk.asm
) when the function is entered. chkstk
touches every page that is needed on the stack, thus giving the OS the opportunity to commit more memory to the stack. So, this exe runs ok.
all: gsOK.exe gsFail.exe gsOk_2.exe
gsOK.exe: $(@B).obj
link /nologo $** /subsystem:console /entry:start /nodefaultlib user32.lib kernel32.lib /OUT:$@
gsFail.exe: $(@B).obj
link /nologo $** /subsystem:console /entry:start /nodefaultlib user32.lib kernel32.lib /OUT:$@
gsOK_2.exe: $(@B).obj
link /nologo $** /subsystem:console /entry:start /nodefaultlib user32.lib kernel32.lib chkstk.obj /OUT:$@
gsOK.obj: gs.c
cl /nologo /GS- /c /W4 /DBUFSIZE=20 $** /Fo$@
gsFail.obj: gs.c
cl /nologo /GS- /Gs1000000 /c /W4 /DBUFSIZE=200000 $** /Fo$@
gsOk_2.obj: gs.c
cl /nologo /GS- /c /W4 /DBUFSIZE=200000 $** /Fo$@
chkstk.obj: chkstk.asm
ml $** /Fo$@
clean:
del *.obj *.exe
chkstk.asm
This file is basically a copy lf
chkstk.asm
as found in
Visual Studio's CRT. It is explicitly needed for this example because the example does not link with default libs.
In an application in the wild, this would not really be needed because the function is included with the CRT DLL.
;
; chkstk.asm: taken from Microsoft's Runtime chkstk.asm
;
_PAGESIZE_ equ 1000h
CODESEG
page
public _alloca_probe
_chkstk proc
_alloca_probe = _chkstk
push ecx
; Calculate new TOS.
lea ecx, [esp] + 8 - 4 ; TOS before entering function + size for ret value
sub ecx, eax ; new TOS
; Handle allocation size that results in wraparound.
; Wraparound will result in StackOverflow exception.
sbb eax, eax ; 0 if CF==0, ~0 if CF==1
not eax ; ~0 if TOS did not wrapped around, 0 otherwise
and ecx, eax ; set to 0 if wraparound
mov eax, esp ; current TOS
and eax, not ( _PAGESIZE_ - 1) ; Round down to current page boundary
cs10:
cmp ecx, eax ; Is new TOS
jb short cs20 ; in probed page?
mov eax, ecx ; yes.
pop ecx
xchg esp, eax ; update esp
mov eax, dword ptr [eax] ; get return address
mov dword ptr [esp], eax ; and put it at new TOS
ret
; Find next lower page and probe
cs20:
sub eax, _PAGESIZE_ ; decrease by PAGESIZE
test dword ptr [eax],eax ; probe page.
jmp short cs10
_chkstk endp
end