November 20th, 2025
like1 reaction

In the commit-on-demand pattern, what happens if an access violation straddles multiple pages?

Some time ago, I discussed the technique of reserving a block of address space and committing memory on demand. In the code, I left the exercise

    // Exercise: What happens if the faulting memory access
    // spans two pages?

As far as I can tell, nobody has addressed the exercise, so I’ll answer it.

If the faulting memory access spans two pages, neither of which is present, then an access violation is raised for one of the pages. (The processor chooses which one.) The exception handler commits that page and then requests execution to continue.

When execution continues, it tries to access the memory again, and the access still fails because one of the required pages is missing. But this time the faulting address will be an address on the missing page.

In practice, what happens is that the access violation is raised repeatedly until all of the problems are fixed. Each time it is raised, an address is reported which, if repaired, would allow the instruction to make further progress. The hope is that eventually, you will fix all of the problems,¹ and execution can resume normally.

Bonus chatter: For the x86-64 and x86-32 instruction sets, I think the most number of pages required by a single instruction is six, for the movsw instruction. This reads two bytes from es:rsi/esi, and writes them to ds:rdi/edi. If both addresses straddle a page, that’s four data pages. And the instruction itself is two bytes, so that can straddle two code pages, for a total of six. (There are other things that could go wrong, like an LDT page miss, but those will be handled in kernel mode and are not observable in user mode.)

Bonus exercises: I may as well answer the other exercises on that page. We don’t have to worry about integer overflow in the calculation of sizeof(WCHAR) * (Result + 1) because we have already verified that Result is in the range [1, MaxChars), so Result + 1 ≤ MaxChars, and we also know that MaxChars = Buffer.Length / sizeof(WCHAR), so multiplying both sides by sizeof(WCHAR) tells us that sizeof(WCHAR) * (Result + 1) ≤ Buffer.Length.

For the final exercise, we use CopyMemory instead of StringCchCopy because the result may contain embedded nulls, and we don’t want to stop copying at the first null.

¹ Though it’s possible that your attempt to fix one problem may undo a previous fix, putting you into an infinite cycle of repair.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

1 comment

Sort by :
  • Melissa P

    "or the x86-64 and x86-32 instruction sets, I think the most number of pages required by a single instruction is six" --- depends on if you count e.g. "rep movsw" as a single instruction or treat it as multiple instructions where rip doesnt change until rcx is zero; theoretically you can have nearly unlimited page faults with that one; the CPU trace flag triggers after every single data transfer so I'd say it's not a single instruction

    the trickier question is now... what's the order of the 6 page faults with "movsw"... code first of course... then data... but then load/store...

    Read more