blob: 29fa762c6462e0ef1bd5fb176d039d4abf7be1af [file] [log] [blame]
Danylo Piliaiev9f18e5d2019-07-03 11:12:50 +03001/*
2 * Copyright © 2019 Intel Corporation
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 */
23
24#include "glmemshadow.hpp"
25
26#include <unordered_map>
27#include <algorithm>
28
29#include <assert.h>
30
31#ifdef _WIN32
32
33#include <windows.h>
34
35#else
36
37#include <unistd.h>
38#include <signal.h>
39#include <sys/mman.h>
40
41#endif
42
43#include "gltrace.hpp"
44#include "os_thread.hpp"
45#include "os.hpp"
46
47static bool sInitialized = false;
48
49static std::unordered_map<size_t, GLMemoryShadow*> sPages;
50static size_t sPageSize;
51
52static os::mutex mutex;
53
54enum class MemProtection {
55#ifdef _WIN32
56 NO_ACCESS = PAGE_NOACCESS,
57 READ_ONLY = PAGE_READONLY,
58 READ_WRITE = PAGE_READWRITE,
59#else
60 NO_ACCESS = PROT_NONE,
61 READ_ONLY = PROT_READ,
62 READ_WRITE = PROT_READ | PROT_WRITE,
63#endif
64};
65
66size_t getSystemPageSize() {
67#ifdef _WIN32
68 SYSTEM_INFO info;
69 GetSystemInfo(&info);
70 return info.dwPageSize;
71#else
72 return sysconf(_SC_PAGESIZE);
73#endif
74}
75
76void memProtect(void *addr, size_t size, MemProtection protection) {
77#ifdef _WIN32
78 DWORD flOldProtect;
79 BOOL bRet = VirtualProtect(addr, size, static_cast<DWORD>(protection), &flOldProtect);
80 if (!bRet) {
81 DWORD dwLastError = GetLastError();
82 os::log("apitrace: error: VirtualProtect failed with error 0x%lx\n", dwLastError);
83 os::abort();
84 }
85#else
86 const int err = mprotect(addr, size, static_cast<int>(protection));
87 if (err) {
88 const char *errorStr = strerror(err);
89 os::log("apitrace: error: mprotect failed with error \"%s\"\n", errorStr);
90 os::abort();
91 }
92#endif
93}
94
95template<typename T, typename U>
96auto divRoundUp(T a, U b) -> decltype(a / b) {
97 return (a + b - 1) / b;
98}
99
100#ifdef _WIN32
101static LONG CALLBACK
102VectoredHandler(PEXCEPTION_POINTERS pExceptionInfo)
103{
104 PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord;
105 DWORD ExceptionCode = pExceptionRecord->ExceptionCode;
106
107 if (ExceptionCode == EXCEPTION_ACCESS_VIOLATION &&
108 pExceptionRecord->NumberParameters >= 2 &&
109 pExceptionRecord->ExceptionInformation[0] == 1) { // writing
110
111 const uintptr_t addr = static_cast<uintptr_t>(pExceptionRecord->ExceptionInformation[1]);
112 const size_t page = addr / sPageSize;
113
114 os::unique_lock<os::mutex> lock(mutex);
115
116 const auto it = sPages.find(page);
117 if (it != sPages.end()) {
118 GLMemoryShadow *shadow = it->second;
119 shadow->onAddressWrite(addr, page);
120 return EXCEPTION_CONTINUE_EXECUTION;
121 } else {
122 os::log("apitrace: error: %s: access violation at non-tracked page\n", __FUNCTION__);
123 os::abort();
124 }
125 }
126
127 return EXCEPTION_CONTINUE_SEARCH;
128}
129
130#else
131
132void PageGuardExceptionHandler(int sig, siginfo_t *si, void *unused) {
133 if (sig == SIGSEGV && si->si_code == SEGV_ACCERR) {
134 const uintptr_t addr = reinterpret_cast<uintptr_t>(si->si_addr);
135 const size_t page = addr / sPageSize;
136
137 os::unique_lock<os::mutex> lock(mutex);
138
139 const auto it = sPages.find(page);
140 if (it != sPages.end()) {
141 GLMemoryShadow *shadow = it->second;
142 shadow->onAddressWrite(addr, page);
143 } else {
144 os::log("apitrace: error: %s: access violation at non-tracked page\n", __FUNCTION__);
145 os::abort();
146 }
147 }
148}
149#endif
150
151void initializeGlobals()
152{
153 sPageSize = getSystemPageSize();
154
155#ifdef _WIN32
156 if (AddVectoredExceptionHandler(1, VectoredHandler) == NULL) {
157 os::log("apitrace: error: %s: add vectored exception handler failed\n", __FUNCTION__);
158 }
159#else
160 struct sigaction sa, oldSa;
161 sa.sa_flags = SA_SIGINFO;
162 sigemptyset(&sa.sa_mask);
163 sa.sa_sigaction = PageGuardExceptionHandler;
164 if (sigaction(SIGSEGV, &sa, &oldSa) == -1) {
165 os::log("apitrace: error: %s: set page guard exception handler failed\n", __FUNCTION__);
166 }
167#endif
168}
169
170GLMemoryShadow::~GLMemoryShadow()
171{
172 os::unique_lock<os::mutex> lock(mutex);
173
174 const size_t startPage = reinterpret_cast<uintptr_t>(shadowMemory) / sPageSize;
175 for (size_t i = 0; i < nPages; i++) {
176 sPages.erase(startPage + i);
177 }
178
179#ifdef _WIN32
180 VirtualFree(shadowMemory, nPages * sPageSize, MEM_RELEASE);
181#else
182 munmap(shadowMemory, nPages * sPageSize);
183#endif
184}
185
186bool GLMemoryShadow::init(const void *data, size_t size)
187{
188 if (!sInitialized) {
189 initializeGlobals();
190 sInitialized = true;
191 }
192
193 nPages = divRoundUp(size, sPageSize);
194 const size_t adjustedSize = nPages * sPageSize;
195
196#ifdef _WIN32
197 shadowMemory = reinterpret_cast<uint8_t*>(VirtualAlloc(nullptr, adjustedSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
198#else
199 shadowMemory = reinterpret_cast<uint8_t*>(mmap(nullptr, adjustedSize, PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
200#endif
201
202 if (!shadowMemory) {
203 os::log("apitrace: error: %s: Failed to allocate shadow memory!\n", __FUNCTION__);
204 return false;
205 }
206
207 if (data != nullptr) {
208 memcpy(shadowMemory, data, size);
209 }
210
211 memProtect(shadowMemory, adjustedSize, MemProtection::NO_ACCESS);
212
213 {
214 os::unique_lock<os::mutex> lock(mutex);
215
216 const size_t startPage = reinterpret_cast<uintptr_t>(shadowMemory) / sPageSize;
217 for (size_t i = 0; i < nPages; i++) {
218 sPages.emplace(startPage + i, this);
219 }
220 }
221
222 dirtyPages.resize(divRoundUp(nPages, 32));
223
224 return true;
225}
226
227void *GLMemoryShadow::map(gltrace::Context *_ctx, void *_glMemory, GLbitfield _flags, size_t start, size_t size)
228{
229 ctx = _ctx;
230 glMemory = reinterpret_cast<uint8_t*>(_glMemory);
231 flags = _flags;
232 mappedStart = start;
233 mappedSize = size;
234
235 mappedStartPage = start / sPageSize;
236 mappedEndPage = divRoundUp(start + size, sPageSize);
237
238 uint8_t *protectStart = shadowMemory + mappedStartPage * sPageSize;
239 const size_t protectSize = (mappedEndPage - mappedStartPage) * sPageSize;
240
241 // The buffer may have been updated before the mapping.
242 // TODO: handle write only buffers
243 if (flags & GL_MAP_READ_BIT) {
244 memProtect(protectStart, protectSize, MemProtection::READ_WRITE);
245 memcpy(shadowMemory + start, glMemory, size);
246 }
247
248 memProtect(protectStart, protectSize, MemProtection::READ_ONLY);
249
250 return shadowMemory + start;
251}
252
253void GLMemoryShadow::unmap(Callback callback)
254{
255 if (isDirty) {
256 os::unique_lock<os::mutex> lock(mutex);
257 commitWrites(callback);
258 }
259
260 {
261 os::unique_lock<os::mutex> lock(mutex);
262
263 auto it = std::find(ctx->dirtyShadows.begin(), ctx->dirtyShadows.end(), this);
264 if (it != ctx->dirtyShadows.end()) {
265 ctx->dirtyShadows.erase(it);
266 }
267 }
268
269 memProtect(shadowMemory, nPages * sPageSize, MemProtection::NO_ACCESS);
270
271 ctx = nullptr;
272 glMemory = nullptr;
273 flags = 0;
274 mappedStart = 0;
275 mappedSize = 0;
276 pagesToDirtyOnConsecutiveWrites = 1;
277}
278
279void GLMemoryShadow::onAddressWrite(uintptr_t addr, size_t page)
280{
281 const size_t relativePage = (addr - reinterpret_cast<uintptr_t>(shadowMemory)) / sPageSize;
282 if (isPageDirty(relativePage)) {
283 // It is possible if writing to the same buffer from two threads
284 return;
285 }
286
287 if ((relativePage == lastDirtiedRelativePage + 1) && isPageDirty(relativePage - 1)) {
288 /* Ensure that we would have log(n) page exceptions if traced application writes
289 * to n consecutive pages.
290 */
291 pagesToDirtyOnConsecutiveWrites *= 2;
292 } else {
293 pagesToDirtyOnConsecutiveWrites = 1;
294 }
295
296 const size_t endPageToDirty = std::min(relativePage + pagesToDirtyOnConsecutiveWrites, nPages);
297 for (size_t pageToDirty = relativePage; pageToDirty < endPageToDirty; pageToDirty++) {
298 setPageDirty(pageToDirty);
299 }
300
301 lastDirtiedRelativePage = endPageToDirty - 1;
302
303 memProtect(reinterpret_cast<void*>(page * sPageSize),
304 (endPageToDirty - relativePage) * sPageSize, MemProtection::READ_WRITE);
305}
306
307GLbitfield GLMemoryShadow::getMapFlags() const
308{
309 return flags;
310}
311
312void GLMemoryShadow::setPageDirty(size_t relativePage)
313{
314 assert(relativePage < nPages);
315 dirtyPages[relativePage / 32] |= 1U << (relativePage % 32);
316
317 if (!isDirty) {
318 ctx->dirtyShadows.push_back(this);
319 isDirty = true;
320 }
321}
322
323bool GLMemoryShadow::isPageDirty(size_t relativePage)
324{
325 assert(relativePage < nPages);
326 return dirtyPages[relativePage / 32] & (1U << (relativePage % 32));
327}
328
329void GLMemoryShadow::commitWrites(Callback callback)
330{
331 assert(isDirty);
332
333 uint8_t *shadowSlice = shadowMemory + mappedStartPage * sPageSize;
334 const size_t glStartOffset = mappedStart % sPageSize;
335
336 /* Other thread may write to the buffers at this very moment
337 * so we need to protect pages before we read from them.
338 * The other thread will have to wait until we commit all writes we want.
339 */
340 for (size_t i = mappedStartPage; i < mappedEndPage; i++) {
341 if (isPageDirty(i)) {
342 memProtect(shadowMemory + i * sPageSize, sPageSize, MemProtection::READ_ONLY);
343 }
344 }
345
Danylo Piliaievc2087972019-07-30 16:44:17 +0300346 for (size_t i = mappedStartPage; i < mappedEndPage; i++) {
Danylo Piliaiev9f18e5d2019-07-03 11:12:50 +0300347 if (isPageDirty(i)) {
Danylo Piliaievc2087972019-07-30 16:44:17 +0300348 // We coalesce consecutive writes into one
349 size_t firstDirty = i;
350 while (++i < mappedEndPage && isPageDirty(i)) { }
Danylo Piliaiev9f18e5d2019-07-03 11:12:50 +0300351
Danylo Piliaievc2087972019-07-30 16:44:17 +0300352 const size_t pages = i - firstDirty;
353 if (firstDirty != mappedStartPage) {
354 const size_t shadowOffset = (firstDirty - mappedStartPage) * sPageSize;
355 const size_t glOffset = shadowOffset - glStartOffset;
356 const size_t size = std::min(glStartOffset + mappedSize - shadowOffset, sPageSize * pages);
357
358 memcpy(glMemory + glOffset, shadowSlice + shadowOffset, size);
359 callback(shadowSlice + shadowOffset, size);
360 } else {
361 const size_t size = std::min(sPageSize * pages - glStartOffset, mappedSize);
362
363 memcpy(glMemory, shadowSlice + glStartOffset, size);
364 callback(shadowSlice + glStartOffset, size);
365 }
Danylo Piliaiev9f18e5d2019-07-03 11:12:50 +0300366 }
367 }
368
369 std::fill(dirtyPages.begin(), dirtyPages.end(), 0);
370 isDirty = false;
371 pagesToDirtyOnConsecutiveWrites = 1;
372 lastDirtiedRelativePage = UINT32_MAX - 1;
373}
374
375void GLMemoryShadow::updateForReads()
376{
377 uint8_t *protectStart = shadowMemory + mappedStartPage * sPageSize;
378 const size_t protectSize = (mappedEndPage - mappedStartPage) * sPageSize;
379
380 memProtect(protectStart, protectSize, MemProtection::READ_WRITE);
381
382 memcpy(shadowMemory + mappedStart, glMemory + mappedStart, mappedSize);
383
384 memProtect(protectStart, protectSize, MemProtection::READ_ONLY);
385}
386
387void GLMemoryShadow::commitAllWrites(gltrace::Context *_ctx, Callback callback)
388{
389 if (!_ctx->dirtyShadows.empty()) {
390 os::unique_lock<os::mutex> lock(mutex);
391
392 for (GLMemoryShadow *memoryShadow : _ctx->dirtyShadows) {
393 memoryShadow->commitWrites(callback);
394 }
395
396 _ctx->dirtyShadows.clear();
397 }
398}
399
400void GLMemoryShadow::syncAllForReads(gltrace::Context *_ctx)
401{
402 if (!_ctx->bufferToShadowMemory.empty()) {
403 os::unique_lock<os::mutex> lock(mutex);
404
405 for (auto& it : _ctx->bufferToShadowMemory) {
406 GLMemoryShadow* memoryShadow = it.second.get();
407 if (memoryShadow->getMapFlags() & GL_MAP_READ_BIT) {
408 memoryShadow->updateForReads();
409 }
410 }
411 }
412}