xref: /aosp_15_r20/external/cronet/base/profiler/stack_base_address_posix.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "base/profiler/stack_base_address_posix.h"
6 
7 #include "base/check_op.h"
8 #include "base/logging.h"
9 #include "base/process/process_handle.h"
10 #include "build/build_config.h"
11 
12 #if BUILDFLAG(IS_ANDROID)
13 #include <inttypes.h>
14 #include <stdio.h>
15 
16 #include "base/files/file_util.h"
17 #include "base/files/scoped_file.h"
18 #endif
19 
20 #if BUILDFLAG(IS_CHROMEOS)
21 extern "C" void* __libc_stack_end;
22 #endif
23 
24 namespace base {
25 
26 namespace {
27 
28 #if BUILDFLAG(IS_ANDROID)
GetAndroidMainThreadStackBaseAddressImpl()29 std::optional<uintptr_t> GetAndroidMainThreadStackBaseAddressImpl() {
30   char line[1024];
31   base::ScopedFILE fp(base::OpenFile(base::FilePath("/proc/self/maps"), "r"));
32   uintptr_t stack_addr = reinterpret_cast<uintptr_t>(line);
33   if (!fp)
34     return std::nullopt;
35   while (fgets(line, sizeof(line), fp.get()) != nullptr) {
36     uintptr_t start, end;
37     if (sscanf(line, "%" SCNxPTR "-%" SCNxPTR, &start, &end) == 2) {
38       if (start <= stack_addr && stack_addr < end)
39         return end;
40     }
41   }
42   return std::nullopt;
43 }
44 #endif
45 
46 #if !BUILDFLAG(IS_LINUX)
GetThreadStackBaseAddressImpl(pthread_t pthread_id)47 uintptr_t GetThreadStackBaseAddressImpl(pthread_t pthread_id) {
48   pthread_attr_t attr;
49   // pthread_getattr_np will crash on ChromeOS & Linux if we are in the sandbox
50   // and pthread_id refers to a different thread, due to the use of
51   // sched_getaffinity().
52   int result = pthread_getattr_np(pthread_id, &attr);
53   // pthread_getattr_np should never fail except on Linux, and Linux will never
54   // call this function. See
55   // https://crrev.com/c/chromium/src/+/4064700/comment/ef75d2b7_c255168c/ for
56   // discussion of crashing vs. returning nullopt in this case.
57   CHECK_EQ(result, 0) << "pthread_getattr_np returned "
58                       << logging::SystemErrorCodeToString(result);
59   // See crbug.com/617730 for limitations of this approach on Linux-like
60   // systems.
61   void* address;
62   size_t size;
63   result = pthread_attr_getstack(&attr, &address, &size);
64   CHECK_EQ(result, 0) << "pthread_attr_getstack returned "
65                       << logging::SystemErrorCodeToString(result);
66   pthread_attr_destroy(&attr);
67   const uintptr_t base_address = reinterpret_cast<uintptr_t>(address) + size;
68   return base_address;
69 }
70 #endif  // !BUILDFLAG(IS_LINUX)
71 
72 }  // namespace
73 
GetThreadStackBaseAddress(PlatformThreadId id,pthread_t pthread_id)74 std::optional<uintptr_t> GetThreadStackBaseAddress(PlatformThreadId id,
75                                                    pthread_t pthread_id) {
76 #if BUILDFLAG(IS_LINUX)
77   // We don't currently support Linux; pthread_getattr_np() fails for the main
78   // thread after zygote forks. https://crbug.com/1394278. Since we don't
79   // support stack profiling at all on Linux, we just return nullopt instead of
80   // trying to work around the problem.
81   return std::nullopt;
82 #else
83   const bool is_main_thread = id == GetCurrentProcId();
84   if (is_main_thread) {
85 #if BUILDFLAG(IS_ANDROID)
86     // The implementation of pthread_getattr_np() in Bionic reads proc/self/maps
87     // to find the main thread base address, and throws SIGABRT when it fails to
88     // read or parse the file. So, try to read the maps to get the main thread
89     // stack base and cache the result. Other thread base addresses are sourced
90     // from pthread state so are cheap to get.
91     static const std::optional<uintptr_t> main_thread_base_address =
92         GetAndroidMainThreadStackBaseAddressImpl();
93     return main_thread_base_address;
94 #elif BUILDFLAG(IS_CHROMEOS)
95     // Similarly, the sandbox will prevent pthread_getattr_np() from working
96     // on the main thread in ChromeOS. Here, we have a simpler solution.
97     return reinterpret_cast<uintptr_t>(__libc_stack_end);
98 #endif
99   }
100   return GetThreadStackBaseAddressImpl(pthread_id);
101 #endif  // !BUILDFLAG(IS_LINUX)
102 }
103 
104 }  // namespace base
105