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