物理メモリが共有されコピーオンライトで複製される様子を図示する

Windows で同じプログラムを複数起動すると、それぞれに仮想アドレス空間が割り当てられます。プロセス分離です。しかし、完全に分離しているのではなく、裏では一部の物理メモリが共有されています。
また、この共有されたメモリに書き込みを行おうとすると、ほかのプロセスに影響を与えないようメモリが自動的に複製されます。コピーオンライト(Copy-on-Write)です。
この物理メモリの共有やコピーオンライトの実際の動作を見てみます。

動作確認環境

  • Windows 11 Home 24H2
  • Visual Studio Community 2022 (Visual C++)
  • WinDbg 1.2502.25002.0

データやコードを書き換えるプログラムを作成

テスト用に、次の処理を順番に実行するプログラムを作りました。

  • 変数の値 “Hello, Japan!” をメッセージボックスに表示
  • 変数の値を “Hello, Anpan!” に書き換えてメッセージボックスを表示
  • WinMain 関数の先頭のコードを 0xCC 0xCC 0xCC 0x00 に書き換えてメッセージボックスを表示

ソースコード test.c です。

#include <windows.h>

char *pGlobal = "Hello, Japan!";

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // メッセージボックスを表示
    MessageBox(0, pGlobal, "test #1", MB_OK);

    // データを書き換えて、メッセージボックスを表示
    *(pGlobal + 7) = 'A';
    *(pGlobal + 8) = 'n';
    MessageBox(0, pGlobal, "test #2", MB_OK);

    // コードを書き換えて、メッセージボックスを表示
    HANDLE hProcess = GetCurrentProcess();
    LPVOID lpAddress = WinMain;
    int newValue = 0xCCCCCC; // 0xCC = int 3 = 'フ'
    BOOL bSuccess = WriteProcessMemory(hProcess, lpAddress, &newValue, sizeof(newValue), NULL);
    if (! bSuccess)
    {
        MessageBox(0, "error", "test #3", MB_OK);
        return 1;
    }
    MessageBox(0, (char *)WinMain, "test #3", MB_OK);

    return 0;
}

x64 Native Tools Command Prompt for VS 2022 のコマンドプロンプトを開きます。

cl コマンドでビルドします。IDE を使っても構いませんが、軽いからです。オプションの「/Od」は最適化なし、「/Zi」はデバッグを情報を生成、の意味です。

C:\tmp> cl /Od /Zi test.c /link user32.lib

test.exe が生成されました。

メモリレイアウトの確認

test.exe を 2 個起動します。

各プロセスの仮想アドレス空間を調べたところ、レイアウトはまったく同じでした。たとえば、

  • メインルーチンである WinMain 関数のコード(正確には WinMain 関数にジャンプするコード)
  • グローバル変数にセットしている “Hello, Japan!” のデータ
  • OS の user32.dll が提供する MessageBox 関数のコード(正確には ASCII 用の MessageBoxA 関数のコード)

は、今回、以下の場所にロードされていました。

そして、各仮想アドレスに対応する物理アドレスは次のようになっていました。

WinMain (青)や MessageBox (緑)のコードが共有されていることがわかります。コードは基本的に読み取り専用なので共有が可能で、共有すれば物理メモリを節約できます。
一方、”Hello, Japan!” (赤)のデータは、プロセスごとに専用の物理メモリが割り当てられていました。データは書き換えられる可能性が高いので最初から分けておこうという判断でしょう

もともと .exe/.dll ファイルの内部は、ここが読み取り専用のコード領域、ここが読み書き可能なデータ領域などと複数のセクションに分かれています。Visual Studio 付属の dumpbin ツールで見ると確認できます。

C:\tmp> dumpbin /headers test.exe
......
SECTION HEADER #1
   .text name              ★ テキストセクション
......
         Code              ★ コード
         Execute Read      ★ 実行可能, 読み取り専用

......
SECTION HEADER #3
   .data name              ★ データセクション
......
         Initialized Data  ★ 初期化済みデータ
         Read Write        ★ 読み書き可能
......

データ領域やコード領域に書き込みを行うと

プロセス 1 で [OK] ボタンを押して、変数の値を “Hello, Japan!” から “Hello, Anpan!” に書き換えます。

プロセス 1 専用のデータ領域が書き換えられました。もともとプロセス 2 とは実体が異なるため、相手方には干渉していません。

