-
Notifications
You must be signed in to change notification settings - Fork 89
/
palMemTrackerImpl.h
331 lines (284 loc) · 14.6 KB
/
palMemTrackerImpl.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
/*
***********************************************************************************************************************
*
* Copyright (c) 2014-2024 Advanced Micro Devices, Inc. All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
**********************************************************************************************************************/
/**
***********************************************************************************************************************
* @file palMemTrackerImpl.h
* @brief PAL utility collection MemTracker class implementations.
***********************************************************************************************************************
*/
#pragma once
#if PAL_MEMTRACK
#include "palIntrusiveListImpl.h"
#include "palMemTracker.h"
#include "palSysMemory.h"
#include <cstring>
namespace Util
{
/// Table to convert a blockType to a string. Used by the logging routines.
constexpr const char* MemBlkTypeStr[] =
{
"Malloc", ///< MemBlkType::Malloc
"New", ///< MemBlkType::New
"NewArray", ///< MemBlkType::NewArray
};
// =====================================================================================================================
template <typename Allocator>
MemTracker<Allocator>::MemTracker(
Allocator*const pAllocator)
:
m_markerSizeUints(MarkerSizeUints),
m_markerSizeBytes(MarkerSizeBytes),
m_pAllocator(pAllocator),
m_nextAllocNum(1),
m_breakOnAllocNum(0)
{
}
// =====================================================================================================================
template <typename Allocator>
MemTracker<Allocator>::~MemTracker()
{
// Clean-up leaked memory if needed
if (m_trackerList.IsEmpty() == false)
{
// If the list isn't empty, we have a leak. The leak could either be caused by an internal PAL leak,
// a client leak, or even the application not destroying API objects.
PAL_ALERT_ALWAYS();
// Dump out a list of unfreed blocks.
MemoryReport();
FreeLeakedMemory();
}
}
// =====================================================================================================================
template <typename Allocator>
Result MemTracker<Allocator>::Init()
{
return Result::Success;
}
// =====================================================================================================================
// Adds the newly allocated memory block to the list of blocks for tracking.
//
// The tracking information includes things like filename, line numbers, and type of block. Also, given a pointer,
// adds the Underrun/Overrun markers to the memory allocated, and return a pointer to the actual client usable memory.
//
// See MemTracker::Alloc() which is used to allocate memory that is being tracked.
template <typename Allocator>
void* MemTracker<Allocator>::AddMemElement(
void* pMem, // [in,out] Original pointer allocated by MemTracker::Alloc.
size_t bytes, // Client requested allocation size in bytes.
size_t align, // The max of the client-requested alignment or the internal alignment, in bytes.
MemBlkType blockType, // Block type based on calling allocation routine.
const char* pFilename, // Client filename that is requesting the memory.
uint32 lineNumber) // Line number in client file that is requesting the memory.
{
// Our internal data is all relative to the client pointer so find that first. See Alloc for more details.
// (align1)(MemTrackerList::Node)(MemTrackerElem)(underflow tracker)(client allocation)(align2)(overflow tracker)
constexpr size_t InternalSize = sizeof(MemTrackerList::Node) + sizeof(MemTrackerElem);
void*const pClientMem = VoidPtrAlign(VoidPtrInc(pMem, m_markerSizeBytes + InternalSize), align);
uint32* pUnderrun = static_cast<uint32*>(VoidPtrDec(pClientMem, m_markerSizeBytes));
uint32* pOverrun = static_cast<uint32*>(VoidPtrInc(pClientMem, Pow2Align(bytes, sizeof(uint32))));
auto*const pNewElement = static_cast<MemTrackerElem*>(VoidPtrDec(pUnderrun, sizeof(MemTrackerElem)));
void*const pNewNodeMem = VoidPtrDec(pNewElement, sizeof(MemTrackerList::Node));
auto*const pNewNode = PAL_PLACEMENT_NEW(pNewNodeMem) MemTrackerList::Node(pNewElement);
// Mark the memory with the underrun/overrun marker.
for (uint32 markerUints = 0; markerUints < m_markerSizeUints; ++markerUints)
{
*pUnderrun++ = UnderrunSentinel;
*pOverrun++ = OverrunSentinel;
}
pNewElement->size = bytes;
pNewElement->pFilename = pFilename;
pNewElement->lineNumber = lineNumber;
pNewElement->blockType = blockType;
pNewElement->pClientMem = pClientMem;
pNewElement->pOrigMem = pMem;
pNewElement->pList = &m_trackerList;
MutexAuto lock(&m_mutex);
// Trigger an assert if we're about to allocate the break-on-allocation number.
if (m_nextAllocNum == m_breakOnAllocNum)
{
PAL_ASSERT_ALWAYS();
}
pNewElement->allocNum = m_nextAllocNum;
++m_nextAllocNum;
m_trackerList.PushFront(pNewNode);
return pClientMem;
}
// =====================================================================================================================
// Removes an allocated block from the list of blocks used for tracking.
//
// The routine checks for invalid frees (and duplicate frees). Also, the routine is able to detect mismatched alloc/free
// usage based on the blockType. The routine is called with the pointer to the client usable memory and returns the
// pointer to the allocated memory.
//
// See MemTracker::Free() which is used to free memory that is being tracked.
template <typename Allocator>
void* MemTracker<Allocator>::RemoveMemElement(
void* pClientMem, // Pointer to client usable memory.
MemBlkType blockType) // Block type based on calling deallocation routine.
{
void* pOrigPtr = nullptr;
// Recall that this is our internal memory layout. See Alloc for more details.
// (align1)(MemTrackerList::Node)(MemTrackerElem)(underflow tracker)(client allocation)(align2)(overflow tracker)
uint32* pUnderrun = static_cast<uint32*>(VoidPtrDec(pClientMem, m_markerSizeBytes));
auto*const pCurrent = static_cast<MemTrackerElem*>(VoidPtrDec(pUnderrun, sizeof(MemTrackerElem)));
auto*const pCurrentNode = static_cast<MemTrackerList::Node*>(VoidPtrDec(pCurrent, sizeof(MemTrackerList::Node)));
uint32* pOverrun = static_cast<uint32*>(VoidPtrInc(pClientMem, Pow2Align(pCurrent->size, sizeof(uint32))));
// We should not be trying to free something twice or trying to free something which has not been allocated
// by this MemTracker. We can verify both of these things by checking that the tracker's pList is equal to the
// MemTracker's list.
if (pCurrent->pList != &m_trackerList)
{
// A free was attempted on an unrecognized pointer.
PAL_DPERROR("Invalid Free Attempted with ptr = : (%#x)", pClientMem);
}
else if (pCurrent->blockType != blockType)
{
// We have a mismatch in the alloc/free pair, e.g. PAL_NEW with PAL_FREE etc. return early here without freeing
// the memory so it shows up as a leak.
PAL_DPERROR("Trying to Free %s as %s.",
MemBlkTypeStr[static_cast<uint32>(pCurrent->blockType)],
MemBlkTypeStr[static_cast<uint32>(blockType)]);
}
else
{
// We should check for memory corruption due to overflow or underflow before continuing because any underflow
// might indicate that our internal state is corrupted. This could lead to a crash in the code below.
for (uint32 markerUints = 0; markerUints < m_markerSizeUints; ++markerUints)
{
PAL_ASSERT(*pUnderrun++ == UnderrunSentinel);
PAL_ASSERT(*pOverrun++ == OverrunSentinel);
}
// Remove our tracker from the list and set it's pList to null to detect a double-free in the future.
MutexAuto lock(&m_mutex);
m_trackerList.Erase(pCurrentNode);
pCurrent->pList = nullptr;
pOrigPtr = pCurrent->pOrigMem;
}
// Return a pointer to the actual allocated block.
return pOrigPtr;
}
// =====================================================================================================================
// Allocates a block of memory and tracks it using the memory tracker.
template <typename Allocator>
void* MemTracker<Allocator>::Alloc(
const AllocInfo& allocInfo)
{
// Allocating zero bytes of memory results in undefined behavior.
PAL_ASSERT(allocInfo.bytes > 0);
void* pMem = nullptr;
// We want to allocate extra memory from the caller's allocator, in this layout:
// (align1)(MemTrackerList::Node)(MemTrackerElem)(underflow tracker)(client allocation)(align2)(overflow tracker)
// Here's why we need each of those sections:
// 1. align1 is zero or more bytes needed to align the client allocation and our internal data.
// 2. The MemTrackerList::Node object, which is used to link this allocation into m_trackerList.
// 3. The MemTrackerElem struct contains bookkeeping data we need to report memory errors.
// 4. The underflow and overflow trackers detect out of bounds writes. They are optional.
// 5. The client allocation, which is actually returned to the caller.
// 6. align2 is zero or more bytes needed to DWORD-align the overflow tracker.
constexpr size_t InternalAlignment = Max(alignof(MemTrackerList::Node), alignof(MemTrackerElem));
const size_t paddedAlignBytes = Max(allocInfo.alignment, InternalAlignment);
const size_t paddedSizeBytes = (paddedAlignBytes + // 1
sizeof(MemTrackerList::Node) + // 2
sizeof(MemTrackerElem) + // 3
m_markerSizeBytes + // 4.a
Pow2Align(allocInfo.bytes, sizeof(uint32)) + // 5 & 6
m_markerSizeBytes); // 4.b
const AllocInfo memTrackerInfo(paddedSizeBytes, paddedAlignBytes, allocInfo.zeroMem, allocInfo.allocType,
allocInfo.blockType, allocInfo.pFilename, allocInfo.lineNumber);
pMem = m_pAllocator->Alloc(memTrackerInfo);
if (pMem != nullptr)
{
// Don't bother adding a failed allocation to the Memtrack list.
pMem = AddMemElement(pMem,
allocInfo.bytes,
paddedAlignBytes,
allocInfo.blockType,
allocInfo.pFilename,
allocInfo.lineNumber);
}
return pMem;
}
// =====================================================================================================================
// Frees a block of memory. The routine is called with the pointer to the client usable memory.
//
// See MemTracker::RemoveMemElement() which is used to validate the free.
template <typename Allocator>
void MemTracker<Allocator>::Free(
const FreeInfo& freeInfo)
{
// Don't want to call RemoveMemElement if the ptr is null.
if (freeInfo.pClientMem != nullptr)
{
void* pMem = RemoveMemElement(freeInfo.pClientMem, freeInfo.blockType);
// If this free call is valid (RemoveMemElement doesn't return nullptr), release the memory.
if (pMem != nullptr)
{
m_pAllocator->Free(FreeInfo(pMem, freeInfo.blockType));
}
}
}
// =====================================================================================================================
// Frees all memory that has not been explicitly freed (in other words, memory that has leaked). This function is only
// expected to be called when the memory tracker is being destroyed.
template <typename Allocator>
void MemTracker<Allocator>::FreeLeakedMemory()
{
for (MemTrackerList::Iter iter = m_trackerList.Begin(); iter.IsValid(); )
{
MemTrackerElem*const pCurrent = iter.Get();
// Free will release the memory for tracking and the actual element. This will invalidate our list iterator
// unless we advance the iterator first.
iter.Next();
Free(FreeInfo(pCurrent->pClientMem, pCurrent->blockType));
}
}
// =====================================================================================================================
// Outputs information about leaked memory by traversing the memory tracker list.
template <typename Allocator>
void MemTracker<Allocator>::MemoryReport()
{
// When this env var is set to non-zero, don't report leaks.
// Useful for crashing apps that don't give us a chance to clean up.
const char* pToggle = getenv("AMDPAL_NO_LEAK_REPORT");
if ((pToggle == nullptr) || (atoi(pToggle) > 0))
{
PAL_DPWARN("================ List of Leaked Blocks ================");
for (MemTrackerList::Iter iter = m_trackerList.Begin(); iter.IsValid(); iter.Next())
{
MemTrackerElem*const pCurrent = iter.Get();
PAL_DPWARN(
"ClientMem = 0x%p, AllocSize = %8d, MemBlkType = %s, File = %-15s, LineNumber = %8d, AllocNum = %8d",
pCurrent->pClientMem,
pCurrent->size,
MemBlkTypeStr[static_cast<uint32>(pCurrent->blockType)],
pCurrent->pFilename,
pCurrent->lineNumber,
pCurrent->allocNum);
}
PAL_DPWARN("================ End of List ===========================");
}
}
} // Util
#endif