xref: /aosp_15_r20/external/cronet/base/profiler/module_cache_posix.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2018 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/module_cache.h"
6 
7 #include <dlfcn.h>
8 #include <elf.h>
9 
10 #include <optional>
11 #include <string_view>
12 
13 #include "base/debug/elf_reader.h"
14 #include "build/build_config.h"
15 
16 #if BUILDFLAG(IS_ANDROID)
17 extern "C" {
18 // &__executable_start is the start address of the current module.
19 extern const char __executable_start;
20 // &__etext is the end addesss of the code segment in the current module.
21 extern const char _etext;
22 }
23 #endif
24 
25 namespace base {
26 
27 namespace {
28 
29 // Returns the unique build ID for a module loaded at |module_addr|. Returns the
30 // empty string if the function fails to get the build ID.
31 //
32 // Build IDs follow a cross-platform format consisting of several fields
33 // concatenated together:
34 // - the module's unique ID, and
35 // - the age suffix for incremental builds.
36 //
37 // On POSIX, the unique ID is read from the ELF binary located at |module_addr|.
38 // The age field is always 0.
GetUniqueBuildId(const void * module_addr)39 std::string GetUniqueBuildId(const void* module_addr) {
40   debug::ElfBuildIdBuffer build_id;
41   size_t build_id_length = debug::ReadElfBuildId(module_addr, true, build_id);
42   if (!build_id_length)
43     return std::string();
44 
45   // Append 0 for the age value.
46   return std::string(build_id, build_id_length) + "0";
47 }
48 
49 // Returns the offset from |module_addr| to the first byte following the last
50 // executable segment from the ELF file mapped at |module_addr|.
51 // It's defined this way so that any executable address from this module is in
52 // range [addr, addr + GetLastExecutableOffset(addr)).
53 // If no executable segment is found, returns 0.
GetLastExecutableOffset(const void * module_addr)54 size_t GetLastExecutableOffset(const void* module_addr) {
55   const size_t relocation_offset = debug::GetRelocationOffset(module_addr);
56   size_t max_offset = 0;
57   for (const Phdr& header : debug::GetElfProgramHeaders(module_addr)) {
58     if (header.p_type != PT_LOAD || !(header.p_flags & PF_X))
59       continue;
60 
61     max_offset = std::max(
62         max_offset, static_cast<size_t>(
63                         header.p_vaddr + relocation_offset + header.p_memsz -
64                         reinterpret_cast<uintptr_t>(module_addr)));
65   }
66 
67   return max_offset;
68 }
69 
GetDebugBasenameForModule(const void * base_address,std::string_view file)70 FilePath GetDebugBasenameForModule(const void* base_address,
71                                    std::string_view file) {
72 #if BUILDFLAG(IS_ANDROID)
73   // Preferentially identify the library using its soname on Android. Libraries
74   // mapped directly from apks have the apk filename in |dl_info.dli_fname|, and
75   // this doesn't distinguish the particular library.
76   std::optional<std::string_view> library_name =
77       debug::ReadElfLibraryName(base_address);
78   if (library_name)
79     return FilePath(*library_name);
80 #endif  // BUILDFLAG(IS_ANDROID)
81 
82 #if BUILDFLAG(IS_CHROMEOS)
83   // SetProcessTitleFromCommandLine() does not play well with dladdr(). In
84   // particular, after calling our setproctitle(), calling dladdr() with an
85   // address in the main binary will return the complete command line of the
86   // program, including all arguments, in dli_fname. If we get a complete
87   // command-line like "/opt/google/chrome/chrome --type=gpu-process
88   // --gpu-sandbox-failures-fatal=yes --enable-logging ...", strip off
89   // everything that looks like an argument. This is safe on ChromeOS, where we
90   // control the directory and file names and know that no chrome binary or
91   // system library will have a " --" in the path.
92   base::StringPiece::size_type pos = file.find(" --");
93   if (pos != base::StringPiece::npos) {
94     file = file.substr(0, pos);
95   }
96 #endif  // BUILDFLAG(IS_CHROMEOS)
97 
98   return FilePath(file).BaseName();
99 }
100 
101 class PosixModule : public ModuleCache::Module {
102  public:
103   PosixModule(uintptr_t base_address,
104               const std::string& build_id,
105               const FilePath& debug_basename,
106               size_t size);
107 
108   PosixModule(const PosixModule&) = delete;
109   PosixModule& operator=(const PosixModule&) = delete;
110 
111   // ModuleCache::Module
GetBaseAddress() const112   uintptr_t GetBaseAddress() const override { return base_address_; }
GetId() const113   std::string GetId() const override { return id_; }
GetDebugBasename() const114   FilePath GetDebugBasename() const override { return debug_basename_; }
GetSize() const115   size_t GetSize() const override { return size_; }
IsNative() const116   bool IsNative() const override { return true; }
117 
118  private:
119   uintptr_t base_address_;
120   std::string id_;
121   FilePath debug_basename_;
122   size_t size_;
123 };
124 
PosixModule(uintptr_t base_address,const std::string & build_id,const FilePath & debug_basename,size_t size)125 PosixModule::PosixModule(uintptr_t base_address,
126                          const std::string& build_id,
127                          const FilePath& debug_basename,
128                          size_t size)
129     : base_address_(base_address),
130       id_(build_id),
131       debug_basename_(debug_basename),
132       size_(size) {}
133 
134 }  // namespace
135 
136 // static
CreateModuleForAddress(uintptr_t address)137 std::unique_ptr<const ModuleCache::Module> ModuleCache::CreateModuleForAddress(
138     uintptr_t address) {
139   Dl_info info;
140   if (!dladdr(reinterpret_cast<const void*>(address), &info)) {
141 #if BUILDFLAG(IS_ANDROID)
142     // dladdr doesn't know about the Chrome module in Android targets using the
143     // crazy linker. Explicitly check against the module's extents in that case.
144     // This is checked after dladdr because if dladdr CAN find the Chrome
145     // module, it will return a better fallback basename in `info.dli_fname`.
146     if (address >= reinterpret_cast<uintptr_t>(&__executable_start) &&
147         address < reinterpret_cast<uintptr_t>(&_etext)) {
148       const void* const base_address =
149           reinterpret_cast<const void*>(&__executable_start);
150       return std::make_unique<PosixModule>(
151           reinterpret_cast<uintptr_t>(&__executable_start),
152           GetUniqueBuildId(base_address),
153           // Extract the soname from the module. It is expected to exist, but if
154           // it doesn't use an empty string.
155           GetDebugBasenameForModule(base_address, /* file = */ ""),
156           GetLastExecutableOffset(base_address));
157     }
158 #endif
159     return nullptr;
160   }
161 
162   return std::make_unique<PosixModule>(
163       reinterpret_cast<uintptr_t>(info.dli_fbase),
164       GetUniqueBuildId(info.dli_fbase),
165       GetDebugBasenameForModule(info.dli_fbase, info.dli_fname),
166       GetLastExecutableOffset(info.dli_fbase));
167 }
168 
169 }  // namespace base
170