続いて、もう一度プロセス 1 で [OK] ボタンを押し、WinMain の先頭に 0xCC 0xCC 0xCC 0x00(= int 3, int 3, int 3 = ‘フフフ’)を書き込みます。ポインタを使って普通にコード領域に書き込みを行うとメモリアクセス違反で例外が発生するので、Windows API の WriteProcessMemory 関数を利用して書き込みます。

書き込み後のレイアウトは次の通りです。

物理メモリ内の WinMain が OS によって自動的に複製され、複製された側のコード領域が書き換えられました。コピーオンライト(書き込み時コピー)です。この動作により、プロセス 2 への影響が回避できています。

また、プロセス 1 の仮想アドレス空間における WinMain のアドレスは変わらず、裏でこっそり物理アドレスとのマッピングが変更されています。これにより、プロセス 1 はメモリが複製されたことを意識することなく動作を継続できます。

もう一方のプロセスからも書き込み

もう一方のプロセス 2 で [OK] ボタンを押して、変数の値を “Hello, Japan!” から “Hello, Anpan!” に書き換えます。

プロセス 2 専用のデータ領域が書き換えられました。

続いて、もう一度プロセス 2 で [OK] ボタンを押し、WinMain の先頭に 0xCC 0xCC 0xCC 0x00 を書き込んでみます。

書き込み後のレイアウトは次の通りです。

物理メモリ内の WinMain が OS によって自動的に複製され、複製された側のコード領域が書き換えられました。
物理メモリ内の旧 WinMain コードを参照しているのはすでにプロセス 2 だけなので、そのまま書き換えても問題ないように思えましたが、コピーオンライトが作動しました。理由はわかりません。

なお、書き込みを行っていない MessageBox 関数は、最初から最後まで共有された状態です。

調査手順について

上記の調査は参考文献 [1] [2] に示した WinDbg のカーネルデバッグ機能を使って行いました。参考までに手順の一部を書き留めておきます。

0: kd> !sym noisy  ★ シンボルのロード状況を表示するよう指定。
noisy mode - symbol prompts on

0: kd> .symfix c:\symbols  ★ シンボルの格納先を c:\symbols に設定。
......

0: kd> .reload /f  ★ シンボル一式のロード。
......

0: kd> !process 0 0 test.exe  ★ test プロセスの基本情報を表示。
PROCESS ffff9b076bb34080
    SessionId: none  Cid: 0880    Peb: ea3aa83000  ParentCid: 13c4
    DirBase: 21351d002  ObjectTable: ffffdf09d7951d00  HandleCount: 114.
    Image: test.exe

PROCESS ffff9b076cc870c0
    SessionId: none  Cid: 10f0    Peb: e513481000  ParentCid: 13c4
    DirBase: 1e1b4a002  ObjectTable: ffffdf09d716d6c0  HandleCount: 117.
    Image: test.exe

0: kd> .process /i /p ffff9b076bb34080  ★ ひとつ目のプロセスに、
You need to continue execution (press 'g' <enter>) for the context
to be switched. When the debugger breaks in again, you will be in
the new process context.

0: kd> g  ★ 侵入して、
Break instruction exception - code 80000003 (first chance)
.....

1: kd> .reload /user  ★ ユーザーモードのシンボルをロード。
......

