{"id":112152,"date":"2026-03-19T07:00:00","date_gmt":"2026-03-19T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112152"},"modified":"2026-03-19T13:12:36","modified_gmt":"2026-03-19T20:12:36","slug":"20260319-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260319-00\/?p=112152\/","title":{"rendered":"Windows stack limit checking retrospective: amd64, also known as x86-64"},"content":{"rendered":"<p>Our survey of stack limit checking reaches the modern day with amd64, also known as x86-64. This time, there are two versions of the function, one for user mode and one for kernel mode. We&#8217;ll look at the user mode version.<\/p>\n<p>Actually, there are two user mode versions. One is in <tt>msvcrt<\/tt>, the legacy runtime.<\/p>\n<pre>; on entry, rax is the number of bytes to allocate\r\n; on exit, stack has been validated (but not adjusted)\r\n\r\nchkstk:\r\n    sub     rsp, 16\r\n    mov     [rsp], r10          ; save temporary register\r\n    mov     [rsp][8], r11       ; save temporary register\r\n\r\n    xor     r11, r11            ; r11 = 0\r\n    lea     r10, [rsp][16][8]   ; r10 = caller's rsp\r\n    sub     r10, rax            ; r10 = desired new stack pointer\r\n    cmovb   r10, r11            ; clamp underflow to zero\r\n\r\n    mov     r11, gs:[StackLimit]; user mode stack limit\r\n\r\n    cmp     r10, r11            ; are we inside the limit?\r\n    jae     done                ; Y: nothing to do\r\n\r\n    and     r10w, #-PAGE_SIZE   ; round down to page start\r\n\r\nprobe:\r\n    lea     r11, [r11][-PAGE_SIZE]  ; move to previous page\r\n    test    [r11], r11b         ; probe it\r\n    cmp     r10, r11            ; finished probing?\r\n    jb      probe               ; N: keep going\r\n\r\ndone:\r\n    mov     r10, [rsp]          ; restore temporary register\r\n    mov     r11, [rsp][8]       ; restore temporary register\r\n    add     rsp, 16             ; clean up stack\r\n    ret\r\n<\/pre>\n<p><b>Bonus reading<\/b>: <a title=\"Windows is not a Microsoft Visual C\/C++ Run-Time delivery channel\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20140411-00\/?p=1273\"> Windows is not a Microsoft Visual C\/C++ Run-Time delivery channel<\/a>.<\/p>\n<p>The other is in <tt>ucrtbase<\/tt>, the so-called universal runtime. That one is identical except that <a title=\"Windows stack limit checking retrospective: Alpha AXP\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260318-00\/?p=112146\"> the probing is done by writing rather than reading<\/a>.<\/p>\n<pre>    mov     byte ptr [r11], 0   ; probe it\r\n<\/pre>\n<p>In both cases, the function ensures that the stack has expanded the necessary amount but leaves it the caller&#8217;s responsibility to adjust the stack after the call returns. This design preserves compliance with shadow stacks (which Intel calls Control-Flow Enforcement Technology, or CET).<\/p>\n<p>A typical usage might go like this:<\/p>\n<pre>    mov     eax, #17328         ; desired stack frame size (zero-extended)\r\n    call    chkstk              ; validate that there is enough stack\r\n    sub     rsp, rax            ; allocate it\r\n<\/pre>\n<p>Next time, we&#8217;ll wrap up the series with a look at AArch64, also known as arm64.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Reaching the modern day.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-112152","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Reaching the modern day.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112152","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=112152"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112152\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=112152"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112152"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112152"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}