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 <libkern/OSByteOrder.h> |
| 7 #include <mach/mach.h> |
| 8 #include <mach/thread_status.h> |
| 9 #include <mach-o/swap.h> |
| 10 #include <stdlib.h> |
| 11 #include <uuid/uuid.h> |
| 12 |
| 13 #include <map> |
| 14 |
| 15 #include "base/profiler/native_stack_sampler.h" |
| 16 #include "base/strings/string_number_conversions.h" |
| 17 |
| 18 namespace base { |
| 19 |
| 20 namespace { |
| 21 |
| 22 // Copy of x86_64 thread context structure from x86_thread_state64_t type. |
| 23 // Copied struct since fields can have different names on different versions of |
| 24 // Darwin. |
| 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 (represents stack state on any function call that |
| 50 // pushes a frame pointer). |
| 51 struct StackFrame { |
| 52 // Pointer to caller's frame (rbp). |
| 53 uintptr_t base_pointer; |
| 54 // Address in caller for callee to return to. |
| 55 uintptr_t return_address; |
| 56 }; |
| 57 |
| 58 // Helper that swaps byte order in |x| if |swap| flag is set. |
| 59 uint32_t SwapIfBig32(uint32_t x, bool swap) { |
| 60 if (swap) |
| 61 return OSSwapBigToHostInt32(x); |
| 62 return x; |
| 63 } |
| 64 |
| 65 // Overwrites |dst| with StackFrame referenced in |src|. Returns true if stack |
| 66 // read was successful. Failure implies address in |src| is corrupt. |
| 67 bool SafeStackFrameRead(uintptr_t src, StackFrame* dst) { |
| 68 vm_size_t ignored_bytes_copied = 0; |
| 69 return vm_read_overwrite(mach_task_self(), static_cast<vm_address_t>(src), |
| 70 static_cast<vm_size_t>(sizeof(StackFrame)), |
| 71 reinterpret_cast<vm_address_t>(dst), |
| 72 &ignored_bytes_copied) == KERN_SUCCESS; |
| 73 } |
| 74 |
| 75 // Functions related to retrieving Mach-O Identifer |
| 76 // This walker only supports modules built for x86_64 architecture. More detail |
| 77 // can be found in the OS X ABI Mach-O File Format Reference. |
| 78 |
| 79 // Returns offset in bytes where the x86_64 header is located in binary |
| 80 // loaded at |module_addr|. Returns 0 if |module_addr| is not a valid FAT |
| 81 // Mach-O binary or has not been built for x86_64. |
| 82 off_t GetMach64HeaderOffset(const void* module_addr) { |
| 83 const fat_header* header = reinterpret_cast<const fat_header*>(module_addr); |
| 84 if (header->magic != FAT_MAGIC && header->magic != FAT_CIGAM) |
| 85 return 0; |
| 86 |
| 87 // Search all FAT architectures for x86_64. |
| 88 const fat_arch* fat_arches = reinterpret_cast<const fat_arch*>( |
| 89 reinterpret_cast<const uint8_t*>(module_addr) + sizeof(header)); |
| 90 uint32_t n_arches = OSSwapBigToHostInt32(header->nfat_arch); |
| 91 for (uint32_t i = 0; i < n_arches; ++i) { |
| 92 const fat_arch& arch = fat_arches[i]; |
| 93 if (OSSwapBigToHostInt32(arch.cputype) == CPU_TYPE_X86_64) |
| 94 return OSSwapBigToHostInt32(arch.offset); |
| 95 } |
| 96 return 0; |
| 97 } |
| 98 |
| 99 // Returns true if Mach-O binary at |module_addr| was built specifically for |
| 100 // x86_64 cpu architecture. |
| 101 bool IsX64Header(const void* module_addr) { |
| 102 const mach_header_64* header = |
| 103 reinterpret_cast<const mach_header_64*>(module_addr); |
| 104 if (header->magic != MH_MAGIC_64 && header->magic != MH_CIGAM_64) |
| 105 return false; |
| 106 bool swap = header->magic == MH_CIGAM_64; |
| 107 return SwapIfBig32(header->cputype, swap) == CPU_TYPE_X86_64; |
| 108 } |
| 109 |
| 110 // Fills |id| with the UUID of the x86_64 Mach-O binary loaded at |module_addr|. |
| 111 // |offset| is the offset in bytes into |module_addr| where the x86_64 header |
| 112 // is located. |offset| is only relevant if binary is FAT and contains |
| 113 // multiple architecture headers. Returns false if header is malformed or the |
| 114 // header does not specify the UUID load command. |
| 115 bool GetX64UUIDAt(const void* module_addr, unsigned char* id, off_t offset) { |
| 116 const mach_header_64* header = reinterpret_cast<const mach_header_64*>( |
| 117 reinterpret_cast<const uint8_t*>(module_addr) + offset); |
| 118 if (header->magic != MH_MAGIC_64 && header->magic != MH_CIGAM_64) |
| 119 return false; |
| 120 |
| 121 bool swap = header->magic == MH_CIGAM_64; |
| 122 // Search all load commands for UUID command. |
| 123 offset += sizeof(mach_header_64); |
| 124 for (uint32_t i = 0; i < SwapIfBig32(header->ncmds, swap); ++i) { |
| 125 const load_command* current_cmd = reinterpret_cast<const load_command*>( |
| 126 reinterpret_cast<const uint8_t*>(module_addr) + offset); |
| 127 |
| 128 if (SwapIfBig32(current_cmd->cmd, swap) == LC_UUID) { |
| 129 const uuid_command* uuid_cmd = |
| 130 reinterpret_cast<const uuid_command*>(current_cmd); |
| 131 static_assert(sizeof(uuid_cmd->uuid) == sizeof(uuid_t), |
| 132 "UUID field of UUID command should be 16 bytes."); |
| 133 memcpy(id, &uuid_cmd->uuid, sizeof(uuid_t)); |
| 134 return true; |
| 135 } |
| 136 offset += SwapIfBig32(current_cmd->cmdsize, swap); |
| 137 } |
| 138 return false; |
| 139 } |
| 140 |
| 141 // Fills |id| with Mach-O UUID retrieved from Mach-O binary loaded at |
| 142 // |module_addr|. This function returns false if the binary was not built for |
| 143 // X86_64 or if UUID cannot be found. |
| 144 bool GetUUID(const void* module_addr, unsigned char* id) { |
| 145 off_t offset = 0; |
| 146 // If module is not x86_64 exclusive, it could be a module that supports |
| 147 // multiple architectures. In this case, the appropriate header will be at |
| 148 // some non-zero offset. |
| 149 if (!IsX64Header(module_addr) && |
| 150 !(offset = GetMach64HeaderOffset(module_addr))) { |
| 151 return false; |
| 152 } |
| 153 return GetX64UUIDAt(module_addr, id, offset); |
| 154 } |
| 155 |
| 156 // Returns hex encoding of a 16-byte ID for binary loaded at |module_addr|. |
| 157 // Returns empty string if UUID cannot be found at |module_addr|. |
| 158 std::string GetUniqueId(const void* module_addr) { |
| 159 unsigned char id[sizeof(uuid_t)]; |
| 160 if (!GetUUID(module_addr, id)) |
| 161 return ""; |
| 162 return HexEncode(id, sizeof(uuid_t)); |
| 163 } |
| 164 |
| 165 // Functions related to grabbing a stack trace --------------------------------- |
| 166 |
| 167 // Fills |state| with |target_thread|'s context. |
| 168 bool GetThreadContext(thread_act_t target_thread, ThreadContext* state) { |
| 169 mach_msg_type_number_t count = |
| 170 static_cast<mach_msg_type_number_t>(MACHINE_THREAD_STATE_COUNT); |
| 171 return thread_get_state(target_thread, x86_THREAD_STATE64, |
| 172 reinterpret_cast<thread_state_t>(state), |
| 173 &count) == KERN_SUCCESS; |
| 174 } |
| 175 |
| 176 // Walks |thread_handle|'s stack and fills |instruction_pointers|. |
| 177 // Returns number of frames in stack, unless there's a corrupt frame pointer |
| 178 // (likely if module compiled with -fno_omit_frame_pointer), in which this |
| 179 // function will return the number of frames up until the frame with a corrupt |
| 180 // frame pointer. 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 uintptr_t instruction_pointers[]) { |
| 185 ThreadContext state; |
| 186 if (!GetThreadContext(thread_handle, &state)) |
| 187 return 0; |
| 188 |
| 189 StackFrame frame; |
| 190 frame.base_pointer = state.rbp; |
| 191 int i = 0; |
| 192 for (; i < max_stack_size; i++) { |
| 193 // Three cases for end-of-stack condition: |
| 194 // 1) A frame pointer was corrupt. |
| 195 // 2) The next recovered rsp is not lower than the previously recovered |
| 196 // one, indicating the stack is not growing down. |
| 197 // 3) Return (instruction) address is 0. |
| 198 uintptr_t old_rsp = frame.base_pointer; |
| 199 if (!SafeStackFrameRead(frame.base_pointer, &frame) || |
| 200 frame.base_pointer < old_rsp || !frame.return_address) { |
| 201 return i; |
| 202 } |
| 203 instruction_pointers[i] = frame.return_address; |
| 204 } |
| 205 return i; |
| 206 } |
| 207 |
| 208 // Forcibly binds the system calls we use during stackwalking to prevent |
| 209 // deadlocking on dyld, including thread_get_state and vm_read_overwrite. |
| 210 // Returns base address of loaded module. |
| 211 void* BindSystemCalls() { |
| 212 return dlopen("/usr/lib/system/libsystem_kernel.dylib", RTLD_NOW); |
| 213 } |
| 214 |
| 215 // NativeStackSamplerMac ------------------------------------------------------ |
| 216 |
| 217 class NativeStackSamplerMac : public NativeStackSampler { |
| 218 public: |
| 219 explicit NativeStackSamplerMac(pid_t thread_handle); |
| 220 ~NativeStackSamplerMac() override; |
| 221 |
| 222 // StackSamplingProfiler::NativeStackSampler: |
| 223 void ProfileRecordingStarting( |
| 224 std::vector<StackSamplingProfiler::Module>* modules) override; |
| 225 void RecordStackSample(StackSamplingProfiler::Sample* sample) override; |
| 226 void ProfileRecordingStopped() override; |
| 227 |
| 228 private: |
| 229 // Gets the index for the Module containing |instruction_pointer| in |
| 230 // |modules|, adding it if it's not already present. Returns |
| 231 // StackSamplingProfiler::Frame::kUnknownModuleIndex if no Module can be |
| 232 // determined for |module|. |
| 233 size_t GetModuleIndex(const uintptr_t instruction_pointer, |
| 234 std::vector<StackSamplingProfiler::Module>* modules); |
| 235 |
| 236 // Copies the stack information represented by |instruction_pointers| into |
| 237 // |sample| and |modules|. |
| 238 void CopyToSample(const uintptr_t instruction_pointers[], |
| 239 int stack_depth, |
| 240 StackSamplingProfiler::Sample* sample, |
| 241 std::vector<StackSamplingProfiler::Module>* modules); |
| 242 |
| 243 // Weak reference: Mach handle for thread being profiled. |
| 244 mach_port_t thread_handle_; |
| 245 |
| 246 // Weak. Points to the modules associated with the profile being recorded |
| 247 // between ProfileRecordingStarting() and ProfileRecordingStopped(). |
| 248 std::vector<StackSamplingProfiler::Module>* current_modules_; |
| 249 |
| 250 // Maps a module's base address to the corresponding Module's index within |
| 251 // current_modules_. |
| 252 std::map<const void*, size_t> profile_module_index_; |
| 253 |
| 254 DISALLOW_COPY_AND_ASSIGN(NativeStackSamplerMac); |
| 255 }; |
| 256 |
| 257 // The PlatformThreadId of the given thread is actually a typedef of |
| 258 // mach_port_t. |
| 259 // (base/threading/platform_thread_posix.cc:128) |
| 260 NativeStackSamplerMac::NativeStackSamplerMac(pid_t thread_handle) |
| 261 : thread_handle_(static_cast<mach_port_t>(thread_handle)), |
| 262 current_modules_(nullptr) {} |
| 263 |
| 264 NativeStackSamplerMac::~NativeStackSamplerMac() {} |
| 265 |
| 266 void NativeStackSamplerMac::ProfileRecordingStarting( |
| 267 std::vector<StackSamplingProfiler::Module>* modules) { |
| 268 current_modules_ = modules; |
| 269 profile_module_index_.clear(); |
| 270 } |
| 271 |
| 272 void NativeStackSamplerMac::RecordStackSample( |
| 273 StackSamplingProfiler::Sample* sample) { |
| 274 DCHECK(current_modules_); |
| 275 |
| 276 const int max_stack_size = 64; |
| 277 uintptr_t instruction_pointers[max_stack_size] = {0}; |
| 278 |
| 279 void* syscall_handle = BindSystemCalls(); |
| 280 if (syscall_handle) { |
| 281 thread_suspend(thread_handle_); |
| 282 int stack_depth = |
| 283 RecordStack(thread_handle_, max_stack_size, instruction_pointers); |
| 284 thread_resume(thread_handle_); |
| 285 dlclose(syscall_handle); |
| 286 CopyToSample(instruction_pointers, stack_depth, sample, current_modules_); |
| 287 } |
| 288 } |
| 289 |
| 290 void NativeStackSamplerMac::ProfileRecordingStopped() { |
| 291 current_modules_ = nullptr; |
| 292 } |
| 293 |
| 294 size_t NativeStackSamplerMac::GetModuleIndex( |
| 295 const uintptr_t instruction_pointer, |
| 296 std::vector<StackSamplingProfiler::Module>* modules) { |
| 297 Dl_info inf; |
| 298 if (!dladdr(reinterpret_cast<const void*>(instruction_pointer), &inf)) |
| 299 return StackSamplingProfiler::Frame::kUnknownModuleIndex; |
| 300 |
| 301 auto module_index = profile_module_index_.find(inf.dli_fbase); |
| 302 if (module_index == profile_module_index_.end()) { |
| 303 StackSamplingProfiler::Module module(inf.dli_fbase, |
| 304 GetUniqueId(inf.dli_fbase), |
| 305 base::FilePath(inf.dli_fname)); |
| 306 modules->push_back(module); |
| 307 module_index = profile_module_index_.insert( |
| 308 std::make_pair(inf.dli_fbase, modules->size() - 1)).first; |
| 309 } |
| 310 return module_index->second; |
| 311 } |
| 312 |
| 313 void NativeStackSamplerMac::CopyToSample( |
| 314 const uintptr_t instruction_pointers[], |
| 315 int stack_depth, |
| 316 StackSamplingProfiler::Sample* sample, |
| 317 std::vector<StackSamplingProfiler::Module>* modules) { |
| 318 sample->clear(); |
| 319 sample->reserve(stack_depth); |
| 320 |
| 321 for (int i = 0; i < stack_depth; i++) { |
| 322 sample->push_back(StackSamplingProfiler::Frame( |
| 323 reinterpret_cast<const void*>(instruction_pointers[i]), |
| 324 GetModuleIndex(instruction_pointers[i], modules))); |
| 325 } |
| 326 } |
| 327 |
| 328 } // namespace |
| 329 |
| 330 scoped_ptr<NativeStackSampler> NativeStackSampler::Create( |
| 331 PlatformThreadId thread_id) { |
| 332 #if defined(__i386__) |
| 333 return nullptr; |
| 334 #endif |
| 335 return scoped_ptr<NativeStackSampler>(new NativeStackSamplerMac(thread_id)); |
| 336 } |
| 337 |
| 338 } // namespace base |
OLD | NEW |