A developer discovers that swapping two equivalent macros caused unexpected integer changes in unrelated functions, and the blog post investigates the mystery, questioning an LLM's explanation about control flow guard.
<p>A colleague made some code changes that should not have had any effect on the generated binary. Specifically, they migrated from <a href="https://github.com/tpn/winsdk-10/blob/9b69fd26ac0c7d0b83d378dba01080e93349c2ed/Include/10.0.16299.0/km/ndis.h#L1153"> the <code>NDIS_<wbr />STRING_<wbr />CONST</code> macro</a> to <a href="https://github.com/tpn/winsdk-10/blob/9b69fd26ac0c7d0b83d378dba01080e93349c2ed/Include/10.0.14393.0/shared/ntdef.h#L2037"> the more type-safe <code>RTL_<wbr />CONSTANT_<wbr />STRING</code> macro</a>. The two macros produce the same results at the end of the day, so the expectation was that this would not result in any change to the binary.</p>
<p>But they found a change to the binary.</p>
<p>Specifically, four functions changed, and what is particularly strange is that none of them involved the macro changes. Three of the functions are in one source file, and the fourth is in a source file that wasn’t even touched!</p>
<p>The changes looked like this:</p>
<table class="cp3" style="border-collapse: collapse;" border="1" cellspacing="0" cellpadding="3">
<tbody>
<tr>
<th>Before</th>
<th>After</th>
</tr>
<tr>
<td colspan="2" align="center"><tt>contoso!EvtWdfWidgetContextCleanup</tt></td>
</tr>
<tr>
<td><tt>mov rax, [contoso!WdfFunctions_01031]</tt><br />
<tt>lea rcx, [??_C@__0DK@MPBCIIPN@...]</tt><br />
<tt>mov [rsp+20h], rcx</tt><br />
<tt>mov r9d, <span style="border: solid 1px currentcolor;">62Bh</span></tt><br />
<tt>mov r8d, 52467443h</tt><br />
<tt>mov rcx, [contoso!WdfDriverGlobals]</tt><br />
<tt>mov rdx, rbx</tt><br />
<tt>mov rax, [rax+670h]</tt><br />
<tt>call __guard_dispatch_call</tt></td>
<td><tt>mov rax, [contoso!WdfFunctions_01031]</tt><br />
<tt>lea rcx, [??_C@__0DK@MPBCIIPN@...]</tt><br />
<tt>mov [rsp+20h], rcx</tt><br />
<tt>mov r9d, <span style="border: solid 1px currentcolor;">62Ah</span></tt><br />
<tt>mov r8d, 52467443h</tt><br />
<tt>mov rcx, [contoso!WdfDriverGlobals]</tt><br />
<tt>mov rdx, rbx</tt><br />
<tt>mov rax, [rax+670h]</tt><br />
<tt>call __guard_dispatch_call</tt></td>
</tr>
<tr>
<td colspan="2" align="center"><tt>contoso!Function2</tt></td>
</tr>
<tr>
<td><tt>mov rax, [contoso!WdfFunctions_01031]</tt><br />
<tt>lea rcx, [??_C@__0DK@MPBCIIPN@...]</tt><br />
<tt>mov [rsp+20h], rcx</tt><br />
<tt>mov r9d, <span style="border: solid 1px currentcolor;">616h</span></tt><br />
<tt>mov rcx, [contoso!WdfDriverGlobals]</tt><br />
<tt>mov r8d, 52467443h</tt><br />
<tt>mov rdx, rdi</tt><br />
<tt>mov rax, [rax+668h]</tt><br />
<tt>call __guard_dispatch_call</tt></td>
<td><tt>mov rax, [contoso!WdfFunctions_01031]</tt><br />
<tt>lea rcx, [??_C@__0DK@MPBCIIPN@...]</tt><br />
<tt>mov [rsp+20h], rcx</tt><br />
<tt>mov r9d, <span style="border: solid 1px currentcolor;">615h</span></tt><br />
<tt>mov rcx, [contoso!WdfDriverGlobals]</tt><br />
<tt>mov r8d, 52467443h</tt><br />
<tt>mov rdx, rdi</tt><br />
<tt>mov rax, [rax+668h]</tt><br />
<tt>call __guard_dispatch_call</tt></td>
</tr>
<tr>
<td colspan="2" align="center"><tt>contoso!Function3</tt></td>
</tr>
<tr>
<td><tt>mov rax, [contoso!WdfFunctions_01031]</tt><br />
<tt>lea rcx, [??_C@__0DK@MPBCIIPN@...]</tt><br />
<tt>mov [r11-20h], rcx</tt><br />
<tt>xor r8d, r8d</tt><br />
<tt>mov rcx, [contoso!WdfDriverGlobals]</tt><br />
<tt>mov r9d, <span style="border: solid 1px currentcolor;">35Dh</span></tt><br />
<tt>mov rax, [rax+0DB0h]</tt><br />
<tt>call __guard_dispatch_call</tt></td>
<td><tt>mov rax, [contoso!WdfFunctions_01031]</tt><br />
<tt>lea rcx, [??_C@__0DK@MPBCIIPN@...]</tt><br />
<tt>mov [r11-20h], rcx</tt><br />
<tt>xor r8d, r8d</tt><br />
<tt>mov rcx, [contoso!WdfDriverGlobals]</tt><br />
<tt>mov r9d, <span style="border: solid 1px currentcolor;">35Ch</span></tt><br />
<tt>mov rax, [rax+0DB0h]</tt><br />
<tt>call __guard_dispatch_call</tt></td>
</tr>
<tr>
<td colspan="2" align="center"><tt>contoso!Function4</tt></td>
</tr>
<tr>
<td><tt>mov rax, [contoso!WdfFunctions_01031]</tt><br />
<tt>lea rcx, [??_C@__0DK@MPBCIIPN@...]</tt><br />
<tt>mov rdx, [rbp+8]</tt><br />
<tt>mov r9d, <span style="border: solid 1px currentcolor;">377h</span></tt><br />
<tt>mov [rsp+20h], rcx</tt><br />
<tt>mov r8d, 49507443h</tt><br />
<tt>mov rcx, [contoso!WdfDriverGlobals]</tt><br />
<tt>mov rax, [rax+0DB8h]</tt><br />
<tt>call __guard_dispatch_call</tt></td>
<td><tt>mov rax, [contoso!WdfFunctions_01031]</tt><br />
<tt>lea rcx, [??_C@__0DK@MPBCIIPN@...]</tt><br />
<tt>mov rdx, [rbp+8]</tt><br />
<tt>mov r9d, <span style="border: solid 1px currentcolor;">376h</span></tt><br />
<tt>mov [rsp+20h], rcx</tt><br />
<tt>mov r8d, 49507443h</tt><br />
<tt>mov rcx, [contoso!WdfDriverGlobals]</tt><br />
<tt>mov rax, [rax+0DB8h]</tt><br />
<tt>call __guard_dispatch_call</tt></td>
</tr>
</tbody>
</table>
<p>In all of the cases, the change is that a single integer changed to a value one smaller.</p>
<p>My colleague asked an LLM to explain this change, and it suggested that the changes were related to control flow guard metadata. Does this make sense?</p>
<p>It didn’t make sense to me, on two points. First, for the guard dispatch call, the only parameter to control flow guard is the <tt>rax</tt> register, which is the function being checked. All the other registers contain the parameters to the called function. Since the changes are to the <tt>r9d</tt> register, they are not related to control flow guard.</p>
<p>Second, the control flow guard metadata is not stored in code. It’s stored <a title="What to do when you have a crash in the runtime control flow guard check" href="https://devblogs.microsoft.com/oldnewthing/20251029-00/?p=111738"> as a data block inside the binary</a>.</p>
<p>So what are we seeing?</p>
<p>I took a look a <code>EvtWdfWidgetContextCleanup</code>.</p>
<pre>void EvtWdfWidgetContextCleanup(_In_ WDFOBJECT Object)
{
auto widgetContext = GetContextFromWidgetHandle(Object);
if (widgetContext->NeedsDereference)
{
widgetContext->NeedsDereference = FALSE;
WdfObjectDereferenceWithTag(Object, CONTOSO_WIDGET_TAG);
}
}
</pre>
<p>The compiler points to the <code>WdfObjectDereferenceWithTag</code> as the location of the change. And we see that <a href="https://github.com/microsoft/Windows-Driver-Frameworks/blob/b6191d9543441329154da32f7ab9bdd97228dd3c/src/publicinc/wdf/kmdf/1.31/wdfobject.h#L848-L849"> it is defined as a macro</a>:</p>
<pre>#define WdfObjectDereferenceWithTag(Handle, Tag) \
WdfObjectDereferenceActual(Handle, Tag, __LINE__, __FILE__)
</pre>
<p>which is itself <a href="https://github.com/microsoft/Windows-Driver-Frameworks/blob/b6191d9543441329154da32f7ab9bdd97228dd3c/src/publicinc/wdf/kmdf/1.31/wdfobject.h#L701-L715"> an inline function: </a></p>
<pre>_IRQL_requires_max_(DISPATCH_LEVEL)
VOID
FORCEINLINE
WdfObjectReferenceActual(
_In_
WDFOBJECT Handle,
_In_opt_
PVOID Tag,
_In_
LONG Line,
_In_z_
PCCH File
)
{
((PFN_WDFOBJECTREFERENCEACTUAL) WdfFunctions[WdfObjectReferenceActualTableIndex])
(WdfDriverGlobals, Handle, Tag, Line, File);
}
</pre>
<p>The last little detail is that <a href="https://github.com/microsoft/Windows-Driver-Frameworks/blob/b6191d9543441329154da32f7ab9bdd97228dd3c/src/publicinc/wdf/umdf/1.31/wdf.h#L60"> <code>WdfFunctions</code> is a macro that expands to <code>WdfFunctions_01031</code></a>. The WDF header files give each version a unique name so that mismatched versions lead to a linker error rather than undefined behavior at runtime.</p>
<p>Now we can see how this code maps to the compiler output.</p>
<pre> mov rax, [contoso!WdfFunctions_01031] ; WdfFunctions
lea rcx, [??_C@__0DK@MPBCIIPN@...] ; Address of something
mov [rsp+20h], rcx ; is the File parameter
mov r9d, 62Bh ; Line parameter
mov r8d, 52467443h ; Tag parameter
mov rcx, [contoso!WdfDriverGlobals] ; hard-coded parameter
mov rdx, rbx ; Handle parameter
mov rax, [rax+670h] ; Load the function pointer
call __guard_dispatch_call ; Validate and call¹
</pre>
<p>So the value that changed is the <i>line number</i>.</p>
<p>I went back to the pull request and observed that the pull requested deleted a line from the source file.</p>
<pre>#include <strsafe.h>
<span style="text-decoration: line-through;">#include "stringutils.h"</span>
</pre>
<p>Part of the pull request included deleting the no-longer-needed header because it contained a private definition of the <code>NDIS_<wbr />STRING_<wbr />CONST</code> macro, which the code no longer uses.</p>
<p>Deleting a line from the source file causes all the line numbers to shift by one!</p>
<p>So what they were seeing was just a change to the line numbers. No change in functionality.</p>
<p>If they really wanted to make this a “no binary effect” change, they could replace the <code>#include "stringutils.h</code> with a comment or just leave it as a blank line.</p>
<p>Or they could just accept that line numbers can change when you change lines.</p>
<p><b>Bonus chatter</b>: But wait, I said that three of the changes were in one file, the one with the deleted line, but a fourth was in a file that didn’t change at all. What’s that about?</p>
<p>The fourth function contained a call to a function in the modified file, and link-time code generation decided to inline that call. The changed line number propagated into the inline function and resulted in a code generation change in a file that wasn’t even affected by the pull request.</p>
<p>¹ Recall that <a title="The other kind of control flow guard check: The combined validate and call" href="https://devblogs.microsoft.com/oldnewthing/20260708-00/?p=112510"> in the validate-and-call pattern</a>, the function pointer is passed in the <code>rax</code> register, and everthing else is set up as if you were calling the function yourself.</p>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260710-00/?p=112514">The case of the mysterious changes to integers when there shouldn’t have been any code generation effect</a> appeared first on <a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>.</p>
# The case of the mysterious changes to integers when there shouldn't have been any code generation effect - The Old New Thing
Source: [https://devblogs.microsoft.com/oldnewthing/20260710-00?p=112514](https://devblogs.microsoft.com/oldnewthing/20260710-00?p=112514)
A colleague made some code changes that should not have had any effect on the generated binary\. Specifically, they migrated from[the`NDIS\_STRING\_CONST`macro](https://github.com/tpn/winsdk-10/blob/9b69fd26ac0c7d0b83d378dba01080e93349c2ed/Include/10.0.16299.0/km/ndis.h#L1153)to[the more type\-safe`RTL\_CONSTANT\_STRING`macro](https://github.com/tpn/winsdk-10/blob/9b69fd26ac0c7d0b83d378dba01080e93349c2ed/Include/10.0.14393.0/shared/ntdef.h#L2037)\. The two macros produce the same results at the end of the day, so the expectation was that this would not result in any change to the binary\.
But they found a change to the binary\.
Specifically, four functions changed, and what is particularly strange is that none of them involved the macro changes\. Three of the functions are in one source file, and the fourth is in a source file that wasn’t even touched\!
The changes looked like this:
BeforeAftercontoso\!EvtWdfWidgetContextCleanupmov rax, \[contoso\!WdfFunctions\_01031\]
lea rcx, \[??\_C@\_\_0DK@MPBCIIPN@\.\.\.\]
mov \[rsp\+20h\], rcx
mov r9d,62Bh
mov r8d, 52467443h
mov rcx, \[contoso\!WdfDriverGlobals\]
mov rdx, rbx
mov rax, \[rax\+670h\]
call \_\_guard\_dispatch\_callmov rax, \[contoso\!WdfFunctions\_01031\]
lea rcx, \[??\_C@\_\_0DK@MPBCIIPN@\.\.\.\]
mov \[rsp\+20h\], rcx
mov r9d,62Ah
mov r8d, 52467443h
mov rcx, \[contoso\!WdfDriverGlobals\]
mov rdx, rbx
mov rax, \[rax\+670h\]
call \_\_guard\_dispatch\_callcontoso\!Function2mov rax, \[contoso\!WdfFunctions\_01031\]
lea rcx, \[??\_C@\_\_0DK@MPBCIIPN@\.\.\.\]
mov \[rsp\+20h\], rcx
mov r9d,616h
mov rcx, \[contoso\!WdfDriverGlobals\]
mov r8d, 52467443h
mov rdx, rdi
mov rax, \[rax\+668h\]
call \_\_guard\_dispatch\_callmov rax, \[contoso\!WdfFunctions\_01031\]
lea rcx, \[??\_C@\_\_0DK@MPBCIIPN@\.\.\.\]
mov \[rsp\+20h\], rcx
mov r9d,615h
mov rcx, \[contoso\!WdfDriverGlobals\]
mov r8d, 52467443h
mov rdx, rdi
mov rax, \[rax\+668h\]
call \_\_guard\_dispatch\_callcontoso\!Function3mov rax, \[contoso\!WdfFunctions\_01031\]
lea rcx, \[??\_C@\_\_0DK@MPBCIIPN@\.\.\.\]
mov \[r11\-20h\], rcx
xor r8d, r8d
mov rcx, \[contoso\!WdfDriverGlobals\]
mov r9d,35Dh
mov rax, \[rax\+0DB0h\]
call \_\_guard\_dispatch\_callmov rax, \[contoso\!WdfFunctions\_01031\]
lea rcx, \[??\_C@\_\_0DK@MPBCIIPN@\.\.\.\]
mov \[r11\-20h\], rcx
xor r8d, r8d
mov rcx, \[contoso\!WdfDriverGlobals\]
mov r9d,35Ch
mov rax, \[rax\+0DB0h\]
call \_\_guard\_dispatch\_callcontoso\!Function4mov rax, \[contoso\!WdfFunctions\_01031\]
lea rcx, \[??\_C@\_\_0DK@MPBCIIPN@\.\.\.\]
mov rdx, \[rbp\+8\]
mov r9d,377h
mov \[rsp\+20h\], rcx
mov r8d, 49507443h
mov rcx, \[contoso\!WdfDriverGlobals\]
mov rax, \[rax\+0DB8h\]
call \_\_guard\_dispatch\_callmov rax, \[contoso\!WdfFunctions\_01031\]
lea rcx, \[??\_C@\_\_0DK@MPBCIIPN@\.\.\.\]
mov rdx, \[rbp\+8\]
mov r9d,376h
mov \[rsp\+20h\], rcx
mov r8d, 49507443h
mov rcx, \[contoso\!WdfDriverGlobals\]
mov rax, \[rax\+0DB8h\]
call \_\_guard\_dispatch\_callIn all of the cases, the change is that a single integer changed to a value one smaller\.
My colleague asked an LLM to explain this change, and it suggested that the changes were related to control flow guard metadata\. Does this make sense?
It didn’t make sense to me, on two points\. First, for the guard dispatch call, the only parameter to control flow guard is theraxregister, which is the function being checked\. All the other registers contain the parameters to the called function\. Since the changes are to ther9dregister, they are not related to control flow guard\.
Second, the control flow guard metadata is not stored in code\. It’s stored[as a data block inside the binary](https://devblogs.microsoft.com/oldnewthing/20251029-00/?p=111738)\.
So what are we seeing?
I took a look a`EvtWdfWidgetContextCleanup`\.
```
void EvtWdfWidgetContextCleanup(_In_ WDFOBJECT Object)
{
auto widgetContext = GetContextFromWidgetHandle(Object);
if (widgetContext->NeedsDereference)
{
widgetContext->NeedsDereference = FALSE;
WdfObjectDereferenceWithTag(Object, CONTOSO_WIDGET_TAG);
}
}
```
The compiler points to the`WdfObjectDereferenceWithTag`as the location of the change\. And we see that[it is defined as a macro](https://github.com/microsoft/Windows-Driver-Frameworks/blob/b6191d9543441329154da32f7ab9bdd97228dd3c/src/publicinc/wdf/kmdf/1.31/wdfobject.h#L848-L849):
```
#define WdfObjectDereferenceWithTag(Handle, Tag) \
WdfObjectDereferenceActual(Handle, Tag, __LINE__, __FILE__)
```
which is itself[an inline function:](https://github.com/microsoft/Windows-Driver-Frameworks/blob/b6191d9543441329154da32f7ab9bdd97228dd3c/src/publicinc/wdf/kmdf/1.31/wdfobject.h#L701-L715)
```
_IRQL_requires_max_(DISPATCH_LEVEL)
VOID
FORCEINLINE
WdfObjectReferenceActual(
_In_
WDFOBJECT Handle,
_In_opt_
PVOID Tag,
_In_
LONG Line,
_In_z_
PCCH File
)
{
((PFN_WDFOBJECTREFERENCEACTUAL) WdfFunctions[WdfObjectReferenceActualTableIndex])
(WdfDriverGlobals, Handle, Tag, Line, File);
}
```
The last little detail is that[`WdfFunctions`is a macro that expands to`WdfFunctions\_01031`](https://github.com/microsoft/Windows-Driver-Frameworks/blob/b6191d9543441329154da32f7ab9bdd97228dd3c/src/publicinc/wdf/umdf/1.31/wdf.h#L60)\. The WDF header files give each version a unique name so that mismatched versions lead to a linker error rather than undefined behavior at runtime\.
Now we can see how this code maps to the compiler output\.
```
mov rax, [contoso!WdfFunctions_01031] ; WdfFunctions
lea rcx, [??_C@__0DK@MPBCIIPN@...] ; Address of something
mov [rsp+20h], rcx ; is the File parameter
mov r9d, 62Bh ; Line parameter
mov r8d, 52467443h ; Tag parameter
mov rcx, [contoso!WdfDriverGlobals] ; hard-coded parameter
mov rdx, rbx ; Handle parameter
mov rax, [rax+670h] ; Load the function pointer
call __guard_dispatch_call ; Validate and call¹
```
So the value that changed is the*line number*\.
I went back to the pull request and observed that the pull requested deleted a line from the source file\.
```
#include <strsafe.h>
#include "stringutils.h"
```
Part of the pull request included deleting the no\-longer\-needed header because it contained a private definition of the`NDIS\_STRING\_CONST`macro, which the code no longer uses\.
Deleting a line from the source file causes all the line numbers to shift by one\!
So what they were seeing was just a change to the line numbers\. No change in functionality\.
If they really wanted to make this a “no binary effect” change, they could replace the`\#include "stringutils\.h`with a comment or just leave it as a blank line\.
Or they could just accept that line numbers can change when you change lines\.
**Bonus chatter**: But wait, I said that three of the changes were in one file, the one with the deleted line, but a fourth was in a file that didn’t change at all\. What’s that about?
The fourth function contained a call to a function in the modified file, and link\-time code generation decided to inline that call\. The changed line number propagated into the inline function and resulted in a code generation change in a file that wasn’t even affected by the pull request\.
¹ Recall that[in the validate\-and\-call pattern](https://devblogs.microsoft.com/oldnewthing/20260708-00/?p=112510), the function pointer is passed in the`rax`register, and everthing else is set up as if you were calling the function yourself\.
### Category
### Topics
## 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\.
A developer recounts using LLMs to solve a decade-old bug in a Swift/C++ cross-platform music app, demonstrating how AI can assist in debugging complex issues.
A Rust programmer discovers a compiler bug where casting 'bool as u32' produces incorrect results, leading to a parser error. The bug is reported and linked to GitHub issue #158206.
Raymond Chen follows up on his previous article about stack limit checking on ARM64, addressing a detail about the unconventional use of the x15 register in stack probe functions and comparing register usage across multiple architectures.
A deep dive into how L1 instruction cache set conflicts and code alignment caused an unexpected performance regression in Go, and the investigation process.
A technical blog post by Raymond Chen investigating a memory corruption bug where a single byte 0x01 corrupts HMODULE handles, causing DLLs to be improperly freed and leading to crashes at process termination.