1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "BaseProfilerSharedLibraries.h"
7 
8 #include "platform.h"
9 
10 #include "mozilla/Unused.h"
11 #include <AvailabilityMacros.h>
12 
13 #include <dlfcn.h>
14 #include <mach-o/arch.h>
15 #include <mach-o/dyld_images.h>
16 #include <mach-o/dyld.h>
17 #include <mach-o/loader.h>
18 #include <mach/mach_init.h>
19 #include <mach/mach_traps.h>
20 #include <mach/task_info.h>
21 #include <mach/task.h>
22 #include <sstream>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <vector>
26 
27 // Architecture specific abstraction.
28 #if defined(GP_ARCH_x86)
29 typedef mach_header platform_mach_header;
30 typedef segment_command mach_segment_command_type;
31 #  define MACHO_MAGIC_NUMBER MH_MAGIC
32 #  define CMD_SEGMENT LC_SEGMENT
33 #  define seg_size uint32_t
34 #else
35 typedef mach_header_64 platform_mach_header;
36 typedef segment_command_64 mach_segment_command_type;
37 #  define MACHO_MAGIC_NUMBER MH_MAGIC_64
38 #  define CMD_SEGMENT LC_SEGMENT_64
39 #  define seg_size uint64_t
40 #endif
41 
42 struct NativeSharedLibrary {
43   const platform_mach_header* header;
44   std::string path;
45 };
46 static std::vector<NativeSharedLibrary>* sSharedLibrariesList = nullptr;
47 
48 class MOZ_RAII SharedLibrariesLock {
49  public:
SharedLibrariesLock()50   SharedLibrariesLock() { sSharedLibrariesMutex.Lock(); }
51 
~SharedLibrariesLock()52   ~SharedLibrariesLock() { sSharedLibrariesMutex.Unlock(); }
53 
54   SharedLibrariesLock(const SharedLibrariesLock&) = delete;
55   void operator=(const SharedLibrariesLock&) = delete;
56 
57  private:
58   static mozilla::baseprofiler::detail::BaseProfilerMutex sSharedLibrariesMutex;
59 };
60 
61 mozilla::baseprofiler::detail::BaseProfilerMutex
62     SharedLibrariesLock::sSharedLibrariesMutex;
63 
SharedLibraryAddImage(const struct mach_header * mh,intptr_t vmaddr_slide)64 static void SharedLibraryAddImage(const struct mach_header* mh,
65                                   intptr_t vmaddr_slide) {
66   // NOTE: Presumably for backwards-compatibility reasons, this function accepts
67   // a mach_header even on 64-bit where it ought to be a mach_header_64. We cast
68   // it to the right type here.
69   auto header = reinterpret_cast<const platform_mach_header*>(mh);
70 
71   Dl_info info;
72   if (!dladdr(header, &info)) {
73     return;
74   }
75 
76   SharedLibrariesLock lock;
77   if (!sSharedLibrariesList) {
78     return;
79   }
80 
81   NativeSharedLibrary lib = {header, info.dli_fname};
82   sSharedLibrariesList->push_back(lib);
83 }
84 
SharedLibraryRemoveImage(const struct mach_header * mh,intptr_t vmaddr_slide)85 static void SharedLibraryRemoveImage(const struct mach_header* mh,
86                                      intptr_t vmaddr_slide) {
87   // NOTE: Presumably for backwards-compatibility reasons, this function accepts
88   // a mach_header even on 64-bit where it ought to be a mach_header_64. We cast
89   // it to the right type here.
90   auto header = reinterpret_cast<const platform_mach_header*>(mh);
91 
92   SharedLibrariesLock lock;
93   if (!sSharedLibrariesList) {
94     return;
95   }
96 
97   uint32_t count = sSharedLibrariesList->size();
98   for (uint32_t i = 0; i < count; ++i) {
99     if ((*sSharedLibrariesList)[i].header == header) {
100       sSharedLibrariesList->erase(sSharedLibrariesList->begin() + i);
101       return;
102     }
103   }
104 }
105 
Initialize()106 void SharedLibraryInfo::Initialize() {
107   // NOTE: We intentionally leak this memory here. We're allocating dynamically
108   // in order to avoid static initializers.
109   sSharedLibrariesList = new std::vector<NativeSharedLibrary>();
110 
111   _dyld_register_func_for_add_image(SharedLibraryAddImage);
112   _dyld_register_func_for_remove_image(SharedLibraryRemoveImage);
113 }
114 
addSharedLibrary(const platform_mach_header * header,const char * path,SharedLibraryInfo & info)115 static void addSharedLibrary(const platform_mach_header* header,
116                              const char* path, SharedLibraryInfo& info) {
117   const struct load_command* cmd =
118       reinterpret_cast<const struct load_command*>(header + 1);
119 
120   seg_size size = 0;
121   unsigned long long start = reinterpret_cast<unsigned long long>(header);
122   // Find the cmd segment in the macho image. It will contain the offset we care
123   // about.
124   const uint8_t* uuid_bytes = nullptr;
125   for (unsigned int i = 0;
126        cmd && (i < header->ncmds) && (uuid_bytes == nullptr || size == 0);
127        ++i) {
128     if (cmd->cmd == CMD_SEGMENT) {
129       const mach_segment_command_type* seg =
130           reinterpret_cast<const mach_segment_command_type*>(cmd);
131 
132       if (!strcmp(seg->segname, "__TEXT")) {
133         size = seg->vmsize;
134       }
135     } else if (cmd->cmd == LC_UUID) {
136       const uuid_command* ucmd = reinterpret_cast<const uuid_command*>(cmd);
137       uuid_bytes = ucmd->uuid;
138     }
139 
140     cmd = reinterpret_cast<const struct load_command*>(
141         reinterpret_cast<const char*>(cmd) + cmd->cmdsize);
142   }
143 
144   std::string uuid;
145   if (uuid_bytes != nullptr) {
146     static constexpr char digits[16] = {'0', '1', '2', '3', '4', '5', '6', '7',
147                                         '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
148     for (int i = 0; i < 15; ++i) {
149       uint8_t byte = uuid_bytes[i];
150       uuid += digits[byte >> 4];
151       uuid += digits[byte & 0xFu];
152     }
153     // breakpad id age.
154     uuid += '0';
155   }
156 
157   std::string pathStr = path;
158 
159   size_t pos = pathStr.rfind('\\');
160   std::string nameStr =
161       (pos != std::string::npos) ? pathStr.substr(pos + 1) : pathStr;
162 
163   const NXArchInfo* archInfo =
164       NXGetArchInfoFromCpuType(header->cputype, header->cpusubtype);
165 
166   info.AddSharedLibrary(SharedLibrary(start, start + size, 0, uuid, nameStr,
167                                       pathStr, nameStr, pathStr, std::string{},
168                                       archInfo ? archInfo->name : ""));
169 }
170 
171 // Translate the statically stored sSharedLibrariesList information into a
172 // SharedLibraryInfo object.
GetInfoForSelf()173 SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf() {
174   SharedLibrariesLock lock;
175   SharedLibraryInfo sharedLibraryInfo;
176 
177   for (auto& info : *sSharedLibrariesList) {
178     addSharedLibrary(info.header, info.path.c_str(), sharedLibraryInfo);
179   }
180 
181   // Add the entry for dyld itself.
182   // We only support macOS 10.12+, which corresponds to dyld version 15+.
183   // dyld version 15 added the dyldPath property.
184   task_dyld_info_data_t task_dyld_info;
185   mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
186   if (task_info(mach_task_self(), TASK_DYLD_INFO, (task_info_t)&task_dyld_info,
187                 &count) != KERN_SUCCESS) {
188     return sharedLibraryInfo;
189   }
190 
191   struct dyld_all_image_infos* aii =
192       (struct dyld_all_image_infos*)task_dyld_info.all_image_info_addr;
193   if (aii->version >= 15) {
194     const platform_mach_header* header =
195         reinterpret_cast<const platform_mach_header*>(
196             aii->dyldImageLoadAddress);
197     addSharedLibrary(header, aii->dyldPath, sharedLibraryInfo);
198   }
199 
200   return sharedLibraryInfo;
201 }
202