| Revision | 3e634bb7ca3e31f1d0dbd4491b2d33a2e4e8aca8 (tree) |
|---|---|
| Time | 2020-12-14 17:29:03 |
| Author | Levashev Ivan 卜根 <bu_ <gen@octa...> |
| Commiter | Levashev Ivan 卜根 <bu_ |
Open sourcing as is
| @@ -0,0 +1,18 @@ | ||
| 1 | +Copyright 2020 Levashev Ivan Aleksandrovich | |
| 2 | + | |
| 3 | +Licensed under the Apache License, Version 2.0 (the "License"); | |
| 4 | +you may not use this file except in compliance with the License. | |
| 5 | +You may obtain a copy of the License at | |
| 6 | + | |
| 7 | + http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | + | |
| 9 | +Unless required by applicable law or agreed to in writing, software | |
| 10 | +distributed under the License is distributed on an "AS IS" BASIS, | |
| 11 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 12 | +See the License for the specific language governing permissions and | |
| 13 | +limitations under the License. | |
| 14 | + | |
| 15 | + | |
| 16 | +Fragments of code are licensed under GPLv3 with Runtime Exception, as | |
| 17 | +indicated in specific files, in their header comments. COPYING3 and | |
| 18 | +COPYING.RUNTIME are ONLY applicable to these specific files. |
| @@ -0,0 +1,14 @@ | ||
| 1 | +Memory manager with ability to catch several memory errors: dangling | |
| 2 | +pointers and buffer overflows. | |
| 3 | + | |
| 4 | +SafeMM was for Delphi 7, SafeMM2 was for Delphi XE2. On Delphi 10.2 it was | |
| 5 | +broken again. It was rewritten from scratch. | |
| 6 | + | |
| 7 | +Previously used only internally, provided as is. Maybe it will be of some | |
| 8 | +help. | |
| 9 | + | |
| 10 | +Supported platforms so far: Windows i686 and Windows amd64. | |
| 11 | + | |
| 12 | +See video https://youtu.be/zaOqK7aDcuI | |
| 13 | + | |
| 14 | +2020, Levashev Ivan Aleksandrovich, Saint Petersburg |
| @@ -0,0 +1,54 @@ | ||
| 1 | +(*---------------------------------------------------------------------------- | |
| 2 | +-- Copyright 2020 Levashev Ivan Aleksandrovich -- | |
| 3 | +-- -- | |
| 4 | +-- Licensed under the Apache License, Version 2.0 (the "License"); -- | |
| 5 | +-- you may not use this file except in compliance with the License. -- | |
| 6 | +-- You may obtain a copy of the License at -- | |
| 7 | +-- -- | |
| 8 | +-- http://www.apache.org/licenses/LICENSE-2.0 -- | |
| 9 | +-- -- | |
| 10 | +-- Unless required by applicable law or agreed to in writing, software -- | |
| 11 | +-- distributed under the License is distributed on an "AS IS" BASIS, -- | |
| 12 | +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- | |
| 13 | +-- See the License for the specific language governing permissions and -- | |
| 14 | +-- limitations under the License. -- | |
| 15 | +----------------------------------------------------------------------------*) | |
| 16 | + | |
| 17 | +(***************************************************************************\ | |
| 18 | +* Отладочный менеджер памяти * | |
| 19 | +***************************************************************************** | |
| 20 | +* - Выделяет под объекты память страницами. * | |
| 21 | +* - При освобождении страницы освобождаются логически, но так, что не может * | |
| 22 | +* тот же виртуальный адрес быть повторно использован. Обращения к * | |
| 23 | +* уничтоженному объекту приводят к A/V сразу на месте без шанса погулять * | |
| 24 | +* по висячим указателям и попортить структуры данных. * | |
| 25 | +* - В силу специфики менеджеров памяти не добавляется в проект. Вместо * | |
| 26 | +* этого в список путей поиска добавляется директория SafeMM3, а в проекте * | |
| 27 | +* перед всеми другими модулями указывается SafeMM3.Install. * | |
| 28 | +* - Принцип действия предполагает постоянный расход виртуальной памяти, так * | |
| 29 | +* что имеет смысл отдавать предпочтение 64разрядным сборкам. * | |
| 30 | +* - Даже на Win64 при полной загрузке АРМ исчерпываются ресурсы. В связи с * | |
| 31 | +* этим применён вероятностный подход, и вероятность задаётся аргументами * | |
| 32 | +* InstallDebugMemoryManager в модуле Install. На слабых рабочих станциях * | |
| 33 | +* придётся уменьшать. * | |
| 34 | +***************************************************************************** | |
| 35 | +* Иван Левашев, май 2020 * | |
| 36 | +****************************************************************************) | |
| 37 | + | |
| 38 | +unit SafeMM3.Install; | |
| 39 | + | |
| 40 | +interface | |
| 41 | + | |
| 42 | +implementation | |
| 43 | + | |
| 44 | +uses | |
| 45 | + SafeMM3; | |
| 46 | + | |
| 47 | +initialization | |
| 48 | + InstallDebugMemoryManager | |
| 49 | + ((* RandomVirtualChance => *) 333, | |
| 50 | + (* UseGuardPages => *) False, | |
| 51 | + (* UseDeveloperHintPages => *) False); | |
| 52 | +finalization | |
| 53 | + // UninstallDebugMemoryManager; | |
| 54 | +end. |
| @@ -0,0 +1,524 @@ | ||
| 1 | +(*---------------------------------------------------------------------------- | |
| 2 | +-- Copyright 2020 Levashev Ivan Aleksandrovich -- | |
| 3 | +-- -- | |
| 4 | +-- Licensed under the Apache License, Version 2.0 (the "License"); -- | |
| 5 | +-- you may not use this file except in compliance with the License. -- | |
| 6 | +-- You may obtain a copy of the License at -- | |
| 7 | +-- -- | |
| 8 | +-- http://www.apache.org/licenses/LICENSE-2.0 -- | |
| 9 | +-- -- | |
| 10 | +-- Unless required by applicable law or agreed to in writing, software -- | |
| 11 | +-- distributed under the License is distributed on an "AS IS" BASIS, -- | |
| 12 | +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- | |
| 13 | +-- See the License for the specific language governing permissions and -- | |
| 14 | +-- limitations under the License. -- | |
| 15 | +----------------------------------------------------------------------------*) | |
| 16 | + | |
| 17 | +(***************************************************************************\ | |
| 18 | +* Отладочный менеджер памяти * | |
| 19 | +***************************************************************************** | |
| 20 | +* - Выделяет под объекты память страницами. * | |
| 21 | +* - При освобождении страницы освобождаются логически, но так, что не может * | |
| 22 | +* тот же виртуальный адрес быть повторно использован. Обращения к * | |
| 23 | +* уничтоженному объекту приводят к A/V сразу на месте без шанса походить * | |
| 24 | +* по висячим указателям и попортить структуры данных. * | |
| 25 | +* - В силу специфики менеджеров памяти не добавляется в проект. Вместо * | |
| 26 | +* этого в список путей поиска добавляется директория SafeMM3, а в проекте * | |
| 27 | +* перед всеми другими модулями указывается SafeMM3.Install. * | |
| 28 | +* - Принцип действия предполагает постоянный расход виртуальной памяти, так * | |
| 29 | +* что имеет смысл отдавать предпочтение 64разрядным сборкам. * | |
| 30 | +* - Даже на Win64 при полной загрузке АРМ исчерпываются ресурсы. В связи с * | |
| 31 | +* этим применён вероятностный подход, и вероятность задаётся аргументами * | |
| 32 | +* InstallDebugMemoryManager в модуле Install. На слабых рабочих станциях * | |
| 33 | +* придётся уменьшать. * | |
| 34 | +* - TThreadPool.TAbstractWorkerData.FreeInstance не вызывает inherited, а * | |
| 35 | +* напрямую FreeMem, что не вполне корректно и не было совместимо с * | |
| 36 | +* SafeMM3. Для обхода этой проблемы указатели, полученные VirtualAlloc и * | |
| 37 | +* vOldManager.GetMem, стало можно смешивать. * | |
| 38 | +***************************************************************************** | |
| 39 | +* Иван Левашев, май 2020 * | |
| 40 | +****************************************************************************) | |
| 41 | + | |
| 42 | +unit SafeMM3; | |
| 43 | + | |
| 44 | +interface | |
| 45 | + | |
| 46 | +var | |
| 47 | + vRandomVirtualChance: Integer = 333; | |
| 48 | + | |
| 49 | +function DebugGetMem(Size: NativeInt): Pointer; | |
| 50 | +function DebugFreeMem(P: Pointer): Integer; | |
| 51 | +function DebugReallocMem(P: Pointer; Size: NativeInt): Pointer; | |
| 52 | +function DebugAllocMem(ASize: NativeInt): Pointer; | |
| 53 | + | |
| 54 | +function DebugMaybeVirtualGetMem(Size: NativeInt): PByte; | |
| 55 | +function DebugVirtualGetMem(Size: NativeInt): PByte; | |
| 56 | +function DebugNonVirtualGetMem(Size: NativeInt): PByte; | |
| 57 | + | |
| 58 | +const | |
| 59 | + DebugMemoryManager: TMemoryManagerEx = ( | |
| 60 | + GetMem: DebugGetMem; | |
| 61 | + FreeMem: DebugFreeMem; | |
| 62 | + ReallocMem: DebugReallocMem; | |
| 63 | + AllocMem: DebugAllocMem; | |
| 64 | + RegisterExpectedMemoryLeak: nil; | |
| 65 | + UnregisterExpectedMemoryLeak: nil; | |
| 66 | + ); | |
| 67 | + | |
| 68 | +/// <param name="RandomVirtualChance"> | |
| 69 | +/// Шанс в промилле, что объект будет выделен VirtualAlloc | |
| 70 | +/// </param> | |
| 71 | +/// <param name="UseGuardPages"> | |
| 72 | +/// Использовать защитные страницы после области выделенной памяти на случай, | |
| 73 | +/// если с объектом пытаются работать как с экземпляром, чьего класса он не | |
| 74 | +/// является | |
| 75 | +/// </param> | |
| 76 | +/// <param name="UseDeveloperHintPages"> | |
| 77 | +/// Использовать ли страницы, подсказывающие разработчику, что по некоторому | |
| 78 | +/// адресу был выделен объект. Они расположены перед объектом, и надо | |
| 79 | +/// прокрутить сильно назад, чтоб их увидеть. И это только для уничтоженных | |
| 80 | +/// объектов. И отладчик незакоммиченные страницы может не отображать как ??, | |
| 81 | +/// может отображать как 00, при том, что в CPU подсказка для того же адреса | |
| 82 | +/// может быть [...] = ???? Размер страницы в шестнадцатеричном коде $1000, | |
| 83 | +/// так что насколько далеко прокручивать, должно быть легко посчитать. | |
| 84 | +/// Крутим вверх обзор памяти, пока не стали три последние цифры нулями, а | |
| 85 | +/// выше должна начаться страница, вся заполненная одним и тем же, а именно | |
| 86 | +/// TClass уничтоженного объекта. Можно в Evaluate/Modify запросить | |
| 87 | +/// TClass($....).ClassName | |
| 88 | +/// </param> | |
| 89 | +procedure InstallDebugMemoryManager | |
| 90 | + (RandomVirtualChance: Integer = 333; | |
| 91 | + UseGuardPages: Boolean = False; | |
| 92 | + UseDeveloperHintPages: Boolean = False); | |
| 93 | +procedure UninstallDebugMemoryManager; | |
| 94 | + | |
| 95 | +implementation | |
| 96 | + | |
| 97 | +var | |
| 98 | + vOldManager: TMemoryManagerEx; | |
| 99 | + vNewInstanceAddressBegin, | |
| 100 | + vNewInstanceAddressEnd, | |
| 101 | + vFreeInstanceAddressBegin, | |
| 102 | + vFreeInstanceAddressEnd: PByte; | |
| 103 | + vPageSize, vAllocationGranularity: UInt32; | |
| 104 | + vGuardSize, vDeveloperHintSize: UInt32; // vPageSize или 0 | |
| 105 | + vUseGuardPages: Boolean = False; | |
| 106 | + vUseDeveloperHintPages: Boolean = False; | |
| 107 | + | |
| 108 | +type | |
| 109 | + TSafeMM3Preamble = record | |
| 110 | + FullSize: NativeInt; // отрицательные значения = обычный менеджер памяти | |
| 111 | + MagicWord: UInt64; // должно быть последним полем | |
| 112 | + end; | |
| 113 | + PSafeMM3Preamble = ^TSafeMM3Preamble; | |
| 114 | + | |
| 115 | +const | |
| 116 | + cSafeMM3MagicWord = $DEFA15EEDFBE01C0; | |
| 117 | + | |
| 118 | +type | |
| 119 | + TSystemInfo = record | |
| 120 | + case Integer of | |
| 121 | + 0: ( | |
| 122 | + dwOemId: UInt32); | |
| 123 | + 1: ( | |
| 124 | + wProcessorArchitecture: UInt16; | |
| 125 | + wReserved: UInt16; | |
| 126 | + dwPageSize: UInt32; | |
| 127 | + lpMinimumApplicationAddress: Pointer; | |
| 128 | + lpMaximumApplicationAddress: Pointer; | |
| 129 | + dwActiveProcessorMask: ^UInt32; | |
| 130 | + dwNumberOfProcessors: UInt32; | |
| 131 | + dwProcessorType: UInt32; | |
| 132 | + dwAllocationGranularity: UInt32; | |
| 133 | + wProcessorLevel: UInt16; | |
| 134 | + wProcessorRevision: UInt16); | |
| 135 | + end; | |
| 136 | +procedure GetSystemInfo(var SystemInfo: TSystemInfo); stdcall; | |
| 137 | + external 'kernel32.dll' name 'GetSystemInfo'; | |
| 138 | + | |
| 139 | +procedure MeasureSystem; | |
| 140 | +type | |
| 141 | + TNewInstanceHandler = function: TObject of object; | |
| 142 | + TFreeInstanceHandler = procedure of object; | |
| 143 | +var | |
| 144 | + newInstanceMethod: TNewInstanceHandler; | |
| 145 | + fakeObject: TClass; // отсюда читается VMT | |
| 146 | + freeInstanceMethod: TFreeInstanceHandler; | |
| 147 | + systemInfo: TSystemInfo; | |
| 148 | +begin | |
| 149 | + newInstanceMethod := TObject.NewInstance; | |
| 150 | + vNewInstanceAddressBegin := TMethod(newInstanceMethod).Code; | |
| 151 | + vNewInstanceAddressEnd := vNewInstanceAddressBegin + $50; // реальный размер $22 | |
| 152 | + fakeObject := TObject; | |
| 153 | + freeInstanceMethod := TObject(@fakeObject).FreeInstance; | |
| 154 | + vFreeInstanceAddressBegin := TMethod(freeInstanceMethod).Code; | |
| 155 | + vFreeInstanceAddressEnd := vFreeInstanceAddressBegin + $50; // реально $29 (x64) | |
| 156 | + GetSystemInfo(systemInfo); | |
| 157 | + vPageSize := systemInfo.dwPageSize; | |
| 158 | + vAllocationGranularity := systemInfo.dwAllocationGranularity; | |
| 159 | +end; | |
| 160 | + | |
| 161 | +procedure InstallDebugMemoryManager | |
| 162 | + (RandomVirtualChance: Integer = 333; | |
| 163 | + UseGuardPages: Boolean = False; | |
| 164 | + UseDeveloperHintPages: Boolean = False); | |
| 165 | +begin | |
| 166 | + Assert(GetHeapStatus.TotalAllocated = 0); | |
| 167 | + GetMemoryManager(vOldManager); | |
| 168 | + vRandomVirtualChance := RandomVirtualChance; | |
| 169 | + vUseGuardPages := UseGuardPages; | |
| 170 | + vUseDeveloperHintPages := UseDeveloperHintPages; | |
| 171 | + SetMemoryManager(DebugMemoryManager); | |
| 172 | + MeasureSystem; | |
| 173 | + if UseGuardPages then begin | |
| 174 | + vGuardSize := vPageSize; | |
| 175 | + end | |
| 176 | + else begin | |
| 177 | + vGuardSize := 0; | |
| 178 | + end {if}; | |
| 179 | + if UseDeveloperHintPages then begin | |
| 180 | + vDeveloperHintSize := vPageSize; | |
| 181 | + end | |
| 182 | + else begin | |
| 183 | + vDeveloperHintSize := 0; | |
| 184 | + end {if}; | |
| 185 | + Randomize; | |
| 186 | +end; | |
| 187 | + | |
| 188 | +procedure UninstallDebugMemoryManager; | |
| 189 | +begin | |
| 190 | + SetMemoryManager(vOldManager); | |
| 191 | +end; | |
| 192 | + | |
| 193 | +{$IFDEF WIN64} | |
| 194 | +function GetReturnAddress2: PByte; | |
| 195 | +// Обычный ReturnAddress возвращает адрес в System._GetMem, а нам это не | |
| 196 | +// интересно, нам бы надо на ещё один уровень глубже | |
| 197 | + | |
| 198 | +// В Win32 процедуры сначала копируют esp в ebp, потом уменьшают esp | |
| 199 | +// В Win64 процедуры сначала уменьшают rsp, потом копируют rsp в rbp | |
| 200 | +// Как результат, на Win32 довольно просто пробегать по стеку, а на Win64 | |
| 201 | +// по-хорошему нужно активно обращаться к WinAPI вроде RtlLookupFunctionEntry. | |
| 202 | +// Пример есть в ECallStack.TEurekaBaseStackList.Caller | |
| 203 | + | |
| 204 | +// Здесь сделана попытка всё же по-простому узнавать недостающий адрес, опираясь | |
| 205 | +// на информацию об устройстве стека DebugGetMem и System._GetMem и т.п. | |
| 206 | +// функций. | |
| 207 | + | |
| 208 | +// Но из-за этого при обновлении Delphi может ломаться. Данный модуль не для | |
| 209 | +// релизных сборок, а для разработчиков, так что пока и так сойдёт. | |
| 210 | +asm | |
| 211 | + // mov rax, [rbp+$30] | |
| 212 | + // mov rax, [rax+$8] | |
| 213 | + // // System._FreeMem не делает push rbp, вместо этого rbx | |
| 214 | + // // короче, проще со стека по смещению от rsp достать | |
| 215 | + mov rax, [rsp+$70] | |
| 216 | +end; | |
| 217 | +{$ENDIF WIN64} | |
| 218 | +{$IFDEF WIN32} | |
| 219 | +function GetReturnAddress2: PByte; | |
| 220 | +asm | |
| 221 | + mov eax, [esp+$18] | |
| 222 | +end; | |
| 223 | +{$ENDIF} | |
| 224 | + | |
| 225 | +function DebugGetMem(Size: NativeInt): Pointer; | |
| 226 | +var | |
| 227 | + returnAddress2: PByte; | |
| 228 | +begin | |
| 229 | + returnAddress2 := GetReturnAddress2; | |
| 230 | + if (returnAddress2 >= vNewInstanceAddressBegin) and | |
| 231 | + (returnAddress2 <= vNewInstanceAddressEnd) | |
| 232 | + then begin | |
| 233 | + Result := DebugMaybeVirtualGetMem(Size); | |
| 234 | + end | |
| 235 | + else begin | |
| 236 | + Result := DebugNonVirtualGetMem(Size); | |
| 237 | + end {if}; | |
| 238 | +end; | |
| 239 | + | |
| 240 | +function DebugReallocMem(P: Pointer; Size: NativeInt): Pointer; | |
| 241 | +begin | |
| 242 | + // Методы TObject не вызывают ReallocMem | |
| 243 | + Dec(PByte(P), SizeOf(TSafeMM3Preamble)); | |
| 244 | + Result := vOldManager.ReallocMem(P, SizeOf(TSafeMM3Preamble) + Size); | |
| 245 | + if not Assigned(Result) then begin | |
| 246 | + Exit; | |
| 247 | + end {if}; | |
| 248 | + PSafeMM3Preamble(Result).FullSize := -(SizeOf(TSafeMM3Preamble) + Size); | |
| 249 | + Inc(PByte(Result), SizeOf(TSafeMM3Preamble)); | |
| 250 | +end; | |
| 251 | + | |
| 252 | +function DebugAllocMem(ASize: NativeInt): Pointer; | |
| 253 | +begin | |
| 254 | + // Методы TObject не вызывают AllocMem | |
| 255 | + Result := DebugNonVirtualGetMem(ASize); | |
| 256 | + if Assigned(Result) then begin | |
| 257 | + FillChar(Result^, ASize, 0); | |
| 258 | + end {if}; | |
| 259 | +end; | |
| 260 | + | |
| 261 | +function VirtualAlloc | |
| 262 | + (lpvAddress: Pointer; dwSize: NativeUInt; | |
| 263 | + flAllocationType, flProtect: UInt32): Pointer; stdcall; | |
| 264 | + external 'kernel32.dll' name 'VirtualAlloc'; | |
| 265 | +function VirtualFree | |
| 266 | + (lpAddress: Pointer; dwSize: NativeUInt; | |
| 267 | + dwFreeType: UInt32): LongBool; stdcall; | |
| 268 | + external 'kernel32.dll' name 'VirtualFree'; | |
| 269 | +function VirtualProtect | |
| 270 | + (lpAddress: Pointer; dwSize: NativeUInt; | |
| 271 | + flNewProtect: UInt32; var OldProtect: UInt32): LongBool; stdcall; | |
| 272 | + external 'kernel32.dll' name 'VirtualProtect'; | |
| 273 | + | |
| 274 | +const | |
| 275 | + MEM_COMMIT = $00001000; | |
| 276 | + MEM_RESERVE = $00002000; | |
| 277 | + MEM_DECOMMIT = $00004000; | |
| 278 | + MEM_RELEASE = $00008000; | |
| 279 | + PAGE_NOACCESS = $01; | |
| 280 | + PAGE_READONLY = $02; | |
| 281 | + PAGE_READWRITE = $04; | |
| 282 | + | |
| 283 | +function DebugMaybeVirtualGetMem(Size: NativeInt): PByte; | |
| 284 | +begin | |
| 285 | + if Random(1000) >= vRandomVirtualChance then begin | |
| 286 | + // выделение обычным менеджером памяти | |
| 287 | + Exit(DebugNonVirtualGetMem(Size)); | |
| 288 | + end | |
| 289 | + else begin | |
| 290 | + Exit(DebugVirtualGetMem(Size)); | |
| 291 | + end {if}; | |
| 292 | +end; | |
| 293 | + | |
| 294 | +threadvar | |
| 295 | + tvReservedMemory: PByte; | |
| 296 | + /// <remarks> | |
| 297 | + /// Округление до dwPageSize | |
| 298 | + /// </remarks> | |
| 299 | + tvReservedAmount: NativeInt; | |
| 300 | + | |
| 301 | +/// <summary> | |
| 302 | +/// Данная функция выделяет память обычным VirtualAlloc и сохраняет в | |
| 303 | +/// threadvar, если (а это, скорее всего, так) в блоке | |
| 304 | +/// dwAllocationGranularity ещё осталось пространство. Последующие вызовы по | |
| 305 | +/// возможности отъедают место от уже выделенного блока | |
| 306 | +/// </summary> | |
| 307 | +/// <remarks> | |
| 308 | +/// Для памяти не сделан MEM_COMMIT | |
| 309 | +/// </remarks> | |
| 310 | +function CompactVirtualAlloc(Size: NativeInt): PByte; | |
| 311 | +// VirtualAlloc из WinAPI выделяет память блоками по dwAllocationGranularity | |
| 312 | +// (64кб), а хотелось бы dwPageSize (4кб), ведь это разница на целый порядок | |
| 313 | +// Если поддерживать честный Free, то нужно было бы дополнительные структуры | |
| 314 | +// в памяти устраивать, чтоб распиливать dwAllocationGranularity на dwPageSize | |
| 315 | +// Но менеджер памяти только для отладки, и можно кое-что упростить. FreeMem | |
| 316 | +// реализуется через MEM_DECOMMIT, а для MEM_DECOMMIT принадлежность блоку | |
| 317 | +// MEM_RESERVE не важна. Только для MEM_RELEASE нужно адрес указывать на начало | |
| 318 | +// блока, полученного от VirtualAlloc, и он высвобождается сразу весь. Нет | |
| 319 | +// MEM_RELEASE, нет проблем. | |
| 320 | + | |
| 321 | +var | |
| 322 | + // округление до dwPageSize | |
| 323 | + sizeRoundedUpToPageSize, newFreeSpace: NativeInt; | |
| 324 | + | |
| 325 | + // округление до dwAllocationGranularity | |
| 326 | + newAllocBlockSize: NativeInt; | |
| 327 | +begin | |
| 328 | + sizeRoundedUpToPageSize := Size + vPageSize - 1; | |
| 329 | + sizeRoundedUpToPageSize := sizeRoundedUpToPageSize - | |
| 330 | + (sizeRoundedUpToPageSize mod vPageSize); | |
| 331 | + | |
| 332 | + if tvReservedAmount < sizeRoundedUpToPageSize then begin | |
| 333 | + newAllocBlockSize := sizeRoundedUpToPageSize + vAllocationGranularity - 1; | |
| 334 | + newAllocBlockSize := newAllocBlockSize - | |
| 335 | + (newAllocBlockSize mod vAllocationGranularity); | |
| 336 | + | |
| 337 | + Result := VirtualAlloc | |
| 338 | + ((* lpvAddress => *) nil, | |
| 339 | + (* dwSize => *) newAllocBlockSize, | |
| 340 | + (* flAllocationType => *) MEM_RESERVE, | |
| 341 | + (* flProtect => *) PAGE_NOACCESS); | |
| 342 | + | |
| 343 | + if not Assigned(Result) then begin | |
| 344 | + Exit; | |
| 345 | + end {if}; | |
| 346 | + | |
| 347 | + // В новом блоке памяти свободного места может быть больше, чем старом | |
| 348 | + | |
| 349 | + newFreeSpace := newAllocBlockSize - sizeRoundedUpToPageSize; | |
| 350 | + if newFreeSpace < tvReservedAmount then begin | |
| 351 | + Exit; | |
| 352 | + end {if}; | |
| 353 | + | |
| 354 | + // Тогда лучше заменить на новый | |
| 355 | + | |
| 356 | + tvReservedMemory := Result + sizeRoundedUpToPageSize; | |
| 357 | + tvReservedAmount := newFreeSpace; | |
| 358 | + end | |
| 359 | + else if tvReservedAmount > sizeRoundedUpToPageSize then begin | |
| 360 | + Result := tvReservedMemory; | |
| 361 | + Inc(tvReservedMemory, sizeRoundedUpToPageSize); | |
| 362 | + Dec(tvReservedAmount, sizeRoundedUpToPageSize); | |
| 363 | + end | |
| 364 | + else begin | |
| 365 | + Result := tvReservedMemory; | |
| 366 | + tvReservedMemory := nil; | |
| 367 | + tvReservedAmount := 0; | |
| 368 | + end {if}; | |
| 369 | +end; | |
| 370 | + | |
| 371 | +function DebugVirtualGetMem(Size: NativeInt): PByte; | |
| 372 | +var | |
| 373 | + fullSize: NativeInt; | |
| 374 | +begin | |
| 375 | + fullSize := | |
| 376 | + vDeveloperHintSize + // подсказка разработчику | |
| 377 | + | |
| 378 | + // здесь неявно присутствует выравнивание по | |
| 379 | + // границе между страницами | |
| 380 | + | |
| 381 | + SizeOf(TSafeMM3Preamble) + // преамбула | |
| 382 | + ((Size + 7) and not 7) + // память объекта округлена по границе 8 байт | |
| 383 | + vGuardSize; // защитная страница | |
| 384 | + | |
| 385 | + Result := CompactVirtualAlloc(fullSize); | |
| 386 | + | |
| 387 | + if not Assigned(Result) then begin | |
| 388 | + Exit; | |
| 389 | + end {if}; | |
| 390 | + | |
| 391 | + Inc(Result, vDeveloperHintSize); // у живых объектов подсказка не заполнена | |
| 392 | + | |
| 393 | + // Выделен цельный диапазон виртуальной памяти без логической. | |
| 394 | + // Теперь в этом цельном диапазоне нужно выделить страницы кроме отладочных | |
| 395 | + | |
| 396 | + if not Assigned(VirtualAlloc | |
| 397 | + ((* lpvAddress => *) Result, | |
| 398 | + (* dwSize => *) fullSize - vGuardSize - vDeveloperHintSize, | |
| 399 | + (* flAllocationType => *) MEM_COMMIT, | |
| 400 | + (* flProtect => *) PAGE_READWRITE)) | |
| 401 | + then begin | |
| 402 | + Exit(nil); | |
| 403 | + end {if}; | |
| 404 | + | |
| 405 | + // Теперь сдвигаем Result так, чтоб прижать вплотную к защитной странице | |
| 406 | + | |
| 407 | + Inc(Result, (vPageSize - fullSize mod vPageSize) mod vPageSize); | |
| 408 | + | |
| 409 | + // Заполняем преамбулу | |
| 410 | + | |
| 411 | + PSafeMM3Preamble(Result).FullSize := fullSize; | |
| 412 | + PSafeMM3Preamble(Result).MagicWord := cSafeMM3MagicWord; | |
| 413 | + | |
| 414 | + // Готовим окончательный результат | |
| 415 | + | |
| 416 | + Inc(Result, SizeOf(TSafeMM3Preamble)); | |
| 417 | +end; | |
| 418 | + | |
| 419 | +procedure SetupDeveloperHintPage(HintPage: PByte; WasClass: TClass); | |
| 420 | +type | |
| 421 | + PClass = ^TClass; | |
| 422 | +var | |
| 423 | + index: Integer; | |
| 424 | + oldProtect: UInt32; | |
| 425 | +begin | |
| 426 | + // Сначала нужно эту страницу закоммитить | |
| 427 | + | |
| 428 | + if not Assigned(VirtualAlloc | |
| 429 | + ((* lpvAddress => *) HintPage, | |
| 430 | + (* dwSize => *) vDeveloperHintSize, | |
| 431 | + (* flAllocationType => *) MEM_COMMIT, | |
| 432 | + (* flProtect => *) PAGE_READWRITE)) | |
| 433 | + then begin | |
| 434 | + Exit; // видимо, не будет у разработчика подсказки | |
| 435 | + end {if}; | |
| 436 | + | |
| 437 | + for index := 0 to vPageSize div SizeOf(TClass) - 1 do begin | |
| 438 | + PClass(HintPage + SizeOf(TClass) * index)^ := WasClass; | |
| 439 | + end {loop}; | |
| 440 | + | |
| 441 | + // Для тяжёлых случаев можно ещё в страницу вставить дамп с вершины стека, | |
| 442 | + // чтоб потом, при A/V, вычислять, кто уничтожил объект | |
| 443 | + | |
| 444 | + // Закроем доступ на запись | |
| 445 | + | |
| 446 | + VirtualProtect | |
| 447 | + ((* lpAddress => *) HintPage, | |
| 448 | + (* dwSize => *) vDeveloperHintSize, | |
| 449 | + (* flNewProtect => *) PAGE_READONLY, | |
| 450 | + (* OldProtect => *) oldProtect); | |
| 451 | +end; | |
| 452 | + | |
| 453 | +function DebugFreeMem(P: Pointer): Integer; | |
| 454 | +var | |
| 455 | + fullSize: NativeInt; | |
| 456 | + wasClass: TClass; | |
| 457 | +begin | |
| 458 | + // возвращаемся к преамбуле | |
| 459 | + Dec(PByte(P), SizeOf(TSafeMM3Preamble)); | |
| 460 | + | |
| 461 | + if PSafeMM3Preamble(P).MagicWord <> cSafeMM3MagicWord then begin | |
| 462 | + Exit(1); | |
| 463 | + end {if}; | |
| 464 | + | |
| 465 | + fullSize := PSafeMM3Preamble(P).FullSize; | |
| 466 | + | |
| 467 | + if fullSize <= 0 then begin | |
| 468 | + // память была выделена не через VirtualAlloc | |
| 469 | + | |
| 470 | + // напоследок испортим байты так, чтобы это не было похоже на хороший | |
| 471 | + // указатель | |
| 472 | + | |
| 473 | + FillChar(P^, -fullSize, Byte($FE)); | |
| 474 | + | |
| 475 | + Exit(0); // и если уж портить, то чтоб без переиспользования | |
| 476 | + // Exit(vOldManager.FreeMem(P)); | |
| 477 | + end {if}; | |
| 478 | + | |
| 479 | + if vUseDeveloperHintPages then begin | |
| 480 | + // Класс надо успеть сохранить сейчас | |
| 481 | + wasClass := TObject(PByte(P) + SizeOf(TSafeMM3Preamble)).ClassType; | |
| 482 | + end {if}; | |
| 483 | + | |
| 484 | + // Возвращаемся к самому началу страницы | |
| 485 | + | |
| 486 | + Dec(PByte(P), (vPageSize - fullSize mod vPageSize) mod vPageSize); | |
| 487 | + | |
| 488 | + // Оставляем виртуальные адреса навечно в резерве | |
| 489 | + | |
| 490 | + if not VirtualFree | |
| 491 | + ((* lpAddress => *) P, | |
| 492 | + (* dwSize => *) fullSize - vGuardSize - vDeveloperHintSize, | |
| 493 | + (* dwFreeType => *) MEM_DECOMMIT) | |
| 494 | + then begin | |
| 495 | + Exit(1); | |
| 496 | + end {if}; | |
| 497 | + | |
| 498 | + if vUseDeveloperHintPages then begin | |
| 499 | + SetupDeveloperHintPage(PByte(P) - vDeveloperHintSize, wasClass); | |
| 500 | + end {if}; | |
| 501 | + | |
| 502 | + Exit(0); | |
| 503 | +end; | |
| 504 | + | |
| 505 | +function DebugNonVirtualGetMem(Size: NativeInt): PByte; | |
| 506 | +begin | |
| 507 | + Result := vOldManager.GetMem(SizeOf(TSafeMM3Preamble) + Size); | |
| 508 | + | |
| 509 | + if not Assigned(Result) then begin | |
| 510 | + Exit; | |
| 511 | + end {if}; | |
| 512 | + | |
| 513 | + // Заполняем преамбулу | |
| 514 | + | |
| 515 | + PSafeMM3Preamble(Result).FullSize := -(SizeOf(TSafeMM3Preamble) + Size); | |
| 516 | + PSafeMM3Preamble(Result).MagicWord := cSafeMM3MagicWord; | |
| 517 | + | |
| 518 | + // Готовим окончательный результат | |
| 519 | + | |
| 520 | + Inc(Result, SizeOf(TSafeMM3Preamble)); | |
| 521 | +end; | |
| 522 | + | |
| 523 | +end. | |
| 524 | + |