    ИССЛЕДОВАНИЕ ВИРТУАЛЬНОЙ ПАМЯТИ 

    глава  6 

    D предыдущей главе мы выяснили, как система управляет виртуальной памятью, 
как процесс получает свое адресное пространство и что оно собой представляет. 
А сейчас мы перейдем от теории к практике и рассмотрим некоторые 
У^т32-функции, сообщающие о состоянии системной памяти и виртуального 
адресного пространства в том или ином процессе. 

    Системная информация 

    Чтобы понять, как Win32 использует виртуальную память, нужно узнать, что 
представляет собой данная реализация Win32. Эту информацию (а также сведения о 
виртуальной памяти) сообщает функция GetSystemInfo: 

    VOID GetSystemInfo(LPSYSTEM_INFO IpSystemInfo): 

    Вы должны передать в GetSystemInfo адрес структуры SYSTEM_INFO, и функция 
инициализирует элементы этой структуры: 

    typedef struct _SYSTEM_INFO { union { 
    DWORD dwOemId:   // не используйте этот элемент struct { 
    WORD wProcessorArchitecture; 
    WORD wReserved: 
    DWORD dwPageSize: 
    LPVOID IpMinimumApplicationAddress; 
    LPVOID IpMaximumApplicationAddress; 
    DWORD dwActiveProcessorMask; 
    DWORD dwNurnberOfProcessors: 
    DWORD dwProcessorType: 
    DWORD dwAllocationGranularity; 
    WORD wProcessorLevel; 
    WORD wProcessorRevision; 
    } SYSTEM_INFO, *LPSYSTEM_INFO; 

    При загрузке система определяет значения элементов этой структуры; для 
конкретной системы их значения постоянны. Функция GetSystemInfo предусмотрена 
специально для того, чтобы и приложения могли получить эту информацию. Из всех 
элементов структуры лишь четыре имеют отношение к памяти. Они описаны в 
следующей таблице: 

    105 

    106 _______________________________Windows для профессионалов 

    Элемент                  Описание 

    dwPageSize  Размер страницы памяти. На процессорах xS6, 
MIPS и PowerPC это значение равно 4096, а на процессоре Alpha — 8192 байтам. 
    IpMinimumApplicationAddress Минимальный адрес памяти доступного адресного 
пространства для каждого процесса. В Windows 95 это значение равно 4 194 304, 
или 0х00400000, поскольку нижние 4 Мб адресного пространства каждого процесса 
недоступны. В Windows NT это значение равно 65 536, или 0х00010000, так как в 
этой системе резервируются лишь первые 64 Кб адресного пространства каждого 
процесса. 
   IpMaximumApplicationAddress Максимальный адрес памяти доступного 
адресного пространства, отведенного в «личное пользование» каждому процессу. В 
Windows 95 этот адрес равен 2 147 483 647, или Ox7FFFFFFF, так как верхние 2 
Гб занимают общие файлы, проецируемые в память, и разделяемый код операционной 
системы. В Windows NT значение этого адреса — 2 147418 111, или Ox7FFEFFFF, 
поскольку в ней неиспользуемое адресное пространство начинается на 64 Кб ниже 
последних 2 Гб. 
   dwAllocationGranularity   Гранулярность резервирования регионов адресного 
пространства. На момент написания книги это значение составляет 64 Кб для всех 
реализации Win32. 

    Программа-пример Syslnfo 

    Syslnfo (SYSINFO.EXE) (см. листинг на рис. 6-1) — программа весьма 
простая; она вызывает функцию GetSystemInfo и выводит на экран информацию, 
возвращенную в структуре SYSTEM_INFO. Файлы исходного кода и ресурсов этой 
программы находятся в каталоге SYSINFO на компакт-диске, прилагаемом к книге. 
Диалоговые окна с результатами выполнения программы Syslnfo на разных 
платформах выглядят так: 

    Processor Architecture:                    Intel 
    Processor level:                 Pentium 
    Processor revision:               [unknown] 
    Number of processors:                       1 
    Active processor mask:             0х00000001 
    Allocation granularity:                   65.536 
    Page size:                    4.096 
    Minimum app. address:               4.194.304 
    Maximum app. address:           2.147.483.647 


    ________________________________I  Windows 95 на процессоре Intel x86 
    

    Processor Architecture:                     Intel 
    Processor level:                  Pentium 
    Processor revision:       Model 2. Stepping 4 
    Number of processors:                        2 
    Active processor mask:               0х00000003 
    Allocation granularity:                   65,536 
    Page size:                    4.096 
    Minimum app. address:                   65,536 
    Maximum app. address:             2.147.41В.1П 

    ________________________________I Windows NT на процессоре Intel x86 



    ___________________Глава 6. Исследование виртуальной памяти 107 


    Processor Architecture:                    MIPS 
    Processor level:                   R4000 
    Processor revision:                      34 
    Number of processors:                       1 
    Active processor mask:              0х00000001 
    Allocation granularity:                   65,536 
    Page size:                    4.096 
    Minimum app. address:                   65,536 
    Maximum app. address:             2,147.418,111 

    _________________________________I Windows NT на процессоре MIPS R4000 

    Processor Architecture:                   Alpha 
    Processor level:                   21064 
    Processor revision:          Model A. Pass 3 
    Number of processors:                        1 
    Active processor mask:               0х00000001 
    Allocation granularity:                   65,536 
    Page size:                    8.192 
    Minimum app. address:                   65.536 
    
    Maximum app. address:             2,147.418.111 

    ________________________I Windows NT на процессоре DEC Alpha 
    
    Processor Architecture:                 PowerPC 
    Processor level:                     601 
    Processor revision:                      0,2 
    Number of processors:                        1 
    Active processor mask:               0х00000001 
    Allocation granularity:                   65.536 
    Page size:                    4.096 
    Minimum app. address:                   65,536 
    Maximum app. address:             2,147,418,111 

    ________________________________I Windows NT на процессоре PowerPC 

    SYSINFO.C 
    Модуль; SysInfo.C 
    Автор: Copyright © 1995-1997, Джеффри Рихтер (Jeffrey Richter) 
    ftinclude ", . \CinnHdr, H"                 /* см. приложение В */ 
    ftinclude <windows,h> 
    ftinclude <windowsx.h> 
    ftinclude <tchar.h> 
    ffinclude <stdio.h> 
    ffinclude "Resource.H" 

    Рис. 6-1. Листинг программы. Syslnfo 


    108 ____________________________Windows для профессионалов 


    РИС. 6-1 (продолжение) 

    / I устанавливаем TRUE, если программа выполняется в Windows 95 
    BOOL g_fWin95IsHost = FALSE; 
    ///////////////////////////// 
    // 3id функции иринимау1 число и преобразуег его в ороку, 
    // вставляя в нужных местах запятые 
    LPTSTR BigNumToString (LONG INum, LPTSTR szBuf) { 
    WORD wNumDigits = 0. wNumChars = 0; 
    do { 
    // помещаем последнюю цифру из строки в символьный буфер 
    szBuf[wNumChars++] = (TCHAR) (INum % 10 + __ТЕХТ(•0')); 
    
    // увеличиваем счетчик цифр, помещенных в строку 
    wNumDigits++; 
    // каждые три цифры, помещенные в строку, дополняем запятой 
    if (wNumDigits % 3 == 0) 
    szBuf[wNumChars++] = __ТЕХТ(','): 
    // делим число на 10 и повторяем процесс 
    INum /= 10; 
    // продолжаем добавлять цифры в строку, пока число не равно нулю 
    } while (INum != 0); 
    // если последний символ, добавленный в строку, - запятая, 
    // отбрасываем его 
    if (szBuf[wNumChars - 1] == __ТЕХТ(',')) 
    szBuf[wNumChars - 1] = 0; 
    // завершаем строку нулевым символом 
    szBuf[wNumChars] = 0: 
    // мы добавляли все символы в строку в обратном порядке - теперь 
    // его надо поменять 
    _tcsrev(szBuf); 
    // Возвращаем адрес строки. Он совпадает с тем, что был передан 
    // нам изначально. Возвращая его здесь, мы упрощаем последующий 
    // вызов функции, использующей эту строку. 
    return(szBuf); 
    /////////////////////////////////////////////////////////////////////////////// 

    см. след. стр. 


    Глава 6. Исследование виртуальной памяти 109 


    РИС. 6-1 (продолжение) 

    void ShowCPUInfo (HWND hwnd, WORD wProcessorArchitecture, 
    WORD wProcessorLevel, WORD wProcessorRevision) { 
    TCHAR szCPUArch[64]; 
    TCHAR szCPULevel[64]; 
    TCHAR szCPURev[64]: 
    szCPULevel[0] = 0; 
    szCPURev[0] = 0; 
    switch (wProcessorArchitecture) { 
    case PROCESSOR_ARCHITECTURE_INTEL: 
    lstrcpy(szCPUArch, __TEXT("Intel")); 
    switch (wProcessorLevel) { 
    case 3: case 4: 
    wsprintf(szCPULevel, __TEXT("80%c86"), wProcessorLevel + '0'); 
    if (!g_fWin95IsHost) 
    wsprintf(szCPURev, __TEXT("%c%d"), 
    HIBYTE(wProcessorRevision) + __TEXT('A'), 
    LOBYTE(wProcessorRevision)): 
    else 
    lstrcpy(szCPURev, __TEXT("(unknown)")); 
    break; 
    case 5: 
    wsprintf(szCPIJLevel, __TEXT(" Pentium")): 
    if Ug_fWin95IsHost) 
    wsprintf(szCPURev, 
    
    __TEXT("Model %d, Stepping %d"), 
    HIBYTE(wProcessorRevision), 
    LOBYTE(wProcessorRevision)); 
    else 
    1st rcpy(szCPURev, __TEXT("(unknown)")): 
    break; 
    } 
    break; 
    case PROCESSOR_ARCHITECTURE_MIPS: 
    lstrcpy(szCPUArch, __TEXT("MIPS")); 
    wsprintf(szCPULevel, __TEXT("R%04d"), LOBYTE(wProcessorLevel) * 1000): 
    wsprintf(szCPURev, __TEXT("%d"), LOBYTE(wProcessorRevision)); 
    break; 
    case PROCESSOR_ARCHITECTURE_ALPHA: 
    1st rcpy(szCPUArch, __TEXT("Alpha")); 
    wsprintf(szCPULevel, __TEXT("%d"), wProcessorLevel): 
    wsprintf(szGPURev, __TEXT("Model %c, Pass %d"), 
    HIBYTE(wProcessorRevision) + __TEXT('A'). 
    LOBYTE(wProcessorRevision)); 
    break; 

    см. след. стр. 

    110 _______________________________Windows для профессионалов 


    РИС. 6-1 (продолжение) 

    case PROCESSOR_ARCHITECTURE_PPC: 
    lstrcpy(szCPUArch. __TEXT("PowerPC")); 
    switch (wProcessorLevel) { 
    case 1: case 3: case 4; case 20: 
    wsprintf(szCPULevel, __TEXT("%d"), 600 + wProcessorLevel): 
    break; 
    case 6: 
    wsprintf(szCPULevel, __TEXT("603+"), wProcessorLevel); 
    break; 
    case 9: 
    wsprintf(szCPULevel, __TEXT("604+"), wProcessorLevel); 
    break; 
    wsprintf(szCPURev, __TEXT("%d.%d"), 
    HIBYTE(wProcessorRevision), 
    LOBYTE(wProcessorRevision)); 
    break; 
    case PROCESSOR_ARCHITECTURE_UNKNOWN: 
    default: 
    wsprintf(szCPUArch. __TEXT("Unknown")); 
    break; 
    i / 
    SetDlgItemText(hwnd, IDC_PROCARCH,  szCPUArch);. 
    SetDlgItemText(hwnd, IDC_PROCLEVEL, szCPULevel); 
    SetDlgItemText(hwnd, IDC_PROCREV,   szCPURev); 
    ////////////////////// 
    BOOL Dlg_OnInitDialog (HWND hwnd, HWND hwndFocus, LPARAM IParam) { 
    TCHAR szBuf[50]; 
    SYSTEM_INFO sinf: 
    // связываем значок с диалоговым окном 
    chSETDLGICONS(hwnd, IDI_SYSINFO, IDI_SYSINFO): 
    GetSystemInfo(&sinf); 
    if (g_fWin95IsHost) { 
    sinf.wProcessorLevel = (WORD) (sinf.dwProcessorType / 100); 
    i / 
    ShowCPUInfo(hwnd, sinf.wProcessorArchitecture, 
    sinf.wProcessorLevel, sinf.wProcessorRevision); 
    SetDlgItemText(hwnd, IDC_PAGESIZE, 
    BigNumToString(sinf.dwPageSize, szBuf)); 
    см. след. стр. 


    ___________________Глава 6. Исследование виртуальной памяти 111 


    РИС. 6-1 (продолжение) 
    SetDlgItemText(hwnd, IDC_MINAPPADDR, 
    BigNumToString((LONG) sinf.IpMinimumAppllcationAddress, szBuf)): 
    SetDlgItemText(hwnd, IDC_MAXAPPADDR, 
    BigNumToString((LONG) sinf. IpMaxirnumApplicationAddress, szBuf)): 
    _stprlntf(szBuf, __TEXT("Ox%08X"), sinf.dwActiveProcessorMask); 
    SetDlgItemText(hwnd, IDC_ACTIVEPROCMASK, szBuf); 
    SetDlgItemText(hwnd, IDC_NUMOFPROCS, 
    BigNumToString(sinf.dwNumberOfProcessors, szBuf)); 
    SetDlgIteniText(hwnd, IDC_ALLOGGRAN, 
    BigNumToString(sinf.dwAllocationGranularity, szBuf)); 
    return(TRUE); 
    /////////////////////////////////////////////////////////////////////////////// 
    void Dig^OnCommand (HWND hwnd. int id, HWND hwndCti, UINT codeNotify) { 
    switch (id) { 
    case IOCANCEL: 
    End0ialog(hwnd, id); 
    break; 
    /////////////////////////////////////////////////////////////////////////////// 
    BOOL CALLBACK Dlg_Proc (HWND hDlg. UINT uMsg, WPARAM wParam, LPARAM 
IParam) { 
    BOOL fProcessed = TRUE; 
    switch (uMsg) { 
    chHANDLE_DLGMSG(hDlg, WM_INITDIALOG, Dlg_OnInitDialog); 
    chHANDLE_DLGMSG(hDlg, WM_COMMAND, Dlg_OnCommand); 
    default: 
    fProcessed = FALSE; 
    break; 
    } 
    return(fProcessed); 
    } 
    /////////////////////////////////////////////////////////////////////////////// 
    см. след. стр. 


    112 ______________________________Windows для профессионалов 


    РИС. 6-1 (продолжение) 

    int WINAPI „tWinMain (HINSTANCE hinstExe, 
    HINSTANCE hinstPrev, LPTSTR pszCmdLine, int nCmdShow) { 
    OSVERSIONINFO osvi; 
    chWARNIFUNICODEUNDERWIN95(); 
    osvi.dwOSVersionInfoSize = sizeof(osvi): 
    GetVersionEx(&osvi); 
    g_fWin95IsHost = (osvi,dwPlatformId == VER_PLATFORM_WIN32_WINDOWS); 
    DialogBox(hinstExe, MAKEINTRESOURCE(IDO_SYSINFO), NULL, Dlg_Proc); 
    return(O); 
    1111111111111111111/111/1111'/ конец файла ///////////////////////////////// 


    Статус виртуальной памяти 

    Ут32-функция GhbalMemoryStatus позволяет отслеживать текущее состояние памяти: 

    VOID GlobalMemoryStatusCLPMEMORYSTATUS IpmstMemStat); 

    На мой взгляд, она названа крайне неудачно; имя GhbalMemoryStatus 
подразумевает, что функция каким-то образом связана с глобальными кучами — как 
в 16-разрядной Windows. Но в Win32 нет глобальной кучи, хоть в нем и 
предусмотрены старые функции вроде GhbalAlloc — для упрощения переноса 
приложений 16-разрядной Windows в Win32. Мне кажется, лучше было бы назвать 
функцию GhbalMemoryStatus по-другому — скажем, VirtualMemoryStatus. 
    При вызове GlobalMemoryStatus Вы должны передать ей адрес структуры 
MEMORYSTATUS. Вот эта структура: 

    typedef struct J1EMORYSTATUS { 
    DWORD dwLength; 
    DWORD dwMemoryLoad; 
    DWORD dwTotalPhys; 
    DWORD dwAvailPhys; 
    DWORD dwTotalPageFile; 
    DWORD dwAvailPageFile; 
    DWORD dwTotalVirtual; 
    DWORD dwAvallVirtual; 
    } MEMORYSTATUS, *LPMEMORYSTATUS; 

    Перед вызовом GlobalMemoryStatus надо записать в элемент dwLength размер 
структуры в байтах, т. е. sizeof (MEMORYSTATUS). Такой принцип вызова функции 
дает возможность расширить эту структуру в будущих версиях Win32 API, не 
нарушая работьГсуществующих приложений. После вызова GlobalMemoryStatus 
инициализирует остальные элементы структуры и возвращает управление. 
Назначение элементов этой структуры Вы узнаете из следующего раздела, в 
котором рассматривается программа-пример VMStat. 

    ___________________Глава 6. Исследование виртуальной памяти 113 


    Программа-пример VMStat 

    VMStat (VMSTAT.EXE) (см. листинг на рис. 6-2) выводит на экран диалоговое 
окно с результатами вызова GlobalMemoryStatus. Информация в окне обновляется 
каждую секунду, так что VMStat вполне пригодна для мониторинга памяти в 
системе. Файлы исходного кода и ресурсов этой программы находятся в каталоге 
VMSTAT на компакт-диске, прилагаемом к книге. Вот как выглядит диалоговое окно 
программы VMStat после запуска в Windows NT на машине с процессором Intel 
Pentium и 32 Мб оперативной памяти: 

    Memory load:             83 
    TotalPhys:       32,956,'Пб 
    AvailPhys:         1.1-')2.78'1 
    TotalPageFile:    Б7.50Э.744 
    AvailPageFile:    33.816.576 
    TotalVirtual:    2.1'17.352.576 
    AvailVirtual:    2.131.615.744 

    Элемент dwMemoryLoad (показываемый как Memory Load) позволяет оценить, 
насколько занята система управления памятью. Это число может быть любым в 
диапазоне от О до 100. В Windows 95 и Windows NT алгоритмы, используемые для 
его подсчета, различны. Кроме того, в будущих версиях операционных систем этот 
алгоритм почти наверняка придется модифицировать. Но, честно говоря, на 
практике от значения этого элемента толку немного. 
    Элемент dwTotalPhys (показываемый как TotalPhys) отражает общий объем 
физической (оперативной) памяти в байтах. На данной Pentium-машине с 32 Мб 
оперативной памяти его значение составляет 32 956 416, что на 598 016 байтов 
меньше 32 Мб. Причина, по которой GlobalMemoryStatus не сообщает о полных 32 
Мб, кроется в том, что система при загрузке резервирует небольшой участок 
оперативной памяти, недоступный даже ядру. А элемент dwAvailPhys (показываемый 
как AvailPhys) дает число байтов свободной физической памяти. 
    Элемент dwTotalPageFile (показываемый как TotalPageFile) сообщает 
максимальное количество байтов, которое может содержаться в страничном файле 
(файлах) на жестком диске (дисках). Хотя VMStat показывает, что текущий размер 
страничного файла составляет 57 503 744 байта, система может варьировать его 
по своему усмотрению. Элемент dwAvailPageFile (показываемый как AvailPageFile) 
подсказывает, что в данный момент 33816576 байтов в страничном файле свободно 
и может быть передано любому процессу. 
    Элемент dwTotalVirtual (показываемый как TotalVirtual) отражает общее 
количество байтов, отведенных под закрытое адресное пространство процесса. 
Значение 2 147 352 576 ровно на 128 Кб меньше 2 Гб. Два раздела недоступного 
адресного пространства — от 0х00000000 до OxOOOOFFFF и от Ox7FFFOOOO до 
Ox7FFFFFFF — как раз и составляют эту разницу в 128 Кб. Запустив VMStat в 
Windows 95, Вы увидите, что значение этого элемента поменялось на 2 143 289 
344 (2 Гб за вычетом 4 Мб). Разница в 4 Мб возникает из-за того, что Windows 
95 блокирует нижний раздел от 0х00000000 до Ox003FFFFF (размером в 4 Мб). 
    И, наконец, dwAvailVirtual (показываемый как AvailVirtual) — единственный 
элемент структуры, специфичный для конкретного процесса, вызывающего 
GlobalMemoryStatus (остальные элементы относятся исключительно к самой системе 
и не зависят от того, какой именно процесс вызывает эту функцию). При подсчете 
значения dwAvailVirtual функция суммирует размеры всех свободных регионов в 
адресном пространстве вызывающего процесса. В данном случае его значение 
говорит о том, что в распоряжении программы VMStat 

    114 ______________________________Windows для профессионалов 

    имеется 2 131 615 744 байта свободного адресного пространства. Вычтя из 
значения dwTotalVirtual величину dwAvailVirtual, Вы получите 15 736 832 байта 
— такой объем памяти VMStat зарезервировала в своем виртуальном адресном 
пространстве. Отдельного элемента, который сообщал бы количество физической 
памяти, используемой процессом в данный момент, не предусмотрено. 

    VMSTAT.C 
    Модуль: VMStat.С 
    Автор: Copyright © 1995-1997, Джеффри Рихтер (Jeffrey Richter) 
    ,,*,.,*»***»»*,**********,*,,.,*.....*.**....*...,,***»***,****,*...**.***.*,,/ 
    #include ", .\CninHdr, H"                  /* см. приложение В */ 
    ftinclude <windows.h> 
    «include <windowsx.h> 
    #include <tchar.h> 
    #include "Resource.H" 
    /////////////////////////////////////////////////////////////////////////////// 
    «define IDT_UPDATE 1 
    /////////////////////////////////////////////////////////////////////////////// 
    // эта функция принимает число и преобразует его в строку, 
    // вставляя в нужных местах запятые 
    LPTSTR WINAPI BigNumToString (LONG INum, LPTSTR szBuf) { 
    WORD wNumDigits = 0, wNumChars = 0; 
    do { 
    // помещаем последнюю цифру из строки в символьный буфер 
    szBuf[wNumChars++] = (TCHAR) (INum % 10 + __ТЕХТ(•0')); 
    // увеличиваем счетчик цифр, помещенных в строку 
    wNumDigits++; 
    // каждые три цифры, помещенные в строку, дополняем запятой 
    if (wNumDigits % 3 == 0) 
    szBuf[wNumChars++] = __ТЕХТ(','); 
    // Делим число на 10 и повторяем процесс 
    INum /= 10: 
    // продолжаем добавлять цифры в строку, пока число не равно нулю 
    } while (INum != 0); 
    // если последний символ, добавленный в строку, - запятая, 
    // отбрасываем его 
    см. след. стр 

    Рис. 6-2. Листинг программы VMStat 


    ___________________Глава 6. Исследование виртуальной памяти 115 


    РИС. 6-2, (продолжение) 
    if (szBufLwNumChars - 1] == __ТЕХТ(',')) 
    szBuf[wNumChars - 1] = 0; 
    // завершаем строку нулевым символом 
    szBuf[wNumChars] = 0; 
    // мы добавляли все символы в строку в обратном порядке - теперь 
    // его надо поменять 
    _tcsrev(szBuf); 
    // Возвращаем адрес строки. Он совпадает с тем, что был передан 
    // нам изначально. Возвращая его здесь, мы упрощаем последующий 
    // вызов функции, использующей эту строку. 
    return(szBuf); 
    ll/llllllllll/llll/ltll/l/l//lll/l!lllillllll/llll/ll/l///l/H/ll/lllllllllllll 
    BOOL Dlg_OnInitDialog (HWNO hwnd, HWND hwndFocus, LPARAM IParam) { 
    // связываем значок с диалоговым окном 
    chSETDLGICONS(hwnd, IDI_VMSTAT, IDI_VMSTAT); 
    // устанавливаем таймер так, чтобы периодически обновлять информацию 
    SetTinier(hwnd. IDT_UPDATE, 1 * 1000, NULL): 
    // выдаем сообщение таймера для первого обновления 
    FORWARD_WMJ"IMER(hwnd, IDTJJPDATE, SendMessage); 
    return(TRUE); 
    } 
    lll/lll|l|l|lllll/i^/•/i/^:/!/l/l^!:///l/i!i:ii/il/l/i///////!///i/i/!!/i/i^!/ 
    void Dlg_OnTimer(HWNO hwnd, UINT id) { 
    TCHAR szBuf[50]; 
    MEMORYSTATUS ms; 
    // прежде чем передать структуру функции GlopalMemoryStatus, 
    // заносим в элемент dwLength ее длину 
    ms.dwLength = sizeof(ms); 
    GlobalMemoryStatus(&(ns); 
    SetDlgIternTextChwnd, IDC_MEMLOAD, 
    BigNumToString(ms.dwMemoryLoad, szBuf)); 
    SetDlgItemText(hwnd, IDC_TOTALPHYS, 
    BigNumToString(ms. dwTotalPhys, szBLif)); 
    см. след. стр. 

    116 _______________________________Windows для профессионалов 


    РИС. 6~2 (продолжение) 

    SetDlgItemTextChwnd, IDCJWAILPHYS. 
    BigNuniToString(ms.dwAvailPriys, szBuf)): 
    SetDlgIteniText(hwnd, IDC_TOTALPAGEFILE, 
    BigNumToString(ms. dwTotalPageFile, szBuf)); 
    SetDlgIteinText(hwnd, IDC_AVAILPAGEFILE, 
    BigNurnToString(ms.dwAvailPageFile, szBuf)): 
    SetDlgItefiiText(hwnd, IDC_TOTALVIRTUAL, 
    ВigNumToString(ms.dwTotalVirtual, szBuf)); 
    SetDlgItemText(hwnd, IDC_AVAILVIRTUAL, 
    ВigNumToString(ms.dwAva11 Virtual. szBuf)); 
    /////////////////////////////////////////////////////////////////////////////// 
    void Dlg_OnCommand (HWND hwnd, int id, HWND hwndCtI, UINT codeNotify) { 
    switch (id) { 
    case IDCANCEL: 
    KillTimer(hwnd, IDT_UPDATE); 
    EndDialog(hwnd, id); 
    break; 
    } 
    1 
    /////i//l^l/l/////!/i/!i^//l/://ll//lll/////l/l//l//lllii!//!/:/:/!!:i!:/::.;.; 
    BOOL CALLBACK Dlg_Proc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM 
IParam) { 
    switch (uMsg) { 
    chHANDLE_OLGMSG(hwnd, WM_INITDIALOG, Dlg_OhInitDialog); 
    chHANDLE^DLGMSG(hwnd. WM_COMMAND.    01g_OnComfnand); 
    chHANDLE_DLGMSG(hwnd, WM_TIMER,      Dlg_OnTimer); 
    } return(FALSE): 
    } 
    /////////////////////////////////////////////////////////////////////////////// 
    int WINAPI _tWinMain (HINSTANCE hinstExe, 
    HINSTANCE hinstPrev, LPTSTR pszCmdLine, int nCmdShow) { 
    chWARNIFUNICODEUNDERWIN95(); 
    DialogBox(hinstExe, MAKEINTRESOURCE(IDD_VMSTAT), NULL, Dlg_Proc); 
    ;      return(O); 
    i 
    ////////////////////////////////// конец файла ///////////////////////////////// 


    ___________________Глава 6. Исследование виртуальной памяти 117 


    Определение состояния адресного пространства 

    В Win32 есть функция, позволяющая запрашивать определенную информацию об 
участке памяти по заданному адресу: размер, тип памяти и атрибуты защиты. В 
частности, с ее помощью программа VMMap (ее листинг см. на рис. 6-4) выводит 
карты виртуальной памяти, с которыми мы познакомились в главе 5. Эта 
У^ш32-функция называется VirtualQuery. 

    DWORD VirtualQuery(LPVOID IpAddress. 
    PMEMORY_BASIC_INFORMATION IpBuffer, 
    DWORD dwLength); 

    При вызове VirtualQuery первый параметр (IpAddress) должен содержать адрес 
виртуальной памяти, об участке которой Вы хотите получить информацию. Параметр 
IpBuffer — это адрес структуры MEMORY_BASIC_INFORMATION, которую надо создать 
перед вызовом функции. Данная структура определена в файле WINNT.H так: 

    typedef struct _MEMORY_BASIC_INFORMATION { 
    PVOID BaseAddress: 
    PVOID AllocationBase; 
    DWORD AllocationProtect; 
    DWORD RegionSize; 
    DWORD State; 
    DWORD Protect: 
    DWORD Type; 
    } MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION; 

    Параметр dwLength задает размер структуры MEMORY_BASIC_INFORMATION. 
Функция VirtualQuery возвращает число байтов, скопированных в буфер. 
    Используя адрес, указанный Вами в параметре IpAddress, функция заполняет 
структуру информацией о диапазоне смежных страниц, имеющих одинаковые 
состояние, атрибуты защиты и тип. Описание элементов структуры приведено в таблице: 

    Элемент       Описание 

    BaseAddress     Сообщает то же значение, что и параметр IpAddress, но 
округленное до ближайшего меньшего адреса, кратного размеру страницы. 
    AllocationBase    Идентифицирует базовый адрес региона, включающего в себя 
адрес, указанный в параметре IpAddress. 
    AllocationProtect Идентифицирует атрибут защиты, присвоенный региону при 
его резервировании. 
    RegionSize      Сообщает суммарный размер (в байтах) группы страниц, 
которые начинаются с базового адреса BaseAddress и имеют те же атрибуты 
защиты, состояние и тип, что и страница, расположенная по адресу, указанному в 
параметре IpAddress. 
    State          Сообщает состояние (MEM_FREE, MEM_RESERVE или МЕМ_СОММ1Т) всех 
    смежных страниц, которые имеют те же атрибуты защиты, состояние и тип, что 
и страница, расположенная по адресу, указанному в параметре IpAddress. Если 
состояние MEM_FREE, элементы AllocationBase, AllocationProtect, Protect и Type 
содержат неопределенные значения. А если состояние MEM_RESERVE, то 
неопределенное значение содержит элемент Protect. 
    Protect         Идентифицирует атрибут защиты (PAGE_*) всех смежных 
страниц, которые 
    имеют те же атрибуты защиты, состояние и тип, что и страница, 
расположенная по адресу, указанному в параметре IpAddress. 
    см. след. стр. 


    118 ______________________________Windows для профессионалов 


    продолжение 

    Элемент       Описание 
    Type           Идентифицирует тип физической памяти (MEM_IMAGE, MEM_MAPPED 
или MEM_PRIVATE), переданной группе смежных страниц, которые имеют те же 
атрибуты защиты, состояние и тип, что и страница, расположенная по адресу, 
указанному в параметре IpAddress. В Windows 95 этот элемент всегда дает MEM_PRIVATE. 

    Функция VMQuery 

    Начиная изучать архитектуру памяти в Win32, я пользовался функцией 
VirtualQuery как «поводырем». Если Вы читали первое издание моей книги, то 
заметите, что программа VMMAP.EXE была гораздо проще ее нынешней версии, 
представленной в следующем разделе. Прежняя была построена на очень простом 
цикле, из которого периодически вызывалась функция VirtualQuery, и для каждого 
вызова я формировал одну строку, содержавшую элементы структуры 
MEMORY_BASIC_INFORMATION. Изучая полученные дампы и сверяясь с Windows NT 3.1 
SDK (в то время весьма неудачным), я пытался разобраться в архитектуре памяти 
Win32 и принципах ее управления. Что ж, «я прошел долгий путь, бэйби» — 
теперь-то я знаю, что функция VirtualQuery и структура 
MEMORY_BASIC_INFORMATION — не лучшие средства для создания карты виртуального 
адресного пространства. 
    Проблема в том, что в MEMORY_BASIC_INFORMATION возвращается отнюдь не вся 
информация, имеющаяся в распоряжении системы. Если Вам нужны простейшие данные 
о состоянии памяти по конкретному адресу, VirtualQuery действительно 
незаменима. Она отлично работает, если Вас интересует, передана ли по этому 
адресу физическая память и доступен ли он для операций чтения-записи. Но 
попробуйте с ее помощью узнать общий размер зарезервированного региона и 
количество блоков в нем или выяснить, не содержит ли этот регион стек потока, 
— ничего не выйдет. 
    Чтобы получить более серьезную информацию о памяти, я создал собственную 
функцию и назвал ее VMQuery. BOOL VMQuery(PVOID pvAddress, PVMQUERY pVMQ); 
    Она, как и VirtualQuery, в первом параметре (pvAddress) принимает адрес 
памяти, а во втором (pVMQ) — указатель на структуру, заполняемую самой 
функцией. Структура VMQUERY (тоже мое детище) представляет собой вот что: 

    typedef struct { 
    // информация о регионе 
    PVOID pvRgnBaseAddress; 
  
    DWORD dwRgnProtection;     // PAGE_* 
    DWORD dwRgnSize; 
    
    DWORD dwRgnStorage;        // MEM_*: Free, Image, Mapped, Private 
    DWORD dwRgnBlocks; 
    
    DWORD dwRgnGuardBlks;      // если > О, регион содержит стек потока 
    BOOL fRgnIsAStack;        // TRUE, если регион содержит стек потока 
    
    // информация о блоке 
    PVOID pvBlkBaseAddress; 
    
    DWORD dwBlkProtection;     // PAGE_* 
    DWORD dwBlkSize; 
    
    DWORD dwBlkStorage;        //MEM_*: Free, Reserve, Image, Mapped, Private 
    } VMQUERY, *PVMQUERY; 


    ___________________Глава 6. Исследование виртуальной памяти 119 


    С первого взгляда заметно, что моя структура VMQUERY содержит куда больше 
информации, чем MEMORY_BASIC_INFORMATION, заполняемая функцией VirtualQuery. 
Она разбита (условно, конечно) на две части: в одной — информация о регионе, в 
другой — информация о блоке (адрес которого указан в параметре pvAddress). 
Элементы этой структуры описаны в таблице: 

    Элемент          Описание 

    pvRgnBaseAddress    Идентифицирует базовый адрес региона виртуального 
адресного пространства, включающего адрес, указанный в параметре pvAddress. 
    dwRgnProtection     Сообщает атрибут защиты, присвоенный региону при его резервировании. 
    dwRgnSize          Указывает размер (в байтах) зарезервированного региона. 
    dwRgnStorage       Идентифицирует тип физической памяти, переданной группе 
блоков данного региона: MEM_FREE, MEMJMAGE, MEM_MAPPED или MEM_PRIVATE. 
Поскольку Windows 95 не различает типы памяти, в этой операционной системе 
данный элемент содержит либо MEM_FREE, либо MEM_PRIVATE. 
    dwRgnBlocks        Содержит значение — число блоков в указанном регионе. 
    dwRgnGuardBlks    Указывает число блоков с установленным флагом атрибутов 
защиты PAGE_GUARD. Обычно это значение либо 0, либо 1. Если оно равно 1, то 
регион скорее всего зарезервирован под стек потока. В Windows 95 этот элемент 
всегда равен 0. 
    fRgnIsAStack       Сообщает, есть ли в данном регионе стек потока. 
Результат определяется на основе взвешенной оценки, так как невозможно дать 
стопроцентной гарантии тому, что в регионе содержится стек. 
    pvBlkBaseAddress    Идентифицирует базовый адрес блока, включающего адрес, 
указанный в параметре pvAddress. 
    dwBlkProtection     Идентифицирует атрибут защиты блока, включающего 
адрес, указанный в параметре pvAddress. 
    dwBlkSize          Содержит значение — размер блока (в байтах), 
включающего адрес, указанный в параметре pvAddress. 
    dwBlkStorage       Сообщает тип памяти для блока, включающего адрес, 
указанный в параметре pvAddress. Принимает одно из значений: MEM_FREE, 
MEM_RESERVE, MEMJMAGE, MEMJMAPPED или MEM_PRIVATE. В Windows 95 этот элемент 
никогда не принимает значений MEM_IMAGE и MEMJMAPPED. 
    Чтобы получить всю эту информацию, VMQuery, естественно, приходится 
выполнять гораздо больше операций (в том числе многократно вызывать 
^VirtualQuery), и из-за этого она работает гораздо медленнее VirtualQuery. 
Поэтому Вы должны все тщательно взвесить, прежде чем сделать свой выбор. Если 
Вам не нужна дополнительная информация, возвращаемая VMQuery, используйте VirtualQuery. 
    Листинг файла VMQUERY.C (см. рис. 6-3) показывает, как я получаю и 
обрабатываю данные, необходимые для инициализации элементов структуры VMQUERY. 
(Файлы VMQUERY.C и VMQUERY.H содержатся в каталоге VMMAP на компакт-диске, 
прилагаемом к книге.) Чтобы не объяснять подробности обработки данных «на 
пальцах», я снабдил тексты программ массой комментариев, вольно разбросанных 
по всему коду. 


    120 -______________________________Windows для профессионалов 


    VMQUERY.C 
    Модуль: VMQuery.C Автор: Copyright © 1995-1997, Джеффри Рихтер (Jeffrey Richter) 
    flinclude "..\CmnHdr.H"              /* см. приложение В */ 
    Sinclude <windows.h> 
    ftinclude <windowsx.h> 
    Sinclude "VMOuery.H" 
    /llllllillll/llllllllllllllllll/lllil/llllillllllHIIIIII/llll/lllll/lllll 
    typedef struct { 
    DWORD dwRgnSize; 
    DWORD dwRgnStorage;    // MEM_*: Free, Image, Mapped, Private 
    DWORD dwRgnBlocks; 
    DWORD dwRgnGuardBlks:  // если > О, в регионе содержится стек потока 
    BOOL fRgnIsAStack;    // TRUE, если в регионе содержится стек потока 
    } VMQUERY_HELP; 
    // глобальная статическая переменная, содержащая значение 
    // гранулярность выделения ресурсов на данном типе процессора; 
    // инициализируется при первом вызове VMQuery 
    static DWORD gs_dwAllocGran = 0; 
    Illllllllllllllllllllll/llllllllllll/llllllllllll/l/ll/lllll/lllll/llll/lllllll 
    II если определен идентификатор NTBUG_VIRTUALQUERY, то следующий код 
    // компенсирует "жучок" в функции VirtualQuery, реализованной в Windows NT 
    ffdefine NTBUG_VIRTUALQUERY 
    tfifdef NTBUG_VIRTUALQUERY 
    DWORD NTBug_VirtualQuery (LPVOID IpvAddress, 
    PMEMORY_BASIC_INFORMATION pmblBurfer, DWORD cbLength) { 
    DWORD dwRetVal = VirtualQuery(lpvAddress, pmbiBuffer, cbLength); 
    if (dwRetVal == cbLength) { 
    // если условие выполнено, корректируем элементы структуры MBI 
    if (((DWORD) pmbiBuffer->AllocationBase % 0х1000) == OxFFF) { 
    // если элемент AllocationBase завершается как OxFFF, 
    // адрес должен быть на 1 байт больше 
    pmbiBuffer-->AHocationBase = (PVOID) 
    ((PBYTE) pmbiBuffer->AllocationBase + 1); 
    i 
    см. слей. стр. 

    Рис. 6-3. Листинг функции VMQuery 


    ___________________Глава 6. Исследование виртуальной памяти 121 


    РИС. 6-3 (продолжение) 

    if ((pmbiBuffer->RegionSize % 0х1000) == OxFFF) { 
    // если элемент RegionSize завершается как OxFFF, 
    // размер должен быть на 1 байт больше 
    pmbiBuffer->RegionSize++: 
    1 
    if ((pmbiBuffer->State != MEM_FREE) && 
    (pmbiBuffer->AllocationProtect == 0)) { 
    // если регион занят и элемент AllocationProtect равен О, 
    // то AllocationProtect должен быть PAGE_READONLY 
    pmbiBuffei-->AllocationProtect = PAGE_READONLY; 
    i i 
    \ return(dwRetVal); 
    Sdefine VirtualQuery NTBug_VirtualQuery 
    ffendif 
    /////////////////////////////////////////////////////////////////////////////// 
    // эта функция проходит по всем блокам в регионе 
    // и инициализирует структуру найденными значениями 
    static BOOL VMQueryHelp (PVOID pvAddress, VMQUERY_HELP *pVMQHelp) { 
    MEMORY_BASIC_INFORMATION MBI; 
    PVOID pvRgnBaseAddress. pvAddressBlk; 
    BOOL fOk; 
    DWORD dwProtectBlock[4] = { 0 }: 
    // 0 = reserved, PAGE_NOACCESS, PAGE_READWRITE 
    // обнуляем содержимое структуры 
    chINITSTRUCT(*pVMQHelp, FALSE); 
    // исходя из переданного адреса памяти, получаем базовый адрес 
    // включающего его региона 
    f0k = (VlrtualQuery(pvAddress, &MBI, slzeof(MBI)) == sizeor(MBI)): 
    if (!f0k) { 
    // если не удается получить информацию по переданному адресу, 
    // возвращаем FALSE, указывая на ошибку; суть проблемы сообщит 
    // функция GetLastErrorO 
    return(fOk); 
    } 
    // pvRgnBaseAddress идентифицирует базовый адрес региона 
    // и никогда не изменится 
    pvRgnBaseAddress = MBI.AllocationBase; 
    см. след. стр. 


    122 ______________________________Windows для профессионалов 


    РИС. 6-3 (продолжение) 
    II pvAddress идентифицирует адрес первого блока и будет 
    // изменяться по мере нашего прохождения по блокам 
    pvAddressBlk = pvRgnBaseAddress; 
    // запоминаем тип физической памяти, переданной данному блоку 
    pVMQHelp->dwRgnStorage = MBI.Type; 
    for (;;) { 
    // получаем информацию о текущем блоке 
    f0k = VirtualQuery(pvAddressBlk. &MBI, sizeof(MBI)); 
    if (!f0k) { 
    // не удалось получить информацию; прекращаем цикл 
    break; 
    ) 
    
    // проверяем, принадлежит ли текущий блок запрошенному региону 
    if (MBI.AllocationBase != pvRgnBaseAddress) { 
    // блок принадлежит следующему региону; прекращаем цикл 
    break; 
    i 
    // блок принадлежит запрошенному региону 
    // следующий оператор if служит для обнаружения стеков 
    // в Windows 95; в этой системе стеки размещаются в последних 
    // 4 блоках региона: "резервный", PAGE_NOACCESS, 
    // PAGE_READWRITE и еще один "резервный" 
    if (pVMQHelp->dwRgnBlocks < 4) { 
    // если это блок 0-3, отмечаем тип защиты блока 
    // в нашем массиве 
    dwProtectBlock[pVMQHelp->dwRgnBlocks] = 
    (MBI.State == MEM_RESERVE) ? О ; MBI.Protect; 
    } else { 
    // мы уже просмотрели 4 блока в этом регионе: 
    // смещаем элементы массива с атрибутами защиты 
    
    MoveMemory(&dwProtectBlock[0], &dwProtectBlock[1], 
    sizeof(dwProtectBlock) - sizeor(DWORD)); 
    
    // добавляем новые значения атрибутов защиты в конец массива 
    dwProtectBlock[3] = (MBI.State == MEM_RESERVE) '? О : MBI.Protect; 
    
    // увеличиваем счетчик блоков в этом регионе на 1 
    pVMQHelp->dwRgnBlocks++; 
    
    // добавляем размер блока к размеру зарезервированного региона 
    pVMQHelp->dwRgnSize += MBI.RegionSize; 
    см. след. стр. 

    ___________________Глава 6. Исследование виртуальной памяти 123 


    РИС. 6-3 (продолжение) 

    // если блок имеет флаг PAGE_GUARD, добавляем 1 к счетчику блоков 
    // с этим флагом 
    if (MBI.Protect & PAGE_GUARD) { 
    pVMQHelp->dwRgnGuardBlks++; 
    ( 
    // Делаем наиболее вероятное предположение о типе физической памяти, 
    // переданной данному блоку. Стопроцентной гарантии дать нельзя, 
    // потому что некоторые блоки могли быть преобразованы из MEM_IMAGE 
    // в MEM_PRIVATE или из MEM_MAPPED в MEM_PRIVATE: MEM_PRIVATE в любой 
    // момент может быть замещен на MEM_IMAGE или MEM_MAPPED. 
    if (pVMQHelp-XJwRgnStorage == MEM_PRIVATE) { 
    pVMQHelp->dwRgnStorage = MBI.Type: 
    // получаем адрес следующего блока 
    pvAddressBlk = (PVOID) ((PBYTE) pvAddressBlk + MBI.RegionSize); 
    } 
    // обследовав регион, думаем: не стек ли это7 
    // Windows NT:  да - если в регионе содержится хотя бы 1 блок 
    //              с флагом PAGE_GUARD. 
    // Windows 95:  да - если в регионе содержится хотя бы 4 блока, 
    //              и они имеют такие атрибуты: 
    
    //              3-й блок от конца: reserved 
    //              2-й блок от конца: PAGE_NOACCESS 
    //              1-й блок от конца: PAGE_READWRITE 
    //              последний блок: reserved 
    pVMOHelp->fRgnIsAStack = 
    (pVMQHelp->dwRgnGuardBlks > 0) :: 
    ((pVMQHelp->dwRgnBlocks >= 4) && 
    (dwProtectBlock[0] == 0) && 
    (dwProtectBlock[1] == PAGE_NOACCESS) && 
    (dwProtectBlock[2] == PAGE_READWRITE) && 
    (dwProtectBlock[3] == 0)): 
    // сообщаем, что функция выполнена успешно 
    return(TRUE); 
    1 
    { 
    
    //////////////////////////////////////////////
    BOOL VMQuery (PVOID pvAddress, PVMQUERY pVMQ) { 
    MEMORY_BASIC_INFORMATION MBI; 
    VMQUERY_HELP VMOHelp: 
    BOOL fOk; 
    см. след. стр. 


    124 ______________________________Windows для профессионалов 


    РИС. 6-3 (продолжение) 

    if (gs_dwAllocGran == 0) { 
    // если поток в этом приложении вызывает нас впервые, надо 
    // получить размер страницы, используемый в этой системе, 
    // и сохранить его в глобальной статической переменной 
    SYSTEM_INFO SI; 
    QetSystemInfo(&SI); 
    gs_dwAllocGran = SI,dwAllocationGranularity: 
    
    // обнуляем содержимое структуры 
    chINITSTRUCT(*pVMQ, FALSE): 
    
    // получаем MEMORY_BASIC_INFORMATION для переданного адреса 
    f0k = VirtitalQuery(pvAddress, &MBI, sizeof(MBI)) == sizeof(MBI); 
    if (!f0k) { 
    
    // если не удается получить информацию по переданному адресу, 
    // возвращаем FALSE, указывая на ошибку; суть проблемы сообщит 
    // функция GetLastErrorO 
    return(fOk); 
    
    // структура MEMORY_BASIC_INFORMATION содержит действительную 
    // информацию - пора заполнить элементы нашей структуры VMQUERY 
    
    // Во-первых, заполним элементы, описывающие состояние блока. Данные 
    // по региону получим позже. 
    switch (MBI.State) { 
    case MEM_FREE: 
    // это незарезервированный блок свободного адресного 
    // пространства 
    pVMQ->pvBlkBaseAddress = NULL; 
    pVMQ->dwBlkSize = 0; 
    pVMQ->dwBlkProtection = 0; 
    pVMQ->dwBlkStorage - MEM_FREE; 
    break; 
    case MEM_RESERVE: 
    
    // это блок зарезервированного адресного пространства, 
    // которому НЕ передана физическая память 
    pVMQ->pvBlkBaseAddress = MBI.BaseAddress; 
    pVMQ->dwBlkSize = MBI,RegionSize; 
    
    // Для блока, которому не передана физическая память, 
    // элемент MBI.Protect недействителен. Поэтому мы покажем, 
    // что зарезервированный блок унаследовал атрибут защиты 
    // того региона, в котором он содержится. 
    pVMQ->dwBlkProtection = MBI.AIlocatlonProtect; 
    pVMQ->dwBlkStorage = MEM_RESERVE; 
    break; 
    см. след. стр. 


    ____________________Глава 6.  Исследование виртуальной памяти 125 


    РИС. 6-3 (продолжение) 
    case MEM_COMMIT: 
    
    // это блок зарезервированного адресного пространства, 
    // которому передана физическая память 
    pVMQ->pvBlkBaseAddress = MBI.BaseAddress; 
    pVMQ->dwBlk8ize = MBI.RegionSize; 
    pVMQ->dwBlkProtection = MBI.Protect: 
    pVMQ->dwBlkStorage = MBI,Type; 
    Preak; 
    } 
    
    // Во-вторых, заполним элементы, относящиеся к региону (теперь мы 
    // используем данные MBI, полученные от первого вызова VirtualQuery). 
    // Нам придется вызвать VirtualQuery еще раз, чтобы получить полную 
    // информацию по региону. 
    switch (MBI.State) { 
    case MEM_FREE: 
    // это незарезервированный блок адресного пространства 
    pVMQ->pvRgnBaseAddress = MBI.BaseAddress; 
    pVMQ->dwRgnProtection = MBI.AllocationProtect; 
    pVMQ->dwRgnSize = MBI. RegionSize; 
    pVMQ->dwRgnStorage = MEM_FREE: 
    pVMQ->dwRgnBlocks = 0; 
    pVMQ->dwRgnGuardBlks = 0; 
    pVMQ->fRgn!sAStack = FALSE: 
    break; 
    case MEM_RESERVE: 
    
    // это зарезервированный регион, которому НЕ передана 
    // физическая память 
    pVMQ->pvRgnBaseAddress = MBI.AllocationBase: 
    pVMQ->dwRgnProtection = MBI.AllocationProtect: 
    
    // чтобы получить полную информацию по региону, нам придется 
    // выполнить итераций по всем его блокам 
    VMQueryHelp(pvAddress, &VMQHelp); 
    pVMQ->dwRgnSize = VMQHelp.dwRgnSize; 
    pVMQ->dwRgnStorage = VMQHelp.dwRgnStorage; 
    pVMQ->dwRgnBlocks = VMQHelp.dwRgnBlocks; 
    pVMQ->dwRgnGuardBlks = VMQHelp.dwRgnGuardBlks; 
    pVMQ->fRgn!sAStack = VMQHelp.fRgnIsAStack; 
    break; 
    case MEM_COMMIT: 
    
    // это зарезервированный регион, которому передана 
    // физическая память 
    pVMQ->pvRgnBaseAddress = MBI.AllocationBase: 
    pVMQ->dwRgnProtection = MBI.AllocationProtect; 
    LM. L/li.4/. 1. flip. 


    126 _______________________________Windows для профессионалов 


    РИС. 6~3 (продолжение) 
    
    II чтобы получить полную информацию по региону, нам придется 
    // выполнить итерацию по всем его блокам 
    VMQueryHelp(pvAddress, &VMQHelp); 
    pVMQ->dwRgn8ize = VMQHelp.dwRgnSize; 
    pVMQ->dwRgnStorage = VMQHelp.dwRgnStorage; 
    pVMQ->dwRgnBlocks = VMQHelp.dwRgnBlocks; 
    pVMQ->dwRgnGuardBlks = VMQHelp.dwRgnGuardBlks; 
    pVMQ->fRgnIsAStack = VMQHelp.fRgnIsAStack; 
    break; 
    i 
    
    // сообщаем, что функция выполнена успешно 
    return(fOk); 
    ////////////////////////////////// конец файла ///////////////////////////////// 

    VMQUERY.H 
    Модуль: VMQuery.H 
    Автор: Copyright © 1995-1997, Джеффри Рихтер (Jeffrey Richter) 
    typedef struct { 
    
    // информация о регионе 
    PVOID pvRgnBaseAddress; 
    
    DWORD dwRgnProtection;     // PAGE_* 
    DWORD dwRgnSize; 
    
    DWORD dwRgnStorage;        //MEM_*; Free, Image, Mapped, Private 
    DWORD dwRgnBlocks; 
    
    DWORD dwRgnGuardBlks;      // если > О, регион содержит стек потока 
    BOOL fRgnIsAStack;        // TRUE, если регион содержит стек потока 
    
    // информация о блоке 
    PVOID pvBlkBaseAddress; 
    
    DWORD dwBlkProtection;     // PAGE_* 
    DWORD dwBlkSize; 
    
    DWORD dwBlkStorage;        // MEM_*: Free, Reserve, Image, Mapped, Private 
    } VMQUERY, *PVMQUERY: 
    
    i'///////////////////////////////////////////'^////////t///'///////////'////l^ 
    BOOL VMQuery (PVOID pvAddress, PVMQUERY pVMQ); 
    
    Illlllllllllllllllllllllll/lllllll конец файла !/! I I I I I I I I I I I I 


    ___________________Глава 6. Исследование виртуальной памяти 127 


    Программа-пример VMMap 

    VMMap (VMMAP.ЕХЕ) (см. листинг на рис. 6-4) — программа, которая 
просматривает свое адресное пространство и показывает содержащиеся в нем 
регионы и блоки, присутствующие в регионах. Файлы исходного кода и ресурсов 
этой программы находятся в каталоге VMMAP на компакт-диске, прилагаемом к 
книге. Запустив программу, Вы увидите такое окно: 

    OOOiOOUO     Pri-vace        4.096      i. -RW-                            
    OOOLOOOO Pri.va.ce         1096          -RW- -
    OOOLLOOO     Free           61-110 
    00020000     Pri-va.Ce         4.096       L -RW-                          
    00020000 Pri-vaCe        1096         -RW- -
    0002LOOO     Free           61.14.0 
    00030000     Pri-vace     1-013576      3 -RW-     reread sca.ck 
    00030000 Reserve     1.036288         -RW- -
    001-20000 Pri.va.ce         4.096          -RW- c
    001-2e000 Pri.va.ce         SL92          -RW- -
    OOL30000     PrtvaCe         4.096      L -RW
    001-30000 Pri-vace        1096         -RW- -
    OOL31-000     Free           6L11Q 
    ;OOL10000     Pri-va.Ce     1.018576      2 -RW-     DeEa.u.Lc Process Heap 
    001-4.0000 Pri-va.Ce        1.2238          -RW- -
    001-4.3000 Reserve     1036288         -RW- -
    00210000     Mapped        65536      2 -RW
    00210000 Mapped         1096         -RW- -
    0024.LOOO Reserve        61-110          -RW- -
    00250000     Маржей        901.1.2       1. -R" 
    002SOOOO Mapped        901.1-2         -R-- -
    00266000     Free          10960                                           , 
    00270000     Mapped       L17156      L -R--                             'J 
    
    Карты виртуальной памяти, представленные в главе 5 на рис. 5-5, 5-6 и 5.7, 
созданы с помощью именно этой программы. 
    Каждый элемент в списке — результат вызова моей функции VMQuery. Основной 
цикл программы VMMap выглядит так: 

    PVOID pvAddress = 0х00000000; 
    BOOL f0k = TRUE: 
    VMQUERY VMQ; 
    while(fOk) { 
    f0k = VMQuery(pvAddress, &VMQ): 
    if (f0k) { 
    // формируем строку для вывода на экран 
    // и добавляем ее в окно списка 
    ConstructRgnInfoLine(&VMQ, szLine, sizeof(szLine)); 
    ListBox_AddString(hWndLB, szLine): 
    #if 1 
    
    // меняем 1 на 0, если блоки внутри регионов 
    // просматривать не надо 
    
    for (dwBlock = 0; f0k && (dwBlock < VMQ.dwRgnBlocks); dwBlock++) { 
    ConstructRgnInfoLine(&VMQ, szLine, sizeof(szLine)); 
    ListBox_AddString(hWndLB, szLine); 
    
    // получаем адрес следующего региона 
    pvAddress = ((BYTE *) pvAddress + VMQ,dwBlkSize); 
    см. след. стр. 


    128 _______________________________Windows для профессионалов 


    if (dwBlock < VMQ.dwRgnBlocks - 1) { 
    // нельзя запрашивать информацию о памяти 
    // за последним блоком 
    f0k = VMQuery(pvAddress, &VMQ); 
    } 
    ftendif 
    
    // получаем адрес следующего региона 
    pvAddress = ((BYTE *) VMQ.pvRgnBaseAddress + VMQ.dwRgnSize); 
    I. 
    ) 
    } 

    Этот цикл начинает работу с виртуального адреса 0х00000000 и 
заканчивается, когда VMQuery возвращает FALSE, что указывает на невозможность 
дальнейшего просмотра адресного пространства процесса. На каждой итерации 
цикла вызывается функция ConstructRgnInfoLine; она заполняет символьный буфер 
информацией о регионе. Потом эти данные вносятся в список. 
    В основной цикл вложен еще один цикл — он позволяет получать информацию о 
каждом блоке текущего региона. На каждой итерации из данного цикла вызывается 
функция ConstructRgnInfoLine, заполняющая символьный буфер информацией о 
блоках региона. Эти данные тоже добавляются к списку. В общем, с помощью 
VMQuery просматривать адресное пространство процесса очень легко. 

    VMMAP.C 
    
    Модуль: VMMap.C 
    Автор: Copyright © 1995-1997, Джеффри Рихтер (Jeffrey Richter) 
    ftinclude ". .\CrnnHdr. H"           /* см. приложение В */ 
    «include <windows.h> 
    «include <windowsx.h> 
    «include <tchar.h> 
    «include <stdio.h>               // для sprintf 
    «include <string.h>              // для strchr 
    «include "Resource.H" 
    «include "VMQuery.H" 
    //////////////////////////////////////////////////У//////////'/////////'//,''//'//'/ 
    
    // установите COPYTOCLIPBOARD как TRUE, если хотите скопировать 
    // карту памяти в буфер обмена 
    «define COPYTOCLIPBOARD   TRUE//FALSE 
    «if COPYTOCLIPBOARD 
    
    // Функция, копирующая содержимое окна списка в буфер обмена, 
    // Я использовал эту функцию, чтобы получить карты памяти, 
    // приведенные в книге. 
    см. след. стр. 

    Рис. 6-4. Листинг программы VMMap 


    ___________________Глава 6. Исследование виртуальной памяти 129 


    РИС. 6-4 (продолжение) 
    
    void CopyControlToClipboard (HWND hwnd) { 
    int nCount, nNum: 
    TCHAR szClipData[20000] = { 0 }; 
    HGLOBAL hClipData; 
    LPTSTR IpClipData; 
    BOOL fOk: 
    nCount = ListBox_GetCount(hwnd): 
    for (nNum = 0; nNum < nCount; nNum++) { 
    TCHAR szLine[1000]; 
    ListBox_GetText(hwnd, nNum, szLine); 
    _tcscat(szClipData, szLine): 
    _tcscat(szClipData. __TEXT("\r\n")); 
    OpenClipboard(NULL): 
    EmptyClipboard(); 
    
    // буфер обмена принимает только данные, находящиеся в блоке, 
    // выделенном функцией GlobalAlloc с флагами GMEM_MOVEABLE и 
    // GMEM_DDESHARE 
    
    hClipData = GlobalAlloc(GMEM_MOVEABLE ; GMEM_DDESHARE, 
    sizeof(TCHAR) * (_tcslen(szClipData) + 1)); 
    IpClipData = (LPTSTR) GlobalLock(hClipData); 
    _tcscpy(lpClipData, szClipData): 
    #ifdef UNICODE 
    fOk = (SetClipboardData(CF_UNICODETEXT, hClipData) == hClipData); 
    #else 
    fOk = (SetClipboardData(CF_TEXT, hClipData) == hClipData); 
    ffendif 
    CloseClipboard(): 
    if (!f0k) { 
    GlobalFree(hClipData); 
    MessageBox(Get Focus(), 
    __TEXT("Error putting text on the clipboard"), 
    NULL, MB_OK ; MB_ICONINFORMATION); 
    } 
    
    #endif 


    130 _______________________________Windows для профессионалов ] 


    РИС. 6-4 (продолжение) 
    
    LPCTSTR GetMemStorageText (DWORD dwStorage) { 
    LPCTSTR p = __TEXT("Unknown"); 
    switch (dwStorage) { 
    case MEM_FREE:      p = __TEXT("Free   "); break; 
    case MEM_RESERVE:   p = __TEXT("Reserve"); break; 
    case MEM_IMAGE:     p = __TEXT("Image "); break; 
    case MEM_MAPPED:    p = __TEXT("Mapped "); break; 
    case MEM_PRIVATE:   p = __TEXT("Private"); break; 
    } 
    return(p); 
    } 
    
////////////////7/7//////////////////////////////'//////,///'1////.//.1'1'1•/ 
•' •' • : .1 / ' 
    
    LPTSTR GetProtectText (DWORD dwProtect, LPTSTR szBuf, 
    BOOL fShowFlags) { 
    LPCTSTR p = __TEXT("Unknown"); 
    switch (dwProtect & ~(PAGE_GUARD ; PAGE_NOCACHE)) { 
    case PAGE_READONLY:           p = __TEXT("-R--"); break; 
    case PAGE_READWRITE:          p = __TEXT("-RW-"); break; 
    case PAGE_WRITECOPY:          p = __TEXT("-RWC"): break; 
    case PAGE_EXECUTE:            p = __TEXT("E---"); break; 
    case PAGE_EXECUTE_READ:       p = __TEXT("ER--"); break: 
    case PAGE_EXECUTE_READWRITE: p = __TEXT("ERW-"); break; 
    case PAGE_EXECUTE_WRITECOPY: p = __TEXT("ERWC"); break; 
    case PAGE_NOACCESS:           p = __TEXT("----"); break; 
    } 
    _tcscpy(szBuf, p);1 
    if (fShowFlags) { 
    _tcscat(szBur, __TEXT(" ")); 
    
    _tcscat(szBuf, (dwProtect & PAGE_GUARD) ? 
    __TEXT("G") : __TEXT( "-••)); 
    
    _tcscat(szBuf, (dwProtect & PAGE_NOCACHE) ? 
    __TEXT("N") : __TEXT("-")); 
    
    } 
    retnrn(szBuf); 
    f 
    
    //////////////////////////////////////////////////////////////////
    void ConstructRgnInfoLine (PVMQUERY pVMQ, LPTSTR szLine, int nMaxLen) { 
    int nLen; 
    
    _stprintf(szLine, __TEXT("%08X     %s %10u "), 
    pVMQ->pvRgnBaseAddress, 
    GetMemStorageText(pVMQ->dwRgn3torage), 
    pVMQ->dwRgnSize); 
    w. след. стр. 


    ___________________Глава 6. Исследование виртуальной памяти 131 


    РИС. 6-4 (продолжение) 

    if (pVMQ->dwRgnStorage != MEM_FREE) { 
    _stprintf(_tcschr(szLine, 0), __TEXT("%5u "), pVMQ->dwRgnBlocks); 
    GetProtectText(pVMQ->dwRgnProtection, _tcschr(szLine, 0), FALSE); 
    } 
    _tcscat(szLine, „_TEXT("     ")); 
    // пытаемся получить полное имя модуля для этого региона 
    nLen = _tcslen(szLine); 
    if (pVMQ->pvRgnBaseAddress != NULL) 
    GetModuleFileName((HINSTANCE) pVMQ->pvRgnBaseAddress, 
    szLlne + nLen, nMaxLen - nLen); 
    if (pVMQ->pvRgnBaseAddress == GetProcessHeap()) { 
    _tcscat(szLlne, __TEXT("Default Process Heap")); 
    if (pVMO->fRgn!sAStack) { 
    _tcscat(szLine, __TEXT("Thread Stack")); 
    } 
    /////////////////////////////////////////////////////////////////////////////// 
    void ConstructBlkInfoLine (PVMQUERY pVMQ, LPTSTR szLine, int nMaxLen) { 
    
    _stprintf(szLine, __TEXT("   Й08Х %s %10u         "), 
    pVMQ->pvBlkBaseAddress, 
    GetMemStorageText(pVMQ->dwBlkStorage), 
    pVMQ->dwBlkSize); 
    
    if (pVMQ->dwBlkStorage != MEM_FREE) { 
    GetProtectText(pVMQ->dwBlkProtection, 
    _tcschr(szLine, 0), TRUE); 
    /////////////////////////////////////////////////////////////////////////////// 
    void Dlg_OnSize (HWND hwnd, UINT state, int ex. int cy) { 
    SetWindowPos(Get01gItem(hwnd, IDC_LISTBOX), NULL, О, О, 
    ex, cy, SWP_NOZORDER); 
    » 
    ) 
    /////////////////////////////////////////////////////////////////////////////// 
    т. след. стр. 


    132 ______________________________Windows для профессионалов 


    РИС. 6-4 (продолжение) 

    BOOL Dlg_0nlnit0ialog (HWND hwnd, HWND hwndFocus, LPARAM iParam) { 
    HWND hWndLB = GetDlgItem(hwnd, IDC_LISTBOX); 
    PVOID pvAddress = 0х00000000: 
    
    TCHAR szLine[200]:                                                         
                 j 
    RECT re:                                                                   
                 i 
    DWORD dwBlock;                                                             
                 | 
    VMQUERY VMQ;                                   •                           
                 I 
    BOOL fOk = TRUE;                                                           
                \ 
    I I связываем значок с диалоговым окном 
    chSETDLGICONS(hwnd, IDI_VMMAP, IDI_VMMAP): 
    l 
    
    // создаем в окне списка горизонтальную полосу прокрутки                   
                | 
    ListBox_SetHorizontalExtent(hWndLB, 150 * LOWORD(GetDialogBaseUnits())): 
    
    // сначала окно списка надо отмасштабировать, потому что система 
    // не посылает сообщение WM_SIZE диалоговому окну в момент его 
    // создания 
    GetClientRect(hwnd, &rc); 
    SetWindowPos(hWndLB, NULL, 0, 0, гс.right, re.bottom, SWP_NOZORDER):       
                , i 
    
    // просматриваем виртуальное адресное пространство, 
    // добавляя элементы в окно списка 
    while (f0k) { 
    f0k = VMQuery(pvAddress, &VMO);                                            
             • 
    if (f0k) { 
    // формируем строку для вывода на экран                                    
         : 
    // и добавляем ее в окно списка 
    ConstructRgnInfoLine(&VMQ, szLine, sizeof(szLine));                        
          ' 
    ListBox_AddString(hWndLB, szLine); 
    #if 1                                                                      
                    : 
    // меняем 1 на 0, если блоки внутри регионов 
    // просматривать не надо 
    for (dwBlock = 0; f0k && (dwBlock < VMQ.dwRgnBlocks); 
    dwBlock++) { 
    ConstructBlkInfoLine(&VMQ, szLine, sizeof(szLine)); 
    ListBox_AddString(hWndLB, szLine); 

    , ; , :,,', стр. 


    ___________________Глава 6. Исследование виртуальной памяти 133 


    РИС. 6-4 (продолжение) 
    
    // получаем адрес следующего региона 
    pvAddress = ((BYTE *) pvAddress + VMQ.dwBlkSize); 
    if (dwBlock < VMQ.dwRgnBlocks - 1) { 
    // нельзя запрашивать информацию о памяти 
    // за последним блоком 
    f0k = VMQuery(pvAddress, &VMO); 
    > ftendif 
    // получаем адрес следующего региона 
    pvAddress = ((BYTE *) VMQ.pvRgnBaseAddress + VMQ,dwRgnSize); 
    < 
    f 
    #if COPYTOCLIPBOARD 
    CopyControlToClipboard(hWndLB); 
    ffendif 
    return(TRUE); 
    } 
    //////////// ///////////////// //////////////// /////////////// /////////// 
    void Dlg_OnCoffiiriand (HWND hwnd, int id, HWND hwndCtI, UINT codeNotify) { 
    
    switch (id) { 
    case IDCANCEL: 
    End0ialog(hwnd, id); 
    break; 
    \ f 
    f 
    
    ////// ///////// //////////// ///////////// ///////////// /////////// ////////////// 
    BOOL CALLBACK Dlg_Proc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM IParam) { 
    switch (uMsg) { 
    chHANDLE_DLGMSG(hwnd, WM_INITDIALOQ, Dlg_0nlnit0ialog); 
    chHANDLE_DLGMSG(hwnd, WM_COMMA'ND, Dlg_OnCommand); 
    chHANDLE_DLGMSG(hwnd, WM_SIZE, Dlg_OnSize); 
    } 
    return(FALSE); 
    I 
    I/II/I/III/III/IIIII//II//II/IIIII/II/IIIIIIIIIIIIIIII/IIIII/II/IIII/////III/II 
    см. след. rrnp. 


    134 ______________________________Windows для профессионалов 


    РИС. 6-4 (продолжение) 

    int WINAPI „tWinMain (HINSTANCE hinstExe. 
    HINSTANCE hinstPrev. LPTSTR pszCmdLine, int nCmdShow) < 
    chWARNIFUNICODEUNDERWIN95(); 
    DialogBox(hinstExe, MAKEINTRESOURCE(IDD_VMMAP), NULL, Dlg_Proc); 
    roturn(O); 
    111111111111/111111111/111/1111111 конец файла I / I I I I / I/I/I I I I I 
