1// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2// for details. All rights reserved. Use of this source code is governed by a
3// BSD-style license that can be found in the LICENSE file.
4
5#include "vm/globals.h"
6#if defined(DART_HOST_OS_LINUX)
7
8#include "vm/os.h"
9
10#include <dlfcn.h> // NOLINT
11#include <elf.h> // NOLINT
12#include <errno.h> // NOLINT
13#include <fcntl.h> // NOLINT
14#include <limits.h> // NOLINT
15#include <malloc.h> // NOLINT
16#include <sys/mman.h> // NOLINT
17#include <sys/resource.h> // NOLINT
18#include <sys/stat.h> // NOLINT
19#include <sys/syscall.h> // NOLINT
20#include <sys/time.h> // NOLINT
21#include <sys/types.h> // NOLINT
22#include <time.h> // NOLINT
23#include <unistd.h> // NOLINT
24
25#include "platform/memory_sanitizer.h"
26#include "platform/utils.h"
27#include "vm/code_comments.h"
28#include "vm/code_observers.h"
29#include "vm/dart.h"
30#include "vm/flags.h"
31#include "vm/image_snapshot.h"
32#include "vm/isolate.h"
33#include "vm/lockers.h"
34#include "vm/os_thread.h"
35#include "vm/timeline.h"
36#include "vm/zone.h"
37
38namespace dart {
39
40// Used to choose between Elf32/Elf64 types based on host archotecture bitsize.
41#if defined(ARCH_IS_64_BIT)
42#define ElfW(Type) Elf64_##Type
43#else
44#define ElfW(Type) Elf32_##Type
45#endif
46
47// Missing from older versions of <elf.h>.
48#if !defined(EM_RISCV)
49#define EM_RISCV 243
50#endif
51
52#ifndef PRODUCT
53
54DEFINE_FLAG(bool,
55 generate_perf_events_symbols,
56 false,
57 "Generate events symbols for profiling with perf (disables dual "
58 "code mapping)");
59
60DEFINE_FLAG(bool,
61 generate_perf_jitdump,
62 false,
63 "Generate jitdump file to use with perf-inject (disables dual code "
64 "mapping)");
65
66DECLARE_FLAG(bool, write_protect_code);
67DECLARE_FLAG(bool, write_protect_vm_isolate);
68#if !defined(DART_PRECOMPILED_RUNTIME)
69DECLARE_FLAG(bool, code_comments);
70#endif
71
72// Linux CodeObservers.
73
74// Simple perf support: generate /tmp/perf-<pid>.map file that maps
75// memory ranges to symbol names for JIT generated code. This allows
76// perf-report to resolve addresses falling into JIT generated code.
77// However perf-annotate does not work in this mode because JIT code
78// is transient and does not exist anymore at the moment when you
79// invoke perf-report.
80class PerfCodeObserver : public CodeObserver {
81 public:
82 PerfCodeObserver() : out_file_(nullptr) {
83 Dart_FileOpenCallback file_open = Dart::file_open_callback();
84 if (file_open == nullptr) {
85 return;
86 }
87 intptr_t pid = getpid();
88 char* filename = OS::SCreate(zone: nullptr, format: "/tmp/perf-%" Pd ".map", pid);
89 out_file_ = (*file_open)(filename, true);
90 free(ptr: filename);
91 }
92
93 ~PerfCodeObserver() {
94 Dart_FileCloseCallback file_close = Dart::file_close_callback();
95 if ((file_close == nullptr) || (out_file_ == nullptr)) {
96 return;
97 }
98 (*file_close)(out_file_);
99 }
100
101 virtual bool IsActive() const {
102 return FLAG_generate_perf_events_symbols && (out_file_ != nullptr);
103 }
104
105 virtual void Notify(const char* name,
106 uword base,
107 uword prologue_offset,
108 uword size,
109 bool optimized,
110 const CodeComments* comments) {
111 Dart_FileWriteCallback file_write = Dart::file_write_callback();
112 if ((file_write == nullptr) || (out_file_ == nullptr)) {
113 return;
114 }
115 const char* marker = optimized ? "*" : "";
116 char* buffer =
117 OS::SCreate(zone: Thread::Current()->zone(), format: "%" Px " %" Px " %s%s\n", base,
118 size, marker, name);
119 {
120 MutexLocker ml(CodeObservers::mutex());
121 (*file_write)(buffer, strlen(s: buffer), out_file_);
122 }
123 }
124
125 private:
126 void* out_file_;
127
128 DISALLOW_COPY_AND_ASSIGN(PerfCodeObserver);
129};
130
131// Code observer that generates a JITDUMP[1] file that can be interpreted by
132// perf-inject to generate ELF images for JIT generated code objects, which
133// allows both perf-report and perf-annotate to recognize them.
134//
135// Usage:
136//
137// $ perf record -k mono dart --generate-perf-jitdump benchmark.dart
138// $ perf inject -j -i perf.data -o perf.data.jitted
139// $ perf report -i perf.data.jitted
140//
141// [1] see linux/tools/perf/Documentation/jitdump-specification.txt for
142// JITDUMP binary format.
143class JitDumpCodeObserver : public CodeObserver {
144 public:
145 JitDumpCodeObserver() : pid_(getpid()) {
146 char* const filename = OS::SCreate(zone: nullptr, format: "/tmp/jit-%" Pd ".dump", pid_);
147 const int fd = open(file: filename, O_CREAT | O_TRUNC | O_RDWR, 0666);
148 free(ptr: filename);
149
150 if (fd == -1) {
151 return;
152 }
153
154 // Map JITDUMP file, this mapping will be recorded by perf. This allows
155 // perf-inject to find this file later.
156 const long page_size = sysconf(_SC_PAGESIZE); // NOLINT(runtime/int)
157 if (page_size == -1) {
158 close(fd: fd);
159 return;
160 }
161
162 mapped_ =
163 mmap(addr: nullptr, len: page_size, PROT_READ | PROT_EXEC, MAP_PRIVATE, fd: fd, offset: 0);
164 if (mapped_ == nullptr) {
165 close(fd: fd);
166 return;
167 }
168 mapped_size_ = page_size;
169
170 out_file_ = fdopen(fd: fd, modes: "w+");
171 if (out_file_ == nullptr) {
172 close(fd: fd);
173 return;
174 }
175
176 // Buffer the output to avoid high IO overheads - we are going to be
177 // writing all JIT generated code out.
178 setvbuf(stream: out_file_, buf: nullptr, _IOFBF, n: 2 * MB);
179
180 // Disable code write protection and vm isolate write protection, because
181 // calling mprotect on the pages filled with JIT generated code objects
182 // confuses perf.
183 FLAG_write_protect_code = false;
184 FLAG_write_protect_vm_isolate = false;
185
186#if !defined(DART_PRECOMPILED_RUNTIME)
187 // Enable code comments.
188 FLAG_code_comments = true;
189#endif
190
191 // Write JITDUMP header.
192 WriteHeader();
193 }
194
195 ~JitDumpCodeObserver() {
196 if (mapped_ != nullptr) {
197 munmap(addr: mapped_, len: mapped_size_);
198 mapped_ = nullptr;
199 }
200
201 if (out_file_ != nullptr) {
202 fclose(stream: out_file_);
203 out_file_ = nullptr;
204 }
205 }
206
207 virtual bool IsActive() const {
208 return FLAG_generate_perf_jitdump && (out_file_ != nullptr);
209 }
210
211 virtual void Notify(const char* name,
212 uword base,
213 uword prologue_offset,
214 uword size,
215 bool optimized,
216 const CodeComments* comments) {
217 MutexLocker ml(CodeObservers::mutex());
218
219 const char* marker = optimized ? "*" : "";
220 char* buffer = OS::SCreate(zone: Thread::Current()->zone(), format: "%s%s", marker, name);
221 const size_t name_length = strlen(s: buffer);
222
223 WriteDebugInfo(base, comments);
224
225 CodeLoadEvent ev;
226 ev.event = BaseEvent::kLoad;
227 ev.size = sizeof(ev) + (name_length + 1) + size;
228 ev.time_stamp = OS::GetCurrentMonotonicTicks();
229 ev.process_id = getpid();
230 ev.thread_id = syscall(SYS_gettid);
231 ev.vma = base;
232 ev.code_address = base;
233 ev.code_size = size;
234 ev.code_id = code_id_++;
235
236 WriteFully(buffer: &ev, size: sizeof(ev));
237 WriteFully(buffer, size: name_length + 1);
238 WriteFully(buffer: reinterpret_cast<void*>(base), size);
239 }
240
241 private:
242 struct Header {
243 const uint32_t magic = 0x4A695444;
244 const uint32_t version = 1;
245 const uint32_t size = sizeof(Header);
246 uint32_t elf_mach_target;
247 const uint32_t reserved = 0xDEADBEEF;
248 uint32_t process_id;
249 uint64_t time_stamp;
250 const uint64_t flags = 0;
251 };
252
253 struct BaseEvent {
254 enum Event {
255 kLoad = 0,
256 kMove = 1,
257 kDebugInfo = 2,
258 kClose = 3,
259 kUnwindingInfo = 4
260 };
261
262 uint32_t event;
263 uint32_t size;
264 uint64_t time_stamp;
265 };
266
267 struct CodeLoadEvent : BaseEvent {
268 uint32_t process_id;
269 uint32_t thread_id;
270 uint64_t vma;
271 uint64_t code_address;
272 uint64_t code_size;
273 uint64_t code_id;
274 };
275
276 struct DebugInfoEvent : BaseEvent {
277 uint64_t address;
278 uint64_t entry_count;
279 // DebugInfoEntry entries[entry_count_];
280 };
281
282 struct DebugInfoEntry {
283 uint64_t address;
284 int32_t line_number;
285 int32_t column;
286 // Followed by nul-terminated name.
287 };
288
289 static uint32_t GetElfMachineArchitecture() {
290#if TARGET_ARCH_IA32
291 return EM_386;
292#elif TARGET_ARCH_X64
293 return EM_X86_64;
294#elif TARGET_ARCH_ARM
295 return EM_ARM;
296#elif TARGET_ARCH_ARM64
297 return EM_AARCH64;
298#elif TARGET_ARCH_RISCV32 || TARGET_ARCH_RISCV64
299 return EM_RISCV;
300#else
301 UNREACHABLE();
302 return 0;
303#endif
304 }
305
306 void WriteDebugInfo(uword base, const CodeComments* comments) {
307 if (comments == nullptr || comments->Length() == 0) {
308 return;
309 }
310
311 // Open the comments file for the given code object.
312 // Note: for some reason we can't emit all comments into a single file
313 // the mapping between PCs and lines goes out of sync (might be
314 // perf-annotate bug).
315 char* comments_file_name =
316 OS::SCreate(zone: nullptr, format: "/tmp/jit-%" Pd "-%" Pd ".cmts", pid_, code_id_);
317 const intptr_t filename_length = strlen(s: comments_file_name);
318 FILE* comments_file = fopen(filename: comments_file_name, modes: "w");
319 setvbuf(stream: comments_file, buf: nullptr, _IOFBF, n: 2 * MB);
320
321 // Count the number of DebugInfoEntry we are going to emit: one
322 // per PC.
323 intptr_t entry_count = 0;
324 for (uint64_t i = 0, len = comments->Length(); i < len;) {
325 const intptr_t pc_offset = comments->PCOffsetAt(index: i);
326 while (i < len && comments->PCOffsetAt(index: i) == pc_offset) {
327 i++;
328 }
329 entry_count++;
330 }
331
332 DebugInfoEvent info;
333 info.event = BaseEvent::kDebugInfo;
334 info.time_stamp = OS::GetCurrentMonotonicTicks();
335 info.address = base;
336 info.entry_count = entry_count;
337 info.size = sizeof(info) +
338 entry_count * (sizeof(DebugInfoEntry) + filename_length + 1);
339 const int32_t padding = Utils::RoundUp(x: info.size, alignment: 8) - info.size;
340 info.size += padding;
341
342 // Write out DebugInfoEvent record followed by entry_count DebugInfoEntry
343 // records.
344 WriteFully(buffer: &info, size: sizeof(info));
345 intptr_t line_number = 0; // Line number within comments_file.
346 for (intptr_t i = 0, len = comments->Length(); i < len;) {
347 const intptr_t pc_offset = comments->PCOffsetAt(index: i);
348 while (i < len && comments->PCOffsetAt(index: i) == pc_offset) {
349 line_number += WriteLn(f: comments_file, comment: comments->CommentAt(index: i));
350 i++;
351 }
352 DebugInfoEntry entry;
353 entry.address = base + pc_offset + sizeof(ElfW(Ehdr));
354 entry.line_number = line_number;
355 entry.column = 0;
356 WriteFully(buffer: &entry, size: sizeof(entry));
357 WriteFully(buffer: comments_file_name, size: filename_length + 1);
358 }
359
360 // Write out the padding.
361 const char padding_bytes[8] = {0};
362 WriteFully(buffer: padding_bytes, size: padding);
363
364 fclose(stream: comments_file);
365 free(ptr: comments_file_name);
366 }
367
368 void WriteHeader() {
369 Header header;
370 header.elf_mach_target = GetElfMachineArchitecture();
371 header.process_id = getpid();
372 header.time_stamp = OS::GetCurrentTimeMicros();
373 WriteFully(buffer: &header, size: sizeof(header));
374 }
375
376 // Returns number of new-lines written.
377 intptr_t WriteLn(FILE* f, const char* comment) {
378 fputs(s: comment, stream: f);
379 fputc(c: '\n', stream: f);
380
381 intptr_t line_count = 1;
382 while ((comment = strstr(s1: comment, s2: "\n")) != nullptr) {
383 line_count++;
384 }
385 return line_count;
386 }
387
388 void WriteFully(const void* buffer, size_t size) {
389 const char* ptr = static_cast<const char*>(buffer);
390 while (size > 0) {
391 const size_t written = fwrite(ptr: ptr, size: 1, n: size, s: out_file_);
392 if (written == 0) {
393 UNREACHABLE();
394 break;
395 }
396 size -= written;
397 ptr += written;
398 }
399 }
400
401 const intptr_t pid_;
402
403 FILE* out_file_ = nullptr;
404 void* mapped_ = nullptr;
405 long mapped_size_ = 0; // NOLINT(runtime/int)
406
407 intptr_t code_id_ = 0;
408
409 DISALLOW_COPY_AND_ASSIGN(JitDumpCodeObserver);
410};
411
412#endif // !PRODUCT
413
414intptr_t OS::ProcessId() {
415 return static_cast<intptr_t>(getpid());
416}
417
418static bool LocalTime(int64_t seconds_since_epoch, tm* tm_result) {
419 time_t seconds = static_cast<time_t>(seconds_since_epoch);
420 if (seconds != seconds_since_epoch) return false;
421 struct tm* error_code = localtime_r(timer: &seconds, tp: tm_result);
422 return error_code != nullptr;
423}
424
425const char* OS::GetTimeZoneName(int64_t seconds_since_epoch) {
426 tm decomposed;
427 bool succeeded = LocalTime(seconds_since_epoch, tm_result: &decomposed);
428 // If unsuccessful, return an empty string like V8 does.
429 return (succeeded && (decomposed.tm_zone != nullptr)) ? decomposed.tm_zone
430 : "";
431}
432
433int OS::GetTimeZoneOffsetInSeconds(int64_t seconds_since_epoch) {
434 tm decomposed;
435 bool succeeded = LocalTime(seconds_since_epoch, tm_result: &decomposed);
436 // Even if the offset was 24 hours it would still easily fit into 32 bits.
437 // If unsuccessful, return zero like V8 does.
438 return succeeded ? static_cast<int>(decomposed.tm_gmtoff) : 0;
439}
440
441int64_t OS::GetCurrentTimeMillis() {
442 return GetCurrentTimeMicros() / 1000;
443}
444
445int64_t OS::GetCurrentTimeMicros() {
446 // gettimeofday has microsecond resolution.
447 struct timeval tv;
448 if (gettimeofday(tv: &tv, tz: nullptr) < 0) {
449 UNREACHABLE();
450 return 0;
451 }
452 return (static_cast<int64_t>(tv.tv_sec) * 1000000) + tv.tv_usec;
453}
454
455int64_t OS::GetCurrentMonotonicTicks() {
456 struct timespec ts;
457 if (clock_gettime(CLOCK_MONOTONIC, tp: &ts) != 0) {
458 UNREACHABLE();
459 return 0;
460 }
461 // Convert to nanoseconds.
462 int64_t result = ts.tv_sec;
463 result *= kNanosecondsPerSecond;
464 result += ts.tv_nsec;
465 return result;
466}
467
468int64_t OS::GetCurrentMonotonicFrequency() {
469 return kNanosecondsPerSecond;
470}
471
472int64_t OS::GetCurrentMonotonicMicros() {
473 int64_t ticks = GetCurrentMonotonicTicks();
474 ASSERT(GetCurrentMonotonicFrequency() == kNanosecondsPerSecond);
475 return ticks / kNanosecondsPerMicrosecond;
476}
477
478int64_t OS::GetCurrentThreadCPUMicros() {
479 struct timespec ts;
480 if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, tp: &ts) != 0) {
481 UNREACHABLE();
482 return -1;
483 }
484 int64_t result = ts.tv_sec;
485 result *= kMicrosecondsPerSecond;
486 result += (ts.tv_nsec / kNanosecondsPerMicrosecond);
487 return result;
488}
489
490int64_t OS::GetCurrentMonotonicMicrosForTimeline() {
491#if defined(SUPPORT_TIMELINE)
492 if (Timeline::recorder_discards_clock_values()) return -1;
493 return GetCurrentMonotonicMicros();
494#else
495 return -1;
496#endif
497}
498
499// TODO(5411554): May need to hoist these architecture dependent code
500// into a architecture specific file e.g: os_ia32_linux.cc
501intptr_t OS::ActivationFrameAlignment() {
502#if defined(TARGET_ARCH_IA32) || defined(TARGET_ARCH_X64) || \
503 defined(TARGET_ARCH_ARM64) || defined(TARGET_ARCH_RISCV32) || \
504 defined(TARGET_ARCH_RISCV64)
505 const int kMinimumAlignment = 16;
506#elif defined(TARGET_ARCH_ARM)
507 const int kMinimumAlignment = 8;
508#else
509#error Unsupported architecture.
510#endif
511 intptr_t alignment = kMinimumAlignment;
512 // TODO(5411554): Allow overriding default stack alignment for
513 // testing purposes.
514 // Flags::DebugIsInt("stackalign", &alignment);
515 ASSERT(Utils::IsPowerOfTwo(alignment));
516 ASSERT(alignment >= kMinimumAlignment);
517 return alignment;
518}
519
520int OS::NumberOfAvailableProcessors() {
521 return sysconf(_SC_NPROCESSORS_ONLN);
522}
523
524void OS::Sleep(int64_t millis) {
525 int64_t micros = millis * kMicrosecondsPerMillisecond;
526 SleepMicros(micros);
527}
528
529void OS::SleepMicros(int64_t micros) {
530 struct timespec req; // requested.
531 struct timespec rem; // remainder.
532 int64_t seconds = micros / kMicrosecondsPerSecond;
533 micros = micros - seconds * kMicrosecondsPerSecond;
534 int64_t nanos = micros * kNanosecondsPerMicrosecond;
535 req.tv_sec = seconds;
536 req.tv_nsec = nanos;
537 while (true) {
538 int r = nanosleep(requested_time: &req, remaining: &rem);
539 if (r == 0) {
540 break;
541 }
542 // We should only ever see an interrupt error.
543 ASSERT(errno == EINTR);
544 // Copy remainder into requested and repeat.
545 req = rem;
546 }
547}
548
549// TODO(regis): Function called only from the simulator.
550void OS::DebugBreak() {
551 __builtin_trap();
552}
553
554DART_NOINLINE uintptr_t OS::GetProgramCounter() {
555 return reinterpret_cast<uintptr_t>(
556 __builtin_extract_return_addr(__builtin_return_address(0)));
557}
558
559void OS::Print(const char* format, ...) {
560 va_list args;
561 va_start(args, format);
562 VFPrint(stdout, format, args);
563 va_end(args);
564}
565
566void OS::VFPrint(FILE* stream, const char* format, va_list args) {
567 vfprintf(s: stream, format: format, arg: args);
568 fflush(stream: stream);
569}
570
571char* OS::SCreate(Zone* zone, const char* format, ...) {
572 va_list args;
573 va_start(args, format);
574 char* buffer = VSCreate(zone, format, args);
575 va_end(args);
576 return buffer;
577}
578
579char* OS::VSCreate(Zone* zone, const char* format, va_list args) {
580 // Measure.
581 va_list measure_args;
582 va_copy(measure_args, args);
583 intptr_t len = Utils::VSNPrint(str: nullptr, size: 0, format, args: measure_args);
584 va_end(measure_args);
585
586 char* buffer;
587 if (zone != nullptr) {
588 buffer = zone->Alloc<char>(len: len + 1);
589 } else {
590 buffer = reinterpret_cast<char*>(malloc(size: len + 1));
591 }
592 ASSERT(buffer != nullptr);
593
594 // Print.
595 va_list print_args;
596 va_copy(print_args, args);
597 Utils::VSNPrint(str: buffer, size: len + 1, format, args: print_args);
598 va_end(print_args);
599 return buffer;
600}
601
602bool OS::StringToInt64(const char* str, int64_t* value) {
603 ASSERT(str != nullptr && strlen(str) > 0 && value != nullptr);
604 int32_t base = 10;
605 char* endptr;
606 int i = 0;
607 if (str[0] == '-') {
608 i = 1;
609 } else if (str[0] == '+') {
610 i = 1;
611 }
612 if ((str[i] == '0') && (str[i + 1] == 'x' || str[i + 1] == 'X') &&
613 (str[i + 2] != '\0')) {
614 base = 16;
615 }
616 errno = 0;
617 if (base == 16) {
618 // Unsigned 64-bit hexadecimal integer literals are allowed but
619 // immediately interpreted as signed 64-bit integers.
620 *value = static_cast<int64_t>(strtoull(nptr: str, endptr: &endptr, base: base));
621 } else {
622 *value = strtoll(nptr: str, endptr: &endptr, base: base);
623 }
624 return ((errno == 0) && (endptr != str) && (*endptr == 0));
625}
626
627void OS::RegisterCodeObservers() {
628#ifndef PRODUCT
629 if (FLAG_generate_perf_events_symbols) {
630 CodeObservers::Register(observer: new PerfCodeObserver);
631 }
632
633 if (FLAG_generate_perf_jitdump) {
634 CodeObservers::Register(observer: new JitDumpCodeObserver);
635 }
636#endif // !PRODUCT
637}
638
639void OS::PrintErr(const char* format, ...) {
640 va_list args;
641 va_start(args, format);
642 VFPrint(stderr, format, args);
643 va_end(args);
644}
645
646void OS::Init() {}
647
648void OS::Cleanup() {}
649
650void OS::PrepareToAbort() {}
651
652void OS::Abort() {
653 PrepareToAbort();
654 abort();
655}
656
657void OS::Exit(int code) {
658 exit(status: code);
659}
660
661OS::BuildId OS::GetAppBuildId(const uint8_t* snapshot_instructions) {
662 // First return the build ID information from the instructions image if
663 // available.
664 const Image instructions_image(snapshot_instructions);
665 if (auto* const image_build_id = instructions_image.build_id()) {
666 return {.len: instructions_image.build_id_length(), .data: image_build_id};
667 }
668 Dl_info snapshot_info;
669 if (dladdr(address: snapshot_instructions, info: &snapshot_info) == 0) {
670 return {.len: 0, .data: nullptr};
671 }
672 const uint8_t* dso_base =
673 static_cast<const uint8_t*>(snapshot_info.dli_fbase);
674 const ElfW(Ehdr)& elf_header = *reinterpret_cast<const ElfW(Ehdr)*>(dso_base);
675 const ElfW(Phdr)* const phdr_array =
676 reinterpret_cast<const ElfW(Phdr)*>(dso_base + elf_header.e_phoff);
677 for (intptr_t i = 0; i < elf_header.e_phnum; i++) {
678 const ElfW(Phdr)& header = phdr_array[i];
679 if (header.p_type != PT_NOTE) continue;
680 if ((header.p_flags & PF_R) != PF_R) continue;
681 const uint8_t* const note_addr = dso_base + header.p_vaddr;
682 const Elf32_Nhdr& note_header =
683 *reinterpret_cast<const Elf32_Nhdr*>(note_addr);
684 if (note_header.n_type != NT_GNU_BUILD_ID) continue;
685 const char* const note_contents =
686 reinterpret_cast<const char*>(note_addr + sizeof(Elf32_Nhdr));
687 // The note name contains the null terminator as well.
688 if (note_header.n_namesz != strlen(ELF_NOTE_GNU) + 1) continue;
689 if (strncmp(ELF_NOTE_GNU, s2: note_contents, n: note_header.n_namesz) == 0) {
690 return {.len: static_cast<intptr_t>(note_header.n_descsz),
691 .data: reinterpret_cast<const uint8_t*>(note_contents +
692 note_header.n_namesz)};
693 }
694 }
695 return {.len: 0, .data: nullptr};
696}
697
698} // namespace dart
699
700#endif // defined(DART_HOST_OS_LINUX)
701

source code of flutter_engine/third_party/dart/runtime/vm/os_linux.cc