{"id":107598,"date":"2022-12-16T07:00:00","date_gmt":"2022-12-16T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=107598"},"modified":"2022-12-15T15:53:59","modified_gmt":"2022-12-15T23:53:59","slug":"20221216-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20221216-00\/?p=107598","title":{"rendered":"Why doesn&#8217;t Windows use the 64-bit virtual address space below <CODE>0x00000000`7ffe0000<\/CODE>?"},"content":{"rendered":"<p>A customer used VMMap and observed that for all of their 64-bit processes, nothing was allocated at any addresses below <code>0x00000000`7ffe0000<\/code>. Why does the virtual address space start at <code>0x00000000`7ffe0000<\/code>? Is it to make it easier to catch pointer truncation bugs? And what&#8217;s so special about <code>0x00000000`7ffe0000<\/code>?<\/p>\n<p>Okay, let&#8217;s go through the questions one at a time.<\/p>\n<p>First, is it even true that the virtual address space starts at <code>0x00000000`7ffe0000<\/code>?<\/p>\n<p>No. The virtual address space starts at the 64KB boundary. You can confirm this by calling <code>GetSystemInfo<\/code> and checking the <code>lpMinimum\u00adApplication\u00adAddress<\/code>. It will be <code>0x00000000`00010000<\/code>.<\/p>\n<p>If the address space starts at 64KB, why is the lower 2GB pretty much ignored?<\/p>\n<p>Because it turns out that the total address space is really big.<\/p>\n<p>Address Space Layout Randomization (ASLR) tries to put things at unpredictable addresses. The full user-mode address space on x86-64 is 128TB, and a randomly-generated 47-bit address is very unlikely to begin with 15 consecutive zero bits. The first 2GB of address space is only 0.003% of the total available address space, so it&#8217;s a pretty small target.<\/p>\n<p>But why is there a page of memory consistently allocated at exactly <code>0x00000000`7ffe0000<\/code>?<\/p>\n<p>That is a special page of memory that is mapped read-only into user mode from kernel mode, and it contains things like the current time, so that applications can get this information quickly without having to take a kernel transition. This page is at a fixed location for performance reasons.<\/p>\n<p>If a 64-bit application is not marked <code>\/LARGEADDRESSAWARE<\/code>, then it receives a shrunken address space of only 2GB so that it doesn&#8217;t have to deal with any &#8220;large addresses&#8221; (namely, those above 2GB). These &#8220;64-bit processes that don&#8217;t understand addresses above 2GB&#8221; (also known <a href=\"https:\/\/en.wikipedia.org\/wiki\/What_Is..._Cliff_Clavin%3F\"> in my kitchen<\/a> as &#8220;really stupid 64-bit processes&#8221;) still need to access the shared data, so the shared data must go below the 2GB boundary.<\/p>\n<p>Since virtual address space granularity on Windows is 64KB, reserving a single page actually reserves an entire 64KB block, from <code>0x00000000`7ffe0000<\/code> to <code>0x00000000`7ffeffff<\/code>. Also in this range is a second read-only page mapped from kernel mode, this time for sharing information with the hypervisor, specifically the <a href=\"https:\/\/docs.microsoft.com\/en-us\/virtualization\/hyper-v-on-windows\/tlfs\/timers#partition-reference-time-stamp-counter-page\"> partition reference time stamp counter<\/a>. This second page is placed at a random location inside the 64KB range, not so much for ASLR reasons (since there are only 15 choices, which isn&#8217;t very random), but just to make sure that nobody accidentally takes a dependency on a fixed address. As noted in the documentation, you find this partition reference time stamp counter page by reading the <code>HV_<wbr \/>X64_<wbr \/>MSR_<wbr \/>REFERENCE_<wbr \/>TSC<\/code> model-specific register.<\/p>\n<p>Okay, but why are these special shared pages in the range <code>0x00000000`7ffe0000<\/code> to <code>0x00000000`7ffeffff<\/code>? Why not put them right up at the 2GB boundary of <code>0x00000000`7fff0000<\/code> to <code>0x00000000`7fffffff<\/code>?<\/p>\n<p>One reason is that the 64KB region immediately above and below the 2GB boundary are marked permanently invalid in order to <a title=\"Why is there a 64KB no-man's land near the end of the user-mode address space?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20141009-00\/?p=43883\"> simplify address validity checks<\/a>: On 32-bit systems, the 2GB boundary marks the traditional boundary between user mode and kernel mode. If you put a &#8220;no man&#8217;s land&#8221; between user mode and kernel mode, then you can validate a memory range by checking that it starts in user mode, and verifying that every page is accessible. You&#8217;ll run into the inaccessible page before you get to the kernel mode addresses.<\/p>\n<p>Okay, that explains why there&#8217;s a no man&#8217;s land on 32-bit systems, but why do we also have it on 64-bit systems?<\/p>\n<p>On the Alpha AXP, most 32-bit constants can be generated in at most two instructions. But there&#8217;s a range of values that <a title=\"The Alpha AXP, part 3: Integer constants\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20170809-00\/?p=96785\"> requires three instructions<\/a>: <code>0x7fff8000<\/code> to <code>0x7fffffff<\/code>. Blocking off that region means that <a title=\"Why is address space allocation granularity 64KB?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20031008-00\/?p=42223\"> you never have to provide a relocation fixup that targets the problematic memory range<\/a>. And <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220106-00\/?p=106122\"> the initial target of 64-bit Windows was the Alpha AXP<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>It does use it, although it doesn&#8217;t look like it.<\/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":[26],"class_list":["post-107598","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-other"],"acf":[],"blog_post_summary":"<p>It does use it, although it doesn&#8217;t look like it.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107598","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=107598"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107598\/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=107598"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=107598"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=107598"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}