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