{"id":112535,"date":"2026-07-15T07:00:00","date_gmt":"2026-07-15T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112535"},"modified":"2026-07-15T08:18:46","modified_gmt":"2026-07-15T15:18:46","slug":"20260715-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260715-00\/?p=112535","title":{"rendered":"The case of the invalid function pointer when shutting down the display control panel"},"content":{"rendered":"<p>The number one crash in the display control panel looks like this:<\/p>\n<pre>rax=ffffffff924bbde0 rbx=0000000000000001 rcx=0000000000030440\r\nrdx=0000000000000002 rsi=0000000000030440 rdi=0000000080006011\r\nrip=00007ffac835cd1e rsp=000000155e48e3f8 rbp=000000155e48e749\r\n r8=0000000000000000  r9=0000000000000000 r10=007fffffffe41b69\r\nr11=00007df502390000 r12=0000000000000000 r13=0000000000000000\r\nr14=0000000000000002 r15=0000000000000000\r\niopl=0         nv up ei pl nz na pe nc\r\ncs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206\r\nntdll!LdrpDispatchUserCallTarget+0xe:\r\n00007fff`924acd1e mov     r11,qword ptr [r11+r10*8] ds:04007df5`0159db48=????????????????\r\n0:000&gt; k\r\nCall Site\r\nntdll!LdrpDispatchUserCallTarget+0xe\r\nuser32!UserCallWinProcCheckWow+0x2bd\r\nuser32!DispatchClientMessage+0x9c\r\nuser32!__fnDWORD+0x33\r\nntdll!KiUserCallbackDispatcherContinue\r\nwin32u!ZwUserDestroyWindow+0x14\r\ncomctl32!_RealPropertySheet+0x36d\r\ncomctl32!_PropertySheet+0x47\r\nDisplay!PropertySheetW+0x5d\r\nDisplay!AdvancedSettingSheetHelper+0x3be\r\nDisplay!ShowAdapterSettings+0x89\r\nrundll32!CallRunDllFunction+0x1c\r\nrundll32!wWinMain+0x2bf\r\nrundll32!__wmainCRTStartup+0x1c9\r\nkernel32!BaseThreadInitThunk+0x14\r\nntdll!RtlUserThreadStart+0x21\r\n<\/pre>\n<p>From the stack, we see that we have a display adapter settings property sheet. We are destroying it, and we crash trying to validate the window procedure address.<\/p>\n<p>We saw some time ago 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\"> you can pull out the bad address by inspection<\/a>.<\/p>\n<pre>0:000&gt; u .-e .\r\nntdll!LdrpDispatchUserCallTarget:\r\n00007fff`924acd10 mov     r11,qword ptr [ntdll+0x001813a8]\r\n00007fff`924acd17 mov     r10,rax\r\n00007fff`924acd1a shr     r10,9\r\n00007fff`924acd1e mov     r11,qword ptr [r11+r10*8]\r\n<\/pre>\n<p>The register that is the source of the shift is <code>rax<\/code>, so that&#8217;s the function pointer. And from the register dump, we see that the address is<\/p>\n<pre>rax=ffffffff924bbde0\r\n<\/pre>\n<p>Yeah, that address doesn&#8217;t look like a valid function pointer.<\/p>\n<p>On 64-bit systems, user-mode pointers have low addresses (which start with <code>0000<\/code>), and kernel-mode pointers have high addresses (which start with <code>ffff<\/code>). So this function pointer is clearly invalid for user mode.<\/p>\n<p>Maybe we can fix it so it&#8217;s valid again. Let&#8217;s see what code addresses are valid in this process.<\/p>\n<pre>0:000&gt; lm\r\nstart             end                 module name\r\n00000001`80000000 00000001`80043000   contoso\r\n00007ff6`44570000 00007ff6`44587000   rundll32\r\n00007fff`6a4f0000 00007fff`6a6b7000   d3d9\r\n00007fff`6e600000 00007fff`6e6a9000   comctl32_7fff6e600000\r\n00007fff`6f5d0000 00007fff`6f5e5000   pcacli\r\n00007fff`753b0000 00007fff`753c1000   sfc_os\r\n...\r\n00007fff`91020000 00007fff`910f0000   comdlg32\r\n00007fff`912b0000 00007fff`915e6000   combase\r\n00007fff`91600000 00007fff`91794000   user32\r\n00007fff`917a0000 00007fff`91852000   kernel32\r\n00007fff`918e0000 00007fff`91989000   SHCore\r\n00007fff`91990000 00007fff`91ae6000   ole32\r\n00007fff`91af0000 00007fff`91b16000   gdi32\r\n00007fff`91b20000 00007fff`91bc3000   advapi32\r\n00007fff`91bd0000 00007fff`91c67000   sechost\r\n00007fff`91c70000 00007fff`91cc2000   shlwapi\r\n00007fff`91cd0000 00007fff`91ced000   imagehlp\r\n00007fff`91d50000 00007fff`921c0000   setupapi\r\n00007fff`92220000 00007fff`92355000   msctf\r\n00007fff`92420000 00007fff`92610000   ntdll\r\n...\r\n<\/pre>\n<p>Ny suspicion is that the function pointer got truncated to a 32-bit value, and then was sign-extended back up to a 64-bit value. So we are looking for valid function pointers of the form <code>xxxxxxxx`924bbde0<\/code>. In the above list of valid code addresses, the only ones that have the lower bits in the <code>92xxxxxx<\/code> range all have a high 32 bits of <code>00007fff<\/code>, so let&#8217;s plug that in and see if we get a window procedure.<\/p>\n<pre>0:000&gt; ln 7fff924bbde0\r\n(00007fff`924bbde0)   ntdll!NtdllButtonWndProc_A   |  (00007fff`924bbdf0)   ntdll!NtdllButtonWndProc_W\r\n<\/pre>\n<p>Jackpot.<\/p>\n<p>So the caller probably subclassed a window, and then tried to restore the original window procedure, but messed up and restored only the bottom 32 bits.<\/p>\n<p>But who could that be?<\/p>\n<pre>0:000&gt; k\r\nCall Site\r\nntdll!LdrpICallHandler+0xf\r\nntdll!RtlpExecuteHandlerForException+0xf\r\nntdll!RtlDispatchException+0x219\r\nntdll!KiUserExceptionDispatch+0x2e\r\nntdll!LdrpDispatchUserCallTarget+0xe\r\nuser32!UserCallWinProcCheckWow+0x2bd\r\nuser32!DispatchClientMessage+0x9c\r\nuser32!__fnDWORD+0x33\r\nntdll!KiUserCallbackDispatcherContinue\r\nwin32u!ZwUserDestroyWindow+0x14\r\ncomctl32!_RealPropertySheet+0x36d\r\ncomctl32!_PropertySheet+0x47\r\nDisplay!PropertySheetW+0x5d\r\nDisplay!AdvancedSettingSheetHelper+0x3be\r\nDisplay!ShowAdapterSettings+0x89\r\nrundll32!CallRunDllFunction+0x1c\r\nrundll32!wWinMain+0x2bf\r\nrundll32!__wmainCRTStartup+0x1c9\r\nkernel32!BaseThreadInitThunk+0x14\r\nntdll!RtlUserThreadStart+0x21\r\n<\/pre>\n<p>This is a property sheet, so we should be able to extract the pages of the property sheet. (Note: Requires internal Microsoft symbols, so you won&#8217;t be able to do this at home.)<\/p>\n<pre>0:000&gt; .frame d\r\n09 00000017`85a7e820 00007fff`86e60349 Display!AdvancedSettingSheetHelper+0x3be\r\n0:000&gt; dv\r\n     hwndParent = &lt;value unavailable&gt;\r\n            psh = struct _PROPSHEETHEADERW_V2\r\n      szMonitor = wchar_t [140] \"Generic PnP Monitor\"\r\n         rPages = struct _PSP *[100]\r\n        iResult = 0n0\r\n<\/pre>\n<p><a title=\"How to Register and Implement a Property Sheet Handler for a Control Panel Application\" href=\"https:\/\/learn.microsoft.com\/windows\/win32\/shell\/how-to-register-and-implement-a-property-sheet-handler-for-a-control-panel-application\"> The desktop background control panel is extensible<\/a>, and the way that a plug-in adds a page to the desktop background control panel is by handling the <a href=\"https:\/\/learn.microsoft.com\/windows\/win32\/api\/shobjidl_core\/nf-shobjidl_core-ishellpropsheetext-addpages\">IShellPropSheetExt::<wbr \/>AddPages<\/a> method and calling the provided &#8220;page adding function&#8221; with a <code>HPROPSHEETPAGE<\/code>. What that function does is add the <code>HPROPSHEETPAGE<\/code> to the pages in the property sheet. (We can see that there&#8217;s room for 100 of them in the <code>rPages<\/code>.)<\/p>\n<p>And the <code>psh<\/code> is the <code>PROPSHEETHEADER<\/code>.<\/p>\n<pre>0:000&gt; ?? psh\r\nstruct _PROPSHEETHEADERW_V2\r\n   +0x000 dwSize           : 0x60\r\n   +0x004 dwFlags          : 0x2000001\r\n   +0x008 hwndParent       : 0x00000000`000401aa HWND__\r\n   +0x010 hInstance        : 0x00007fff`86e50000 HINSTANCE__\r\n   +0x018 hIcon            : (null)\r\n   +0x020 pszCaption       : 0x00000017`85a7f100  \"Generic PnP Monitor and Contoso Chipset\"\r\n   +0x028 nPages           : <span style=\"border: solid 1px currentcolor;\">4<\/span>\r\n   +0x030 nStartPage       : 0\r\n   +0x038 ppsp             : 0x00000017`85a7ec70 _PROPSHEETPAGEW\r\n   +0x038 phpage           : 0x00000017`85a7ec70  -&gt; 0x000001d5`4e1aac90 _PSP\r\n<\/pre>\n<p>We see that there are four pages, so we can inspect the first four <code>HPROPSHEETPAGE<\/code>s in <code>rPages<\/code>.<\/p>\n<p>And hey look, we have an array of <code>HPROPSHEETPAGE<\/code> structures<\/p>\n<pre>0:000&gt; ?? psh.phpage[0]\r\nstruct _PSP * 0x000001d5`4e1aac90\r\n0:000&gt; ?? psh.phpage[1]\r\nstruct _PSP * 0x000001d5`4e19e470\r\n0:000&gt; ?? psh.phpage[2]\r\nstruct _PSP * 0x000001d5`4e19e520\r\n0:000&gt; ?? psh.phpage[3]\r\nstruct _PSP * 0x000001d5`4e1d26d0\r\n<\/pre>\n<p>The <code>HPROPSHEETPAGE<\/code> is an opaque structure, but we can dump it and look for interesting things, for entertainment purposes only.<\/p>\n<pre>0:000&gt; dps 0x000001d5`4e1aac90 l4\r\n000001d5`4e1aac90  000001d5`4e1aac60\r\n000001d5`4e1aac98  00000000`00000000\r\n000001d5`4e1aaca0  00004088`00000068\r\n000001d5`4e1aaca8  00007fff`88d70000 deskadp\r\n0:000&gt; dps 0x000001d5`4e19e470 l4\r\n000001d5`4e19e470  000001d5`4e19e440\r\n000001d5`4e19e478  00000000`00000000\r\n000001d5`4e19e480  00004088`00000068\r\n000001d5`4e19e488  00007fff`893e0000 deskmon\r\n0:000&gt; dps 0x000001d5`4e19e520 l4\r\n000001d5`4e19e520  000001d5`4e19e4f0\r\n000001d5`4e19e528  00000000`00000000\r\n000001d5`4e19e530  000040c8`00000068\r\n000001d5`4e19e538  00007fff`86e30000 colorui\r\n0:000&gt; dps 0x000001d5`4e1d26d0 l4\r\n000001d5`4e1d26d0  000001d5`4e1bcb30\r\n000001d5`4e1d26d8  000001d5`4e1d26a0\r\n000001d5`4e1d26e0  0000008a`00000068\r\n000001d5`4e1d26e8  00000001`80000000 contoso\r\n<\/pre>\n<p>There are a bunch of <code>HMODULE<\/code>s here, which are probably the modules that the property sheet page came from. The first three come with Windows. The last one apparently is Contoso. Let&#8217;s focus on at last one.<\/p>\n<p>After the first two values (which look like pointers), we have <code>0x00000068<\/code> which is not-coincidentally <code>sizeof(PROPSHEETPAGE)<\/code>, so I&#8217;m going to guess that this is where the system stores the <code>PROPSHEETPAGE<\/code> that the handle was created from.<\/p>\n<p><b>Note<\/b>: Note that this is an implementation detail and should be used only for debugging purposes. Please don&#8217;t write programs that rely on this, because it can change.\u00b9<\/p>\n<pre>0:000&gt; dt comctl32!_PROPSHEETPAGEW  000001d5`4e1d26e0\r\n   +0x000 dwSize           : 0x68\r\n   +0x004 dwFlags          : 0x8a\r\n   +0x008 hInstance        : 0x00000001`80000000 HINSTANCE__\r\n   +0x010 pszTemplate      : 0x00000000`00000589  \"--- memory read error at address 0x00000000`00000589 ---\"\r\n   +0x010 pResource        : 0x00000000`00000589 DLGTEMPLATE\r\n   +0x018 hIcon            : 0x00000000`000503b9 HICON__\r\n   +0x018 pszIcon          : 0x00000000`000503b9  \"--- memory read error at address 0x00000000`000503b9 ---\"\r\n   +0x020 pszTitle         : 0x000001d5`4e19cde0  \"?????\"\r\n   +0x028 pfnDlgProc       : <span style=\"border: solid 1px currentcolor;\">0x00000001`800047ac<\/span> contoso+0x47ac\r\n   +0x030 lParam           : 0n2015682301296\r\n   +0x038 pfnCallback      : (null)\r\n   +0x040 pcRefParent      : (null)\r\n   +0x048 pszHeaderTitle   : (null)\r\n   +0x050 pszHeaderSubTitle : (null)\r\n   +0x058 hActCtx          : (null)\r\n   +0x060 hbmHeader        : (null)\r\n   +0x060 pszbmHeader      : (null)\r\n<\/pre>\n<p>The dialog procedure is <code>0x00000001`800047ac<\/code>. I&#8217;m hoping I can reverse-engineer it enough to see the place where it subclassed the button incorrectly.<\/p>\n<pre>00000001`800047ac mov     [rsp+8],rbx\r\n00000001`800047b1 mov     [rsp+10h],rbp\r\n00000001`800047b6 mov     [rsp+18h],rsi\r\n00000001`800047bb push    rdi\r\n00000001`800047bc sub     rsp,30h\r\n00000001`800047c0 mov     rdi,r9                ; rdi = r9 = lParam\r\n00000001`800047c3 mov     rbp,r8                ; rbp = r8 = wParam\r\n00000001`800047c6 mov     esi,edx               ; esi = edx = message\r\n00000001`800047c8 mov     rbx,rcx               ; rbx = rcx = hdlg\r\n00000001`800047cb cmp     edx,110h              ; Q: WM_INITDIALOG?\r\n00000001`800047d1 jne     00000001`800047e2     ; N: Skip\r\n00000001`800047d3 mov     r8,[r9+30h]           ; Y: r8 = ((PROPSHEETPAGE*)r9)-&gt;lParam\r\n00000001`800047d7 mov     edx,0FFFFFFEBh        ; edx = -21\r\n                                                ; ecx = hdlg (unchanged)\r\n00000001`800047dc call    [00000001`8002b4a0]   ; mystery function 1\r\n\r\n00000001`800047e2 mov     edx,0FFFFFFEBh        ; edx = -21\r\n00000001`800047e7 mov     rcx,rbx               ; rcx = hdlg\r\n00000001`800047ea call    [00000001`8002b480]   ; mystery function 2\r\n00000001`800047f0 test    rax,rax               ; Q: Failed?\r\n00000001`800047f3 je      00000001`8000480b     ; Y: Bail out\r\n00000001`800047f5 mov     r9,rbp                ; param4 = wParam\r\n00000001`800047f8 mov     r8d,esi               ; param3 = message\r\n00000001`800047fb mov     rdx,rbx               ; param2 = hdlg\r\n00000001`800047fe mov     rcx,rax               ; param1 = from mystery function 2\r\n00000001`80004801 mov     [rsp+20h],rdi         ; param5 = lParam\r\n00000001`80004806 call    00000001`800045fc     ; mystery function 3\r\n00000001`8000480b mov     rbx,[rsp+40h]         ; restore registers\r\n00000001`80004810 mov     rbp,[rsp+48h]\r\n00000001`80004815 mov     rsi,[rsp+50h]\r\n00000001`8000481a add     rsp,30h\r\n00000001`8000481e pop     rdi\r\n00000001`8000481f ret                            ; done\r\n<\/pre>\n<p>We know that the <code>lParam<\/code> parameter to the <code>WM_<wbr \/>INIT\u00adDIALOG<\/code> message is the value passed as the &#8220;parameter&#8221; to functions like <code>CreateDialogParam<\/code>, and specifically for property sheets, it&#8217;s a pointer to a <code>PROPSHEETPAGE<\/code>. And we saw from the structure dump above that offset <code>0x30<\/code> is the <code>lParam<\/code>.<\/p>\n<p>From the structure of this function, it&#8217;s clear that the magic value <code>-21<\/code> is <code>GWLP_<wbr \/>USERDATA<\/code>, mystery function 1 is <code>SetWindowLongPtr<\/code>, and mystery function 2 is <code>GetWindowLongPtr<\/code>. This is a standard pattern for dialog box functions, and it&#8217;s common to use a wrapper function.<\/p>\n<p>The real dialog procedure is the third mystery function, so let&#8217;s look at that.<\/p>\n<pre>00000001`800045fc mov     [rsp+8],rbx\r\n00000001`80004601 mov     [rsp+10h],rbp\r\n00000001`80004606 mov     [rsp+18h],rsi\r\n00000001`8000460b push    rdi\r\n00000001`8000460c push    r12\r\n00000001`8000460e push    r13\r\n00000001`80004610 sub     rsp,20h\r\n00000001`80004614 mov     rsi,[rsp+60h]     ; rsi = lParam\r\n00000001`80004619 mov     rbp,r9            ; rbp = wParam\r\n00000001`8000461c mov     ebx,r8d           ; ebx = message\r\n00000001`8000461f mov     r13,rdx           ; r13 = hdlg\r\n00000001`80004622 mov     rdi,rcx           ; rdi = this\r\n00000001`80004625 cmp     r8d,2Bh           ; Q: WM_DRAWITEM?\r\n00000001`80004629 jne     00000001`80004685 ; N: Skip\r\n<\/pre>\n<p>After the initial register spilling and saving, it checks if the message is <code>0x2B<\/code>: <code>WM_DRAWITEM<\/code>. That&#8217;s not particularly interesting to us, so let&#8217;s assume it&#8217;s not.<\/p>\n<pre>00000001`80004685 sub     ebx,2             ; Q: WM_DESTROY?\r\n00000001`80004688 je      00000001`8000470f\r\n<\/pre>\n<p>Ooh, the <code>WM_<wbr \/>DESTROY<\/code> message is interesting. It&#8217;s probably going to restore the original window procedure in its <code>WM_<wbr \/>DESTROY<\/code> handler, and that&#8217;s where we hope to find the truncation.<\/p>\n<pre>00000001`8000470f mov     rcx,[rdi+110h]        ; rcx = something\r\n00000001`80004716 movsxd  rbx,dword ptr [00000001`80039c50] ; rbx = something\r\n00000001`8000471d mov     edx,668h              ; ecx = some number\r\n00000001`80004722 call    [00000001`8002b4e0]   ; mystery function 4\r\n00000001`80004728 mov     r8,rbx                ; r8 = something\r\n00000001`8000472b mov     edx,0FFFFFFFCh        ; edx = -12\r\n00000001`80004730 mov     rcx,rax               ; rcx = function 4 retval\r\n00000001`80004733 call    [00000001`8002b4a0]   ; mystery function 1 again\r\n<\/pre>\n<p>On receipt of the <code>WM_<wbr \/>DESTROY<\/code> message, the code starts by getting something out of the <code>this<\/code> pointer (which we saw in the prologue was saved in <code>rdi<\/code>), and loads some other thing from a global variable.<\/p>\n<p>Next, it calls mystery function <code>00000001`8002b4e0<\/code> with <code>0x668<\/code> as the second parameter. Not sure what that is, but we&#8217;ll keep it in mind.<\/p>\n<p>Next, we set up for another function call, and this one we recognize: <code>00000001`8002b4a0<\/code> is the import address table entry for <code>SetWindowLongPtr<\/code>. We saw it in the static dialog procedure.<\/p>\n<p>The parameters are the window handle that was obtained from mystery function 4, the constant <code>-12<\/code>, and the 32-bit value we loaded from <code>00000001`80039c50<\/code>. The mystery function 4 was probably <code>Get\u00adDlg\u00adItem<\/code>. And since we figured out that the function being called is <code>SetWindowLongPtr<\/code>, the value <code>-12<\/code> is <code>GWLP_WNDPROC<\/code>.<\/p>\n<p>The value being set is the third parameter, which was loaded by <code>movsxd dword ptr<\/code>, which is a 32-bit to 64-bit sign-extended load. This is a problem because the window procedure is a 64-bit value.<\/p>\n<p>I bet they loaded the value incorrectly.<\/p>\n<pre>0:000&gt; dp 00000001`80039c50 l1\r\n00000001`80039c50  00007fff`924bbde0\r\n<\/pre>\n<p>Hey look, it&#8217;s the full 64-bit pointer we were supposed to have used, except we messed up and truncated the pointer.<\/p>\n<p>The C++ source code probably looked like this:<\/p>\n<pre>SetWindowLongPtr(GetDlgItem(m_hdlg, 0x668),\r\n    GWLP_WNDPROC, (LONG)g_originalWndProc);\r\n<\/pre>\n<p>The cast to <code>LONG<\/code> is what&#8217;s doing the truncation and sign extension. It should be a cast to <code>LONG_PTR<\/code>.<\/p>\n<p>We can patch this into the binary after looking at the processor instruction encoding documentation.<\/p>\n<p>The original instruction was<\/p>\n<pre>00000001`80004716 48631d33550300  movsxd  rbx,dword ptr [00000001`80039c50]\r\n<\/pre>\n<p>The documentation says that the encoding for <code>movxsd r64, r\/m32<\/code> is &#8220;REX.W + 63 \/r&#8221;.<\/p>\n<p>What we want is <code>mov rbx, [00000001`80039c50]<\/code>, and the documentation says that the encoding for <code>mov r64, r\/m64<\/code> is &#8220;REX.W + 8B \/r&#8221;.<\/p>\n<p>So let&#8217;s patch the <code>63<\/code> to <code>8b<\/code>.<\/p>\n<pre>0:000&gt; eb 00000001`80004717 8b\r\n0:000&gt; u 00000001`80004716 l1\r\n00000001`80004716 488b1d33550300  mov     rbx,qword ptr [00000001`80039c50]\r\n<\/pre>\n<p>This is literally a one-byte bug fix.<\/p>\n<p>Next time, we&#8217;ll speculate on how this bug arose.<\/p>\n<p><b>Bonus reading<\/b>: <a title=\"The decoy display control panel\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20060410-17\/?p=32703\"> The decoy control panel<\/a>.<\/p>\n<p>\u00b9 Back in the late 1990&#8217;s, we discovered a program that reverse-engineered the internal data structures of the Windows 95 property sheet manager to the point where instead of passing an <code>HPROPSHEETPAGE<\/code> that was created by the <code>Create\u00adProperty\u00adSheet\u00adPage<\/code> function, it created fake <code>HPROPSHEETPAGE<\/code>s that it had constructed manually in memory. This made adding support for Unicode property sheets that much harder because the internal structure of <code>HPROPSHEETPAGE<\/code>s changed in order to support both ANSI and Unicode property sheet pages, and they were passing the old version. The property sheet manager has to recognize that it is being given a fake <code>HPROPSHEETPAGE<\/code> and convert it on the fly to a real one.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Watching the bits disappear.<\/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-112535","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Watching the bits disappear.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112535","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=112535"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112535\/revisions"}],"predecessor-version":[{"id":112538,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112535\/revisions\/112538"}],"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=112535"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112535"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112535"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}