1//===- FuzzerTraceState.cpp - Trace-based fuzzer mutator ------------------===// 2// 3// The LLVM Compiler Infrastructure 4// 5// This file is distributed under the University of Illinois Open Source 6// License. See LICENSE.TXT for details. 7// 8//===----------------------------------------------------------------------===// 9// This file implements a mutation algorithm based on instruction traces and 10// on taint analysis feedback from DFSan. 11// 12// Instruction traces are special hooks inserted by the compiler around 13// interesting instructions. Currently supported traces: 14// * __sanitizer_cov_trace_cmp -- inserted before every ICMP instruction, 15// receives the type, size and arguments of ICMP. 16// 17// Every time a traced event is intercepted we analyse the data involved 18// in the event and suggest a mutation for future executions. 19// For example if 4 bytes of data that derive from input bytes {4,5,6,7} 20// are compared with a constant 12345, 21// we try to insert 12345, 12344, 12346 into bytes 22// {4,5,6,7} of the next fuzzed inputs. 23// 24// The fuzzer can work only with the traces, or with both traces and DFSan. 25// 26// DataFlowSanitizer (DFSan) is a tool for 27// generalised dynamic data flow (taint) analysis: 28// http://clang.llvm.org/docs/DataFlowSanitizer.html . 29// 30// The approach with DFSan-based fuzzing has some similarity to 31// "Taint-based Directed Whitebox Fuzzing" 32// by Vijay Ganesh & Tim Leek & Martin Rinard: 33// http://dspace.mit.edu/openaccess-disseminate/1721.1/59320, 34// but it uses a full blown LLVM IR taint analysis and separate instrumentation 35// to analyze all of the "attack points" at once. 36// 37// Workflow with DFSan: 38// * lib/Fuzzer/Fuzzer*.cpp is compiled w/o any instrumentation. 39// * The code under test is compiled with DFSan *and* with instruction traces. 40// * Every call to HOOK(a,b) is replaced by DFSan with 41// __dfsw_HOOK(a, b, label(a), label(b)) so that __dfsw_HOOK 42// gets all the taint labels for the arguments. 43// * At the Fuzzer startup we assign a unique DFSan label 44// to every byte of the input string (Fuzzer::CurrentUnit) so that for any 45// chunk of data we know which input bytes it has derived from. 46// * The __dfsw_* functions (implemented in this file) record the 47// parameters (i.e. the application data and the corresponding taint labels) 48// in a global state. 49// * Fuzzer::ApplyTraceBasedMutation() tries to use the data recorded 50// by __dfsw_* hooks to guide the fuzzing towards new application states. 51// 52// Parts of this code will not function when DFSan is not linked in. 53// Instead of using ifdefs and thus requiring a separate build of lib/Fuzzer 54// we redeclare the dfsan_* interface functions as weak and check if they 55// are nullptr before calling. 56// If this approach proves to be useful we may add attribute(weak) to the 57// dfsan declarations in dfsan_interface.h 58// 59// This module is in the "proof of concept" stage. 60// It is capable of solving only the simplest puzzles 61// like test/dfsan/DFSanSimpleCmpTest.cpp. 62//===----------------------------------------------------------------------===// 63 64/* Example of manual usage (-fsanitize=dataflow is optional): 65( 66 cd $LLVM/lib/Fuzzer/ 67 clang -fPIC -c -g -O2 -std=c++11 Fuzzer*.cpp 68 clang++ -O0 -std=c++11 -fsanitize-coverage=edge,trace-cmp \ 69 -fsanitize=dataflow \ 70 test/SimpleCmpTest.cpp Fuzzer*.o 71 ./a.out -use_traces=1 72) 73*/ 74 75#include "FuzzerDFSan.h" 76#include "FuzzerInternal.h" 77 78#include <algorithm> 79#include <cstring> 80#include <unordered_map> 81 82#if !LLVM_FUZZER_SUPPORTS_DFSAN 83// Stubs for dfsan for platforms where dfsan does not exist and weak 84// functions don't work. 85extern "C" { 86dfsan_label dfsan_create_label(const char *desc, void *userdata) { return 0; } 87void dfsan_set_label(dfsan_label label, void *addr, size_t size) {} 88void dfsan_add_label(dfsan_label label, void *addr, size_t size) {} 89const struct dfsan_label_info *dfsan_get_label_info(dfsan_label label) { 90 return nullptr; 91} 92dfsan_label dfsan_read_label(const void *addr, size_t size) { return 0; } 93} // extern "C" 94#endif // !LLVM_FUZZER_SUPPORTS_DFSAN 95 96namespace fuzzer { 97 98// These values are copied from include/llvm/IR/InstrTypes.h. 99// We do not include the LLVM headers here to remain independent. 100// If these values ever change, an assertion in ComputeCmp will fail. 101enum Predicate { 102 ICMP_EQ = 32, ///< equal 103 ICMP_NE = 33, ///< not equal 104 ICMP_UGT = 34, ///< unsigned greater than 105 ICMP_UGE = 35, ///< unsigned greater or equal 106 ICMP_ULT = 36, ///< unsigned less than 107 ICMP_ULE = 37, ///< unsigned less or equal 108 ICMP_SGT = 38, ///< signed greater than 109 ICMP_SGE = 39, ///< signed greater or equal 110 ICMP_SLT = 40, ///< signed less than 111 ICMP_SLE = 41, ///< signed less or equal 112}; 113 114template <class U, class S> 115bool ComputeCmp(size_t CmpType, U Arg1, U Arg2) { 116 switch(CmpType) { 117 case ICMP_EQ : return Arg1 == Arg2; 118 case ICMP_NE : return Arg1 != Arg2; 119 case ICMP_UGT: return Arg1 > Arg2; 120 case ICMP_UGE: return Arg1 >= Arg2; 121 case ICMP_ULT: return Arg1 < Arg2; 122 case ICMP_ULE: return Arg1 <= Arg2; 123 case ICMP_SGT: return (S)Arg1 > (S)Arg2; 124 case ICMP_SGE: return (S)Arg1 >= (S)Arg2; 125 case ICMP_SLT: return (S)Arg1 < (S)Arg2; 126 case ICMP_SLE: return (S)Arg1 <= (S)Arg2; 127 default: assert(0 && "unsupported CmpType"); 128 } 129 return false; 130} 131 132static bool ComputeCmp(size_t CmpSize, size_t CmpType, uint64_t Arg1, 133 uint64_t Arg2) { 134 if (CmpSize == 8) return ComputeCmp<uint64_t, int64_t>(CmpType, Arg1, Arg2); 135 if (CmpSize == 4) return ComputeCmp<uint32_t, int32_t>(CmpType, Arg1, Arg2); 136 if (CmpSize == 2) return ComputeCmp<uint16_t, int16_t>(CmpType, Arg1, Arg2); 137 if (CmpSize == 1) return ComputeCmp<uint8_t, int8_t>(CmpType, Arg1, Arg2); 138 // Other size, == 139 if (CmpType == ICMP_EQ) return Arg1 == Arg2; 140 // assert(0 && "unsupported cmp and type size combination"); 141 return true; 142} 143 144// As a simplification we use the range of input bytes instead of a set of input 145// bytes. 146struct LabelRange { 147 uint16_t Beg, End; // Range is [Beg, End), thus Beg==End is an empty range. 148 149 LabelRange(uint16_t Beg = 0, uint16_t End = 0) : Beg(Beg), End(End) {} 150 151 static LabelRange Join(LabelRange LR1, LabelRange LR2) { 152 if (LR1.Beg == LR1.End) return LR2; 153 if (LR2.Beg == LR2.End) return LR1; 154 return {std::min(LR1.Beg, LR2.Beg), std::max(LR1.End, LR2.End)}; 155 } 156 LabelRange &Join(LabelRange LR) { 157 return *this = Join(*this, LR); 158 } 159 static LabelRange Singleton(const dfsan_label_info *LI) { 160 uint16_t Idx = (uint16_t)(uintptr_t)LI->userdata; 161 assert(Idx > 0); 162 return {(uint16_t)(Idx - 1), Idx}; 163 } 164}; 165 166// For now, very simple: put Size bytes of Data at position Pos. 167struct TraceBasedMutation { 168 size_t Pos; 169 size_t Size; 170 uint64_t Data; 171}; 172 173class TraceState { 174 public: 175 TraceState(const Fuzzer::FuzzingOptions &Options, const Unit &CurrentUnit) 176 : Options(Options), CurrentUnit(CurrentUnit) {} 177 178 LabelRange GetLabelRange(dfsan_label L); 179 void DFSanCmpCallback(uintptr_t PC, size_t CmpSize, size_t CmpType, 180 uint64_t Arg1, uint64_t Arg2, dfsan_label L1, 181 dfsan_label L2); 182 void DFSanSwitchCallback(uint64_t PC, size_t ValSizeInBits, uint64_t Val, 183 size_t NumCases, uint64_t *Cases, dfsan_label L); 184 void TraceCmpCallback(uintptr_t PC, size_t CmpSize, size_t CmpType, 185 uint64_t Arg1, uint64_t Arg2); 186 187 void TraceSwitchCallback(uintptr_t PC, size_t ValSizeInBits, uint64_t Val, 188 size_t NumCases, uint64_t *Cases); 189 int TryToAddDesiredData(uint64_t PresentData, uint64_t DesiredData, 190 size_t DataSize); 191 192 void StartTraceRecording() { 193 if (!Options.UseTraces) return; 194 RecordingTraces = true; 195 Mutations.clear(); 196 } 197 198 size_t StopTraceRecording(FuzzerRandomBase &Rand) { 199 RecordingTraces = false; 200 return Mutations.size(); 201 } 202 203 void ApplyTraceBasedMutation(size_t Idx, fuzzer::Unit *U); 204 205 private: 206 bool IsTwoByteData(uint64_t Data) { 207 int64_t Signed = static_cast<int64_t>(Data); 208 Signed >>= 16; 209 return Signed == 0 || Signed == -1L; 210 } 211 bool RecordingTraces = false; 212 std::vector<TraceBasedMutation> Mutations; 213 LabelRange LabelRanges[1 << (sizeof(dfsan_label) * 8)]; 214 const Fuzzer::FuzzingOptions &Options; 215 const Unit &CurrentUnit; 216}; 217 218LabelRange TraceState::GetLabelRange(dfsan_label L) { 219 LabelRange &LR = LabelRanges[L]; 220 if (LR.Beg < LR.End || L == 0) 221 return LR; 222 const dfsan_label_info *LI = dfsan_get_label_info(L); 223 if (LI->l1 || LI->l2) 224 return LR = LabelRange::Join(GetLabelRange(LI->l1), GetLabelRange(LI->l2)); 225 return LR = LabelRange::Singleton(LI); 226} 227 228void TraceState::ApplyTraceBasedMutation(size_t Idx, fuzzer::Unit *U) { 229 assert(Idx < Mutations.size()); 230 auto &M = Mutations[Idx]; 231 if (Options.Verbosity >= 3) 232 Printf("TBM %zd %zd %zd\n", M.Pos, M.Size, M.Data); 233 if (M.Pos + M.Size > U->size()) return; 234 memcpy(U->data() + M.Pos, &M.Data, M.Size); 235} 236 237void TraceState::DFSanCmpCallback(uintptr_t PC, size_t CmpSize, size_t CmpType, 238 uint64_t Arg1, uint64_t Arg2, dfsan_label L1, 239 dfsan_label L2) { 240 assert(ReallyHaveDFSan()); 241 if (!RecordingTraces) return; 242 if (L1 == 0 && L2 == 0) 243 return; // Not actionable. 244 if (L1 != 0 && L2 != 0) 245 return; // Probably still actionable. 246 bool Res = ComputeCmp(CmpSize, CmpType, Arg1, Arg2); 247 uint64_t Data = L1 ? Arg2 : Arg1; 248 LabelRange LR = L1 ? GetLabelRange(L1) : GetLabelRange(L2); 249 250 for (size_t Pos = LR.Beg; Pos + CmpSize <= LR.End; Pos++) { 251 Mutations.push_back({Pos, CmpSize, Data}); 252 Mutations.push_back({Pos, CmpSize, Data + 1}); 253 Mutations.push_back({Pos, CmpSize, Data - 1}); 254 } 255 256 if (CmpSize > LR.End - LR.Beg) 257 Mutations.push_back({LR.Beg, (unsigned)(LR.End - LR.Beg), Data}); 258 259 260 if (Options.Verbosity >= 3) 261 Printf("DFSanCmpCallback: PC %lx S %zd T %zd A1 %llx A2 %llx R %d L1 %d L2 " 262 "%d MU %zd\n", 263 PC, CmpSize, CmpType, Arg1, Arg2, Res, L1, L2, Mutations.size()); 264} 265 266void TraceState::DFSanSwitchCallback(uint64_t PC, size_t ValSizeInBits, 267 uint64_t Val, size_t NumCases, 268 uint64_t *Cases, dfsan_label L) { 269 assert(ReallyHaveDFSan()); 270 if (!RecordingTraces) return; 271 if (!L) return; // Not actionable. 272 LabelRange LR = GetLabelRange(L); 273 size_t ValSize = ValSizeInBits / 8; 274 bool TryShort = IsTwoByteData(Val); 275 for (size_t i = 0; i < NumCases; i++) 276 TryShort &= IsTwoByteData(Cases[i]); 277 278 for (size_t Pos = LR.Beg; Pos + ValSize <= LR.End; Pos++) 279 for (size_t i = 0; i < NumCases; i++) 280 Mutations.push_back({Pos, ValSize, Cases[i]}); 281 282 if (TryShort) 283 for (size_t Pos = LR.Beg; Pos + 2 <= LR.End; Pos++) 284 for (size_t i = 0; i < NumCases; i++) 285 Mutations.push_back({Pos, 2, Cases[i]}); 286 287 if (Options.Verbosity >= 3) 288 Printf("DFSanSwitchCallback: PC %lx Val %zd SZ %zd # %zd L %d: {%d, %d} " 289 "TryShort %d\n", 290 PC, Val, ValSize, NumCases, L, LR.Beg, LR.End, TryShort); 291} 292 293int TraceState::TryToAddDesiredData(uint64_t PresentData, uint64_t DesiredData, 294 size_t DataSize) { 295 int Res = 0; 296 const uint8_t *Beg = CurrentUnit.data(); 297 const uint8_t *End = Beg + CurrentUnit.size(); 298 for (const uint8_t *Cur = Beg; Cur < End; Cur++) { 299 Cur = (uint8_t *)memmem(Cur, End - Cur, &PresentData, DataSize); 300 if (!Cur) 301 break; 302 size_t Pos = Cur - Beg; 303 assert(Pos < CurrentUnit.size()); 304 if (Mutations.size() > 100000U) return Res; // Just in case. 305 Mutations.push_back({Pos, DataSize, DesiredData}); 306 Mutations.push_back({Pos, DataSize, DesiredData + 1}); 307 Mutations.push_back({Pos, DataSize, DesiredData - 1}); 308 Res++; 309 } 310 return Res; 311} 312 313void TraceState::TraceCmpCallback(uintptr_t PC, size_t CmpSize, size_t CmpType, 314 uint64_t Arg1, uint64_t Arg2) { 315 if (!RecordingTraces) return; 316 int Added = 0; 317 if (Options.Verbosity >= 3) 318 Printf("TraceCmp %zd/%zd: %p %zd %zd\n", CmpSize, CmpType, PC, Arg1, Arg2); 319 Added += TryToAddDesiredData(Arg1, Arg2, CmpSize); 320 Added += TryToAddDesiredData(Arg2, Arg1, CmpSize); 321 if (!Added && CmpSize == 4 && IsTwoByteData(Arg1) && IsTwoByteData(Arg2)) { 322 Added += TryToAddDesiredData(Arg1, Arg2, 2); 323 Added += TryToAddDesiredData(Arg2, Arg1, 2); 324 } 325} 326 327void TraceState::TraceSwitchCallback(uintptr_t PC, size_t ValSizeInBits, 328 uint64_t Val, size_t NumCases, 329 uint64_t *Cases) { 330 if (!RecordingTraces) return; 331 size_t ValSize = ValSizeInBits / 8; 332 bool TryShort = IsTwoByteData(Val); 333 for (size_t i = 0; i < NumCases; i++) 334 TryShort &= IsTwoByteData(Cases[i]); 335 336 if (Options.Verbosity >= 3) 337 Printf("TraceSwitch: %p %zd # %zd; TryShort %d\n", PC, Val, NumCases, 338 TryShort); 339 340 for (size_t i = 0; i < NumCases; i++) { 341 TryToAddDesiredData(Val, Cases[i], ValSize); 342 if (TryShort) 343 TryToAddDesiredData(Val, Cases[i], 2); 344 } 345 346} 347 348static TraceState *TS; 349 350void Fuzzer::StartTraceRecording() { 351 if (!TS) return; 352 if (ReallyHaveDFSan()) 353 for (size_t i = 0; i < static_cast<size_t>(Options.MaxLen); i++) 354 dfsan_set_label(i + 1, &CurrentUnit[i], 1); 355 TS->StartTraceRecording(); 356} 357 358size_t Fuzzer::StopTraceRecording() { 359 if (!TS) return 0; 360 return TS->StopTraceRecording(USF.GetRand()); 361} 362 363void Fuzzer::ApplyTraceBasedMutation(size_t Idx, Unit *U) { 364 assert(TS); 365 TS->ApplyTraceBasedMutation(Idx, U); 366} 367 368void Fuzzer::InitializeTraceState() { 369 if (!Options.UseTraces) return; 370 TS = new TraceState(Options, CurrentUnit); 371 CurrentUnit.resize(Options.MaxLen); 372 // The rest really requires DFSan. 373 if (!ReallyHaveDFSan()) return; 374 for (size_t i = 0; i < static_cast<size_t>(Options.MaxLen); i++) { 375 dfsan_label L = dfsan_create_label("input", (void*)(i + 1)); 376 // We assume that no one else has called dfsan_create_label before. 377 if (L != i + 1) { 378 Printf("DFSan labels are not starting from 1, exiting\n"); 379 exit(1); 380 } 381 } 382} 383 384static size_t InternalStrnlen(const char *S, size_t MaxLen) { 385 size_t Len = 0; 386 for (; Len < MaxLen && S[Len]; Len++) {} 387 return Len; 388} 389 390} // namespace fuzzer 391 392using fuzzer::TS; 393 394extern "C" { 395void __dfsw___sanitizer_cov_trace_cmp(uint64_t SizeAndType, uint64_t Arg1, 396 uint64_t Arg2, dfsan_label L0, 397 dfsan_label L1, dfsan_label L2) { 398 if (!TS) return; 399 assert(L0 == 0); 400 uintptr_t PC = reinterpret_cast<uintptr_t>(__builtin_return_address(0)); 401 uint64_t CmpSize = (SizeAndType >> 32) / 8; 402 uint64_t Type = (SizeAndType << 32) >> 32; 403 TS->DFSanCmpCallback(PC, CmpSize, Type, Arg1, Arg2, L1, L2); 404} 405 406void __dfsw___sanitizer_cov_trace_switch(uint64_t Val, uint64_t *Cases, 407 dfsan_label L1, dfsan_label L2) { 408 if (!TS) return; 409 uintptr_t PC = reinterpret_cast<uintptr_t>(__builtin_return_address(0)); 410 TS->DFSanSwitchCallback(PC, Cases[1], Val, Cases[0], Cases+2, L1); 411} 412 413void dfsan_weak_hook_memcmp(void *caller_pc, const void *s1, const void *s2, 414 size_t n, dfsan_label s1_label, 415 dfsan_label s2_label, dfsan_label n_label) { 416 if (!TS) return; 417 uintptr_t PC = reinterpret_cast<uintptr_t>(caller_pc); 418 uint64_t S1 = 0, S2 = 0; 419 // Simplification: handle only first 8 bytes. 420 memcpy(&S1, s1, std::min(n, sizeof(S1))); 421 memcpy(&S2, s2, std::min(n, sizeof(S2))); 422 dfsan_label L1 = dfsan_read_label(s1, n); 423 dfsan_label L2 = dfsan_read_label(s2, n); 424 TS->DFSanCmpCallback(PC, n, fuzzer::ICMP_EQ, S1, S2, L1, L2); 425} 426 427void dfsan_weak_hook_strncmp(void *caller_pc, const char *s1, const char *s2, 428 size_t n, dfsan_label s1_label, 429 dfsan_label s2_label, dfsan_label n_label) { 430 if (!TS) return; 431 uintptr_t PC = reinterpret_cast<uintptr_t>(caller_pc); 432 uint64_t S1 = 0, S2 = 0; 433 n = std::min(n, fuzzer::InternalStrnlen(s1, n)); 434 n = std::min(n, fuzzer::InternalStrnlen(s2, n)); 435 // Simplification: handle only first 8 bytes. 436 memcpy(&S1, s1, std::min(n, sizeof(S1))); 437 memcpy(&S2, s2, std::min(n, sizeof(S2))); 438 dfsan_label L1 = dfsan_read_label(s1, n); 439 dfsan_label L2 = dfsan_read_label(s2, n); 440 TS->DFSanCmpCallback(PC, n, fuzzer::ICMP_EQ, S1, S2, L1, L2); 441} 442 443void dfsan_weak_hook_strcmp(void *caller_pc, const char *s1, const char *s2, 444 dfsan_label s1_label, dfsan_label s2_label) { 445 if (!TS) return; 446 uintptr_t PC = reinterpret_cast<uintptr_t>(caller_pc); 447 uint64_t S1 = 0, S2 = 0; 448 size_t Len1 = strlen(s1); 449 size_t Len2 = strlen(s2); 450 size_t N = std::min(Len1, Len2); 451 if (N <= 1) return; // Not interesting. 452 // Simplification: handle only first 8 bytes. 453 memcpy(&S1, s1, std::min(N, sizeof(S1))); 454 memcpy(&S2, s2, std::min(N, sizeof(S2))); 455 dfsan_label L1 = dfsan_read_label(s1, Len1); 456 dfsan_label L2 = dfsan_read_label(s2, Len2); 457 TS->DFSanCmpCallback(PC, N, fuzzer::ICMP_EQ, S1, S2, L1, L2); 458} 459 460void __sanitizer_weak_hook_memcmp(void *caller_pc, const void *s1, 461 const void *s2, size_t n) { 462 if (!TS) return; 463 uintptr_t PC = reinterpret_cast<uintptr_t>(caller_pc); 464 uint64_t S1 = 0, S2 = 0; 465 // Simplification: handle only first 8 bytes. 466 memcpy(&S1, s1, std::min(n, sizeof(S1))); 467 memcpy(&S2, s2, std::min(n, sizeof(S2))); 468 TS->TraceCmpCallback(PC, n, fuzzer::ICMP_EQ, S1, S2); 469} 470 471void __sanitizer_weak_hook_strncmp(void *caller_pc, const char *s1, 472 const char *s2, size_t n) { 473 if (!TS) return; 474 uintptr_t PC = reinterpret_cast<uintptr_t>(caller_pc); 475 uint64_t S1 = 0, S2 = 0; 476 size_t Len1 = fuzzer::InternalStrnlen(s1, n); 477 size_t Len2 = fuzzer::InternalStrnlen(s2, n); 478 n = std::min(n, Len1); 479 n = std::min(n, Len2); 480 if (n <= 1) return; // Not interesting. 481 // Simplification: handle only first 8 bytes. 482 memcpy(&S1, s1, std::min(n, sizeof(S1))); 483 memcpy(&S2, s2, std::min(n, sizeof(S2))); 484 TS->TraceCmpCallback(PC, n, fuzzer::ICMP_EQ, S1, S2); 485} 486 487void __sanitizer_weak_hook_strcmp(void *caller_pc, const char *s1, 488 const char *s2) { 489 if (!TS) return; 490 uintptr_t PC = reinterpret_cast<uintptr_t>(caller_pc); 491 uint64_t S1 = 0, S2 = 0; 492 size_t Len1 = strlen(s1); 493 size_t Len2 = strlen(s2); 494 size_t N = std::min(Len1, Len2); 495 if (N <= 1) return; // Not interesting. 496 // Simplification: handle only first 8 bytes. 497 memcpy(&S1, s1, std::min(N, sizeof(S1))); 498 memcpy(&S2, s2, std::min(N, sizeof(S2))); 499 TS->TraceCmpCallback(PC, N, fuzzer::ICMP_EQ, S1, S2); 500} 501 502__attribute__((visibility("default"))) 503void __sanitizer_cov_trace_cmp(uint64_t SizeAndType, uint64_t Arg1, 504 uint64_t Arg2) { 505 if (!TS) return; 506 uintptr_t PC = reinterpret_cast<uintptr_t>(__builtin_return_address(0)); 507 uint64_t CmpSize = (SizeAndType >> 32) / 8; 508 uint64_t Type = (SizeAndType << 32) >> 32; 509 TS->TraceCmpCallback(PC, CmpSize, Type, Arg1, Arg2); 510} 511 512__attribute__((visibility("default"))) 513void __sanitizer_cov_trace_switch(uint64_t Val, uint64_t *Cases) { 514 if (!TS) return; 515 uintptr_t PC = reinterpret_cast<uintptr_t>(__builtin_return_address(0)); 516 TS->TraceSwitchCallback(PC, Cases[1], Val, Cases[0], Cases + 2); 517} 518 519} // extern "C" 520