1 //===-- AppleThreadPlanStepThroughObjCTrampoline.cpp-----------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "AppleThreadPlanStepThroughObjCTrampoline.h" 10 11 #include "AppleObjCTrampolineHandler.h" 12 #include "lldb/Expression/DiagnosticManager.h" 13 #include "lldb/Expression/FunctionCaller.h" 14 #include "lldb/Expression/UtilityFunction.h" 15 #include "lldb/Target/ExecutionContext.h" 16 #include "lldb/Target/Process.h" 17 #include "lldb/Target/Thread.h" 18 #include "lldb/Target/ThreadPlanRunToAddress.h" 19 #include "lldb/Target/ThreadPlanStepOut.h" 20 #include "lldb/Utility/Log.h" 21 22 #include "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h" 23 24 #include <memory> 25 26 using namespace lldb; 27 using namespace lldb_private; 28 29 // ThreadPlanStepThroughObjCTrampoline constructor 30 AppleThreadPlanStepThroughObjCTrampoline:: 31 AppleThreadPlanStepThroughObjCTrampoline( 32 Thread &thread, AppleObjCTrampolineHandler &trampoline_handler, 33 ValueList &input_values, lldb::addr_t isa_addr, lldb::addr_t sel_addr, 34 bool stop_others) 35 : ThreadPlan(ThreadPlan::eKindGeneric, 36 "MacOSX Step through ObjC Trampoline", thread, eVoteNoOpinion, 37 eVoteNoOpinion), 38 m_trampoline_handler(trampoline_handler), 39 m_args_addr(LLDB_INVALID_ADDRESS), m_input_values(input_values), 40 m_isa_addr(isa_addr), m_sel_addr(sel_addr), m_impl_function(nullptr), 41 m_stop_others(stop_others) {} 42 43 // Destructor 44 AppleThreadPlanStepThroughObjCTrampoline:: 45 ~AppleThreadPlanStepThroughObjCTrampoline() {} 46 47 void AppleThreadPlanStepThroughObjCTrampoline::DidPush() { 48 // Setting up the memory space for the called function text might require 49 // allocations, i.e. a nested function call. This needs to be done as a 50 // PreResumeAction. 51 m_thread.GetProcess()->AddPreResumeAction(PreResumeInitializeFunctionCaller, 52 (void *)this); 53 } 54 55 bool AppleThreadPlanStepThroughObjCTrampoline::InitializeFunctionCaller() { 56 if (!m_func_sp) { 57 DiagnosticManager diagnostics; 58 m_args_addr = 59 m_trampoline_handler.SetupDispatchFunction(m_thread, m_input_values); 60 61 if (m_args_addr == LLDB_INVALID_ADDRESS) { 62 return false; 63 } 64 m_impl_function = 65 m_trampoline_handler.GetLookupImplementationFunctionCaller(); 66 ExecutionContext exc_ctx; 67 EvaluateExpressionOptions options; 68 options.SetUnwindOnError(true); 69 options.SetIgnoreBreakpoints(true); 70 options.SetStopOthers(m_stop_others); 71 m_thread.CalculateExecutionContext(exc_ctx); 72 m_func_sp = m_impl_function->GetThreadPlanToCallFunction( 73 exc_ctx, m_args_addr, options, diagnostics); 74 m_func_sp->SetOkayToDiscard(true); 75 PushPlan(m_func_sp); 76 } 77 return true; 78 } 79 80 bool AppleThreadPlanStepThroughObjCTrampoline:: 81 PreResumeInitializeFunctionCaller(void *void_myself) { 82 AppleThreadPlanStepThroughObjCTrampoline *myself = 83 static_cast<AppleThreadPlanStepThroughObjCTrampoline *>(void_myself); 84 return myself->InitializeFunctionCaller(); 85 } 86 87 void AppleThreadPlanStepThroughObjCTrampoline::GetDescription( 88 Stream *s, lldb::DescriptionLevel level) { 89 if (level == lldb::eDescriptionLevelBrief) 90 s->Printf("Step through ObjC trampoline"); 91 else { 92 s->Printf("Stepping to implementation of ObjC method - obj: 0x%llx, isa: " 93 "0x%" PRIx64 ", sel: 0x%" PRIx64, 94 m_input_values.GetValueAtIndex(0)->GetScalar().ULongLong(), 95 m_isa_addr, m_sel_addr); 96 } 97 } 98 99 bool AppleThreadPlanStepThroughObjCTrampoline::ValidatePlan(Stream *error) { 100 return true; 101 } 102 103 bool AppleThreadPlanStepThroughObjCTrampoline::DoPlanExplainsStop( 104 Event *event_ptr) { 105 // If we get asked to explain the stop it will be because something went 106 // wrong (like the implementation for selector function crashed... We're 107 // going to figure out what to do about that, so we do explain the stop. 108 return true; 109 } 110 111 lldb::StateType AppleThreadPlanStepThroughObjCTrampoline::GetPlanRunState() { 112 return eStateRunning; 113 } 114 115 bool AppleThreadPlanStepThroughObjCTrampoline::ShouldStop(Event *event_ptr) { 116 // First stage: we are still handling the "call a function to get the target 117 // of the dispatch" 118 if (m_func_sp) { 119 if (!m_func_sp->IsPlanComplete()) { 120 return false; 121 } else { 122 if (!m_func_sp->PlanSucceeded()) { 123 SetPlanComplete(false); 124 return true; 125 } 126 m_func_sp.reset(); 127 } 128 } 129 130 // Second stage, if all went well with the function calling, then fetch the 131 // target address, and queue up a "run to that address" plan. 132 if (!m_run_to_sp) { 133 Value target_addr_value; 134 ExecutionContext exc_ctx; 135 m_thread.CalculateExecutionContext(exc_ctx); 136 m_impl_function->FetchFunctionResults(exc_ctx, m_args_addr, 137 target_addr_value); 138 m_impl_function->DeallocateFunctionResults(exc_ctx, m_args_addr); 139 lldb::addr_t target_addr = target_addr_value.GetScalar().ULongLong(); 140 Address target_so_addr; 141 target_so_addr.SetOpcodeLoadAddress(target_addr, exc_ctx.GetTargetPtr()); 142 Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP)); 143 if (target_addr == 0) { 144 LLDB_LOGF(log, "Got target implementation of 0x0, stopping."); 145 SetPlanComplete(); 146 return true; 147 } 148 if (m_trampoline_handler.AddrIsMsgForward(target_addr)) { 149 LLDB_LOGF(log, 150 "Implementation lookup returned msgForward function: 0x%" PRIx64 151 ", stopping.", 152 target_addr); 153 154 SymbolContext sc = m_thread.GetStackFrameAtIndex(0)->GetSymbolContext( 155 eSymbolContextEverything); 156 Status status; 157 const bool abort_other_plans = false; 158 const bool first_insn = true; 159 const uint32_t frame_idx = 0; 160 m_run_to_sp = m_thread.QueueThreadPlanForStepOutNoShouldStop( 161 abort_other_plans, &sc, first_insn, m_stop_others, eVoteNoOpinion, 162 eVoteNoOpinion, frame_idx, status); 163 if (m_run_to_sp && status.Success()) 164 m_run_to_sp->SetPrivate(true); 165 return false; 166 } 167 168 LLDB_LOGF(log, "Running to ObjC method implementation: 0x%" PRIx64, 169 target_addr); 170 171 ObjCLanguageRuntime *objc_runtime = 172 ObjCLanguageRuntime::Get(*GetThread().GetProcess()); 173 assert(objc_runtime != nullptr); 174 objc_runtime->AddToMethodCache(m_isa_addr, m_sel_addr, target_addr); 175 LLDB_LOGF(log, 176 "Adding {isa-addr=0x%" PRIx64 ", sel-addr=0x%" PRIx64 177 "} = addr=0x%" PRIx64 " to cache.", 178 m_isa_addr, m_sel_addr, target_addr); 179 180 // Extract the target address from the value: 181 182 m_run_to_sp = std::make_shared<ThreadPlanRunToAddress>( 183 m_thread, target_so_addr, m_stop_others); 184 PushPlan(m_run_to_sp); 185 return false; 186 } else if (m_thread.IsThreadPlanDone(m_run_to_sp.get())) { 187 // Third stage, work the run to target plan. 188 SetPlanComplete(); 189 return true; 190 } 191 return false; 192 } 193 194 // The base class MischiefManaged does some cleanup - so you have to call it in 195 // your MischiefManaged derived class. 196 bool AppleThreadPlanStepThroughObjCTrampoline::MischiefManaged() { 197 return IsPlanComplete(); 198 } 199 200 bool AppleThreadPlanStepThroughObjCTrampoline::WillStop() { return true; } 201 202 // Objective-C uses optimized dispatch functions for some common and seldom 203 // overridden methods. For instance 204 // [object respondsToSelector:]; 205 // will get compiled to: 206 // objc_opt_respondsToSelector(object); 207 // This checks whether the selector has been overridden, directly calling the 208 // implementation if it hasn't and calling objc_msgSend if it has. 209 // 210 // We need to get into the overridden implementation. We'll do that by 211 // setting a breakpoint on objc_msgSend, and doing a "step out". If we stop 212 // at objc_msgSend, we can step through to the target of the send, and see if 213 // that's a place we want to stop. 214 // 215 // A couple of complexities. The checking code might call some other method, 216 // so we might see objc_msgSend more than once. Also, these optimized dispatch 217 // functions might dispatch more than one message at a time (e.g. alloc followed 218 // by init.) So we can't give up at the first objc_msgSend. 219 // That means among other things that we have to handle the "ShouldStopHere" - 220 // since we can't just return control to the plan that's controlling us on the 221 // first step. 222 223 AppleThreadPlanStepThroughDirectDispatch :: 224 AppleThreadPlanStepThroughDirectDispatch( 225 Thread &thread, AppleObjCTrampolineHandler &handler, 226 llvm::StringRef dispatch_func_name, bool stop_others, 227 LazyBool step_in_avoids_code_without_debug_info) 228 : ThreadPlanStepOut(thread, nullptr, true /* first instruction */, 229 stop_others, eVoteNoOpinion, eVoteNoOpinion, 230 0 /* Step out of zeroth frame */, 231 eLazyBoolNo /* Our parent plan will decide this 232 when we are done */ 233 , 234 true /* Run to branch for inline step out */, 235 false /* Don't gather the return value */), 236 m_trampoline_handler(handler), 237 m_dispatch_func_name(std::string(dispatch_func_name)), 238 m_at_msg_send(false), m_stop_others(stop_others) { 239 // Set breakpoints on the dispatch functions: 240 auto bkpt_callback = [&] (lldb::addr_t addr, 241 const AppleObjCTrampolineHandler 242 ::DispatchFunction &dispatch) { 243 m_msgSend_bkpts.push_back(GetTarget().CreateBreakpoint(addr, 244 true /* internal */, 245 false /* hard */)); 246 m_msgSend_bkpts.back()->SetThreadID(GetThread().GetID()); 247 }; 248 handler.ForEachDispatchFunction(bkpt_callback); 249 250 // We'll set the step-out plan in the DidPush so it gets queued in the right 251 // order. 252 253 bool avoid_nodebug = true; 254 255 switch (step_in_avoids_code_without_debug_info) { 256 case eLazyBoolYes: 257 avoid_nodebug = true; 258 break; 259 case eLazyBoolNo: 260 avoid_nodebug = false; 261 break; 262 case eLazyBoolCalculate: 263 avoid_nodebug = GetThread().GetStepInAvoidsNoDebug(); 264 break; 265 } 266 if (avoid_nodebug) 267 GetFlags().Set(ThreadPlanShouldStopHere::eStepInAvoidNoDebug); 268 else 269 GetFlags().Clear(ThreadPlanShouldStopHere::eStepInAvoidNoDebug); 270 // We only care about step in. Our parent plan will figure out what to 271 // do when we've stepped out again. 272 GetFlags().Clear(ThreadPlanShouldStopHere::eStepOutAvoidNoDebug); 273 } 274 275 AppleThreadPlanStepThroughDirectDispatch:: 276 ~AppleThreadPlanStepThroughDirectDispatch() { 277 for (BreakpointSP bkpt_sp : m_msgSend_bkpts) { 278 GetTarget().RemoveBreakpointByID(bkpt_sp->GetID()); 279 } 280 } 281 282 void AppleThreadPlanStepThroughDirectDispatch::GetDescription( 283 Stream *s, lldb::DescriptionLevel level) { 284 switch (level) { 285 case lldb::eDescriptionLevelBrief: 286 s->PutCString("Step through ObjC direct dispatch function."); 287 break; 288 default: 289 s->Printf("Step through ObjC direct dispatch '%s' using breakpoints: ", 290 m_dispatch_func_name.c_str()); 291 bool first = true; 292 for (auto bkpt_sp : m_msgSend_bkpts) { 293 if (!first) { 294 s->PutCString(", "); 295 } 296 first = false; 297 s->Printf("%d", bkpt_sp->GetID()); 298 } 299 (*s) << "."; 300 break; 301 } 302 } 303 304 bool 305 AppleThreadPlanStepThroughDirectDispatch::DoPlanExplainsStop(Event *event_ptr) { 306 if (ThreadPlanStepOut::DoPlanExplainsStop(event_ptr)) 307 return true; 308 309 StopInfoSP stop_info_sp = GetPrivateStopInfo(); 310 311 // Check if the breakpoint is one of ours msgSend dispatch breakpoints. 312 313 StopReason stop_reason = eStopReasonNone; 314 if (stop_info_sp) 315 stop_reason = stop_info_sp->GetStopReason(); 316 317 // See if this is one of our msgSend breakpoints: 318 if (stop_reason == eStopReasonBreakpoint) { 319 ProcessSP process_sp = GetThread().GetProcess(); 320 uint64_t break_site_id = stop_info_sp->GetValue(); 321 BreakpointSiteSP site_sp 322 = process_sp->GetBreakpointSiteList().FindByID(break_site_id); 323 // Some other plan might have deleted the site's last owner before this 324 // got to us. In which case, it wasn't our breakpoint... 325 if (!site_sp) 326 return false; 327 328 for (BreakpointSP break_sp : m_msgSend_bkpts) { 329 if (site_sp->IsBreakpointAtThisSite(break_sp->GetID())) { 330 // If we aren't the only one with a breakpoint on this site, then we 331 // should just stop and return control to the user. 332 if (site_sp->GetNumberOfOwners() > 1) { 333 SetPlanComplete(true); 334 return false; 335 } 336 m_at_msg_send = true; 337 return true; 338 } 339 } 340 } 341 342 // We're done here. If one of our sub-plans explained the stop, they 343 // would have already answered true to PlanExplainsStop, and if they were 344 // done, we'll get called to figure out what to do in ShouldStop... 345 return false; 346 } 347 348 bool AppleThreadPlanStepThroughDirectDispatch 349 ::DoWillResume(lldb::StateType resume_state, bool current_plan) { 350 ThreadPlanStepOut::DoWillResume(resume_state, current_plan); 351 m_at_msg_send = false; 352 return true; 353 } 354 355 bool AppleThreadPlanStepThroughDirectDispatch::ShouldStop(Event *event_ptr) { 356 // If step out plan finished, that means we didn't find our way into a method 357 // implementation. Either we went directly to the default implementation, 358 // of the overridden implementation didn't have debug info. 359 // So we should mark ourselves as done. 360 const bool step_out_should_stop = ThreadPlanStepOut::ShouldStop(event_ptr); 361 if (step_out_should_stop) { 362 SetPlanComplete(true); 363 return true; 364 } 365 366 // If we have a step through plan, then w're in the process of getting 367 // through an ObjC msgSend. If we arrived at the target function, then 368 // check whether we have debug info, and if we do, stop. 369 Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP)); 370 371 if (m_objc_step_through_sp && m_objc_step_through_sp->IsPlanComplete()) { 372 // If the plan failed for some reason, we should probably just let the 373 // step over plan get us out of here... We don't need to do anything about 374 // the step through plan, it is done and will get popped when we continue. 375 if (!m_objc_step_through_sp->PlanSucceeded()) { 376 LLDB_LOGF(log, "ObjC Step through plan failed. Stepping out."); 377 } 378 Status error; 379 if (InvokeShouldStopHereCallback(eFrameCompareYounger, error)) { 380 SetPlanComplete(true); 381 return true; 382 } 383 // If we didn't want to stop at this msgSend, there might be another so 384 // we should just continue on with the step out and see if our breakpoint 385 // triggers again. 386 m_objc_step_through_sp.reset(); 387 for (BreakpointSP bkpt_sp : m_msgSend_bkpts) { 388 bkpt_sp->SetEnabled(true); 389 } 390 return false; 391 } 392 393 // If we hit an msgSend breakpoint, then we should queue the step through 394 // plan: 395 396 if (m_at_msg_send) { 397 LanguageRuntime *objc_runtime 398 = GetThread().GetProcess()->GetLanguageRuntime(eLanguageTypeObjC); 399 // There's no way we could have gotten here without an ObjC language 400 // runtime. 401 assert(objc_runtime); 402 m_objc_step_through_sp 403 = objc_runtime->GetStepThroughTrampolinePlan(GetThread(), m_stop_others); 404 // If we failed to find the target for this dispatch, just keep going and 405 // let the step out complete. 406 if (!m_objc_step_through_sp) { 407 LLDB_LOG(log, "Couldn't find target for message dispatch, continuing."); 408 return false; 409 } 410 // Otherwise push the step through plan and continue. 411 GetThread().QueueThreadPlan(m_objc_step_through_sp, false); 412 for (BreakpointSP bkpt_sp : m_msgSend_bkpts) { 413 bkpt_sp->SetEnabled(false); 414 } 415 return false; 416 } 417 return true; 418 } 419 420 bool AppleThreadPlanStepThroughDirectDispatch::MischiefManaged() { 421 if (IsPlanComplete()) 422 return true; 423 return ThreadPlanStepOut::MischiefManaged(); 424 } 425