1: kd> lm  ★ 各モジュールのアドレスを確認。
start             end                 module name
00007ff7`8d740000 00007ff7`8d7ea000   test     C (private pdb symbols)  C:\tp\checkaddress\test.pdb
00007ffd`99590000 00007ffd`9963b000   TextShaping   (deferred)             
00007ffd`a8e30000 00007ffd`a8f7d000   textinputframework   (deferred)             
00007ffd`af7f0000 00007ffd`afad3000   CoreUIComponents   (deferred)             
00007ffd`b3490000 00007ffd`b35b6000   CoreMessaging   (deferred)             
00007ffd`b3be0000 00007ffd`b3c8f000   uxtheme    (deferred)             
00007ffd`b5d70000 00007ffd`b5d8a000   kernel_appcore   (deferred)             
00007ffd`b6580000 00007ffd`b658c000   CRYPTBASE   (deferred)             
00007ffd`b6e90000 00007ffd`b6eb7000   win32u     (deferred)             
00007ffd`b6ec0000 00007ffd`b6ff2000   gdi32full   (deferred)             
00007ffd`b7090000 00007ffd`b71db000   ucrtbase   (deferred)             
00007ffd`b71e0000 00007ffd`b7279000   bcryptPrimitives   (deferred)             
00007ffd`b7400000 00007ffd`b77cc000   KERNELBASE   (deferred)             
00007ffd`b77d0000 00007ffd`b7944000   wintypes   (deferred)             
00007ffd`b7a10000 00007ffd`b7ab3000   msvcp_win   (deferred)             
00007ffd`b8650000 00007ffd`b86f9000   msvcrt     (deferred)             
00007ffd`b8700000 00007ffd`b8816000   RPCRT4     (deferred)             
00007ffd`b8820000 00007ffd`b88c6000   sechost    (deferred)             
00007ffd`b8de0000 00007ffd`b8f7f000   ole32      (deferred)             
00007ffd`b9090000 00007ffd`b9170000   OLEAUT32   (deferred)             
00007ffd`b91b0000 00007ffd`b9279000   KERNEL32   (deferred)             
00007ffd`b9280000 00007ffd`b92ab000   GDI32      (deferred)             
00007ffd`b9360000 00007ffd`b952a000   USER32     (deferred)             
00007ffd`b9530000 00007ffd`b9560000   IMM32      (deferred)             
00007ffd`b9600000 00007ffd`b975f000   MSCTF      (deferred)             
00007ffd`b9870000 00007ffd`b9922000   advapi32   (deferred)             
00007ffd`b9930000 00007ffd`b9cb4000   combase    (deferred)             
00007ffd`b9d00000 00007ffd`b9f66000   ntdll      (deferred)             
......

