xref: /aosp_15_r20/external/angle/util/posix/crash_handler_posix.cpp (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1 //
2 // Copyright 2019 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 // crash_handler_posix:
7 //    ANGLE's crash handling and stack walking code. Modified from Skia's:
8 //     https://github.com/google/skia/blob/master/tools/CrashHandler.cpp
9 //
10 
11 #include "util/test_utils.h"
12 
13 #include "common/FixedVector.h"
14 #include "common/angleutils.h"
15 #include "common/string_utils.h"
16 #include "common/system_utils.h"
17 
18 #include <fcntl.h>
19 #include <inttypes.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <sys/types.h>
23 #include <sys/wait.h>
24 #include <unistd.h>
25 #include <iostream>
26 
27 #if !defined(ANGLE_PLATFORM_ANDROID) && !defined(ANGLE_PLATFORM_FUCHSIA)
28 #    if defined(ANGLE_PLATFORM_APPLE)
29 // We only use local unwinding, so we can define this to select a faster implementation.
30 #        define UNW_LOCAL_ONLY
31 #        include <cxxabi.h>
32 #        include <libunwind.h>
33 #        include <signal.h>
34 #    elif defined(ANGLE_PLATFORM_POSIX)
35 // We'd use libunwind here too, but it's a pain to get installed for
36 // both 32 and 64 bit on bots.  Doesn't matter much: catchsegv is best anyway.
37 #        include <cxxabi.h>
38 #        include <dlfcn.h>
39 #        include <execinfo.h>
40 #        include <libgen.h>
41 #        include <link.h>
42 #        include <signal.h>
43 #        include <string.h>
44 #    endif  // defined(ANGLE_PLATFORM_APPLE)
45 #endif      // !defined(ANGLE_PLATFORM_ANDROID) && !defined(ANGLE_PLATFORM_FUCHSIA)
46 
47 // This code snippet is coped from Chromium's base/posix/eintr_wrapper.h.
48 #if defined(NDEBUG)
49 #    define HANDLE_EINTR(x)                                         \
50         ({                                                          \
51             decltype(x) eintr_wrapper_result;                       \
52             do                                                      \
53             {                                                       \
54                 eintr_wrapper_result = (x);                         \
55             } while (eintr_wrapper_result == -1 && errno == EINTR); \
56             eintr_wrapper_result;                                   \
57         })
58 #else
59 #    define HANDLE_EINTR(x)                                          \
60         ({                                                           \
61             int eintr_wrapper_counter = 0;                           \
62             decltype(x) eintr_wrapper_result;                        \
63             do                                                       \
64             {                                                        \
65                 eintr_wrapper_result = (x);                          \
66             } while (eintr_wrapper_result == -1 && errno == EINTR && \
67                      eintr_wrapper_counter++ < 100);                 \
68             eintr_wrapper_result;                                    \
69         })
70 #endif  // NDEBUG
71 
72 namespace angle
73 {
74 #if defined(ANGLE_PLATFORM_ANDROID) || defined(ANGLE_PLATFORM_FUCHSIA)
75 
PrintStackBacktrace()76 void PrintStackBacktrace()
77 {
78     // No implementations yet.
79 }
80 
InitCrashHandler(CrashCallback * callback)81 void InitCrashHandler(CrashCallback *callback)
82 {
83     // No implementations yet.
84 }
85 
TerminateCrashHandler()86 void TerminateCrashHandler()
87 {
88     // No implementations yet.
89 }
90 
91 #else
92 namespace
93 {
94 CrashCallback *gCrashHandlerCallback;
95 }  // namespace
96 
97 #    if defined(ANGLE_PLATFORM_APPLE)
98 
99 void PrintStackBacktrace()
100 {
101     printf("Backtrace:\n");
102 
103     unw_context_t context;
104     unw_getcontext(&context);
105 
106     unw_cursor_t cursor;
107     unw_init_local(&cursor, &context);
108 
109     while (unw_step(&cursor) > 0)
110     {
111         static const size_t kMax = 256;
112         char mangled[kMax];
113         unw_word_t offset;
114         unw_get_proc_name(&cursor, mangled, kMax, &offset);
115 
116         int ok          = -1;
117         char *demangled = abi::__cxa_demangle(mangled, nullptr, nullptr, &ok);
118         printf("    %s (+0x%zx)\n", ok == 0 ? demangled : mangled, (size_t)offset);
119         if (ok)
120         {
121             free(demangled);
122         }
123     }
124     printf("\n");
125 }
126 
127 static void Handler(int sig)
128 {
129     printf("\nSignal %d:\n", sig);
130     fflush(stdout);
131 
132     if (gCrashHandlerCallback)
133     {
134         (*gCrashHandlerCallback)();
135     }
136 
137     PrintStackBacktrace();
138     fflush(stdout);
139 
140     // Exit NOW.  Don't notify other threads, don't call anything registered with atexit().
141     _Exit(sig);
142 }
143 
144 #    elif defined(ANGLE_PLATFORM_POSIX)
145 
146 // Can control this at a higher level if required.
147 #        define ANGLE_HAS_ADDR2LINE
148 
149 #        if defined(ANGLE_HAS_ADDR2LINE)
150 namespace
151 {
152 // The following code was adapted from Chromium's "stack_trace_posix.cc".
153 // Describes a region of mapped memory and the path of the file mapped.
154 struct MappedMemoryRegion
155 {
156     enum Permission
157     {
158         READ    = 1 << 0,
159         WRITE   = 1 << 1,
160         EXECUTE = 1 << 2,
161         PRIVATE = 1 << 3,  // If set, region is private, otherwise it is shared.
162     };
163 
164     // The address range [start,end) of mapped memory.
165     uintptr_t start;
166     uintptr_t end;
167 
168     // Byte offset into |path| of the range mapped into memory.
169     unsigned long long offset;
170 
171     // Image base, if this mapping corresponds to an ELF image.
172     uintptr_t base;
173 
174     // Bitmask of read/write/execute/private/shared permissions.
175     uint8_t permissions;
176 
177     // Name of the file mapped into memory.
178     //
179     // NOTE: path names aren't guaranteed to point at valid files. For example,
180     // "[heap]" and "[stack]" are used to represent the location of the process'
181     // heap and stack, respectively.
182     std::string path;
183 };
184 
185 using MemoryRegionArray = std::vector<MappedMemoryRegion>;
186 
187 bool ReadProcMaps(std::string *proc_maps)
188 {
189     // seq_file only writes out a page-sized amount on each call. Refer to header
190     // file for details.
191     const long kReadSize = sysconf(_SC_PAGESIZE);
192 
193     int fd(HANDLE_EINTR(open("/proc/self/maps", O_RDONLY)));
194     if (fd == -1)
195     {
196         fprintf(stderr, "Couldn't open /proc/self/maps\n");
197         return false;
198     }
199     proc_maps->clear();
200 
201     while (true)
202     {
203         // To avoid a copy, resize |proc_maps| so read() can write directly into it.
204         // Compute |buffer| afterwards since resize() may reallocate.
205         size_t pos = proc_maps->size();
206         proc_maps->resize(pos + kReadSize);
207         void *buffer = &(*proc_maps)[pos];
208 
209         ssize_t bytes_read = HANDLE_EINTR(read(fd, buffer, kReadSize));
210         if (bytes_read < 0)
211         {
212             fprintf(stderr, "Couldn't read /proc/self/maps\n");
213             proc_maps->clear();
214             close(fd);
215             return false;
216         }
217 
218         // ... and don't forget to trim off excess bytes.
219         proc_maps->resize(pos + bytes_read);
220 
221         if (bytes_read == 0)
222             break;
223     }
224 
225     close(fd);
226     return true;
227 }
228 
229 bool ParseProcMaps(const std::string &input, MemoryRegionArray *regions_out)
230 {
231     ASSERT(regions_out);
232     MemoryRegionArray regions;
233 
234     // This isn't async safe nor terribly efficient, but it doesn't need to be at
235     // this point in time.
236     std::vector<std::string> lines = SplitString(input, "\n", TRIM_WHITESPACE, SPLIT_WANT_ALL);
237 
238     for (size_t i = 0; i < lines.size(); ++i)
239     {
240         // Due to splitting on '\n' the last line should be empty.
241         if (i == lines.size() - 1)
242         {
243             if (!lines[i].empty())
244             {
245                 fprintf(stderr, "ParseProcMaps: Last line not empty");
246                 return false;
247             }
248             break;
249         }
250 
251         MappedMemoryRegion region;
252         const char *line    = lines[i].c_str();
253         char permissions[5] = {'\0'};  // Ensure NUL-terminated string.
254         uint8_t dev_major   = 0;
255         uint8_t dev_minor   = 0;
256         long inode          = 0;
257         int path_index      = 0;
258 
259         // Sample format from man 5 proc:
260         //
261         // address           perms offset  dev   inode   pathname
262         // 08048000-08056000 r-xp 00000000 03:0c 64593   /usr/sbin/gpm
263         //
264         // The final %n term captures the offset in the input string, which is used
265         // to determine the path name. It *does not* increment the return value.
266         // Refer to man 3 sscanf for details.
267         if (sscanf(line, "%" SCNxPTR "-%" SCNxPTR " %4c %llx %hhx:%hhx %ld %n", &region.start,
268                    &region.end, permissions, &region.offset, &dev_major, &dev_minor, &inode,
269                    &path_index) < 7)
270         {
271             fprintf(stderr, "ParseProcMaps: sscanf failed for line: %s\n", line);
272             return false;
273         }
274 
275         region.permissions = 0;
276 
277         if (permissions[0] == 'r')
278             region.permissions |= MappedMemoryRegion::READ;
279         else if (permissions[0] != '-')
280             return false;
281 
282         if (permissions[1] == 'w')
283             region.permissions |= MappedMemoryRegion::WRITE;
284         else if (permissions[1] != '-')
285             return false;
286 
287         if (permissions[2] == 'x')
288             region.permissions |= MappedMemoryRegion::EXECUTE;
289         else if (permissions[2] != '-')
290             return false;
291 
292         if (permissions[3] == 'p')
293             region.permissions |= MappedMemoryRegion::PRIVATE;
294         else if (permissions[3] != 's' && permissions[3] != 'S')  // Shared memory.
295             return false;
296 
297         // Pushing then assigning saves us a string copy.
298         regions.push_back(region);
299         regions.back().path.assign(line + path_index);
300     }
301 
302     regions_out->swap(regions);
303     return true;
304 }
305 
306 // Set the base address for each memory region by reading ELF headers in
307 // process memory.
308 void SetBaseAddressesForMemoryRegions(MemoryRegionArray &regions)
309 {
310     int mem_fd(HANDLE_EINTR(open("/proc/self/mem", O_RDONLY | O_CLOEXEC)));
311     if (mem_fd == -1)
312         return;
313 
314     auto safe_memcpy = [&mem_fd](void *dst, uintptr_t src, size_t size) {
315         return HANDLE_EINTR(pread(mem_fd, dst, size, src)) == ssize_t(size);
316     };
317 
318     uintptr_t cur_base = 0;
319     for (MappedMemoryRegion &r : regions)
320     {
321         ElfW(Ehdr) ehdr;
322         static_assert(SELFMAG <= sizeof(ElfW(Ehdr)), "SELFMAG too large");
323         if ((r.permissions & MappedMemoryRegion::READ) &&
324             safe_memcpy(&ehdr, r.start, sizeof(ElfW(Ehdr))) &&
325             memcmp(ehdr.e_ident, ELFMAG, SELFMAG) == 0)
326         {
327             switch (ehdr.e_type)
328             {
329                 case ET_EXEC:
330                     cur_base = 0;
331                     break;
332                 case ET_DYN:
333                     // Find the segment containing file offset 0. This will correspond
334                     // to the ELF header that we just read. Normally this will have
335                     // virtual address 0, but this is not guaranteed. We must subtract
336                     // the virtual address from the address where the ELF header was
337                     // mapped to get the base address.
338                     //
339                     // If we fail to find a segment for file offset 0, use the address
340                     // of the ELF header as the base address.
341                     cur_base = r.start;
342                     for (unsigned i = 0; i != ehdr.e_phnum; ++i)
343                     {
344                         ElfW(Phdr) phdr;
345                         if (safe_memcpy(&phdr, r.start + ehdr.e_phoff + i * sizeof(phdr),
346                                         sizeof(phdr)) &&
347                             phdr.p_type == PT_LOAD && phdr.p_offset == 0)
348                         {
349                             cur_base = r.start - phdr.p_vaddr;
350                             break;
351                         }
352                     }
353                     break;
354                 default:
355                     // ET_REL or ET_CORE. These aren't directly executable, so they
356                     // don't affect the base address.
357                     break;
358             }
359         }
360 
361         r.base = cur_base;
362     }
363 
364     close(mem_fd);
365 }
366 
367 // Parses /proc/self/maps in order to compile a list of all object file names
368 // for the modules that are loaded in the current process.
369 // Returns true on success.
370 bool CacheMemoryRegions(MemoryRegionArray &regions)
371 {
372     // Reads /proc/self/maps.
373     std::string contents;
374     if (!ReadProcMaps(&contents))
375     {
376         fprintf(stderr, "CacheMemoryRegions: Failed to read /proc/self/maps\n");
377         return false;
378     }
379 
380     // Parses /proc/self/maps.
381     if (!ParseProcMaps(contents, &regions))
382     {
383         fprintf(stderr, "CacheMemoryRegions: Failed to parse the contents of /proc/self/maps\n");
384         return false;
385     }
386 
387     SetBaseAddressesForMemoryRegions(regions);
388     return true;
389 }
390 
391 constexpr size_t kAddr2LineMaxParameters = 50;
392 using Addr2LineCommandLine = angle::FixedVector<const char *, kAddr2LineMaxParameters>;
393 
394 void CallAddr2Line(const Addr2LineCommandLine &commandLine)
395 {
396     pid_t pid = fork();
397     if (pid < 0)
398     {
399         std::cerr << "Error: Failed to fork()" << std::endl;
400     }
401     else if (pid > 0)
402     {
403         int status;
404         waitpid(pid, &status, 0);
405         // Ignore the status, since we aren't going to handle it anyway.
406     }
407     else
408     {
409         // Child process executes addr2line
410         //
411         // See comment in test_utils_posix.cpp::PosixProcess regarding const_cast.
412         execvp(commandLine[0], const_cast<char *const *>(commandLine.data()));
413         std::cerr << "Error: Child process returned from exevc()" << std::endl;
414         _exit(EXIT_FAILURE);  // exec never returns
415     }
416 }
417 
418 constexpr size_t kMaxAddressLen = 1024;
419 using AddressBuffer             = angle::FixedVector<char, kMaxAddressLen>;
420 
421 const char *ResolveAddress(const MemoryRegionArray &regions,
422                            const std::string &resolvedModule,
423                            const char *address,
424                            AddressBuffer &buffer)
425 {
426     size_t lastModuleSlash = resolvedModule.rfind('/');
427     ASSERT(lastModuleSlash != std::string::npos);
428     std::string baseModule = resolvedModule.substr(lastModuleSlash);
429 
430     for (const MappedMemoryRegion &region : regions)
431     {
432         size_t pathSlashPos = region.path.rfind('/');
433         if (pathSlashPos != std::string::npos && region.path.substr(pathSlashPos) == baseModule)
434         {
435             uintptr_t scannedAddress;
436             int scanReturn = sscanf(address, "%" SCNxPTR, &scannedAddress);
437             ASSERT(scanReturn == 1);
438             scannedAddress -= region.base;
439             char printBuffer[255] = {};
440             size_t scannedSize    = sprintf(printBuffer, "0x%" PRIXPTR, scannedAddress);
441             size_t bufferSize     = buffer.size();
442             buffer.resize(bufferSize + scannedSize + 1, 0);
443             memcpy(&buffer[bufferSize], printBuffer, scannedSize);
444             return &buffer[bufferSize];
445         }
446     }
447 
448     return address;
449 }
450 // This is only required when the current CWD does not match the initial CWD and could be replaced
451 // by storing the initial CWD state globally. It is only changed in vulkan_icd.cpp.
452 std::string RemoveOverlappingPath(const std::string &resolvedModule)
453 {
454     // Build path from CWD in case CWD matches executable directory
455     // but relative paths are from initial cwd.
456     const Optional<std::string> &cwd = angle::GetCWD();
457     if (!cwd.valid())
458     {
459         std::cerr << "Error getting CWD to print the backtrace." << std::endl;
460         return resolvedModule;
461     }
462     else
463     {
464         std::string absolutePath = cwd.value();
465         size_t lastPathSepLoc    = resolvedModule.find_last_of(GetPathSeparator());
466         std::string relativePath = resolvedModule.substr(0, lastPathSepLoc);
467 
468         // Remove "." from the relativePath path
469         // For example: ./out/LinuxDebug/angle_perftests
470         size_t pos = relativePath.find('.');
471         if (pos != std::string::npos)
472         {
473             // If found then erase it from string
474             relativePath.erase(pos, 1);
475         }
476 
477         // Remove the overlapping relative path from the CWD so we can build the full
478         // absolute path.
479         // For example:
480         // absolutePath = /home/timvp/code/angle/out/LinuxDebug
481         // relativePath = /out/LinuxDebug
482         pos = absolutePath.find(relativePath);
483         if (pos != std::string::npos)
484         {
485             // If found then erase it from string
486             absolutePath.erase(pos, relativePath.length());
487         }
488         return absolutePath + GetPathSeparator() + resolvedModule;
489     }
490 }
491 }  // anonymous namespace
492 #        endif  // defined(ANGLE_HAS_ADDR2LINE)
493 
494 void PrintStackBacktrace()
495 {
496     printf("Backtrace:\n");
497 
498     void *stack[64];
499     const int count = backtrace(stack, ArraySize(stack));
500     char **symbols  = backtrace_symbols(stack, count);
501 
502 #        if defined(ANGLE_HAS_ADDR2LINE)
503 
504     MemoryRegionArray regions;
505     CacheMemoryRegions(regions);
506 
507     // Child process executes addr2line
508     constexpr size_t kAddr2LineFixedParametersCount = 6;
509     Addr2LineCommandLine commandLineArgs            = {
510         "addr2line", "-s", "-p", "-f", "-C", "-e",
511     };
512     const char *currentModule = "";
513     std::string resolvedModule;
514     AddressBuffer addressBuffer;
515 
516     for (int i = 0; i < count; i++)
517     {
518         char *symbol = symbols[i];
519 
520         // symbol looks like the following:
521         //
522         //     path/to/module(+localAddress) [address]
523         //
524         // If module is not an absolute path, it needs to be resolved.
525 
526         char *module  = symbol;
527         char *address = strchr(symbol, '[') + 1;
528 
529         *strchr(module, '(')  = 0;
530         *strchr(address, ']') = 0;
531 
532         // If module is the same as last, continue batching addresses.  If commandLineArgs has
533         // reached its capacity however, make the call to addr2line already.  Note that there should
534         // be one entry left for the terminating nullptr at the end of the command line args.
535         if (strcmp(module, currentModule) == 0 &&
536             commandLineArgs.size() + 1 < commandLineArgs.max_size())
537         {
538             commandLineArgs.push_back(
539                 ResolveAddress(regions, resolvedModule, address, addressBuffer));
540             continue;
541         }
542 
543         // If there's a command batched, execute it before modifying currentModule (a pointer to
544         // which is stored in the command line args).
545         if (currentModule[0] != 0)
546         {
547             commandLineArgs.push_back(nullptr);
548             CallAddr2Line(commandLineArgs);
549             addressBuffer.clear();
550         }
551 
552         // Reset the command line and remember this module as the current.
553         resolvedModule = currentModule = module;
554         commandLineArgs.resize(kAddr2LineFixedParametersCount);
555 
556         // First check if the a relative path simply resolved to an absolute one from cwd,
557         // for abolute paths this resolves symlinks.
558         char *realPath = realpath(resolvedModule.c_str(), NULL);
559         if (realPath)
560         {
561             resolvedModule = std::string(realPath);
562             free(realPath);
563         }
564         // We need an absolute path to get to the executable and all of the various shared objects,
565         // but the caller may have used a relative path to launch the executable, so build one up if
566         // we don't see a leading '/'.
567         else if (resolvedModule.at(0) != GetPathSeparator())
568         {
569             // For some modules we receive a relative path from the build directory (executable
570             // directory) instead of the execution directory (current directory). This happens
571             // for libVkLayer_khronos_validation.so. If realpath fails to create an absolute
572             // path, try constructing one from the build directory.
573             // This will resolve paths like `angledata/../libVkLayer_khronos_validation.so` to
574             // `/home/user/angle/out/Debug/libVkLayer_khronos_validation.so`
575             std::string pathFromExecDir =
576                 GetExecutableDirectory() + GetPathSeparator() + resolvedModule;
577             realPath = realpath(pathFromExecDir.c_str(), NULL);
578             if (realPath)
579             {
580                 resolvedModule = std::string(realPath);
581                 free(realPath);
582             }
583             else
584             {
585                 // Try removing overlapping path as a last resort.
586                 // This will resolve `./out/Debug/angle_end2end_tests` to
587                 // `/home/user/angle/out/Debug/angle_end2end_tests` when CWD is
588                 // `/home/user/angle/out/Debug`, which is caused by ScopedVkLoaderEnvironment.
589                 // This is required for printing traces during vk::Renderer init.
590                 // Since we do not store the initial CWD globally we need to reconstruct here
591                 // by removing the overlapping path.
592                 std::string removeOverlappingPath = RemoveOverlappingPath(resolvedModule);
593                 realPath                          = realpath(removeOverlappingPath.c_str(), NULL);
594                 if (realPath)
595                 {
596                     resolvedModule = std::string(realPath);
597                     free(realPath);
598                 }
599                 else
600                 {
601                     WARN() << "Could not resolve path for module with relative path "
602                            << resolvedModule;
603                 }
604             }
605         }
606         else
607         {
608             WARN() << "Could not resolve path for module with absolute path " << resolvedModule;
609         }
610 
611         const char *resolvedAddress =
612             ResolveAddress(regions, resolvedModule, address, addressBuffer);
613 
614         commandLineArgs.push_back(resolvedModule.c_str());
615         commandLineArgs.push_back(resolvedAddress);
616     }
617 
618     // Call addr2line for the last batch of addresses.
619     if (currentModule[0] != 0)
620     {
621         commandLineArgs.push_back(nullptr);
622         CallAddr2Line(commandLineArgs);
623     }
624 #        else
625     for (int i = 0; i < count; i++)
626     {
627         Dl_info info;
628         if (dladdr(stack[i], &info) && info.dli_sname)
629         {
630             // Make sure this is large enough to hold the fully demangled names, otherwise we could
631             // segault/hang here. For example, Vulkan validation layer errors can be deep enough
632             // into the stack that very large symbol names are generated.
633             char demangled[4096];
634             size_t len = ArraySize(demangled);
635             int ok;
636 
637             abi::__cxa_demangle(info.dli_sname, demangled, &len, &ok);
638             if (ok == 0)
639             {
640                 printf("    %s\n", demangled);
641                 continue;
642             }
643         }
644         printf("    %s\n", symbols[i]);
645     }
646 #        endif  // defined(ANGLE_HAS_ADDR2LINE)
647 }
648 
649 static void Handler(int sig)
650 {
651     printf("\nSignal %d [%s]:\n", sig, strsignal(sig));
652 
653     if (gCrashHandlerCallback)
654     {
655         (*gCrashHandlerCallback)();
656     }
657 
658     PrintStackBacktrace();
659 
660     // Exit NOW.  Don't notify other threads, don't call anything registered with atexit().
661     _Exit(sig);
662 }
663 
664 #    endif  // defined(ANGLE_PLATFORM_APPLE)
665 
666 static constexpr int kSignals[] = {
667     SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGTRAP,
668 };
669 
670 void InitCrashHandler(CrashCallback *callback)
671 {
672     gCrashHandlerCallback = callback;
673     for (int sig : kSignals)
674     {
675         // Register our signal handler unless something's already done so (e.g. catchsegv).
676         void (*prev)(int) = signal(sig, Handler);
677         if (prev != SIG_DFL)
678         {
679             signal(sig, prev);
680         }
681     }
682 }
683 
684 void TerminateCrashHandler()
685 {
686     gCrashHandlerCallback = nullptr;
687     for (int sig : kSignals)
688     {
689         void (*prev)(int) = signal(sig, SIG_DFL);
690         if (prev != Handler && prev != SIG_DFL)
691         {
692             signal(sig, prev);
693         }
694     }
695 }
696 
697 #endif  // defined(ANGLE_PLATFORM_ANDROID) || defined(ANGLE_PLATFORM_FUCHSIA)
698 
699 }  // namespace angle
700