blob: 4dfaf85e402dfa9fbf8e888c52bcc36f8acdf449 [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{
Robert Tarasov6ccf5bb2020-01-10 12:31:25 -0800229 sharedRes = _ctx->sharedRes;
Danylo Piliaiev9f18e5d2019-07-03 11:12:50 +0300230 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
Robert Tarasov6ccf5bb2020-01-10 12:31:25 -0800263 shared_context_res_ptr_t res = sharedRes.lock();
264 if (res) {
265 auto it = std::find(res->dirtyShadows.begin(), res->dirtyShadows.end(), this);
266 if (it != res->dirtyShadows.end()) {
267 res->dirtyShadows.erase(it);
268 }
269 } else {
270 os::log("apitrace: error: %s: context(s) are destroyed!\n", __FUNCTION__);
Danylo Piliaiev9f18e5d2019-07-03 11:12:50 +0300271 }
272 }
273
274 memProtect(shadowMemory, nPages * sPageSize, MemProtection::NO_ACCESS);
275
Robert Tarasov6ccf5bb2020-01-10 12:31:25 -0800276 sharedRes.reset();
Danylo Piliaiev9f18e5d2019-07-03 11:12:50 +0300277 glMemory = nullptr;
278 flags = 0;
279 mappedStart = 0;
280 mappedSize = 0;
281 pagesToDirtyOnConsecutiveWrites = 1;
282}
283
284void GLMemoryShadow::onAddressWrite(uintptr_t addr, size_t page)
285{
286 const size_t relativePage = (addr - reinterpret_cast<uintptr_t>(shadowMemory)) / sPageSize;
287 if (isPageDirty(relativePage)) {
288 // It is possible if writing to the same buffer from two threads
289 return;
290 }
291
292 if ((relativePage == lastDirtiedRelativePage + 1) && isPageDirty(relativePage - 1)) {
293 /* Ensure that we would have log(n) page exceptions if traced application writes
294 * to n consecutive pages.
295 */
296 pagesToDirtyOnConsecutiveWrites *= 2;
297 } else {
298 pagesToDirtyOnConsecutiveWrites = 1;
299 }
300
301 const size_t endPageToDirty = std::min(relativePage + pagesToDirtyOnConsecutiveWrites, nPages);
302 for (size_t pageToDirty = relativePage; pageToDirty < endPageToDirty; pageToDirty++) {
303 setPageDirty(pageToDirty);
304 }
305
306 lastDirtiedRelativePage = endPageToDirty - 1;
307
308 memProtect(reinterpret_cast<void*>(page * sPageSize),
309 (endPageToDirty - relativePage) * sPageSize, MemProtection::READ_WRITE);
310}
311
312GLbitfield GLMemoryShadow::getMapFlags() const
313{
314 return flags;
315}
316
317void GLMemoryShadow::setPageDirty(size_t relativePage)
318{
319 assert(relativePage < nPages);
320 dirtyPages[relativePage / 32] |= 1U << (relativePage % 32);
321
322 if (!isDirty) {
Robert Tarasov6ccf5bb2020-01-10 12:31:25 -0800323 shared_context_res_ptr_t res = sharedRes.lock();
324 if (res) {
325 res->dirtyShadows.push_back(this);
326 isDirty = true;
327 } else {
328 os::log("apitrace: error: %s: context(s) are destroyed!\n", __FUNCTION__);
329 }
Danylo Piliaiev9f18e5d2019-07-03 11:12:50 +0300330 }
331}
332
333bool GLMemoryShadow::isPageDirty(size_t relativePage)
334{
335 assert(relativePage < nPages);
336 return dirtyPages[relativePage / 32] & (1U << (relativePage % 32));
337}
338
339void GLMemoryShadow::commitWrites(Callback callback)
340{
341 assert(isDirty);
342
343 uint8_t *shadowSlice = shadowMemory + mappedStartPage * sPageSize;
344 const size_t glStartOffset = mappedStart % sPageSize;
345
346 /* Other thread may write to the buffers at this very moment
347 * so we need to protect pages before we read from them.
348 * The other thread will have to wait until we commit all writes we want.
349 */
350 for (size_t i = mappedStartPage; i < mappedEndPage; i++) {
351 if (isPageDirty(i)) {
352 memProtect(shadowMemory + i * sPageSize, sPageSize, MemProtection::READ_ONLY);
353 }
354 }
355
Danylo Piliaievc2087972019-07-30 16:44:17 +0300356 for (size_t i = mappedStartPage; i < mappedEndPage; i++) {
Danylo Piliaiev9f18e5d2019-07-03 11:12:50 +0300357 if (isPageDirty(i)) {
Danylo Piliaievc2087972019-07-30 16:44:17 +0300358 // We coalesce consecutive writes into one
359 size_t firstDirty = i;
360 while (++i < mappedEndPage && isPageDirty(i)) { }
Danylo Piliaiev9f18e5d2019-07-03 11:12:50 +0300361
Danylo Piliaievc2087972019-07-30 16:44:17 +0300362 const size_t pages = i - firstDirty;
363 if (firstDirty != mappedStartPage) {
364 const size_t shadowOffset = (firstDirty - mappedStartPage) * sPageSize;
365 const size_t glOffset = shadowOffset - glStartOffset;
366 const size_t size = std::min(glStartOffset + mappedSize - shadowOffset, sPageSize * pages);
367
368 memcpy(glMemory + glOffset, shadowSlice + shadowOffset, size);
369 callback(shadowSlice + shadowOffset, size);
370 } else {
371 const size_t size = std::min(sPageSize * pages - glStartOffset, mappedSize);
372
373 memcpy(glMemory, shadowSlice + glStartOffset, size);
374 callback(shadowSlice + glStartOffset, size);
375 }
Danylo Piliaiev9f18e5d2019-07-03 11:12:50 +0300376 }
377 }
378
379 std::fill(dirtyPages.begin(), dirtyPages.end(), 0);
380 isDirty = false;
381 pagesToDirtyOnConsecutiveWrites = 1;
382 lastDirtiedRelativePage = UINT32_MAX - 1;
383}
384
385void GLMemoryShadow::updateForReads()
386{
387 uint8_t *protectStart = shadowMemory + mappedStartPage * sPageSize;
388 const size_t protectSize = (mappedEndPage - mappedStartPage) * sPageSize;
389
390 memProtect(protectStart, protectSize, MemProtection::READ_WRITE);
391
Illia Iorin965128c2019-08-08 14:16:28 +0300392 memcpy(shadowMemory + mappedStart, glMemory, mappedSize);
Danylo Piliaiev9f18e5d2019-07-03 11:12:50 +0300393
394 memProtect(protectStart, protectSize, MemProtection::READ_ONLY);
395}
396
397void GLMemoryShadow::commitAllWrites(gltrace::Context *_ctx, Callback callback)
398{
Robert Tarasov6ccf5bb2020-01-10 12:31:25 -0800399 if (!_ctx->sharedRes->dirtyShadows.empty()) {
Danylo Piliaiev9f18e5d2019-07-03 11:12:50 +0300400 os::unique_lock<os::mutex> lock(mutex);
401
Robert Tarasov6ccf5bb2020-01-10 12:31:25 -0800402 for (GLMemoryShadow *memoryShadow : _ctx->sharedRes->dirtyShadows) {
Danylo Piliaiev9f18e5d2019-07-03 11:12:50 +0300403 memoryShadow->commitWrites(callback);
404 }
405
Robert Tarasov6ccf5bb2020-01-10 12:31:25 -0800406 _ctx->sharedRes->dirtyShadows.clear();
Danylo Piliaiev9f18e5d2019-07-03 11:12:50 +0300407 }
408}
409
410void GLMemoryShadow::syncAllForReads(gltrace::Context *_ctx)
411{
Robert Tarasov6ccf5bb2020-01-10 12:31:25 -0800412 if (!_ctx->sharedRes->bufferToShadowMemory.empty()) {
Danylo Piliaiev9f18e5d2019-07-03 11:12:50 +0300413 os::unique_lock<os::mutex> lock(mutex);
414
Robert Tarasov6ccf5bb2020-01-10 12:31:25 -0800415 for (auto& it : _ctx->sharedRes->bufferToShadowMemory) {
Danylo Piliaiev9f18e5d2019-07-03 11:12:50 +0300416 GLMemoryShadow* memoryShadow = it.second.get();
417 if (memoryShadow->getMapFlags() & GL_MAP_READ_BIT) {
418 memoryShadow->updateForReads();
419 }
420 }
421 }
422}