OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 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 <dlfcn.h> |
| 6 #include <mach/mach.h> |
| 7 #include <mach/thread_status.h> |
| 8 #include <mach-o/swap.h> |
| 9 #include <stdio.h> |
| 10 #include <stdlib.h> |
| 11 |
| 12 #include <map> |
| 13 |
| 14 #include "base/logging.h" |
| 15 #include "base/md5.h" |
| 16 #include "base/profiler/native_stack_sampler.h" |
| 17 #include "base/strings/string_number_conversions.h" |
| 18 #include "base/time/time.h" |
| 19 |
| 20 namespace base { |
| 21 |
| 22 namespace { |
| 23 |
| 24 // Copy of x86_64 thread context structure. |
| 25 struct ThreadContext { |
| 26 uint64_t rax; |
| 27 uint64_t rbx; |
| 28 uint64_t rcx; |
| 29 uint64_t rdx; |
| 30 uint64_t rdi; |
| 31 uint64_t rsi; |
| 32 uint64_t rbp; |
| 33 uint64_t rsp; |
| 34 uint64_t r8; |
| 35 uint64_t r9; |
| 36 uint64_t r10; |
| 37 uint64_t r11; |
| 38 uint64_t r12; |
| 39 uint64_t r13; |
| 40 uint64_t r14; |
| 41 uint64_t r15; |
| 42 uint64_t rip; |
| 43 uint64_t rflags; |
| 44 uint64_t cs; |
| 45 uint64_t fs; |
| 46 uint64_t gs; |
| 47 }; |
| 48 |
| 49 // Struct for stack walking. |
| 50 struct StackFrame { |
| 51 struct StackFrame* prev; |
| 52 uintptr_t return_addr; |
| 53 }; |
| 54 |
| 55 // Overwrites |dst| with StackFrame referenced in |src|. Returns true if stack |
| 56 // jump was successful. Failure implies address in |src| is corrupt. |
| 57 bool SafeJump(const StackFrame* src, StackFrame* dst) { |
| 58 vm_size_t bytesCopied = 0; |
| 59 return vm_read_overwrite(mach_task_self(), (vm_address_t)src, |
| 60 (vm_size_t)sizeof(StackFrame), (vm_address_t)dst, |
| 61 &bytesCopied) == KERN_SUCCESS; |
| 62 } |
| 63 |
| 64 // Functions related to retrieving Mach-O Identifer ---------------------------- |
| 65 // These functions were cannibalized from Mach-O Identifier procedures found in |
| 66 // breakpad/src/common/mac. Support for non-X86_64 architectures and MD5 IDs |
| 67 // were removed to simplify the code. |
| 68 |
| 69 // Returns offset in bytes where the x86_64 header is located in binary |
| 70 // loaded at |module_addr|. Returns 0 if |module_addr| is not a valid FAT |
| 71 // Mach-O binary or has not been built for x86_64. |
| 72 off_t GetMach64HeaderOffset(const void* module_addr) { |
| 73 fat_header hdr; |
| 74 memcpy(&hdr, module_addr, sizeof(hdr)); |
| 75 if (hdr.magic != FAT_MAGIC && hdr.magic != FAT_CIGAM) |
| 76 return 0; |
| 77 |
| 78 if (hdr.magic == FAT_CIGAM) |
| 79 swap_fat_header(&hdr, NXHostByteOrder()); |
| 80 |
| 81 // Search all fat architectures for x86_64. |
| 82 off_t offset = sizeof(hdr); |
| 83 fat_arch arch; |
| 84 for (uint32_t i = 0; i < hdr.nfat_arch; ++i) { |
| 85 memcpy(&arch, (const char*)module_addr + offset, sizeof(arch)); |
| 86 |
| 87 if (NXHostByteOrder() != NX_BigEndian) |
| 88 swap_fat_arch(&arch, 1, NXHostByteOrder()); |
| 89 |
| 90 if (arch.cputype == CPU_TYPE_X86_64) |
| 91 return arch.offset; |
| 92 offset += sizeof(arch); |
| 93 } |
| 94 return 0; |
| 95 } |
| 96 |
| 97 // Returns true if Mach-O binary at |module_addr| was built specifically for |
| 98 // x86_64 cpu architecture. |
| 99 bool IsX64Header(const void* module_addr) { |
| 100 mach_header_64 hdr; |
| 101 memcpy(&hdr, module_addr, sizeof(hdr)); |
| 102 if (hdr.magic != MH_MAGIC_64 && hdr.magic != MH_CIGAM_64) |
| 103 return false; |
| 104 if (hdr.magic == MH_CIGAM_64) |
| 105 swap_mach_header_64(&hdr, NXHostByteOrder()); |
| 106 if (hdr.cputype != CPU_TYPE_X86_64) |
| 107 return false; |
| 108 return true; |
| 109 } |
| 110 |
| 111 // Fills |id| with UUID of x86_64 Mach-O binary loaded at |module_addr|. |
| 112 // |offset| is the offset in bytes into |module_addr| where the x86_64 header |
| 113 // is located. Returns false if header is malformed or the header does not |
| 114 // specify the UUID load command. |
| 115 bool GetX64UUIDAt(const void* module_addr, unsigned char id[16], off_t offset) { |
| 116 mach_header_64 hdr; |
| 117 if (!memcpy(&hdr, (const char*)module_addr + offset, sizeof(hdr)) || |
| 118 (hdr.magic != MH_MAGIC_64 && hdr.magic != MH_CIGAM_64)) |
| 119 return false; |
| 120 |
| 121 bool swap = hdr.magic == MH_CIGAM_64; |
| 122 if (swap) |
| 123 swap_mach_header_64(&hdr, NXHostByteOrder()); |
| 124 |
| 125 // Look through load commands for UUID command. |
| 126 offset += sizeof(hdr); |
| 127 for (uint32_t i = 0; i < hdr.ncmds; ++i) { |
| 128 load_command cmd; |
| 129 memcpy(&cmd, (const char*)module_addr + offset, sizeof(cmd)); |
| 130 |
| 131 if (swap) |
| 132 swap_load_command(&cmd, NXHostByteOrder()); |
| 133 if (cmd.cmd == LC_UUID) { |
| 134 uuid_command uuid_cmd; |
| 135 memcpy(&uuid_cmd, (const char*)module_addr + offset, sizeof(uuid_cmd)); |
| 136 if (swap) |
| 137 swap_uuid_command(&uuid_cmd, NXHostByteOrder()); |
| 138 memcpy(id, &uuid_cmd, sizeof(uuid_cmd)); |
| 139 return true; |
| 140 } |
| 141 offset += cmd.cmdsize; |
| 142 } |
| 143 return false; |
| 144 } |
| 145 |
| 146 // Fills |id| with Mach-O UUID retrieved from Mach-O binary loaded at |
| 147 // |module_addr|. This function returns false if the binary was not built for |
| 148 // X86_64 or if UUID cannot be found. |
| 149 bool GetUUID(const void* module_addr, unsigned char id[16]) { |
| 150 off_t offset = 0; |
| 151 if (!IsX64Header(module_addr) && |
| 152 !(offset = GetMach64HeaderOffset(module_addr))) |
| 153 return false; |
| 154 return GetX64UUIDAt(module_addr, id, offset); |
| 155 } |
| 156 |
| 157 // Returns hex encoding of a 16-byte ID for binary loaded at |module_addr|. |
| 158 // Returns empty string if UUID cannot be found at |module_addr|. |
| 159 std::string GetUniqueId(const void* module_addr) { |
| 160 unsigned char id[16]; |
| 161 if (!GetUUID(module_addr, id)) |
| 162 return ""; |
| 163 return HexEncode(id, 16); |
| 164 } |
| 165 |
| 166 // Functions related to grabbing a stack trace -------------------------------- |
| 167 |
| 168 // Fills |state| with |target_thread|'s context. |
| 169 bool GetThreadState(thread_act_t target_thread, ThreadContext* state) { |
| 170 mach_msg_type_number_t count = |
| 171 static_cast<mach_msg_type_number_t>(MACHINE_THREAD_STATE_COUNT); |
| 172 return thread_get_state(target_thread, x86_THREAD_STATE64, |
| 173 reinterpret_cast<thread_state_t>(state), |
| 174 &count) == KERN_SUCCESS; |
| 175 } |
| 176 |
| 177 // Walks |thread_handle|'s stack and fills |instruction_pointers|. |
| 178 // Returns number of frames in stack, unless there's a corrupt ebp, in which |
| 179 // this function will return the number of frames up until the frame with |
| 180 // a corrupt ebp. This procedure occurs while thread is suspended, so it should |
| 181 // take as little time as possible. |
| 182 int RecordStack(mach_port_t thread_handle, |
| 183 int max_stack_size, |
| 184 const void* instruction_pointers[]) { |
| 185 ThreadContext state; |
| 186 if (!GetThreadState(thread_handle, &state)) |
| 187 return 0; |
| 188 |
| 189 StackFrame frame; |
| 190 frame.prev = reinterpret_cast<StackFrame*>(state.rbp); |
| 191 int i = 0; |
| 192 for (; i < max_stack_size; i++) { |
| 193 if (!SafeJump(frame.prev, &frame) || !frame.return_addr) |
| 194 return i; |
| 195 instruction_pointers[i] = reinterpret_cast<const void*>(frame.return_addr); |
| 196 } |
| 197 return i; |
| 198 } |
| 199 |
| 200 // Adds library referenced by |instruction_pointer| to |sample| and |modules|. |
| 201 // Records library's (1) filepath, (2) base address, and (3) unique ID. |
| 202 void AddModule(StackSamplingProfiler::Sample* sample, |
| 203 std::vector<StackSamplingProfiler::Module>* modules, |
| 204 std::map<void*, size_t>* module_to_index, |
| 205 const void* instruction_pointer) { |
| 206 char filepath[PATH_MAX + 1]; |
| 207 Dl_info inf; |
| 208 dladdr(instruction_pointer, &inf); |
| 209 auto loc = module_to_index->find(inf.dli_fbase); |
| 210 if (loc == module_to_index->end()) { |
| 211 realpath(inf.dli_fname, filepath); |
| 212 StackSamplingProfiler::Module module( |
| 213 inf.dli_fbase, GetUniqueId(inf.dli_fbase), base::FilePath(filepath)); |
| 214 modules->push_back(module); |
| 215 |
| 216 loc = module_to_index->insert( |
| 217 std::make_pair(inf.dli_fbase, modules->size() - 1)) |
| 218 .first; |
| 219 } |
| 220 sample->push_back( |
| 221 StackSamplingProfiler::Frame(instruction_pointer, loc->second)); |
| 222 } |
| 223 |
| 224 // Fills |sample| with Frames and Modules referenced by |instruction_pointers|. |
| 225 void FillSample(const void* instruction_pointers[], |
| 226 std::vector<StackSamplingProfiler::Module>* modules, |
| 227 int stack_depth, |
| 228 StackSamplingProfiler::Sample* sample) { |
| 229 std::map<void*, size_t> module_index; |
| 230 for (int i = 0; i < stack_depth; i++) { |
| 231 AddModule(sample, modules, &module_index, instruction_pointers[i]); |
| 232 } |
| 233 } |
| 234 |
| 235 // NativeStackSamplerMac ------------------------------------------------------ |
| 236 |
| 237 class NativeStackSamplerMac : public NativeStackSampler { |
| 238 public: |
| 239 explicit NativeStackSamplerMac(pid_t thread_handle); |
| 240 ~NativeStackSamplerMac() override; |
| 241 |
| 242 // StackSamplingProfiler::NativeStackSampler: |
| 243 void ProfileRecordingStarting( |
| 244 std::vector<StackSamplingProfiler::Module>* modules) override; |
| 245 void RecordStackSample(StackSamplingProfiler::Sample* sample) override; |
| 246 void ProfileRecordingStopped() override; |
| 247 |
| 248 private: |
| 249 mach_port_t thread_handle_; |
| 250 |
| 251 // Weak. Points to the modules associated with the profile being recorded |
| 252 // between ProfileRecordingStarting() and ProfileRecordingStopped(). |
| 253 std::vector<StackSamplingProfiler::Module>* current_modules_; |
| 254 |
| 255 DISALLOW_COPY_AND_ASSIGN(NativeStackSamplerMac); |
| 256 }; |
| 257 |
| 258 // The PlatformThreadId of the given thread is actually a typedef of |
| 259 // mach_port_t. |
| 260 // (base/threading/platform_thread_posix.cc:128) |
| 261 NativeStackSamplerMac::NativeStackSamplerMac(pid_t thread_handle) |
| 262 : thread_handle_(static_cast<mach_port_t>(thread_handle)) {} |
| 263 |
| 264 NativeStackSamplerMac::~NativeStackSamplerMac() {} |
| 265 |
| 266 void NativeStackSamplerMac::ProfileRecordingStarting( |
| 267 std::vector<StackSamplingProfiler::Module>* modules) { |
| 268 current_modules_ = modules; |
| 269 } |
| 270 |
| 271 void NativeStackSamplerMac::RecordStackSample( |
| 272 StackSamplingProfiler::Sample* sample) { |
| 273 DCHECK(current_modules_); |
| 274 const int max_stack_size = 64; |
| 275 const void* instruction_pointers[max_stack_size] = {0}; |
| 276 |
| 277 thread_suspend(thread_handle_); |
| 278 int stack_depth = |
| 279 RecordStack(thread_handle_, max_stack_size, instruction_pointers); |
| 280 thread_resume(thread_handle_); |
| 281 FillSample(instruction_pointers, current_modules_, stack_depth, sample); |
| 282 } |
| 283 |
| 284 void NativeStackSamplerMac::ProfileRecordingStopped() { |
| 285 current_modules_ = nullptr; |
| 286 } |
| 287 |
| 288 } // namespace |
| 289 |
| 290 scoped_ptr<NativeStackSampler> NativeStackSampler::Create( |
| 291 PlatformThreadId thread_id) { |
| 292 #if defined(OS_MACOSX) && !defined(OS_NACL) |
| 293 return scoped_ptr<NativeStackSampler>(new NativeStackSamplerMac(thread_id)); |
| 294 #endif |
| 295 return scoped_ptr<NativeStackSampler>(); |
| 296 } |
| 297 |
| 298 } // namespace base |
OLD | NEW |