]>
Commit | Line | Data |
---|---|---|
f35db108 WM |
1 | //===-- asan_report.cc ----------------------------------------------------===// |
2 | // | |
3 | // This file is distributed under the University of Illinois Open Source | |
4 | // License. See LICENSE.TXT for details. | |
5 | // | |
6 | //===----------------------------------------------------------------------===// | |
7 | // | |
8 | // This file is a part of AddressSanitizer, an address sanity checker. | |
9 | // | |
10 | // This file contains error reporting code. | |
11 | //===----------------------------------------------------------------------===// | |
12 | #include "asan_flags.h" | |
13 | #include "asan_internal.h" | |
14 | #include "asan_mapping.h" | |
15 | #include "asan_report.h" | |
16 | #include "asan_stack.h" | |
17 | #include "asan_thread.h" | |
18 | #include "asan_thread_registry.h" | |
19 | ||
20 | namespace __asan { | |
21 | ||
22 | // -------------------- User-specified callbacks ----------------- {{{1 | |
23 | static void (*error_report_callback)(const char*); | |
24 | static char *error_message_buffer = 0; | |
25 | static uptr error_message_buffer_pos = 0; | |
26 | static uptr error_message_buffer_size = 0; | |
27 | ||
28 | void AppendToErrorMessageBuffer(const char *buffer) { | |
29 | if (error_message_buffer) { | |
30 | uptr length = internal_strlen(buffer); | |
31 | CHECK_GE(error_message_buffer_size, error_message_buffer_pos); | |
32 | uptr remaining = error_message_buffer_size - error_message_buffer_pos; | |
33 | internal_strncpy(error_message_buffer + error_message_buffer_pos, | |
34 | buffer, remaining); | |
35 | error_message_buffer[error_message_buffer_size - 1] = '\0'; | |
36 | // FIXME: reallocate the buffer instead of truncating the message. | |
37 | error_message_buffer_pos += remaining > length ? length : remaining; | |
38 | } | |
39 | } | |
40 | ||
41 | // ---------------------- Helper functions ----------------------- {{{1 | |
42 | ||
43 | static void PrintBytes(const char *before, uptr *a) { | |
44 | u8 *bytes = (u8*)a; | |
e297eb60 | 45 | uptr byte_num = (SANITIZER_WORDSIZE) / 8; |
f35db108 WM |
46 | Printf("%s%p:", before, (void*)a); |
47 | for (uptr i = 0; i < byte_num; i++) { | |
48 | Printf(" %x%x", bytes[i] >> 4, bytes[i] & 15); | |
49 | } | |
50 | Printf("\n"); | |
51 | } | |
52 | ||
53 | static void PrintShadowMemoryForAddress(uptr addr) { | |
54 | if (!AddrIsInMem(addr)) | |
55 | return; | |
56 | uptr shadow_addr = MemToShadow(addr); | |
57 | Printf("Shadow byte and word:\n"); | |
58 | Printf(" %p: %x\n", (void*)shadow_addr, *(unsigned char*)shadow_addr); | |
59 | uptr aligned_shadow = shadow_addr & ~(kWordSize - 1); | |
60 | PrintBytes(" ", (uptr*)(aligned_shadow)); | |
61 | Printf("More shadow bytes:\n"); | |
62 | for (int i = -4; i <= 4; i++) { | |
63 | const char *prefix = (i == 0) ? "=>" : " "; | |
64 | PrintBytes(prefix, (uptr*)(aligned_shadow + i * kWordSize)); | |
65 | } | |
66 | } | |
67 | ||
68 | static void PrintZoneForPointer(uptr ptr, uptr zone_ptr, | |
69 | const char *zone_name) { | |
70 | if (zone_ptr) { | |
71 | if (zone_name) { | |
72 | Printf("malloc_zone_from_ptr(%p) = %p, which is %s\n", | |
73 | ptr, zone_ptr, zone_name); | |
74 | } else { | |
75 | Printf("malloc_zone_from_ptr(%p) = %p, which doesn't have a name\n", | |
76 | ptr, zone_ptr); | |
77 | } | |
78 | } else { | |
79 | Printf("malloc_zone_from_ptr(%p) = 0\n", ptr); | |
80 | } | |
81 | } | |
82 | ||
83 | // ---------------------- Address Descriptions ------------------- {{{1 | |
84 | ||
85 | static bool IsASCII(unsigned char c) { | |
86 | return /*0x00 <= c &&*/ c <= 0x7F; | |
87 | } | |
88 | ||
89 | // Check if the global is a zero-terminated ASCII string. If so, print it. | |
90 | static void PrintGlobalNameIfASCII(const __asan_global &g) { | |
91 | for (uptr p = g.beg; p < g.beg + g.size - 1; p++) { | |
92 | if (!IsASCII(*(unsigned char*)p)) return; | |
93 | } | |
94 | if (*(char*)(g.beg + g.size - 1) != 0) return; | |
95 | Printf(" '%s' is ascii string '%s'\n", g.name, (char*)g.beg); | |
96 | } | |
97 | ||
98 | bool DescribeAddressRelativeToGlobal(uptr addr, const __asan_global &g) { | |
99 | if (addr < g.beg - kGlobalAndStackRedzone) return false; | |
100 | if (addr >= g.beg + g.size_with_redzone) return false; | |
101 | Printf("%p is located ", (void*)addr); | |
102 | if (addr < g.beg) { | |
103 | Printf("%zd bytes to the left", g.beg - addr); | |
104 | } else if (addr >= g.beg + g.size) { | |
105 | Printf("%zd bytes to the right", addr - (g.beg + g.size)); | |
106 | } else { | |
107 | Printf("%zd bytes inside", addr - g.beg); // Can it happen? | |
108 | } | |
109 | Printf(" of global variable '%s' (0x%zx) of size %zu\n", | |
110 | g.name, g.beg, g.size); | |
111 | PrintGlobalNameIfASCII(g); | |
112 | return true; | |
113 | } | |
114 | ||
115 | bool DescribeAddressIfShadow(uptr addr) { | |
116 | if (AddrIsInMem(addr)) | |
117 | return false; | |
118 | static const char kAddrInShadowReport[] = | |
119 | "Address %p is located in the %s.\n"; | |
120 | if (AddrIsInShadowGap(addr)) { | |
121 | Printf(kAddrInShadowReport, addr, "shadow gap area"); | |
122 | return true; | |
123 | } | |
124 | if (AddrIsInHighShadow(addr)) { | |
125 | Printf(kAddrInShadowReport, addr, "high shadow area"); | |
126 | return true; | |
127 | } | |
128 | if (AddrIsInLowShadow(addr)) { | |
129 | Printf(kAddrInShadowReport, addr, "low shadow area"); | |
130 | return true; | |
131 | } | |
132 | CHECK(0 && "Address is not in memory and not in shadow?"); | |
133 | return false; | |
134 | } | |
135 | ||
136 | bool DescribeAddressIfStack(uptr addr, uptr access_size) { | |
137 | AsanThread *t = asanThreadRegistry().FindThreadByStackAddress(addr); | |
138 | if (!t) return false; | |
139 | const sptr kBufSize = 4095; | |
140 | char buf[kBufSize]; | |
141 | uptr offset = 0; | |
142 | const char *frame_descr = t->GetFrameNameByAddr(addr, &offset); | |
143 | // This string is created by the compiler and has the following form: | |
144 | // "FunctioName n alloc_1 alloc_2 ... alloc_n" | |
145 | // where alloc_i looks like "offset size len ObjectName ". | |
146 | CHECK(frame_descr); | |
147 | // Report the function name and the offset. | |
148 | const char *name_end = internal_strchr(frame_descr, ' '); | |
149 | CHECK(name_end); | |
150 | buf[0] = 0; | |
151 | internal_strncat(buf, frame_descr, | |
152 | Min(kBufSize, | |
153 | static_cast<sptr>(name_end - frame_descr))); | |
154 | Printf("Address %p is located at offset %zu " | |
155 | "in frame <%s> of T%d's stack:\n", | |
156 | (void*)addr, offset, buf, t->tid()); | |
157 | // Report the number of stack objects. | |
158 | char *p; | |
159 | uptr n_objects = internal_simple_strtoll(name_end, &p, 10); | |
160 | CHECK(n_objects > 0); | |
161 | Printf(" This frame has %zu object(s):\n", n_objects); | |
162 | // Report all objects in this frame. | |
163 | for (uptr i = 0; i < n_objects; i++) { | |
164 | uptr beg, size; | |
165 | sptr len; | |
166 | beg = internal_simple_strtoll(p, &p, 10); | |
167 | size = internal_simple_strtoll(p, &p, 10); | |
168 | len = internal_simple_strtoll(p, &p, 10); | |
169 | if (beg <= 0 || size <= 0 || len < 0 || *p != ' ') { | |
170 | Printf("AddressSanitizer can't parse the stack frame " | |
171 | "descriptor: |%s|\n", frame_descr); | |
172 | break; | |
173 | } | |
174 | p++; | |
175 | buf[0] = 0; | |
176 | internal_strncat(buf, p, Min(kBufSize, len)); | |
177 | p += len; | |
178 | Printf(" [%zu, %zu) '%s'\n", beg, beg + size, buf); | |
179 | } | |
180 | Printf("HINT: this may be a false positive if your program uses " | |
e297eb60 | 181 | "some custom stack unwind mechanism or swapcontext\n" |
f35db108 WM |
182 | " (longjmp and C++ exceptions *are* supported)\n"); |
183 | DescribeThread(t->summary()); | |
184 | return true; | |
185 | } | |
186 | ||
187 | static void DescribeAccessToHeapChunk(AsanChunkView chunk, uptr addr, | |
188 | uptr access_size) { | |
189 | uptr offset; | |
190 | Printf("%p is located ", (void*)addr); | |
191 | if (chunk.AddrIsInside(addr, access_size, &offset)) { | |
192 | Printf("%zu bytes inside of", offset); | |
193 | } else if (chunk.AddrIsAtLeft(addr, access_size, &offset)) { | |
194 | Printf("%zu bytes to the left of", offset); | |
195 | } else if (chunk.AddrIsAtRight(addr, access_size, &offset)) { | |
196 | Printf("%zu bytes to the right of", offset); | |
197 | } else { | |
198 | Printf(" somewhere around (this is AddressSanitizer bug!)"); | |
199 | } | |
200 | Printf(" %zu-byte region [%p,%p)\n", chunk.UsedSize(), | |
201 | (void*)(chunk.Beg()), (void*)(chunk.End())); | |
202 | } | |
203 | ||
204 | void DescribeHeapAddress(uptr addr, uptr access_size) { | |
205 | AsanChunkView chunk = FindHeapChunkByAddress(addr); | |
206 | if (!chunk.IsValid()) return; | |
207 | DescribeAccessToHeapChunk(chunk, addr, access_size); | |
208 | CHECK(chunk.AllocTid() != kInvalidTid); | |
209 | AsanThreadSummary *alloc_thread = | |
210 | asanThreadRegistry().FindByTid(chunk.AllocTid()); | |
211 | StackTrace alloc_stack; | |
212 | chunk.GetAllocStack(&alloc_stack); | |
213 | AsanThread *t = asanThreadRegistry().GetCurrent(); | |
214 | CHECK(t); | |
215 | if (chunk.FreeTid() != kInvalidTid) { | |
216 | AsanThreadSummary *free_thread = | |
217 | asanThreadRegistry().FindByTid(chunk.FreeTid()); | |
218 | Printf("freed by thread T%d here:\n", free_thread->tid()); | |
219 | StackTrace free_stack; | |
220 | chunk.GetFreeStack(&free_stack); | |
221 | PrintStack(&free_stack); | |
222 | Printf("previously allocated by thread T%d here:\n", alloc_thread->tid()); | |
223 | PrintStack(&alloc_stack); | |
224 | DescribeThread(t->summary()); | |
225 | DescribeThread(free_thread); | |
226 | DescribeThread(alloc_thread); | |
227 | } else { | |
228 | Printf("allocated by thread T%d here:\n", alloc_thread->tid()); | |
229 | PrintStack(&alloc_stack); | |
230 | DescribeThread(t->summary()); | |
231 | DescribeThread(alloc_thread); | |
232 | } | |
233 | } | |
234 | ||
235 | void DescribeAddress(uptr addr, uptr access_size) { | |
236 | // Check if this is shadow or shadow gap. | |
237 | if (DescribeAddressIfShadow(addr)) | |
238 | return; | |
239 | CHECK(AddrIsInMem(addr)); | |
240 | if (DescribeAddressIfGlobal(addr)) | |
241 | return; | |
242 | if (DescribeAddressIfStack(addr, access_size)) | |
243 | return; | |
244 | // Assume it is a heap address. | |
245 | DescribeHeapAddress(addr, access_size); | |
246 | } | |
247 | ||
248 | // ------------------- Thread description -------------------- {{{1 | |
249 | ||
250 | void DescribeThread(AsanThreadSummary *summary) { | |
251 | CHECK(summary); | |
252 | // No need to announce the main thread. | |
253 | if (summary->tid() == 0 || summary->announced()) { | |
254 | return; | |
255 | } | |
256 | summary->set_announced(true); | |
257 | Printf("Thread T%d created by T%d here:\n", | |
258 | summary->tid(), summary->parent_tid()); | |
259 | PrintStack(summary->stack()); | |
260 | // Recursively described parent thread if needed. | |
261 | if (flags()->print_full_thread_history) { | |
262 | AsanThreadSummary *parent_summary = | |
263 | asanThreadRegistry().FindByTid(summary->parent_tid()); | |
264 | DescribeThread(parent_summary); | |
265 | } | |
266 | } | |
267 | ||
268 | // -------------------- Different kinds of reports ----------------- {{{1 | |
269 | ||
270 | // Use ScopedInErrorReport to run common actions just before and | |
271 | // immediately after printing error report. | |
272 | class ScopedInErrorReport { | |
273 | public: | |
274 | ScopedInErrorReport() { | |
275 | static atomic_uint32_t num_calls; | |
276 | static u32 reporting_thread_tid; | |
277 | if (atomic_fetch_add(&num_calls, 1, memory_order_relaxed) != 0) { | |
278 | // Do not print more than one report, otherwise they will mix up. | |
279 | // Error reporting functions shouldn't return at this situation, as | |
280 | // they are defined as no-return. | |
281 | Report("AddressSanitizer: while reporting a bug found another one." | |
282 | "Ignoring.\n"); | |
283 | u32 current_tid = asanThreadRegistry().GetCurrentTidOrInvalid(); | |
284 | if (current_tid != reporting_thread_tid) { | |
285 | // ASan found two bugs in different threads simultaneously. Sleep | |
286 | // long enough to make sure that the thread which started to print | |
287 | // an error report will finish doing it. | |
288 | SleepForSeconds(Max(100, flags()->sleep_before_dying + 1)); | |
289 | } | |
e297eb60 KS |
290 | // If we're still not dead for some reason, use raw Exit() instead of |
291 | // Die() to bypass any additional checks. | |
292 | Exit(flags()->exitcode); | |
f35db108 WM |
293 | } |
294 | __asan_on_error(); | |
295 | reporting_thread_tid = asanThreadRegistry().GetCurrentTidOrInvalid(); | |
296 | Printf("====================================================" | |
297 | "=============\n"); | |
298 | if (reporting_thread_tid != kInvalidTid) { | |
299 | // We started reporting an error message. Stop using the fake stack | |
300 | // in case we call an instrumented function from a symbolizer. | |
301 | AsanThread *curr_thread = asanThreadRegistry().GetCurrent(); | |
302 | CHECK(curr_thread); | |
303 | curr_thread->fake_stack().StopUsingFakeStack(); | |
304 | } | |
305 | } | |
306 | // Destructor is NORETURN, as functions that report errors are. | |
307 | NORETURN ~ScopedInErrorReport() { | |
308 | // Make sure the current thread is announced. | |
309 | AsanThread *curr_thread = asanThreadRegistry().GetCurrent(); | |
310 | if (curr_thread) { | |
311 | DescribeThread(curr_thread->summary()); | |
312 | } | |
313 | // Print memory stats. | |
314 | __asan_print_accumulated_stats(); | |
315 | if (error_report_callback) { | |
316 | error_report_callback(error_message_buffer); | |
317 | } | |
318 | Report("ABORTING\n"); | |
319 | Die(); | |
320 | } | |
321 | }; | |
322 | ||
323 | void ReportSIGSEGV(uptr pc, uptr sp, uptr bp, uptr addr) { | |
324 | ScopedInErrorReport in_report; | |
e297eb60 | 325 | Report("ERROR: AddressSanitizer: SEGV on unknown address %p" |
f35db108 WM |
326 | " (pc %p sp %p bp %p T%d)\n", |
327 | (void*)addr, (void*)pc, (void*)sp, (void*)bp, | |
328 | asanThreadRegistry().GetCurrentTidOrInvalid()); | |
329 | Printf("AddressSanitizer can not provide additional info.\n"); | |
330 | GET_STACK_TRACE_WITH_PC_AND_BP(kStackTraceMax, pc, bp); | |
331 | PrintStack(&stack); | |
332 | } | |
333 | ||
334 | void ReportDoubleFree(uptr addr, StackTrace *stack) { | |
335 | ScopedInErrorReport in_report; | |
e297eb60 | 336 | Report("ERROR: AddressSanitizer: attempting double-free on %p:\n", addr); |
f35db108 WM |
337 | PrintStack(stack); |
338 | DescribeHeapAddress(addr, 1); | |
339 | } | |
340 | ||
341 | void ReportFreeNotMalloced(uptr addr, StackTrace *stack) { | |
342 | ScopedInErrorReport in_report; | |
e297eb60 | 343 | Report("ERROR: AddressSanitizer: attempting free on address " |
f35db108 WM |
344 | "which was not malloc()-ed: %p\n", addr); |
345 | PrintStack(stack); | |
346 | DescribeHeapAddress(addr, 1); | |
347 | } | |
348 | ||
349 | void ReportMallocUsableSizeNotOwned(uptr addr, StackTrace *stack) { | |
350 | ScopedInErrorReport in_report; | |
e297eb60 | 351 | Report("ERROR: AddressSanitizer: attempting to call " |
f35db108 WM |
352 | "malloc_usable_size() for pointer which is " |
353 | "not owned: %p\n", addr); | |
354 | PrintStack(stack); | |
355 | DescribeHeapAddress(addr, 1); | |
356 | } | |
357 | ||
358 | void ReportAsanGetAllocatedSizeNotOwned(uptr addr, StackTrace *stack) { | |
359 | ScopedInErrorReport in_report; | |
e297eb60 | 360 | Report("ERROR: AddressSanitizer: attempting to call " |
f35db108 WM |
361 | "__asan_get_allocated_size() for pointer which is " |
362 | "not owned: %p\n", addr); | |
363 | PrintStack(stack); | |
364 | DescribeHeapAddress(addr, 1); | |
365 | } | |
366 | ||
367 | void ReportStringFunctionMemoryRangesOverlap( | |
368 | const char *function, const char *offset1, uptr length1, | |
369 | const char *offset2, uptr length2, StackTrace *stack) { | |
370 | ScopedInErrorReport in_report; | |
e297eb60 | 371 | Report("ERROR: AddressSanitizer: %s-param-overlap: " |
f35db108 WM |
372 | "memory ranges [%p,%p) and [%p, %p) overlap\n", \ |
373 | function, offset1, offset1 + length1, offset2, offset2 + length2); | |
374 | PrintStack(stack); | |
375 | DescribeAddress((uptr)offset1, length1); | |
376 | DescribeAddress((uptr)offset2, length2); | |
377 | } | |
378 | ||
379 | // ----------------------- Mac-specific reports ----------------- {{{1 | |
380 | ||
381 | void WarnMacFreeUnallocated( | |
382 | uptr addr, uptr zone_ptr, const char *zone_name, StackTrace *stack) { | |
383 | // Just print a warning here. | |
384 | Printf("free_common(%p) -- attempting to free unallocated memory.\n" | |
385 | "AddressSanitizer is ignoring this error on Mac OS now.\n", | |
386 | addr); | |
387 | PrintZoneForPointer(addr, zone_ptr, zone_name); | |
388 | PrintStack(stack); | |
389 | DescribeHeapAddress(addr, 1); | |
390 | } | |
391 | ||
392 | void ReportMacMzReallocUnknown( | |
393 | uptr addr, uptr zone_ptr, const char *zone_name, StackTrace *stack) { | |
394 | ScopedInErrorReport in_report; | |
395 | Printf("mz_realloc(%p) -- attempting to realloc unallocated memory.\n" | |
396 | "This is an unrecoverable problem, exiting now.\n", | |
397 | addr); | |
398 | PrintZoneForPointer(addr, zone_ptr, zone_name); | |
399 | PrintStack(stack); | |
400 | DescribeHeapAddress(addr, 1); | |
401 | } | |
402 | ||
403 | void ReportMacCfReallocUnknown( | |
404 | uptr addr, uptr zone_ptr, const char *zone_name, StackTrace *stack) { | |
405 | ScopedInErrorReport in_report; | |
406 | Printf("cf_realloc(%p) -- attempting to realloc unallocated memory.\n" | |
407 | "This is an unrecoverable problem, exiting now.\n", | |
408 | addr); | |
409 | PrintZoneForPointer(addr, zone_ptr, zone_name); | |
410 | PrintStack(stack); | |
411 | DescribeHeapAddress(addr, 1); | |
412 | } | |
413 | ||
414 | } // namespace __asan | |
415 | ||
416 | // --------------------------- Interface --------------------- {{{1 | |
417 | using namespace __asan; // NOLINT | |
418 | ||
419 | void __asan_report_error(uptr pc, uptr bp, uptr sp, | |
420 | uptr addr, bool is_write, uptr access_size) { | |
421 | ScopedInErrorReport in_report; | |
422 | ||
423 | // Determine the error type. | |
424 | const char *bug_descr = "unknown-crash"; | |
425 | if (AddrIsInMem(addr)) { | |
426 | u8 *shadow_addr = (u8*)MemToShadow(addr); | |
427 | // If we are accessing 16 bytes, look at the second shadow byte. | |
428 | if (*shadow_addr == 0 && access_size > SHADOW_GRANULARITY) | |
429 | shadow_addr++; | |
430 | // If we are in the partial right redzone, look at the next shadow byte. | |
431 | if (*shadow_addr > 0 && *shadow_addr < 128) | |
432 | shadow_addr++; | |
433 | switch (*shadow_addr) { | |
434 | case kAsanHeapLeftRedzoneMagic: | |
435 | case kAsanHeapRightRedzoneMagic: | |
436 | bug_descr = "heap-buffer-overflow"; | |
437 | break; | |
438 | case kAsanHeapFreeMagic: | |
439 | bug_descr = "heap-use-after-free"; | |
440 | break; | |
441 | case kAsanStackLeftRedzoneMagic: | |
442 | bug_descr = "stack-buffer-underflow"; | |
443 | break; | |
444 | case kAsanInitializationOrderMagic: | |
445 | bug_descr = "initialization-order-fiasco"; | |
446 | break; | |
447 | case kAsanStackMidRedzoneMagic: | |
448 | case kAsanStackRightRedzoneMagic: | |
449 | case kAsanStackPartialRedzoneMagic: | |
450 | bug_descr = "stack-buffer-overflow"; | |
451 | break; | |
452 | case kAsanStackAfterReturnMagic: | |
453 | bug_descr = "stack-use-after-return"; | |
454 | break; | |
455 | case kAsanUserPoisonedMemoryMagic: | |
456 | bug_descr = "use-after-poison"; | |
457 | break; | |
a0408454 KS |
458 | case kAsanStackUseAfterScopeMagic: |
459 | bug_descr = "stack-use-after-scope"; | |
460 | break; | |
f35db108 WM |
461 | case kAsanGlobalRedzoneMagic: |
462 | bug_descr = "global-buffer-overflow"; | |
463 | break; | |
464 | } | |
465 | } | |
466 | ||
e297eb60 | 467 | Report("ERROR: AddressSanitizer: %s on address " |
f35db108 WM |
468 | "%p at pc 0x%zx bp 0x%zx sp 0x%zx\n", |
469 | bug_descr, (void*)addr, pc, bp, sp); | |
470 | ||
471 | u32 curr_tid = asanThreadRegistry().GetCurrentTidOrInvalid(); | |
472 | Printf("%s of size %zu at %p thread T%d\n", | |
473 | access_size ? (is_write ? "WRITE" : "READ") : "ACCESS", | |
474 | access_size, (void*)addr, curr_tid); | |
475 | ||
476 | GET_STACK_TRACE_WITH_PC_AND_BP(kStackTraceMax, pc, bp); | |
477 | PrintStack(&stack); | |
478 | ||
479 | DescribeAddress(addr, access_size); | |
480 | ||
481 | PrintShadowMemoryForAddress(addr); | |
482 | } | |
483 | ||
484 | void NOINLINE __asan_set_error_report_callback(void (*callback)(const char*)) { | |
485 | error_report_callback = callback; | |
486 | if (callback) { | |
487 | error_message_buffer_size = 1 << 16; | |
488 | error_message_buffer = | |
489 | (char*)MmapOrDie(error_message_buffer_size, __FUNCTION__); | |
490 | error_message_buffer_pos = 0; | |
491 | } | |
492 | } | |
493 | ||
494 | // Provide default implementation of __asan_on_error that does nothing | |
495 | // and may be overriden by user. | |
496 | SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE NOINLINE | |
497 | void __asan_on_error() {} |