[Lldb-commits] [lldb] 833882b - Adapt the ObjC stepping algorithm to deal with "selector-stubs" in clang.
Jim Ingham via lldb-commits
lldb-commits at lists.llvm.org
Fri Apr 8 17:45:23 PDT 2022
Author: Jim Ingham
Date: 2022-04-08T17:45:16-07:00
New Revision: 833882b32701ce3713c9dd9afdedf1126db691f0
URL: https://github.com/llvm/llvm-project/commit/833882b32701ce3713c9dd9afdedf1126db691f0
DIFF: https://github.com/llvm/llvm-project/commit/833882b32701ce3713c9dd9afdedf1126db691f0.diff
LOG: Adapt the ObjC stepping algorithm to deal with "selector-stubs" in clang.
Clang is adding a feature to ObjC code generation, where instead of calling
objc_msgSend directly with an object & selector, it generates a stub that
gets passed only the object and the stub figures out the selector.
This patch adds support for following that dispatch method into the implementation
function.
Added:
Modified:
lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp
lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp
lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.h
lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.cpp
lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.h
lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.cpp
lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h
lldb/test/API/functionalities/data-formatter/data-formatter-objc/TestDataFormatterObjCKVO.py
Removed:
################################################################################
diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp
index b8104af5c1fa7..ccc68b55f5cb5 100644
--- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp
+++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp
@@ -1554,7 +1554,7 @@ AppleObjCRuntimeV2::DynamicClassInfoExtractor::GetClassInfoUtilityFunctionImpl(
if (!utility_fn_or_error) {
LLDB_LOG_ERROR(
log, utility_fn_or_error.takeError(),
- "Failed to get utility function for implementation lookup: {0}");
+ "Failed to get utility function for dynamic info extractor: {0}");
return {};
}
@@ -1684,7 +1684,7 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::
if (!utility_fn_or_error) {
LLDB_LOG_ERROR(
log, utility_fn_or_error.takeError(),
- "Failed to get utility function for implementation lookup: {0}");
+ "Failed to get utility function for shared class info extractor: {0}");
return nullptr;
}
diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp
index 7164a490078d8..f9ccaf0115a20 100644
--- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp
+++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp
@@ -33,6 +33,7 @@
#include "lldb/Utility/Log.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/ScopeExit.h"
#include "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h"
@@ -45,239 +46,138 @@ const char *AppleObjCTrampolineHandler::g_lookup_implementation_function_name =
"__lldb_objc_find_implementation_for_selector";
const char *AppleObjCTrampolineHandler::
g_lookup_implementation_with_stret_function_code =
- " \n\
-extern \"C\" \n\
-{ \n\
- extern void *class_getMethodImplementation(void *objc_class, void *sel); \n\
- extern void *class_getMethodImplementation_stret(void *objc_class, \n\
- void *sel); \n\
- extern void * object_getClass (id object); \n\
- extern void * sel_getUid(char *name); \n\
- extern int printf(const char *format, ...); \n\
-} \n\
-extern \"C\" void * __lldb_objc_find_implementation_for_selector ( \n\
- void *object, \n\
- void *sel, \n\
- int is_stret, \n\
- int is_super, \n\
- int is_super2, \n\
- int is_fixup, \n\
- int is_fixed, \n\
- int debug) \n\
-{ \n\
- struct __lldb_imp_return_struct \n\
- { \n\
- void *class_addr; \n\
- void *sel_addr; \n\
- void *impl_addr; \n\
- }; \n\
- \n\
- struct __lldb_objc_class { \n\
- void *isa; \n\
- void *super_ptr; \n\
- }; \n\
- struct __lldb_objc_super { \n\
- void *receiver; \n\
- struct __lldb_objc_class *class_ptr; \n\
- }; \n\
- struct __lldb_msg_ref { \n\
- void *dont_know; \n\
- void *sel; \n\
- }; \n\
- \n\
- struct __lldb_imp_return_struct return_struct; \n\
- \n\
- if (debug) \n\
- printf (\"\\n*** Called with obj: 0x%p sel: 0x%p is_stret: %d is_super: %d, \"\n\
- \"is_super2: %d, is_fixup: %d, is_fixed: %d\\n\", \n\
- object, sel, is_stret, is_super, is_super2, is_fixup, is_fixed);\n\
- if (is_super) \n\
- { \n\
- if (is_super2) \n\
- { \n\
- return_struct.class_addr = ((__lldb_objc_super *) object)->class_ptr->super_ptr;\n\
- } \n\
- else \n\
- { \n\
- return_struct.class_addr = ((__lldb_objc_super *) object)->class_ptr;\n\
- } \n\
- } \n\
- else \n\
- { \n\
- // This code seems a little funny, but has its reasons... \n\
- \n\
- // The call to [object class] is here because if this is a \n\
- // class, and has not been called into yet, we need to do \n\
- // something to force the class to initialize itself. \n\
- // Then the call to object_getClass will actually return the \n\
- // correct class, either the class if object is a class \n\
- // instance, or the meta-class if it is a class pointer. \n\
- void *class_ptr = (void *) [(id) object class]; \n\
- return_struct.class_addr = (id) object_getClass((id) object); \n\
- if (debug) \n\
- { \n\
- if (class_ptr == object) \n\
- { \n\
- printf (\"Found a class object, need to use the meta class %p -> %p\\n\",\n\
- class_ptr, return_struct.class_addr); \n\
- } \n\
- else \n\
- { \n\
- printf (\"[object class] returned: %p object_getClass: %p.\\n\", \n\
- class_ptr, return_struct.class_addr); \n\
- } \n\
- } \n\
- } \n\
- \n\
- if (is_fixup) \n\
- { \n\
- if (is_fixed) \n\
- { \n\
- return_struct.sel_addr = ((__lldb_msg_ref *) sel)->sel; \n\
- } \n\
- else \n\
- { \n\
- char *sel_name = (char *) ((__lldb_msg_ref *) sel)->sel; \n\
- return_struct.sel_addr = sel_getUid (sel_name); \n\
- if (debug) \n\
- printf (\"\\n*** Got fixed up selector: %p for name %s.\\n\",\n\
- return_struct.sel_addr, sel_name); \n\
- } \n\
- } \n\
- else \n\
- { \n\
- return_struct.sel_addr = sel; \n\
- } \n\
- \n\
- if (is_stret) \n\
- { \n\
- return_struct.impl_addr = \n\
- class_getMethodImplementation_stret (return_struct.class_addr, \n\
- return_struct.sel_addr); \n\
- } \n\
- else \n\
- { \n\
- return_struct.impl_addr = \n\
- class_getMethodImplementation (return_struct.class_addr, \n\
- return_struct.sel_addr); \n\
- } \n\
- if (debug) \n\
- printf (\"\\n*** Returning implementation: %p.\\n\", \n\
- return_struct.impl_addr); \n\
- \n\
- return return_struct.impl_addr; \n\
-} \n\
-";
+ R"(
+ if (is_stret) {
+ return_struct.impl_addr =
+ class_getMethodImplementation_stret (return_struct.class_addr,
+ return_struct.sel_addr);
+ } else {
+ return_struct.impl_addr =
+ class_getMethodImplementation (return_struct.class_addr,
+ return_struct.sel_addr);
+ }
+ if (debug)
+ printf ("\n*** Returning implementation: %p.\n",
+ return_struct.impl_addr);
+
+ return return_struct.impl_addr;
+}
+)";
const char *
AppleObjCTrampolineHandler::g_lookup_implementation_no_stret_function_code =
- " \n\
-extern \"C\" \n\
-{ \n\
- extern void *class_getMethodImplementation(void *objc_class, void *sel); \n\
- extern void * object_getClass (id object); \n\
- extern void * sel_getUid(char *name); \n\
- extern int printf(const char *format, ...); \n\
-} \n\
-extern \"C\" void * __lldb_objc_find_implementation_for_selector (void *object, \n\
- void *sel, \n\
- int is_stret, \n\
- int is_super, \n\
- int is_super2, \n\
- int is_fixup, \n\
- int is_fixed, \n\
- int debug) \n\
-{ \n\
- struct __lldb_imp_return_struct \n\
- { \n\
- void *class_addr; \n\
- void *sel_addr; \n\
- void *impl_addr; \n\
- }; \n\
- \n\
- struct __lldb_objc_class { \n\
- void *isa; \n\
- void *super_ptr; \n\
- }; \n\
- struct __lldb_objc_super { \n\
- void *receiver; \n\
- struct __lldb_objc_class *class_ptr; \n\
- }; \n\
- struct __lldb_msg_ref { \n\
- void *dont_know; \n\
- void *sel; \n\
- }; \n\
- \n\
- struct __lldb_imp_return_struct return_struct; \n\
- \n\
- if (debug) \n\
- printf (\"\\n*** Called with obj: 0x%p sel: 0x%p is_stret: %d is_super: %d, \" \n\
- \"is_super2: %d, is_fixup: %d, is_fixed: %d\\n\", \n\
- object, sel, is_stret, is_super, is_super2, is_fixup, is_fixed); \n\
- if (is_super) \n\
- { \n\
- if (is_super2) \n\
- { \n\
- return_struct.class_addr = ((__lldb_objc_super *) object)->class_ptr->super_ptr; \n\
- } \n\
- else \n\
- { \n\
- return_struct.class_addr = ((__lldb_objc_super *) object)->class_ptr; \n\
- } \n\
- } \n\
- else \n\
- { \n\
- // This code seems a little funny, but has its reasons... \n\
- // The call to [object class] is here because if this is a class, and has not been called into \n\
- // yet, we need to do something to force the class to initialize itself. \n\
- // Then the call to object_getClass will actually return the correct class, either the class \n\
- // if object is a class instance, or the meta-class if it is a class pointer. \n\
- void *class_ptr = (void *) [(id) object class]; \n\
- return_struct.class_addr = (id) object_getClass((id) object); \n\
- if (debug) \n\
- { \n\
- if (class_ptr == object) \n\
- { \n\
- printf (\"Found a class object, need to return the meta class %p -> %p\\n\", \n\
- class_ptr, return_struct.class_addr); \n\
- } \n\
- else \n\
- { \n\
- printf (\"[object class] returned: %p object_getClass: %p.\\n\", \n\
- class_ptr, return_struct.class_addr); \n\
- } \n\
- } \n\
- } \n\
- \n\
- if (is_fixup) \n\
- { \n\
- if (is_fixed) \n\
- { \n\
- return_struct.sel_addr = ((__lldb_msg_ref *) sel)->sel; \n\
- } \n\
- else \n\
- { \n\
- char *sel_name = (char *) ((__lldb_msg_ref *) sel)->sel; \n\
- return_struct.sel_addr = sel_getUid (sel_name); \n\
- if (debug) \n\
- printf (\"\\n*** Got fixed up selector: %p for name %s.\\n\",\n\
- return_struct.sel_addr, sel_name); \n\
- } \n\
- } \n\
- else \n\
- { \n\
- return_struct.sel_addr = sel; \n\
- } \n\
- \n\
- return_struct.impl_addr = \n\
- class_getMethodImplementation (return_struct.class_addr, \n\
- return_struct.sel_addr); \n\
- if (debug) \n\
- printf (\"\\n*** Returning implementation: 0x%p.\\n\", \n\
- return_struct.impl_addr); \n\
- \n\
- return return_struct.impl_addr; \n\
-} \n\
-";
+ R"(
+ return_struct.impl_addr =
+ class_getMethodImplementation (return_struct.class_addr,
+ return_struct.sel_addr);
+ if (debug)
+ printf ("\n*** getMethodImpletation for addr: 0x%p sel: 0x%p result: 0x%p.\n",
+ return_struct.class_addr, return_struct.sel_addr, return_struct.impl_addr);
+
+ return return_struct.impl_addr;
+}
+)";
+
+const char
+ *AppleObjCTrampolineHandler::g_lookup_implementation_function_common_code =
+ R"(
+extern "C"
+{
+ extern void *class_getMethodImplementation(void *objc_class, void *sel);
+ extern void *class_getMethodImplementation_stret(void *objc_class, void *sel);
+ extern void * object_getClass (id object);
+ extern void * sel_getUid(char *name);
+ extern int printf(const char *format, ...);
+}
+extern "C" void *
+__lldb_objc_find_implementation_for_selector (void *object,
+ void *sel,
+ int is_str_ptr,
+ int is_stret,
+ int is_super,
+ int is_super2,
+ int is_fixup,
+ int is_fixed,
+ int debug)
+{
+ struct __lldb_imp_return_struct {
+ void *class_addr;
+ void *sel_addr;
+ void *impl_addr;
+ };
+
+ struct __lldb_objc_class {
+ void *isa;
+ void *super_ptr;
+ };
+ struct __lldb_objc_super {
+ void *receiver;
+ struct __lldb_objc_class *class_ptr;
+ };
+ struct __lldb_msg_ref {
+ void *dont_know;
+ void *sel;
+ };
+
+ struct __lldb_imp_return_struct return_struct;
+
+ if (debug)
+ printf ("\n*** Called with obj: %p sel: %p is_str_ptr: %d "
+ "is_stret: %d is_super: %d, "
+ "is_super2: %d, is_fixup: %d, is_fixed: %d\n",
+ object, sel, is_str_ptr, is_stret,
+ is_super, is_super2, is_fixup, is_fixed);
+
+ if (is_str_ptr) {
+ if (debug)
+ printf("*** Turning string: '%s'", sel);
+ sel = sel_getUid((char *)sel);
+ if (debug)
+ printf("*** into sel to %p", sel);
+ }
+ if (is_super) {
+ if (is_super2) {
+ return_struct.class_addr
+ = ((__lldb_objc_super *) object)->class_ptr->super_ptr;
+ } else {
+ return_struct.class_addr = ((__lldb_objc_super *) object)->class_ptr;
+ }
+ if (debug)
+ printf("*** Super, class addr: %p\n", return_struct.class_addr);
+ } else {
+ // This code seems a little funny, but has its reasons...
+ // The call to [object class] is here because if this is a class, and has
+ // not been called into yet, we need to do something to force the class to
+ // initialize itself.
+ // Then the call to object_getClass will actually return the correct class,
+ // either the class if object is a class instance, or the meta-class if it
+ // is a class pointer.
+ void *class_ptr = (void *) [(id) object class];
+ return_struct.class_addr = (id) object_getClass((id) object);
+ if (debug) {
+ if (class_ptr == object) {
+ printf ("Found a class object, need to return the meta class %p -> %p\n",
+ class_ptr, return_struct.class_addr);
+ } else {
+ printf ("[object class] returned: %p object_getClass: %p.\n",
+ class_ptr, return_struct.class_addr);
+ }
+ }
+ }
+
+ if (is_fixup) {
+ if (is_fixed) {
+ return_struct.sel_addr = ((__lldb_msg_ref *) sel)->sel;
+ } else {
+ char *sel_name = (char *) ((__lldb_msg_ref *) sel)->sel;
+ return_struct.sel_addr = sel_getUid (sel_name);
+ if (debug)
+ printf ("\n*** Got fixed up selector: %p for name %s.\n",
+ return_struct.sel_addr, sel_name);
+ }
+ } else {
+ return_struct.sel_addr = sel;
+ }
+)";
AppleObjCTrampolineHandler::AppleObjCVTables::VTableRegion::VTableRegion(
AppleObjCVTables *owner, lldb::addr_t header_addr)
@@ -676,7 +576,6 @@ const char *AppleObjCTrampolineHandler::g_opt_dispatch_names[] = {
AppleObjCTrampolineHandler::AppleObjCTrampolineHandler(
const ProcessSP &process_sp, const ModuleSP &objc_module_sp)
: m_process_wp(), m_objc_module_sp(objc_module_sp),
- m_lookup_implementation_function_code(nullptr),
m_impl_fn_addr(LLDB_INVALID_ADDRESS),
m_impl_stret_fn_addr(LLDB_INVALID_ADDRESS),
m_msg_forward_addr(LLDB_INVALID_ADDRESS) {
@@ -729,17 +628,24 @@ AppleObjCTrampolineHandler::AppleObjCTrampolineHandler(
get_impl_name.AsCString());
}
return;
- } else if (m_impl_stret_fn_addr == LLDB_INVALID_ADDRESS) {
+ }
+
+ // We will either set the implementation to the _stret or non_stret version,
+ // so either way it's safe to start filling the m_lookup_..._code here.
+ m_lookup_implementation_function_code.assign(
+ g_lookup_implementation_function_common_code);
+
+ if (m_impl_stret_fn_addr == LLDB_INVALID_ADDRESS) {
// It there is no stret return lookup function, assume that it is the same
// as the straight lookup:
m_impl_stret_fn_addr = m_impl_fn_addr;
// Also we will use the version of the lookup code that doesn't rely on the
// stret version of the function.
- m_lookup_implementation_function_code =
- g_lookup_implementation_no_stret_function_code;
+ m_lookup_implementation_function_code.append(
+ g_lookup_implementation_no_stret_function_code);
} else {
- m_lookup_implementation_function_code =
- g_lookup_implementation_with_stret_function_code;
+ m_lookup_implementation_function_code.append(
+ g_lookup_implementation_with_stret_function_code);
}
// Look up the addresses for the objc dispatch functions and cache
@@ -806,7 +712,7 @@ AppleObjCTrampolineHandler::SetupDispatchFunction(Thread &thread,
// First stage is to make the ClangUtility to hold our injected function:
if (!m_impl_code) {
- if (m_lookup_implementation_function_code != nullptr) {
+ if (!m_lookup_implementation_function_code.empty()) {
auto utility_fn_or_error = exe_ctx.GetTargetRef().CreateUtilityFunction(
m_lookup_implementation_function_code,
g_lookup_implementation_function_name, eLanguageTypeC, exe_ctx);
@@ -891,13 +797,43 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
DispatchFunction vtable_dispatch = {"vtable", false, false, false,
DispatchFunction::eFixUpFixed};
-
- // First step is to look and see if we are in one of the known ObjC
+ // The selector specific stubs are a wrapper for objc_msgSend. They don't get
+ // passed a SEL, but instead the selector string is encoded in the stub
+ // name, in the form:
+ // objc_msgSend$SelectorName
+ // and the stub figures out the uniqued selector. If we find ourselves in
+ // 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$<SELECTOR>, so see if the current
+ // function has that name:
+ Address func_addr;
+ Target &target = thread.GetProcess()->GetTarget();
+ llvm::StringRef sym_name;
+ const DispatchFunction *this_dispatch = nullptr;
+
+ if (target.ResolveLoadAddress(curr_pc, func_addr)) {
+ 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();
+ // 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.
- const DispatchFunction *this_dispatch = FindDispatchFunction(curr_pc);
-
+ if (!in_selector_stub)
+ this_dispatch = FindDispatchFunction(curr_pc);
+
// Next check to see if we are in a vtable region:
if (!this_dispatch && m_vtables_up) {
@@ -910,11 +846,15 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
}
}
+ // Since we set this_dispatch in both the vtable & sel specific stub cases
+ // this if will be used for all three of those cases.
if (this_dispatch) {
Log *log = GetLog(LLDBLog::Step);
// We are decoding a method dispatch. First job is to pull the
- // arguments out:
+ // arguments out. If we are in a regular stub, we get self & selector,
+ // but if we are in a selector-specific stub, we'll have to get that from
+ // the string sym_name.
lldb::StackFrameSP thread_cur_frame = thread.GetStackFrameAtIndex(0);
@@ -944,11 +884,17 @@ 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 (this_dispatch->stret_return) {
+ // 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) {
obj_index = 1;
sel_index = 2;
argument_values.PushValue(void_ptr_value);
@@ -975,15 +921,17 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
}
ExecutionContext exe_ctx(thread.shared_from_this());
- Process *process = exe_ctx.GetProcessPtr();
// isa_addr will store the class pointer that the method is being
// dispatched to - so either the class directly or the super class
// if this is one of the objc_msgSendSuper flavors. That's mostly
// used to look up the class/selector pair in our cache.
lldb::addr_t isa_addr = LLDB_INVALID_ADDRESS;
- lldb::addr_t sel_addr =
- argument_values.GetValueAtIndex(sel_index)->GetScalar().ULongLong();
+ 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();
// 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
@@ -998,14 +946,14 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
// to dig the super out of the class and use that.
Value super_value(*(argument_values.GetValueAtIndex(obj_index)));
- super_value.GetScalar() += process->GetAddressByteSize();
+ super_value.GetScalar() += process_sp->GetAddressByteSize();
super_value.ResolveValue(&exe_ctx);
if (super_value.GetScalar().IsValid()) {
// isa_value now holds the class pointer. The second word of the
// class pointer is the super-class pointer:
- super_value.GetScalar() += process->GetAddressByteSize();
+ super_value.GetScalar() += process_sp->GetAddressByteSize();
super_value.ResolveValue(&exe_ctx);
if (super_value.GetScalar().IsValid())
isa_addr = super_value.GetScalar().ULongLong();
@@ -1024,7 +972,7 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
// this structure.
Value super_value(*(argument_values.GetValueAtIndex(obj_index)));
- super_value.GetScalar() += process->GetAddressByteSize();
+ super_value.GetScalar() += process_sp->GetAddressByteSize();
super_value.ResolveValue(&exe_ctx);
if (super_value.GetScalar().IsValid()) {
@@ -1060,20 +1008,22 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
// Okay, we've got the address of the class for which we're resolving this,
// let's see if it's in our cache:
lldb::addr_t impl_addr = LLDB_INVALID_ADDRESS;
-
+ // If this is a regular dispatch, look up the sel in our addr to sel cache:
if (isa_addr != LLDB_INVALID_ADDRESS) {
- if (log) {
- LLDB_LOGF(log,
- "Resolving call for class - 0x%" PRIx64
- " and selector - 0x%" PRIx64,
- isa_addr, sel_addr);
- }
ObjCLanguageRuntime *objc_runtime =
ObjCLanguageRuntime::Get(*thread.GetProcess());
assert(objc_runtime != nullptr);
-
- impl_addr = objc_runtime->LookupInMethodCache(isa_addr, sel_addr);
+ 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);
+ }
}
+ // If it is a selector-specific stub dispatch, look in the string cache:
if (impl_addr != LLDB_INVALID_ADDRESS) {
// Yup, it was in the cache, so we can run to that address directly.
@@ -1091,20 +1041,52 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
ValueList dispatch_values;
// We've will inject a little function in the target that takes the
- // object, selector and some flags,
+ // object, selector/selector string and some flags,
// and figures out the implementation. Looks like:
// void *__lldb_objc_find_implementation_for_selector (void *object,
// void *sel,
+ // int
+ // is_str_ptr,
// int is_stret,
// int is_super,
// int is_super2,
// int is_fixup,
// int is_fixed,
// int debug)
+ // If we don't have an actual SEL, but rather a string version of the
+ // selector WE injected, set is_str_ptr to true, and sel to the address
+ // of the string.
// So set up the arguments for that call.
dispatch_values.PushValue(*(argument_values.GetValueAtIndex(obj_index)));
- dispatch_values.PushValue(*(argument_values.GetValueAtIndex(sel_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);
+ }
Value flag_value;
CompilerType clang_int_type =
@@ -1114,6 +1096,12 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
// 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;
+ dispatch_values.PushValue(flag_value);
+
if (this_dispatch->stret_return)
flag_value.GetScalar() = 1;
else
@@ -1158,7 +1146,8 @@ AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread,
dispatch_values.PushValue(flag_value);
ret_plan_sp = std::make_shared<AppleThreadPlanStepThroughObjCTrampoline>(
- thread, *this, dispatch_values, isa_addr, sel_addr);
+ thread, *this, dispatch_values, isa_addr, sel_addr, sel_str_addr,
+ sym_name);
if (log) {
StreamString s;
ret_plan_sp->GetDescription(&s, eDescriptionLevelFull);
diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.h b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.h
index 546b500b45293..a6e1e16d1ee06 100644
--- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.h
+++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.h
@@ -38,11 +38,11 @@ class AppleObjCTrampolineHandler {
public:
enum FixUpState { eFixUpNone, eFixUpFixed, eFixUpToFix };
- const char *name;
- bool stret_return;
- bool is_super;
- bool is_super2;
- FixUpState fixedup;
+ const char *name = nullptr;
+ bool stret_return = false;
+ bool is_super = false;
+ bool is_super2 = false;
+ FixUpState fixedup = eFixUpNone;
};
lldb::addr_t SetupDispatchFunction(Thread &thread,
@@ -52,9 +52,19 @@ class AppleObjCTrampolineHandler {
const DispatchFunction &)>);
private:
+ /// These hold the code for the function that finds the implementation of
+ /// an ObjC message send given the class & selector and the kind of dispatch.
+ /// There are two variants depending on whether the platform uses a separate
+ /// _stret passing convention (e.g. Intel) or not (e.g. ARM). The
diff erence
+ /// is only at the very end of the function, so the code is broken into the
+ /// common prefix and the suffix, which get composed appropriately before
+ /// the function gets compiled.
+ /// \{
static const char *g_lookup_implementation_function_name;
+ static const char *g_lookup_implementation_function_common_code;
static const char *g_lookup_implementation_with_stret_function_code;
static const char *g_lookup_implementation_no_stret_function_code;
+ /// \}
class AppleObjCVTables {
public:
@@ -144,7 +154,7 @@ class AppleObjCTrampolineHandler {
MsgsendMap m_opt_dispatch_map;
lldb::ProcessWP m_process_wp;
lldb::ModuleSP m_objc_module_sp;
- const char *m_lookup_implementation_function_code;
+ std::string m_lookup_implementation_function_code;
std::unique_ptr<UtilityFunction> m_impl_code;
std::mutex m_impl_function_mutex;
lldb::addr_t m_impl_fn_addr;
diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.cpp
index b21251877f05d..547a1f6db9761 100644
--- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.cpp
+++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.cpp
@@ -32,13 +32,15 @@ using namespace lldb_private;
AppleThreadPlanStepThroughObjCTrampoline::
AppleThreadPlanStepThroughObjCTrampoline(
Thread &thread, AppleObjCTrampolineHandler &trampoline_handler,
- ValueList &input_values, lldb::addr_t isa_addr, lldb::addr_t sel_addr)
+ ValueList &input_values, lldb::addr_t isa_addr, lldb::addr_t sel_addr,
+ lldb::addr_t sel_str_addr, llvm::StringRef sel_str)
: ThreadPlan(ThreadPlan::eKindGeneric,
"MacOSX Step through ObjC Trampoline", thread, eVoteNoOpinion,
eVoteNoOpinion),
m_trampoline_handler(trampoline_handler),
m_args_addr(LLDB_INVALID_ADDRESS), m_input_values(input_values),
- m_isa_addr(isa_addr), m_sel_addr(sel_addr), m_impl_function(nullptr) {}
+ m_isa_addr(isa_addr), m_sel_addr(sel_addr), m_impl_function(nullptr),
+ m_sel_str_addr(sel_str_addr), m_sel_str(sel_str) {}
// Destructor
AppleThreadPlanStepThroughObjCTrampoline::
@@ -126,8 +128,10 @@ bool AppleThreadPlanStepThroughObjCTrampoline::ShouldStop(Event *event_ptr) {
}
}
- // Second stage, if all went well with the function calling, then fetch the
- // target address, and queue up a "run to that address" plan.
+ // Second stage, if all went well with the function calling, get the
+ // implementation function address, and queue up a "run to that address" plan.
+ Log *log = GetLog(LLDBLog::Step);
+
if (!m_run_to_sp) {
Value target_addr_value;
ExecutionContext exc_ctx;
@@ -142,7 +146,6 @@ bool AppleThreadPlanStepThroughObjCTrampoline::ShouldStop(Event *event_ptr) {
}
Address target_so_addr;
target_so_addr.SetOpcodeLoadAddress(target_addr, exc_ctx.GetTargetPtr());
- Log *log = GetLog(LLDBLog::Step);
if (target_addr == 0) {
LLDB_LOGF(log, "Got target implementation of 0x0, stopping.");
SetPlanComplete();
@@ -174,13 +177,25 @@ bool AppleThreadPlanStepThroughObjCTrampoline::ShouldStop(Event *event_ptr) {
ObjCLanguageRuntime *objc_runtime =
ObjCLanguageRuntime::Get(*GetThread().GetProcess());
assert(objc_runtime != nullptr);
- objc_runtime->AddToMethodCache(m_isa_addr, m_sel_addr, target_addr);
- LLDB_LOGF(log,
- "Adding {isa-addr=0x%" PRIx64 ", sel-addr=0x%" PRIx64
- "} = addr=0x%" PRIx64 " to cache.",
- m_isa_addr, m_sel_addr, target_addr);
-
- // Extract the target address from the value:
+ if (m_sel_str_addr != LLDB_INVALID_ADDRESS) {
+ // Cache the string -> implementation and free the string in the target.
+ Status dealloc_error =
+ GetThread().GetProcess()->DeallocateMemory(m_sel_str_addr);
+ // For now just log this:
+ if (dealloc_error.Fail())
+ LLDB_LOG(log, "Failed to deallocate the sel str at {0} - error: {1}",
+ m_sel_str_addr, dealloc_error);
+ objc_runtime->AddToMethodCache(m_isa_addr, m_sel_str, target_addr);
+ LLDB_LOG(log,
+ "Adding \\{isa-addr={0}, sel-addr={1}\\} = addr={2} to cache.",
+ m_isa_addr, m_sel_str, target_addr);
+ } else {
+ objc_runtime->AddToMethodCache(m_isa_addr, m_sel_addr, target_addr);
+ LLDB_LOGF(log,
+ "Adding {isa-addr=0x%" PRIx64 ", sel-addr=0x%" PRIx64
+ "} = addr=0x%" PRIx64 " to cache.",
+ m_isa_addr, m_sel_addr, target_addr);
+ }
m_run_to_sp = std::make_shared<ThreadPlanRunToAddress>(
GetThread(), target_so_addr, false);
diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.h b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.h
index b5b45079094c2..ba4fb5ace1eda 100644
--- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.h
+++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.h
@@ -24,7 +24,8 @@ class AppleThreadPlanStepThroughObjCTrampoline : public ThreadPlan {
public:
AppleThreadPlanStepThroughObjCTrampoline(
Thread &thread, AppleObjCTrampolineHandler &trampoline_handler,
- ValueList &values, lldb::addr_t isa_addr, lldb::addr_t sel_addr);
+ ValueList &values, lldb::addr_t isa_addr, lldb::addr_t sel_addr,
+ lldb::addr_t sel_str_addr, llvm::StringRef sel_str);
~AppleThreadPlanStepThroughObjCTrampoline() override;
@@ -70,6 +71,13 @@ class AppleThreadPlanStepThroughObjCTrampoline : public ThreadPlan {
FunctionCaller *m_impl_function; /// This is a pointer to a impl function that
/// is owned by the client that pushes this
/// plan.
+ lldb::addr_t m_sel_str_addr; /// If this is not LLDB_INVALID_ADDRESS then it
+ /// is the address we wrote the selector string
+ /// to. We need to deallocate it when the
+ /// function call is done.
+ std::string m_sel_str; /// This is the string we wrote to memory - we
+ /// use it for caching, but only if
+ /// m_sel_str_addr is non-null.
};
class AppleThreadPlanStepThroughDirectDispatch: public ThreadPlanStepOut {
diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.cpp
index cba34f1695632..b387de38d3bf0 100644
--- a/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.cpp
+++ b/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.cpp
@@ -37,7 +37,7 @@ char ObjCLanguageRuntime::ID = 0;
ObjCLanguageRuntime::~ObjCLanguageRuntime() = default;
ObjCLanguageRuntime::ObjCLanguageRuntime(Process *process)
- : LanguageRuntime(process), m_impl_cache(),
+ : LanguageRuntime(process), m_impl_cache(), m_impl_str_cache(),
m_has_new_literals_and_indexing(eLazyBoolCalculate),
m_isa_to_descriptor(), m_hash_to_isa_map(), m_type_size_cache(),
m_isa_to_descriptor_stop_id(UINT32_MAX), m_complete_class_cache(),
@@ -75,6 +75,18 @@ void ObjCLanguageRuntime::AddToMethodCache(lldb::addr_t class_addr,
ClassAndSel(class_addr, selector), impl_addr));
}
+void ObjCLanguageRuntime::AddToMethodCache(lldb::addr_t class_addr,
+ llvm::StringRef sel_str,
+ lldb::addr_t impl_addr) {
+ Log *log = GetLog(LLDBLog::Step);
+
+ LLDB_LOG(log, "Caching: class {0} selector {1} implementation {2}.",
+ class_addr, sel_str, impl_addr);
+
+ m_impl_str_cache.insert(std::pair<ClassAndSelStr, lldb::addr_t>(
+ ClassAndSelStr(class_addr, sel_str), impl_addr));
+}
+
lldb::addr_t ObjCLanguageRuntime::LookupInMethodCache(lldb::addr_t class_addr,
lldb::addr_t selector) {
MsgImplMap::iterator pos, end = m_impl_cache.end();
@@ -84,6 +96,15 @@ lldb::addr_t ObjCLanguageRuntime::LookupInMethodCache(lldb::addr_t class_addr,
return LLDB_INVALID_ADDRESS;
}
+lldb::addr_t ObjCLanguageRuntime::LookupInMethodCache(lldb::addr_t class_addr,
+ llvm::StringRef sel_str) {
+ MsgImplStrMap::iterator pos, end = m_impl_str_cache.end();
+ pos = m_impl_str_cache.find(ClassAndSelStr(class_addr, sel_str));
+ if (pos != end)
+ return (*pos).second;
+ return LLDB_INVALID_ADDRESS;
+}
+
lldb::TypeSP
ObjCLanguageRuntime::LookupInCompleteClassCache(ConstString &name) {
CompleteClassMap::iterator complete_class_iter =
diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h b/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h
index 15fce04ea4652..9431b42502406 100644
--- a/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h
+++ b/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h
@@ -22,6 +22,7 @@
#include "lldb/Symbol/CompilerType.h"
#include "lldb/Symbol/Type.h"
#include "lldb/Target/LanguageRuntime.h"
+#include "lldb/Utility/ConstString.h"
#include "lldb/lldb-private.h"
class CommandObjectObjC_ClassTable_Dump;
@@ -242,11 +243,19 @@ class ObjCLanguageRuntime : public LanguageRuntime {
virtual bool HasReadObjCLibrary() = 0;
+ // These two methods actually use
diff erent caches. The only time we'll
+ // cache a sel_str is if we found a "selector specific stub" for the selector
+ // and conversely we only add to the SEL cache if we saw a regular dispatch.
lldb::addr_t LookupInMethodCache(lldb::addr_t class_addr, lldb::addr_t sel);
+ lldb::addr_t LookupInMethodCache(lldb::addr_t class_addr,
+ llvm::StringRef sel_str);
void AddToMethodCache(lldb::addr_t class_addr, lldb::addr_t sel,
lldb::addr_t impl_addr);
+ void AddToMethodCache(lldb::addr_t class_addr, llvm::StringRef sel_str,
+ lldb::addr_t impl_addr);
+
TypeAndOrName LookupInClassNameCache(lldb::addr_t class_addr);
void AddToClassNameCache(lldb::addr_t class_addr, const char *name,
@@ -343,20 +352,22 @@ class ObjCLanguageRuntime : public LanguageRuntime {
}
private:
- // We keep a map of <Class,Selector>->Implementation so we don't have to call
- // the resolver function over and over.
+ // We keep two maps of <Class,Selector>->Implementation so we don't have
+ // to call the resolver function over and over.
+ // The first comes from regular obj_msgSend type dispatch, and maps the
+ // class + uniqued SEL value to an implementation.
+ // The second comes from the "selector-specific stubs", which are always
+ // of the form _objc_msgSend$SelectorName, so we don't know the uniqued
+ // selector, only the string name.
// FIXME: We need to watch for the loading of Protocols, and flush the cache
// for any
// class that we see so changed.
struct ClassAndSel {
- ClassAndSel() {
- sel_addr = LLDB_INVALID_ADDRESS;
- class_addr = LLDB_INVALID_ADDRESS;
- }
+ ClassAndSel() = default;
- ClassAndSel(lldb::addr_t in_sel_addr, lldb::addr_t in_class_addr)
+ ClassAndSel(lldb::addr_t in_class_addr, lldb::addr_t in_sel_addr)
: class_addr(in_class_addr), sel_addr(in_sel_addr) {}
bool operator==(const ClassAndSel &rhs) {
@@ -379,11 +390,35 @@ class ObjCLanguageRuntime : public LanguageRuntime {
}
}
- lldb::addr_t class_addr;
- lldb::addr_t sel_addr;
+ lldb::addr_t class_addr = LLDB_INVALID_ADDRESS;
+ lldb::addr_t sel_addr = LLDB_INVALID_ADDRESS;
+ };
+
+ struct ClassAndSelStr {
+ ClassAndSelStr() = default;
+
+ ClassAndSelStr(lldb::addr_t in_class_addr, llvm::StringRef in_sel_name)
+ : class_addr(in_class_addr), sel_name(in_sel_name) {}
+
+ bool operator==(const ClassAndSelStr &rhs) {
+ return class_addr == rhs.class_addr && sel_name == rhs.sel_name;
+ }
+
+ bool operator<(const ClassAndSelStr &rhs) const {
+ if (class_addr < rhs.class_addr)
+ return true;
+ else if (class_addr > rhs.class_addr)
+ return false;
+ else
+ return ConstString::Compare(sel_name, rhs.sel_name);
+ }
+
+ lldb::addr_t class_addr = LLDB_INVALID_ADDRESS;
+ ConstString sel_name;
};
typedef std::map<ClassAndSel, lldb::addr_t> MsgImplMap;
+ typedef std::map<ClassAndSelStr, lldb::addr_t> MsgImplStrMap;
typedef std::map<ObjCISA, ClassDescriptorSP> ISAToDescriptorMap;
typedef std::multimap<uint32_t, ObjCISA> HashToISAMap;
typedef ISAToDescriptorMap::iterator ISAToDescriptorIterator;
@@ -391,6 +426,7 @@ class ObjCLanguageRuntime : public LanguageRuntime {
typedef ThreadSafeDenseMap<void *, uint64_t> TypeSizeCache;
MsgImplMap m_impl_cache;
+ MsgImplStrMap m_impl_str_cache;
LazyBool m_has_new_literals_and_indexing;
ISAToDescriptorMap m_isa_to_descriptor;
HashToISAMap m_hash_to_isa_map;
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-objc/TestDataFormatterObjCKVO.py b/lldb/test/API/functionalities/data-formatter/data-formatter-objc/TestDataFormatterObjCKVO.py
index fe4e3ed0e1eca..36727d37c57e5 100644
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-objc/TestDataFormatterObjCKVO.py
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-objc/TestDataFormatterObjCKVO.py
@@ -57,7 +57,7 @@ def cleanup():
' 21 key/value pairs'
])
- lldbutil.run_break_set_by_regexp(self, 'setAtoms')
+ lldbutil.run_break_set_by_symbol(self, '-[Molecule setAtoms:]')
self.runCmd("continue")
self.expect("frame variable _cmd", substrs=['setAtoms:'])
More information about the lldb-commits
mailing list