ヒープメモリのアドレスから、要求したサイズを求める (Windows)

ヒープメモリを確保する際、サイズを指定してポインタ(アドレス)を取得しますが、逆に、アドレスから要求したサイズを求めることもできます。

動作確認環境

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

要求したサイズをプログラム内で求める

プログラムの中で、ヒープメモリを指すポインタ(アドレス)から要求したサイズを知りたい場合は、_msize() 関数を使います。Visual C++ 専用の関数です。

たとえば、

char *p = (char *)malloc(17001);

で 17001 バイトのメモリを要求し、変数 p にアドレスがセットされた場合、

size_t s = _msize(p);

とすれば、要求したサイズ 17001 がリターンします。

次のプログラムをビルドして実行すると、

#include <stdio.h>
#include <windows.h>

int main()
{
    char *p = (char *)malloc(17001);
    size_t s = _msize(p);

    printf("あなたが要求したのは %d バイトですね。\n", s);
    return 0;
}

次のように表示されることになります。

あなたが要求したのは 17001 バイトですね。

要求したサイズをプログラム実行時に求める

ソースコードを変更できない場合は、動作中のプログラムに WinDbg でアタッチして !heap -x <ヒープメモリのアドレス> コマンドを実行すると、要求したサイズを取得できます。

たとえば、次のプログラム getmem.c を、

#include <stdio.h>
#include <windows.h>

int main()
{
    char *p = (char *)malloc(20007);
    printf("p=%p\n", p);

    printf("[Enter] キーで終了");
    getchar();

    return 0;
}

Visual C++ の x86 版(32bit 版)でビルドして、

C:\tmp> cl getmem.c
Microsoft(R) C/C++ Optimizing Compiler Version 19.43.34810 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

getmem.c
Microsoft (R) Incremental Linker Version 14.43.34810.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:getmem.exe
getmem.obj

実行すると、

C:\tmp> getmem.exe
p=00A99FE8
[Enter] キーで終了

確保したヒープのアドレスが表示され、[Enter] キー入力待ちになります。

この状態で WinDbg を起動し、メニューから [ファイル]-[Attach to process] を選択して getmem.exe にアタッチし、

[Command] ウィンドウにて、シンボルファイル設定後、!heap -x <ヒープメモリのアドレス> と入力すると、以下のような情報が得られます。

0:001> .symfix c:\symbols  ★ 必要に応じてシンボルファイルの置き場所を指定
0:001> .reload /f  ★ 必要に応じてシンボルファイルをロード
......
0:001> !heap -x a99fe8  ★ 指定のアドレスを含むヒープブロックの情報を表示
Entry     User      Heap      Segment       Size  PrevSize  Unused    Flags
-----------------------------------------------------------------------------
00a99fe0  00a99fe8  00a80000  00a80000      4e30        20         9  busy 

User の値 0x00a99fe8 が malloc が返したアドレスです。
Size から Unused を引いた値、すなわち 0x4e30 – 0x9 = 0x4e27 = 20007 バイトが、要求したサイズです。

要求したサイズをダンプファイルから求める

ダンプファイル (*.dmp) に対しても、!heap -x <ヒープメモリのアドレス> が使えます。

たとえば、メモリを確保した後、アクセス違反の例外を発生させる次のプログラム getmem2.c を、

#include <stdio.h>
#include <windows.h>

int main()
{
    char *p = (char *)malloc(30007);
    printf("p=%p\n", p);

    p = (char *)NULL;
    *p = 0; // アクセス違反

    return 0;
}

Visual C++ の x86 版(32bit 版)でビルドし、

C:\tmp> cl getmem2.c
Microsoft(R) C/C++ Optimizing Compiler Version 19.43.34810 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

getmem2.c
Microsoft (R) Incremental Linker Version 14.43.34810.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:getmem2.exe
getmem2.obj

32bit プログラムの例外発生時にプロセスのダンプファイルが生成されるよう次のレジストリを設定し、

  • レジストリキー「HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows NT\CurrentVersion\AeDebug」に「Auto (REG_SZ) 0」を設定
  • レジストリキー「HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps」に「DumpType (REG_DWORD) 2」を設定

実行すると、

C:\tmp> getmem2.exe
p=00B4BFE8

確保したヒープのアドレスが表示され、例外が発生し、「%LOCALAPPDATA%\CrashDumps」フォルダにダンプファイルが生成されます。

C:\tmp> dir %LOCALAPPDATA%\CrashDumps
......
...... 7,696,815 getmem2.exe.15416.dmp

WinDbg を起動し、メニューから [ファイル]-[Open dump file] を選択して生成されたダンプファイルを読み込み、[Command] ウィンドウに !heap -x <ヒープメモリのアドレス> と入力すると、以下のような情報が得られます。

0:000> .symfix c:\symbols  ★ 必要に応じてシンボルファイルの置き場所を指定
0:000> .reload /f  ★ 必要に応じてシンボルファイルをロード
......
0:000> !heap -x b4bfe8  ★ 指定のアドレスを含むヒープブロックの情報を表示
Entry     User      Heap      Segment       Size  PrevSize  Unused    Flags
-----------------------------------------------------------------------------
00b4bfe0  00b4bfe8  00b30000  00b30000      7540        30         9  busy 

User の値 0x00b4bfe8 が malloc が返したアドレスです。
Size から Unused を引いた値、すなわち 0x7540 – 0x9 = 0x7537 = 30007 バイトが、要求したサイズです。

おわりに

ヒープメモリのアドレスから要求したサイズを求める方法について説明しました。
このサイズがメモリ上のどこに配置されているのかについては、気が向いたら別の記事に書きたいと思います。