1: kd> uf test!WinMain  ★ WinMain を逆アセンブルして、
test!WinMain [C:\tp\checkaddress\test.c @ 6]:
    6 00007ff7`8d747250 44894c2420      mov     dword ptr [rsp+20h],r9d
    6 00007ff7`8d747255 4c89442418      mov     qword ptr [rsp+18h],r8
    6 00007ff7`8d74725a 4889542410      mov     qword ptr [rsp+10h],rdx
    6 00007ff7`8d74725f 48894c2408      mov     qword ptr [rsp+8],rcx
    6 00007ff7`8d747264 4883ec58        sub     rsp,58h
    8 00007ff7`8d747268 4533c9          xor     r9d,r9d
    8 00007ff7`8d74726b 4c8d05a62d0900  lea     r8,[test!pGlobal+0x18 (00007ff7`8d7da018)]
    8 00007ff7`8d747272 488b15872d0900  mov     rdx,qword ptr [test!pGlobal (00007ff7`8d7da000)]
    8 00007ff7`8d747279 33c9            xor     ecx,ecx
    8 00007ff7`8d74727b ff154fd10900    call    qword ptr [test!_imp_MessageBoxA (00007ff7`8d7e43d0)]
   11 00007ff7`8d747281 488b05782d0900  mov     rax,qword ptr [test!pGlobal (00007ff7`8d7da000)]
   11 00007ff7`8d747288 c6400741        mov     byte ptr [rax+7],41h
   12 00007ff7`8d74728c 488b056d2d0900  mov     rax,qword ptr [test!pGlobal (00007ff7`8d7da000)]  ★ pGlobal 変数の場所と、
   12 00007ff7`8d747293 c640086e        mov     byte ptr [rax+8],6Eh
   13 00007ff7`8d747297 4533c9          xor     r9d,r9d
   13 00007ff7`8d74729a 4c8d057f2d0900  lea     r8,[test!pGlobal+0x20 (00007ff7`8d7da020)]
   13 00007ff7`8d7472a1 488b15582d0900  mov     rdx,qword ptr [test!pGlobal (00007ff7`8d7da000)]
   13 00007ff7`8d7472a8 33c9            xor     ecx,ecx
   13 00007ff7`8d7472aa ff1520d10900    call    qword ptr [test!_imp_MessageBoxA (00007ff7`8d7e43d0)]
   16 00007ff7`8d7472b0 ff1562cd0900    call    qword ptr [test!_imp_GetCurrentProcess (00007ff7`8d7e4018)]
   16 00007ff7`8d7472b6 4889442440      mov     qword ptr [rsp+40h],rax
   17 00007ff7`8d7472bb 488d0555bdffff  lea     rax,[test!ILT+8210(WinMain) (00007ff7`8d743017)]  ★ WinMain 名の書き込み先を特定。
   17 00007ff7`8d7472c2 4889442438      mov     qword ptr [rsp+38h],rax
   18 00007ff7`8d7472c7 c7442430cccccc00 mov     dword ptr [rsp+30h],0CCCCCCh
   19 00007ff7`8d7472cf 48c744242000000000 mov   qword ptr [rsp+20h],0
   19 00007ff7`8d7472d8 41b904000000    mov     r9d,4
   19 00007ff7`8d7472de 4c8d442430      lea     r8,[rsp+30h]
   19 00007ff7`8d7472e3 488b542438      mov     rdx,qword ptr [rsp+38h]
   19 00007ff7`8d7472e8 488b4c2440      mov     rcx,qword ptr [rsp+40h]
   19 00007ff7`8d7472ed ff152dcd0900    call    qword ptr [test!_imp_WriteProcessMemory (00007ff7`8d7e4020)]
   19 00007ff7`8d7472f3 89442434        mov     dword ptr [rsp+34h],eax
   20 00007ff7`8d7472f7 837c243400      cmp     dword ptr [rsp+34h],0
   20 00007ff7`8d7472fc 7520            jne     test!WinMain+0xce (00007ff7`8d74731e)  Branch

test!WinMain+0xae [C:\tp\checkaddress\test.c @ 22]:
   22 00007ff7`8d7472fe 4533c9          xor     r9d,r9d
   22 00007ff7`8d747301 4c8d05202d0900  lea     r8,[test!pGlobal+0x28 (00007ff7`8d7da028)]
   22 00007ff7`8d747308 488d15212d0900  lea     rdx,[test!pGlobal+0x30 (00007ff7`8d7da030)]
   22 00007ff7`8d74730f 33c9            xor     ecx,ecx
   22 00007ff7`8d747311 ff15b9d00900    call    qword ptr [test!_imp_MessageBoxA (00007ff7`8d7e43d0)]
   23 00007ff7`8d747317 b801000000      mov     eax,1
   23 00007ff7`8d74731c eb1b            jmp     test!WinMain+0xe9 (00007ff7`8d747339)  Branch

test!WinMain+0xce [C:\tp\checkaddress\test.c @ 25]:
   25 00007ff7`8d74731e 4533c9          xor     r9d,r9d
   25 00007ff7`8d747321 4c8d05102d0900  lea     r8,[test!pGlobal+0x38 (00007ff7`8d7da038)]
   25 00007ff7`8d747328 488d15e8bcffff  lea     rdx,[test!ILT+8210(WinMain) (00007ff7`8d743017)]
   25 00007ff7`8d74732f 33c9            xor     ecx,ecx
   25 00007ff7`8d747331 ff1599d00900    call    qword ptr [test!_imp_MessageBoxA (00007ff7`8d7e43d0)]
   27 00007ff7`8d747337 33c0            xor     eax,eax

test!WinMain+0xe9 [C:\tp\checkaddress\test.c @ 28]:
   28 00007ff7`8d747339 4883c458        add     rsp,58h
   28 00007ff7`8d74733d c3              ret


1: kd> dp 00007ff7`8d7da000  ★ pGlobal ポインタには 00007ff7`8d7da008 が入っており、
00007ff7`8d7da000  00007ff7`8d7da008 4a202c6f`6c6c6548
......

1: kd> da 00007ff7`8d7da008  ★ "Hello, Japan!" を指していることを確認。
00007ff7`8d7da008  "Hello, Japan!"

1: kd> dpa 00007ff7`8d7da000 L1  ★ 別の確認方法。
00007ff7`8d7da000  00007ff7`8d7da008 "Hello, Japan!"

1: kd> u 00007ff7`8d743017  ★  WinMain 名の書き込み先は、真の WinMain にジャンプするコードになっていたが、WinMain として扱う。
test!ILT+8210(WinMain):
00007ff7`8d743017 e934420000      jmp     test!WinMain (00007ff7`8d747250)
.....

1: kd> !pte 00007ff7`8d743017  ★ プロセス 1 の WinMain のページテーブルエントリーを取得。
                                           VA 00007ff78d743017
PXE at FFFF964B2592C7F8    PPE at FFFF964B258FFEF0    PDE at FFFF964B1FFDE358    PTE at FFFF963FFBC6BA18
contains 8A00000219429867  contains 0A0000017662A867  contains 0A00000218F2B867  contains 01000001447A2025
pfn 219429    ---DA--UW-V  pfn 17662a    ---DA--UWEV  pfn 218f2b    ---DA--UWEV  pfn 1447a2    ----A--UREV  ★ この最後の pfn が重要。

1: kd> !db (1447a2*1000)+(00007ff7`8d743017&FFF) L10   ★ プロセス 1 の WinMain の物理アドレス。
#1447a2017 e9 34 42 00 00 e9 3f 11-06 00 e9 76 52 04 00 e9 .4B...?....vR...

1: kd> !pte 00007ff7`8d7da008  ★ プロセス 1 の "Hello, Japan!" のページテーブルエントリーを取得。
                                           VA 00007ff78d7da008
PXE at FFFF964B2592C7F8    PPE at FFFF964B258FFEF0    PDE at FFFF964B1FFDE358    PTE at FFFF963FFBC6BED0
contains 8A00000219429867  contains 0A0000017662A867  contains 0A00000218F2B867  contains 8100000144E60847
pfn 219429    ---DA--UW-V  pfn 17662a    ---DA--UWEV  pfn 218f2b    ---DA--UWEV  pfn 144e60    ---D---UW-V  ★ この最後の pfn が重要。

1: kd> !db (144e60*1000)+(00007ff7`8d7da008&FFF) L10  ★ "Hello, Japan!" の物理アドレス。
#144e60008 48 65 6c 6c 6f 2c 20 4a-61 70 61 6e 21 00 00 00 Hello, Japan!...

1: kd> u user32!MessageBoxA  ★ MessageBoxA 関数の先頭が適切な命令か確認。
USER32!MessageBoxA:
00007ffd`b93eb2c0 4883ec38        sub     rsp,38h
00007ffd`b93eb2c4 4533db          xor     r11d,r11d
00007ffd`b93eb2c7 44391d6aa50400  cmp     dword ptr [USER32!gfEMIEnable (00007ffd`b9435838)],r11d
00007ffd`b93eb2ce 7425            je      USER32!MessageBoxA+0x35 (00007ffd`b93eb2f5)
00007ffd`b93eb2d0 65488b042530000000 mov   rax,qword ptr gs:[30h]
00007ffd`b93eb2d9 4c8b5048        mov     r10,qword ptr [rax+48h]
00007ffd`b93eb2dd 33c0            xor     eax,eax
00007ffd`b93eb2df f04c0fb11510ab0400 lock cmpxchg qword ptr [USER32!gdwEMIThreadID (00007ffd`b9435df8)],r10

1: kd> !pte user32!MessageBoxA  ★ プロセス 1 の MessageBoxA のページテーブルエントリーを取得。
                                           VA 00007ffdb93eb2c0
PXE at FFFF964B2592C7F8    PPE at FFFF964B258FFFB0    PDE at FFFF964B1FFF6E48    PTE at FFFF963FFEDC9F58
contains 8A00000219429867  contains 0A0000012352C867  contains 0A0000019AC62867  contains 0100000118707025
pfn 219429    ---DA--UW-V  pfn 12352c    ---DA--UWEV  pfn 19ac62    ---DA--UWEV  pfn 118707    ----A--UREV

1: kd> !db (118707*1000)+(user32!MessageBoxA&FFF) L10  ★ プロセス 1 の MessageBoxA の物理アドレス。
#1187072c0 48 83 ec 38 45 33 db 44-39 1d 6a a5 04 00 74 25 H..8E3.D9.j...t%

1: kd> .process /i /p ffff9b076cc870c0  ★ ふたつ目のプロセスに、
......

1: kd> g  ★ 侵入して、
......

1: kd> .reload /user  ★ ユーザーモードのシンボルをロード。
......

1: kd> lm  ★ 各モジュールのアドレスを確認。先のプロセスと同じ。
......

1: kd> uf test!WinMain  ★ WinMain を逆アセンブル。先のプロセスと同じ。
......

1: kd> !pte 00007ff7`8d743017  ★ プロセス 2 の WinMain のページテーブルエントリーを取得。
                                           VA 00007ff78d743017
PXE at FFFF964B2592C7F8    PPE at FFFF964B258FFEF0    PDE at FFFF964B1FFDE358    PTE at FFFF963FFBC6BA18
contains 8A000001CF656867  contains 0A000001D6657867  contains 0A000001C9E58867  contains 01000001447A2005
pfn 1cf656    ---DA--UW-V  pfn 1d6657    ---DA--UWEV  pfn 1c9e58    ---DA--UWEV  pfn 1447a2    -------UREV

1: kd> !db (1447a2*1000)+(00007ff7`8d743017&FFF) L10  ★ プロセス 2 の WinMain の物理アドレス
#1447a2017 e9 34 42 00 00 e9 3f 11-06 00 e9 76 52 04 00 e9 .4B...?....vR...

1: kd> !pte 00007ff7`8d7da008  ★ プロセス 2 の "Hello, Japan!" のページテーブルエントリーを取得。プロセス 1 とは異なる。
                                           VA 00007ff78d7da008
PXE at FFFF964B2592C7F8    PPE at FFFF964B258FFEF0    PDE at FFFF964B1FFDE358    PTE at FFFF963FFBC6BED0
contains 8A000001CF656867  contains 0A000001D6657867  contains 0A000001C9E58867  contains 810000020CB8D867
pfn 1cf656    ---DA--UW-V  pfn 1d6657    ---DA--UWEV  pfn 1c9e58    ---DA--UWEV  pfn 20cb8d    ---DA--UW-V

1: kd> !db (20cb8d*1000)+(00007ff7`8d7da008&FFF) L10  ★ プロセス 2 の "Hello, Japan!" の物理アドレス。
#20cb8d008 48 65 6c 6c 6f 2c 20 4a-61 70 61 6e 21 00 00 00 Hello, Japan!...

1: kd> !pte 00007ffd`b93eb2c0  ★ プロセス 2 の MessageBoxA のページテーブルエントリーを取得。
                                           VA 00007ffdb93eb2c0
PXE at FFFF964B2592C7F8    PPE at FFFF964B258FFFB0    PDE at FFFF964B1FFF6E48    PTE at FFFF963FFEDC9F58
contains 8A000001CF656867  contains 0A000001C9B59867  contains 0A0000020798F867  contains 0100000118707025
pfn 1cf656    ---DA--UW-V  pfn 1c9b59    ---DA--UWEV  pfn 20798f    ---DA--UWEV  pfn 118707    ----A--UREV

1: kd> !db (118707*1000)+(00007ffd`b93eb2c0&FFF) L10   ★ プロセス 2 の MessageBoxA の物理アドレス。
#1187072c0 48 83 ec 38 45 33 db 44-39 1d 6a a5 04 00 74 25 H..8E3.D9.j...t%

1: kd> g  ★ プログラム再開。test.exe のメッセージボックスの [OK] ボタンを押下。

★ WinDbg 画面上の Break ボタンを押してデバッグ再開。

0: kd> .process /i /p ffff9b076bb34080  ★ プロセス 1 に侵入して、
0: kd> g
6: kd> .reload /user

6: kd> !pte 00007ff7`8d743017  ★ プロセス 1 の WinMain のページテーブルエントリーを取得。
                                           VA 00007ff78d743017
PXE at FFFF964B2592C7F8    PPE at FFFF964B258FFEF0    PDE at FFFF964B1FFDE358    PTE at FFFF963FFBC6BA18
contains 8A00000219429867  contains 0A0000017662A867  contains 0A00000218F2B867  contains 01000001447A2005
pfn 219429    ---DA--UW-V  pfn 17662a    ---DA--UWEV  pfn 218f2b    ---DA--UWEV  pfn 1447a2    -------UREV

6: kd> !db (1447a2*1000)+(00007ff7`8d743017&FFF) L10  ★ プロセス 1 の WinMain の物理アドレス。
#1447a2017 e9 34 42 00 00 e9 3f 11-06 00 e9 76 52 04 00 e9 .4B...?....vR...

★ 以下同様の手順で調査。

おわりに

複数のプロセスがメモリを共有する様子や、共有メモリへの書き込み時に内容がコピーされる様子を、物理メモリのレベルで確認しました。

参考文献

[1] やや低レイヤー研究所「WinDbg のカーネルデバッグで使える USB 3.0 ケーブルを作る」
https://yaya.lsv.jp/usb-debug-cable/

[2] やや低レイヤー研究所「2 台の PC を USB デバッグケーブルで接続してカーネルデバッグする方法 (Windows)」
https://yaya.lsv.jp/usb_debug_setup/