| Chris Lattner | 30fdc8d | 2010-06-08 16:52:24 +0000 | [diff] [blame] | 1 | //===-- ProfileObjectiveC.cpp -----------------------------------*- C++ -*-===// |
| 2 | // |
| 3 | // The LLVM Compiler Infrastructure |
| 4 | // |
| 5 | // This file is distributed under the University of Illinois Open Source |
| 6 | // License. See LICENSE.TXT for details. |
| 7 | // |
| 8 | //===----------------------------------------------------------------------===// |
| 9 | // |
| 10 | // Created by Greg Clayton on 10/4/07. |
| 11 | // |
| 12 | //===----------------------------------------------------------------------===// |
| 13 | |
| 14 | #include "ProfileObjectiveC.h" |
| 15 | #include "DNB.h" |
| 16 | #include <objc/objc-runtime.h> |
| 17 | #include <map> |
| 18 | |
| 19 | #if defined (__powerpc__) || defined (__ppc__) |
| 20 | #define OBJC_MSG_SEND_PPC32_COMM_PAGE_ADDR ((nub_addr_t)0xfffeff00) |
| 21 | #endif |
| 22 | |
| 23 | //---------------------------------------------------------------------- |
| 24 | // Constructor |
| 25 | //---------------------------------------------------------------------- |
| 26 | ProfileObjectiveC::ProfileObjectiveC() : |
| 27 | m_pid(INVALID_NUB_PROCESS), |
| 28 | m_objcStats(), |
| 29 | m_hit_count(0), |
| 30 | m_dump_count(0xffff) |
| 31 | { |
| 32 | memset(&m_begin_time, 0, sizeof(m_begin_time)); |
| 33 | } |
| 34 | |
| 35 | //---------------------------------------------------------------------- |
| 36 | // Destructor |
| 37 | //---------------------------------------------------------------------- |
| 38 | ProfileObjectiveC::~ProfileObjectiveC() |
| 39 | { |
| 40 | } |
| 41 | |
| 42 | //---------------------------------------------------------------------- |
| 43 | // Clear any counts that we may have had |
| 44 | //---------------------------------------------------------------------- |
| 45 | void |
| 46 | ProfileObjectiveC::Clear() |
| 47 | { |
| 48 | if (m_pid != INVALID_NUB_PROCESS) |
| 49 | { |
| 50 | DNBBreakpointClear(m_pid, m_objc_msgSend.breakID); |
| 51 | DNBBreakpointClear(m_pid, m_objc_msgSendSuper.breakID); |
| 52 | #if defined (__powerpc__) || defined (__ppc__) |
| 53 | DNBBreakpointClear(m_pid, m_objc_msgSend_rtp.breakID); |
| 54 | #endif |
| 55 | } |
| 56 | m_objc_msgSend.Clear(); |
| 57 | m_objc_msgSendSuper.Clear(); |
| 58 | #if defined (__powerpc__) || defined (__ppc__) |
| 59 | memset(m_objc_msgSend_opcode, 0, k_opcode_size); |
| 60 | m_objc_msgSend_rtp.Clear(); |
| 61 | #endif |
| 62 | memset(&m_begin_time, 0, sizeof(m_begin_time)); |
| 63 | m_objcStats.clear(); |
| 64 | } |
| 65 | |
| 66 | void |
| 67 | ProfileObjectiveC::Initialize(nub_process_t pid) |
| 68 | { |
| 69 | Clear(); |
| 70 | m_pid = pid; |
| 71 | } |
| 72 | |
| 73 | |
| 74 | void |
| 75 | ProfileObjectiveC::ProcessStateChanged(nub_state_t state) |
| 76 | { |
| 77 | //printf("ProfileObjectiveC::%s(%s)\n", __FUNCTION__, DNBStateAsString(state)); |
| 78 | switch (state) |
| 79 | { |
| 80 | case eStateInvalid: |
| 81 | case eStateUnloaded: |
| 82 | case eStateExited: |
| 83 | case eStateDetached: |
| 84 | Clear(); |
| 85 | break; |
| 86 | |
| 87 | case eStateStopped: |
| 88 | #if defined (__powerpc__) || defined (__ppc__) |
| 89 | if (NUB_BREAK_ID_IS_VALID(m_objc_msgSend.breakID) && !NUB_BREAK_ID_IS_VALID(m_objc_msgSend_rtp.breakID)) |
| 90 | { |
| 91 | nub_thread_t tid = DNBProcessGetCurrentThread(m_pid); |
| 92 | DNBRegisterValue pc_value; |
| 93 | if (DNBThreadGetRegisterValueByName(m_pid, tid, REGISTER_SET_ALL, "srr0" , &pc_value)) |
| 94 | { |
| 95 | nub_addr_t pc = pc_value.value.uint32; |
| 96 | if (pc == OBJC_MSG_SEND_PPC32_COMM_PAGE_ADDR) |
| 97 | { |
| 98 | // Restore previous first instruction to 0xfffeff00 in comm page |
| 99 | DNBProcessMemoryWrite(m_pid, OBJC_MSG_SEND_PPC32_COMM_PAGE_ADDR, k_opcode_size, m_objc_msgSend_opcode); |
| 100 | //printf("Setting breakpoint on _objc_msgSend_rtp...\n"); |
| 101 | m_objc_msgSend_rtp.breakID = DNBBreakpointSet(m_pid, OBJC_MSG_SEND_PPC32_COMM_PAGE_ADDR); |
| 102 | if (NUB_BREAK_ID_IS_VALID(m_objc_msgSend_rtp.breakID)) |
| 103 | { |
| 104 | DNBBreakpointSetCallback(m_pid, m_objc_msgSend_rtp.breakID, ProfileObjectiveC::MessageSendBreakpointCallback, this); |
| 105 | } |
| 106 | } |
| 107 | } |
| 108 | } |
| 109 | #endif |
| 110 | DumpStats(m_pid, stdout); |
| 111 | break; |
| 112 | |
| 113 | case eStateAttaching: |
| 114 | case eStateLaunching: |
| 115 | case eStateRunning: |
| 116 | case eStateStepping: |
| 117 | case eStateCrashed: |
| 118 | case eStateSuspended: |
| 119 | break; |
| 120 | |
| 121 | default: |
| 122 | break; |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | void |
| 127 | ProfileObjectiveC::SharedLibraryStateChanged(DNBExecutableImageInfo *image_infos, nub_size_t num_image_infos) |
| 128 | { |
| 129 | //printf("ProfileObjectiveC::%s(%p, %u)\n", __FUNCTION__, image_infos, num_image_infos); |
| 130 | if (m_objc_msgSend.IsValid() && m_objc_msgSendSuper.IsValid()) |
| 131 | return; |
| 132 | |
| 133 | if (image_infos) |
| 134 | { |
| 135 | nub_process_t pid = m_pid; |
| 136 | nub_size_t i; |
| 137 | for (i = 0; i < num_image_infos; i++) |
| 138 | { |
| 139 | if (strcmp(image_infos[i].name, "/usr/lib/libobjc.A.dylib") == 0) |
| 140 | { |
| 141 | if (!NUB_BREAK_ID_IS_VALID(m_objc_msgSend.breakID)) |
| 142 | { |
| 143 | m_objc_msgSend.addr = DNBProcessLookupAddress(pid, "_objc_msgSend", image_infos[i].name); |
| 144 | |
| 145 | if (m_objc_msgSend.addr != INVALID_NUB_ADDRESS) |
| 146 | { |
| 147 | #if defined (__powerpc__) || defined (__ppc__) |
| 148 | if (DNBProcessMemoryRead(pid, m_objc_msgSend.addr, k_opcode_size, m_objc_msgSend_opcode) != k_opcode_size) |
| 149 | memset(m_objc_msgSend_opcode, 0, sizeof(m_objc_msgSend_opcode)); |
| 150 | #endif |
| 151 | m_objc_msgSend.breakID = DNBBreakpointSet(pid, m_objc_msgSend.addr, 4, false); |
| 152 | if (NUB_BREAK_ID_IS_VALID(m_objc_msgSend.breakID)) |
| 153 | DNBBreakpointSetCallback(pid, m_objc_msgSend.breakID, ProfileObjectiveC::MessageSendBreakpointCallback, this); |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | if (!NUB_BREAK_ID_IS_VALID(m_objc_msgSendSuper.breakID)) |
| 158 | { |
| 159 | m_objc_msgSendSuper.addr = DNBProcessLookupAddress(pid, "_objc_msgSendSuper", image_infos[i].name); |
| 160 | |
| 161 | if (m_objc_msgSendSuper.addr != INVALID_NUB_ADDRESS) |
| 162 | { |
| 163 | m_objc_msgSendSuper.breakID = DNBBreakpointSet(pid, m_objc_msgSendSuper.addr, 4, false); |
| 164 | if (NUB_BREAK_ID_IS_VALID(m_objc_msgSendSuper.breakID)) |
| 165 | DNBBreakpointSetCallback(pid, m_objc_msgSendSuper.breakID, ProfileObjectiveC::MessageSendSuperBreakpointCallback, this); |
| 166 | } |
| 167 | } |
| 168 | break; |
| 169 | } |
| 170 | } |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | |
| 175 | void |
| 176 | ProfileObjectiveC::SetStartTime() |
| 177 | { |
| 178 | gettimeofday(&m_begin_time, NULL); |
| 179 | } |
| 180 | |
| 181 | void |
| 182 | ProfileObjectiveC::SelectorHit(objc_class_ptr_t isa, objc_selector_t sel) |
| 183 | { |
| 184 | m_objcStats[isa][sel]++; |
| 185 | } |
| 186 | |
| 187 | nub_bool_t |
| 188 | ProfileObjectiveC::MessageSendBreakpointCallback(nub_process_t pid, nub_thread_t tid, nub_break_t breakID, void *userData) |
| 189 | { |
| 190 | ProfileObjectiveC *profile_objc = (ProfileObjectiveC*)userData; |
| 191 | uint32_t hit_count = profile_objc->IncrementHitCount(); |
| 192 | if (hit_count == 1) |
| 193 | profile_objc->SetStartTime(); |
| 194 | |
| 195 | objc_class_ptr_t objc_self = 0; |
| 196 | objc_selector_t objc_selector = 0; |
| 197 | #if defined (__i386__) |
| 198 | DNBRegisterValue esp; |
| 199 | if (DNBThreadGetRegisterValueByName(pid, tid, REGISTER_SET_ALL, "esp", &esp)) |
| 200 | { |
| 201 | uint32_t uval32[2]; |
| 202 | if (DNBProcessMemoryRead(pid, esp.value.uint32 + 4, 8, &uval32) == 8) |
| 203 | { |
| 204 | objc_self = uval32[0]; |
| 205 | objc_selector = uval32[1]; |
| 206 | } |
| 207 | } |
| 208 | #elif defined (__powerpc__) || defined (__ppc__) |
| 209 | DNBRegisterValue r3; |
| 210 | DNBRegisterValue r4; |
| 211 | if (DNBThreadGetRegisterValueByName(pid, tid, REGISTER_SET_ALL, "r3", &r3) && |
| 212 | DNBThreadGetRegisterValueByName(pid, tid, REGISTER_SET_ALL, "r4", &r4)) |
| 213 | { |
| 214 | objc_self = r3.value.uint32; |
| 215 | objc_selector = r4.value.uint32; |
| 216 | } |
| 217 | #elif defined (__arm__) |
| 218 | DNBRegisterValue r0; |
| 219 | DNBRegisterValue r1; |
| 220 | if (DNBThreadGetRegisterValueByName(pid, tid, REGISTER_SET_ALL, "r0", &r0) && |
| 221 | DNBThreadGetRegisterValueByName(pid, tid, REGISTER_SET_ALL, "r1", &r1)) |
| 222 | { |
| 223 | objc_self = r0.value.uint32; |
| 224 | objc_selector = r1.value.uint32; |
| 225 | } |
| 226 | #else |
| 227 | #error undefined architecture |
| 228 | #endif |
| 229 | if (objc_selector != 0) |
| 230 | { |
| 231 | uint32_t isa = 0; |
| 232 | if (objc_self == 0) |
| 233 | { |
| 234 | profile_objc->SelectorHit(0, objc_selector); |
| 235 | } |
| 236 | else |
| 237 | if (DNBProcessMemoryRead(pid, (nub_addr_t)objc_self, sizeof(isa), &isa) == sizeof(isa)) |
| 238 | { |
| 239 | if (isa) |
| 240 | { |
| 241 | profile_objc->SelectorHit(isa, objc_selector); |
| 242 | } |
| 243 | else |
| 244 | { |
| 245 | profile_objc->SelectorHit(0, objc_selector); |
| 246 | } |
| 247 | } |
| 248 | } |
| 249 | |
| 250 | |
| 251 | // Dump stats if we are supposed to |
| 252 | if (profile_objc->ShouldDumpStats()) |
| 253 | { |
| 254 | profile_objc->DumpStats(pid, stdout); |
| 255 | return true; |
| 256 | } |
| 257 | |
| 258 | // Just let the target run again by returning false; |
| 259 | return false; |
| 260 | } |
| 261 | |
| 262 | nub_bool_t |
| 263 | ProfileObjectiveC::MessageSendSuperBreakpointCallback(nub_process_t pid, nub_thread_t tid, nub_break_t breakID, void *userData) |
| 264 | { |
| 265 | ProfileObjectiveC *profile_objc = (ProfileObjectiveC*)userData; |
| 266 | |
| 267 | uint32_t hit_count = profile_objc->IncrementHitCount(); |
| 268 | if (hit_count == 1) |
| 269 | profile_objc->SetStartTime(); |
| 270 | |
| 271 | // printf("BreakID %u hit count is = %u\n", breakID, hc); |
| 272 | objc_class_ptr_t objc_super = 0; |
| 273 | objc_selector_t objc_selector = 0; |
| 274 | #if defined (__i386__) |
| 275 | DNBRegisterValue esp; |
| 276 | if (DNBThreadGetRegisterValueByName(pid, tid, REGISTER_SET_ALL, "esp", &esp)) |
| 277 | { |
| 278 | uint32_t uval32[2]; |
| 279 | if (DNBProcessMemoryRead(pid, esp.value.uint32 + 4, 8, &uval32) == 8) |
| 280 | { |
| 281 | objc_super = uval32[0]; |
| 282 | objc_selector = uval32[1]; |
| 283 | } |
| 284 | } |
| 285 | #elif defined (__powerpc__) || defined (__ppc__) |
| 286 | DNBRegisterValue r3; |
| 287 | DNBRegisterValue r4; |
| 288 | if (DNBThreadGetRegisterValueByName(pid, tid, REGISTER_SET_ALL, "r3", &r3) && |
| 289 | DNBThreadGetRegisterValueByName(pid, tid, REGISTER_SET_ALL, "r4", &r4)) |
| 290 | { |
| 291 | objc_super = r3.value.uint32; |
| 292 | objc_selector = r4.value.uint32; |
| 293 | } |
| 294 | #elif defined (__arm__) |
| 295 | DNBRegisterValue r0; |
| 296 | DNBRegisterValue r1; |
| 297 | if (DNBThreadGetRegisterValueByName(pid, tid, REGISTER_SET_ALL, "r0", &r0) && |
| 298 | DNBThreadGetRegisterValueByName(pid, tid, REGISTER_SET_ALL, "r1", &r1)) |
| 299 | { |
| 300 | objc_super = r0.value.uint32; |
| 301 | objc_selector = r1.value.uint32; |
| 302 | } |
| 303 | #else |
| 304 | #error undefined architecture |
| 305 | #endif |
| 306 | if (objc_selector != 0) |
| 307 | { |
| 308 | uint32_t isa = 0; |
| 309 | if (objc_super == 0) |
| 310 | { |
| 311 | profile_objc->SelectorHit(0, objc_selector); |
| 312 | } |
| 313 | else |
| 314 | if (DNBProcessMemoryRead(pid, (nub_addr_t)objc_super + 4, sizeof(isa), &isa) == sizeof(isa)) |
| 315 | { |
| 316 | if (isa) |
| 317 | { |
| 318 | profile_objc->SelectorHit(isa, objc_selector); |
| 319 | } |
| 320 | else |
| 321 | { |
| 322 | profile_objc->SelectorHit(0, objc_selector); |
| 323 | } |
| 324 | } |
| 325 | } |
| 326 | |
| 327 | // Dump stats if we are supposed to |
| 328 | if (profile_objc->ShouldDumpStats()) |
| 329 | { |
| 330 | profile_objc->DumpStats(pid, stdout); |
| 331 | return true; |
| 332 | } |
| 333 | |
| 334 | // Just let the target run again by returning false; |
| 335 | return false; |
| 336 | } |
| 337 | |
| 338 | void |
| 339 | ProfileObjectiveC::DumpStats(nub_process_t pid, FILE *f) |
| 340 | { |
| 341 | if (f == NULL) |
| 342 | return; |
| 343 | |
| 344 | if (m_hit_count == 0) |
| 345 | return; |
| 346 | |
| 347 | ClassStatsMap::iterator class_pos; |
| 348 | ClassStatsMap::iterator class_end = m_objcStats.end(); |
| 349 | |
| 350 | struct timeval end_time; |
| 351 | gettimeofday(&end_time, NULL); |
| 352 | int64_t elapsed_usec = ((int64_t)(1000*1000))*((int64_t)end_time.tv_sec - (int64_t)m_begin_time.tv_sec) + ((int64_t)end_time.tv_usec - (int64_t)m_begin_time.tv_usec); |
| 353 | fprintf(f, "%u probe hits for %.2f hits/sec)\n", m_hit_count, (double)m_hit_count / (((double)elapsed_usec)/(1000000.0))); |
| 354 | |
| 355 | for (class_pos = m_objcStats.begin(); class_pos != class_end; ++class_pos) |
| 356 | { |
| 357 | SelectorHitCount::iterator sel_pos; |
| 358 | SelectorHitCount::iterator sel_end = class_pos->second.end(); |
| 359 | for (sel_pos = class_pos->second.begin(); sel_pos != sel_end; ++sel_pos) |
| 360 | { |
| 361 | struct objc_class objc_class; |
| 362 | uint32_t isa = class_pos->first; |
| 363 | uint32_t sel = sel_pos->first; |
| 364 | uint32_t sel_hit_count = sel_pos->second; |
| 365 | |
| 366 | if (isa != 0 && DNBProcessMemoryRead(pid, isa, sizeof(objc_class), &objc_class) == sizeof(objc_class)) |
| 367 | { |
| 368 | /* fprintf(f, "%#.8x\n isa = %p\n super_class = %p\n name = %p\n version = %lx\n info = %lx\ninstance_size = %lx\n ivars = %p\n methodLists = %p\n cache = %p\n protocols = %p\n", |
| 369 | arg1.value.pointer, |
| 370 | objc_class.isa, |
| 371 | objc_class.super_class, |
| 372 | objc_class.name, |
| 373 | objc_class.version, |
| 374 | objc_class.info, |
| 375 | objc_class.instance_size, |
| 376 | objc_class.ivars, |
| 377 | objc_class.methodLists, |
| 378 | objc_class.cache, |
| 379 | objc_class.protocols); */ |
| 380 | |
| 381 | // Print the class name |
| 382 | fprintf(f, "%6u hits for %c[", sel_hit_count, (objc_class.super_class == objc_class.isa ? '+' : '-')); |
| 383 | DNBPrintf(pid, INVALID_NUB_THREAD, (nub_addr_t)objc_class.name, f, "%s "); |
| 384 | } |
| 385 | else |
| 386 | { |
| 387 | fprintf(f, "%6u hits for [<nil> ", sel_hit_count); |
| 388 | } |
| 389 | DNBPrintf(pid, INVALID_NUB_THREAD, sel, f, "%s]\n"); |
| 390 | } |
| 391 | } |
| 392 | } |
| 393 | |