茂木さんに教えてもらったことなんですが。

x64 のプログラムで、32bit アドレッシングをしていたり、うっかりポインタを 32bit にキャストした後に使ってしまったりすると、32bit の範囲を超えるポインタが渡ってきたときに、変なところにアクセスして死んでしまいます。(

普通、メモリは下位アドレスから割り当てられるため、プログラムを起動しても最初のうちは大丈夫だったりして実行時には多少見つけづらかったりします。しかし、この Microsoft のページに書いてある通り、レジストリの HKLM\System\CurrentControlSet\Control\Session Manager\Memory Management キーの下の AllocationPreference という DWORD 値を 0x100000 に設定すると、システム全体であらゆるメモリ割り当てが上位アドレス、具体的には 0x000007ffffffffff (8TB) から下位の方に向かって行われるようになり、上の問題を発見しやすくなります。

ここでポイントなのは「あらゆるメモリ割り当てが」という点です。VirtualAlloc() にはメモリを上位アドレスから割り当てる MEM_TOP_DOWN というオプションがあるのですが、他のメモリ割り当て関数、例えば malloc() や GlobalAlloc() や HeapAlloc() にはありません。上のレジストリ設定をすると、これらの関数であっても上位アドレスから割り当てられるようになります。さらには、スタック領域も上位アドレスからになります。

試しに以下のようなプログラムを作ってみます。

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

#define MEMSIZE (16 * 1024 * 1024)

int main(void)
{
        int i;

        printf("symbols:\n");
        printf("main               = %p\n", main);
        printf("malloc             = %p\n", malloc);
        printf("VirtualAlloc       = %p\n", VirtualAlloc);

        printf("\n");

        printf("memory allocations:\n");
        printf("main thread stack  = %p\n", &i);
        printf("malloc             = %p\n",
            malloc(MEMSIZE));
        printf("GlobalAlloc        = %p\n",
            GlobalAlloc(GPTR, MEMSIZE));
        printf("HeapAlloc          = %p\n",
            HeapAlloc(GetProcessHeap(), 0, MEMSIZE));
        printf("VirtualAlloc       = %p\n",
            VirtualAlloc(NULL, MEMSIZE, MEM_RESERVE, PAGE_NOACCESS));
        printf("VirtualAlloc (MTD) = %p\n",
            VirtualAlloc(NULL, MEMSIZE, MEM_RESERVE | MEM_TOP_DOWN, PAGE_NOACCESS));

        return 0;
}

これを普通の状態の Windows 7 で実行すると、例えば以下のような出力になります。

symbols:
main               = 0000000140001000
malloc             = 0000000140001130
VirtualAlloc       = 0000000076BE67A0

memory allocations:
main thread stack  = 000000000012FEF0
malloc             = 00000000004C0040
GlobalAlloc        = 00000000014D0040
HeapAlloc          = 00000000024E0040
VirtualAlloc       = 00000000034F0000
VirtualAlloc (MTD) = 000007FFFEFB0000

上のレジストリ設定を行った Windows 7 で実行すると、例えば以下のような出力になります。

symbols:
main               = 0000000140001000
malloc             = 0000000140001130
VirtualAlloc       = 00000000774E67A0

memory allocations:
main thread stack  = 000007FFFFF8FEF0
malloc             = 000007FFFEBE0040
GlobalAlloc        = 000007FFFDBD0040
HeapAlloc          = 000007FFFCBC0040
VirtualAlloc       = 000007FFFBBC0000
VirtualAlloc (MTD) = 000007FFFABC0000

残念ながら、DLL の配置等は上位アドレスからにはならないようなので、関数ポインタに関しては効果はありません(通常通り)。

注意点としては「システム全体で」効果があるため、出来の悪いソフトウェアがあると通常の使用に支障が出ることがある点です。茂木さんは某社のプリンタのドライバに問題があってクラッシュするようになったと言っていました。

Trackback

no comment untill now

Add your comment now