diff --git a/lldb/packages/Python/lldbsuite/test/decorators.py b/lldb/packages/Python/lldbsuite/test/decorators.py index bb698ab89939..02c30f96d85c 100644 --- a/lldb/packages/Python/lldbsuite/test/decorators.py +++ b/lldb/packages/Python/lldbsuite/test/decorators.py @@ -1186,6 +1186,16 @@ def skipUnlessBoundsSafety(func): return skipTestIfFn(is_compiler_with_bounds_safety)(func) +def skipUnlessCompilerSupports(flag): + """Decorate the item to skip the test unless the compiler supports this flag.""" + + def does_compiler_support_flag(): + if not _compiler_supports(lldbplatformutil.getCompiler(), flag): + return f"Compiler does not support flag {flag}" + return None + + return skipTestIfFn(does_compiler_support_flag) + def skipIfAsan(func): """Skip this test if the environment is set up to run LLDB *itself* under ASAN.""" return skipTestIfFn(is_running_under_asan)(func) diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp index c1c03e62dea7..1bde72f2ad05 100644 --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp @@ -803,8 +803,6 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread, // one of these stubs, we strip off the selector string and pass that to the // implementation finder function, which looks up the SEL (you have to do this // in process) and passes that to the runtime lookup function. - DispatchFunction sel_stub_dispatch = {"sel-specific-stub", false, false, - false, DispatchFunction::eFixUpNone}; // First step is to see if we're in a selector-specific dispatch stub. // Those are of the form _objc_msgSend$, so see if the current @@ -818,19 +816,24 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread, const Symbol *curr_sym = func_addr.CalculateSymbolContextSymbol(); if (curr_sym) sym_name = curr_sym->GetName().GetStringRef(); - - if (!sym_name.empty() && !sym_name.consume_front("objc_msgSend$")) - sym_name = {}; - else - this_dispatch = &sel_stub_dispatch; } - bool in_selector_stub = !sym_name.empty(); + + // objc has introduced new accelerated dispatch stubs which figure out the + // selector and in some cases the object in one way or another, then call + // objc_msgSend. If we're in one of those stubs, we can use "step through + // direct dispatch" plan to get to the actual dispatch. + if (!sym_name.empty() && (sym_name.consume_front("objc_msgSend$") + || sym_name.consume_front("objc_msgSendClass$"))) { + ret_plan_sp = std::make_shared( + thread, *this); + return ret_plan_sp; + } + // Second step is to look and see if we are in one of the known ObjC // dispatch functions. We've already compiled a table of same, so // consult it. - if (!in_selector_stub) - this_dispatch = FindDispatchFunction(curr_pc); + this_dispatch = FindDispatchFunction(curr_pc); // Next check to see if we are in a vtable region: @@ -882,17 +885,11 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread, int obj_index; int sel_index; - // If this is a selector-specific stub then just push one value, 'cause - // we only get the object. // If this is a struct return dispatch, then the first argument is // the return struct pointer, and the object is the second, and // the selector is the third. // Otherwise the object is the first and the selector the second. - if (in_selector_stub) { - obj_index = 0; - sel_index = 1; - argument_values.PushValue(void_ptr_value); - } else if (this_dispatch->stret_return) { + if (this_dispatch->stret_return) { obj_index = 1; sel_index = 2; argument_values.PushValue(void_ptr_value); @@ -926,10 +923,9 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread, lldb::addr_t isa_addr = LLDB_INVALID_ADDRESS; lldb::addr_t sel_addr = LLDB_INVALID_ADDRESS; - // If we are not in a selector stub, get the sel address from the arguments. - if (!in_selector_stub) - sel_addr = - argument_values.GetValueAtIndex(sel_index)->GetScalar().ULongLong(); + // Get the sel address from the arguments. + sel_addr = + argument_values.GetValueAtIndex(sel_index)->GetScalar().ULongLong(); // Figure out the class this is being dispatched to and see if // we've already cached this method call, If so we can push a @@ -1011,15 +1007,9 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread, ObjCLanguageRuntime *objc_runtime = ObjCLanguageRuntime::Get(*thread.GetProcess()); assert(objc_runtime != nullptr); - if (!in_selector_stub) { - LLDB_LOG(log, "Resolving call for class - {0} and selector - {1}", - isa_addr, sel_addr); - impl_addr = objc_runtime->LookupInMethodCache(isa_addr, sel_addr); - } else { - LLDB_LOG(log, "Resolving call for class - {0} and selector - {1}", - isa_addr, sym_name); - impl_addr = objc_runtime->LookupInMethodCache(isa_addr, sym_name); - } + LLDB_LOG(log, "Resolving call for class - {0} and selector - {1}", + isa_addr, sel_addr); + impl_addr = objc_runtime->LookupInMethodCache(isa_addr, sel_addr); } // If it is a selector-specific stub dispatch, look in the string cache: @@ -1058,46 +1048,18 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread, dispatch_values.PushValue(*(argument_values.GetValueAtIndex(obj_index))); lldb::addr_t sel_str_addr = LLDB_INVALID_ADDRESS; - if (!in_selector_stub) { - // If we don't have a selector string, push the selector from arguments. - dispatch_values.PushValue( - *(argument_values.GetValueAtIndex(sel_index))); - } else { - // Otherwise, inject the string into the target, and push that value for - // the sel argument. - Status error; - sel_str_addr = process_sp->AllocateMemory( - sym_name.size() + 1, ePermissionsReadable | ePermissionsWritable, - error); - if (sel_str_addr == LLDB_INVALID_ADDRESS || error.Fail()) { - LLDB_LOG(log, - "Could not allocate memory for selector string {0}: {1}", - sym_name, error); - return ret_plan_sp; - } - process_sp->WriteMemory(sel_str_addr, sym_name.str().c_str(), - sym_name.size() + 1, error); - if (error.Fail()) { - LLDB_LOG(log, "Could not write string to address {0}", sel_str_addr); - return ret_plan_sp; - } - Value sel_ptr_value(void_ptr_value); - sel_ptr_value.GetScalar() = sel_str_addr; - dispatch_values.PushValue(sel_ptr_value); - } + // Push the selector from arguments. + dispatch_values.PushValue(*(argument_values.GetValueAtIndex(sel_index))); Value flag_value; CompilerType clang_int_type = scratch_ts_sp->GetBuiltinTypeForEncodingAndBitSize( lldb::eEncodingSint, 32); flag_value.SetValueType(Value::ValueType::Scalar); - // flag_value.SetContext (Value::eContextTypeClangType, clang_int_type); flag_value.SetCompilerType(clang_int_type); - if (in_selector_stub) - flag_value.GetScalar() = 1; - else - flag_value.GetScalar() = 0; + // We are passing in a sel addr now a string pointer in all cases for now. + flag_value.GetScalar() = 0; dispatch_values.PushValue(flag_value); if (this_dispatch->stret_return) @@ -1140,7 +1102,7 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread, if (log && log->GetVerbose()) flag_value.GetScalar() = 1; else - flag_value.GetScalar() = 0; // FIXME - Set to 0 when debugging is done. + flag_value.GetScalar() = 0; dispatch_values.PushValue(flag_value); ret_plan_sp = std::make_shared( @@ -1153,20 +1115,19 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread, } } } - - // Finally, check if we have hit an "optimized dispatch" function. This will + + // Next, check if we have hit an "optimized dispatch" function. This will // either directly call the base implementation or dispatch an objc_msgSend // if the method has been overridden. So we just do a "step in/step out", - // setting a breakpoint on objc_msgSend, and if we hit the msgSend, we - // will automatically step in again. That's the job of the + // setting a breakpoint on objc_msgSend, and if we hit the msgSend, we + // will automatically step in again. That's the job of the // AppleThreadPlanStepThroughDirectDispatch. if (!this_dispatch && !ret_plan_sp) { MsgsendMap::iterator pos; pos = m_opt_dispatch_map.find(curr_pc); if (pos != m_opt_dispatch_map.end()) { - const char *opt_name = g_opt_dispatch_names[(*pos).second]; ret_plan_sp = std::make_shared( - thread, *this, opt_name); + thread, *this); } } diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.cpp index 4093cbdd955f..25baa366278e 100644 --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.cpp +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.cpp @@ -240,8 +240,7 @@ bool AppleThreadPlanStepThroughObjCTrampoline::WillStop() { return true; } AppleThreadPlanStepThroughDirectDispatch :: AppleThreadPlanStepThroughDirectDispatch( - Thread &thread, AppleObjCTrampolineHandler &handler, - llvm::StringRef dispatch_func_name) + Thread &thread, AppleObjCTrampolineHandler &handler) : ThreadPlanStepOut(thread, nullptr, true /* first instruction */, false, eVoteNoOpinion, eVoteNoOpinion, 0 /* Step out of zeroth frame */, @@ -250,9 +249,7 @@ AppleThreadPlanStepThroughDirectDispatch :: , true /* Run to branch for inline step out */, false /* Don't gather the return value */), - m_trampoline_handler(handler), - m_dispatch_func_name(std::string(dispatch_func_name)), - m_at_msg_send(false) { + m_trampoline_handler(handler), m_at_msg_send(false) { // Set breakpoints on the dispatch functions: auto bkpt_callback = [&] (lldb::addr_t addr, const AppleObjCTrampolineHandler @@ -290,8 +287,7 @@ void AppleThreadPlanStepThroughDirectDispatch::GetDescription( s->PutCString("Step through ObjC direct dispatch function."); break; default: - s->Printf("Step through ObjC direct dispatch '%s' using breakpoints: ", - m_dispatch_func_name.c_str()); + s->Printf("Step through ObjC direct dispatch using breakpoints: "); bool first = true; for (auto bkpt_sp : m_msgSend_bkpts) { if (!first) { diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.h b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.h index ba4fb5ace1ed..c95033f96158 100644 --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.h +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.h @@ -83,8 +83,7 @@ private: class AppleThreadPlanStepThroughDirectDispatch: public ThreadPlanStepOut { public: AppleThreadPlanStepThroughDirectDispatch(Thread &thread, - AppleObjCTrampolineHandler &handler, - llvm::StringRef dispatch_func_name); + AppleObjCTrampolineHandler &handler); ~AppleThreadPlanStepThroughDirectDispatch() override; @@ -106,8 +105,6 @@ protected: bool DoPlanExplainsStop(Event *event_ptr) override; AppleObjCTrampolineHandler &m_trampoline_handler; - std::string m_dispatch_func_name; /// Which dispatch function we're stepping - /// through. lldb::ThreadPlanSP m_objc_step_through_sp; /// When we hit an objc_msgSend, /// we'll use this plan to get to /// its target. diff --git a/lldb/test/API/lang/objc/objc-class-method/TestObjCClassMethod.py b/lldb/test/API/lang/objc/objc-class-method/TestObjCClassMethod.py index 473d6241485e..879c6f40b1db 100644 --- a/lldb/test/API/lang/objc/objc-class-method/TestObjCClassMethod.py +++ b/lldb/test/API/lang/objc/objc-class-method/TestObjCClassMethod.py @@ -11,40 +11,41 @@ class TestObjCClassMethod(TestBase): # Call super's setUp(). TestBase.setUp(self) # Find the line numbers to break inside main(). - self.main_source = "class.m" - self.break_line = line_number(self.main_source, "// Set breakpoint here.") + self.main_source = lldb.SBFileSpec("class.m") + SHARED_BUILD_TESTCASE = False + NO_DEBUG_INFO_TESTCASE = True + + @skipUnlessDarwin + @skipUnlessCompilerSupports("-fobjc-msgsend-class-selector-stubs") @add_test_categories(["pyapi"]) - def test_with_python_api(self): + def test_without_class_stubs(self): + self.do_test_with_python_api("-fno-objc-msgsend-class-selector-stubs") + + @skipUnlessDarwin + @skipUnlessCompilerSupports("-fobjc-msgsend-class-selector-stubs") + @add_test_categories(["pyapi"]) + def test_using_class_stubs(self): + self.do_test_with_python_api("-fobjc-msgsend-class-selector-stubs") + + def do_test_with_python_api(self, compiler_flags): """Test calling functions in class methods.""" - self.build() - exe = self.getBuildArtifact("a.out") + d = {} + if len(compiler_flags): + d["CFLAGS_EXTRAS"] = compiler_flags - target = self.dbg.CreateTarget(exe) - self.assertTrue(target, VALID_TARGET) + self.build(dictionary=d) - bpt = target.BreakpointCreateByLocation(self.main_source, self.break_line) - self.assertTrue(bpt, VALID_BREAKPOINT) - - # Now launch the process, and do not stop at entry point. - process = target.LaunchSimple(None, None, self.get_process_working_directory()) - - self.assertTrue(process, PROCESS_IS_VALID) - - # The stop reason of the thread should be breakpoint. - thread_list = lldbutil.get_threads_stopped_at_breakpoint(process, bpt) - - # Make sure we stopped at the first breakpoint. - self.assertNotEqual(len(thread_list), 0, "No thread stopped at our breakpoint.") - self.assertEqual( - len(thread_list), 1, "More than one thread stopped at our breakpoint." + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Set a breakpoint here", self.main_source ) # Now make sure we can call a function in the class method we've # stopped in. - frame = thread_list[0].GetFrameAtIndex(0) + frame = thread.GetFrameAtIndex(0) self.assertTrue(frame, "Got a valid frame 0 frame.") + # First check that we can call a class method: cmd_value = frame.EvaluateExpression( '(int)[Foo doSomethingWithString:@"Hello"]' ) @@ -54,3 +55,10 @@ class TestObjCClassMethod(TestBase): print("cmd_value has the value %d" % cmd_value.GetValueAsUnsigned()) self.assertTrue(cmd_value.IsValid()) self.assertEqual(cmd_value.GetValueAsUnsigned(), 5) + + # Now check that we can step INTO class methods: + thread.StepInto() + frame = thread.GetFrameAtIndex(0) + self.assertEqual( + frame.name, "+[Foo doSomethingWithString:]", "Stopped in class method" + ) diff --git a/lldb/test/API/lang/objc/objc-class-method/class.m b/lldb/test/API/lang/objc/objc-class-method/class.m index 18a2c2729bea..01f61ffbd4a8 100644 --- a/lldb/test/API/lang/objc/objc-class-method/class.m +++ b/lldb/test/API/lang/objc/objc-class-method/class.m @@ -20,5 +20,6 @@ int main() { - return 0; // Set breakpoint here. + [Foo doSomethingWithString:@"Set a breakpoint here"]; + return 0; }