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