forked from OSchip/llvm-project
Adding support for fetching the Dynamic Value for ObjC Objects.
llvm-svn: 130701
This commit is contained in:
parent
f4258eb484
commit
61be0903e5
|
@ -138,11 +138,7 @@ public:
|
|||
}
|
||||
|
||||
void
|
||||
SetUpdated ()
|
||||
{
|
||||
m_first_update = false;
|
||||
m_needs_update = false;
|
||||
}
|
||||
SetUpdated ();
|
||||
|
||||
bool
|
||||
NeedsUpdating()
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include "lldb/lldb-private.h"
|
||||
#include "lldb/Core/ClangForward.h"
|
||||
#include "lldb/Core/ConstString.h"
|
||||
#include "lldb/Core/UserID.h"
|
||||
#include "lldb/Symbol/Declaration.h"
|
||||
#include <set>
|
||||
|
@ -250,6 +251,48 @@ protected:
|
|||
ResolveClangType (ResolveState clang_type_resolve_state);
|
||||
};
|
||||
|
||||
|
||||
///
|
||||
/// Sometimes you can find the name of the type corresponding to an object, but we don't have debug
|
||||
/// information for it. If that is the case, you can return one of these objects, and then if it
|
||||
/// has a full type, you can use that, but if not at least you can print the name for informational
|
||||
/// purposes.
|
||||
///
|
||||
|
||||
class TypeAndOrName
|
||||
{
|
||||
public:
|
||||
TypeAndOrName ();
|
||||
TypeAndOrName (lldb::TypeSP &type_sp);
|
||||
TypeAndOrName (const char *type_str);
|
||||
TypeAndOrName (const TypeAndOrName &rhs);
|
||||
TypeAndOrName (ConstString &type_const_string);
|
||||
|
||||
TypeAndOrName &
|
||||
operator= (const TypeAndOrName &rhs);
|
||||
|
||||
ConstString GetName () const;
|
||||
lldb::TypeSP GetTypeSP () const {
|
||||
return m_type_sp;
|
||||
};
|
||||
|
||||
void
|
||||
SetName (ConstString &type_name_const_str);
|
||||
|
||||
void
|
||||
SetName (const char *type_name_str);
|
||||
|
||||
void
|
||||
SetTypeSP (lldb::TypeSP type_sp);
|
||||
|
||||
bool
|
||||
IsEmpty ();
|
||||
|
||||
private:
|
||||
lldb::TypeSP m_type_sp;
|
||||
ConstString m_type_name;
|
||||
};
|
||||
|
||||
} // namespace lldb_private
|
||||
|
||||
#endif // liblldb_Type_h_
|
||||
|
|
|
@ -43,7 +43,7 @@ public:
|
|||
GetObjectDescription (Stream &str, Value &value, ExecutionContextScope *exe_scope) = 0;
|
||||
|
||||
virtual bool
|
||||
GetDynamicValue (ValueObject &in_value, lldb::TypeSP &type_sp, Address &address) = 0;
|
||||
GetDynamicTypeAndAddress (ValueObject &in_value, TypeAndOrName &class_type_or_name, Address &address) = 0;
|
||||
|
||||
// This should be a fast test to determine whether it is likely that this value would
|
||||
// have a dynamic type.
|
||||
|
|
|
@ -55,6 +55,15 @@ public:
|
|||
void
|
||||
AddToMethodCache (lldb::addr_t class_addr, lldb::addr_t sel, lldb::addr_t impl_addr);
|
||||
|
||||
TypeAndOrName
|
||||
LookupInClassNameCache (lldb::addr_t class_addr);
|
||||
|
||||
void
|
||||
AddToClassNameCache (lldb::addr_t class_addr, const char *name, lldb::TypeSP type_sp);
|
||||
|
||||
void
|
||||
AddToClassNameCache (lldb::addr_t class_addr, const TypeAndOrName &class_or_type_name);
|
||||
|
||||
virtual ClangUtilityFunction *
|
||||
CreateObjectChecker (const char *) = 0;
|
||||
|
||||
|
@ -117,7 +126,10 @@ private:
|
|||
};
|
||||
|
||||
typedef std::map<ClassAndSel,lldb::addr_t> MsgImplMap;
|
||||
MsgImplMap m_impl_cache;
|
||||
MsgImplMap m_impl_cache;
|
||||
|
||||
typedef std::map<lldb::addr_t,TypeAndOrName> ClassNameMap;
|
||||
ClassNameMap m_class_name_cache;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN (ObjCLanguageRuntime);
|
||||
};
|
||||
|
|
|
@ -155,6 +155,7 @@ class ThreadPlanTracer;
|
|||
class ThreadSpec;
|
||||
class TimeValue;
|
||||
class Type;
|
||||
class TypeAndOrName;
|
||||
class TypeList;
|
||||
class UUID;
|
||||
class Unwind;
|
||||
|
|
|
@ -357,7 +357,7 @@ SBValue::GetChildAtIndex (uint32_t idx, bool use_dynamic_value)
|
|||
{
|
||||
if (child_sp)
|
||||
{
|
||||
lldb::ValueObjectSP dynamic_sp = child_sp->GetDynamicValue(true);
|
||||
lldb::ValueObjectSP dynamic_sp = child_sp->GetDynamicValue (true);
|
||||
if (dynamic_sp)
|
||||
child_sp = dynamic_sp;
|
||||
}
|
||||
|
@ -410,7 +410,7 @@ SBValue::GetChildMemberWithName (const char *name, bool use_dynamic_value)
|
|||
{
|
||||
if (child_sp)
|
||||
{
|
||||
lldb::ValueObjectSP dynamic_sp = child_sp->GetDynamicValue(true);
|
||||
lldb::ValueObjectSP dynamic_sp = child_sp->GetDynamicValue (true);
|
||||
if (dynamic_sp)
|
||||
child_sp = dynamic_sp;
|
||||
}
|
||||
|
|
|
@ -1644,6 +1644,16 @@ ValueObject::EvaluationPoint::SyncWithProcessState()
|
|||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
ValueObject::EvaluationPoint::SetUpdated ()
|
||||
{
|
||||
m_first_update = false;
|
||||
m_needs_update = false;
|
||||
if (m_process_sp)
|
||||
m_stop_id = m_process_sp->GetStopID();
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
ValueObject::EvaluationPoint::SetContext (ExecutionContextScope *exe_scope)
|
||||
{
|
||||
|
|
|
@ -135,7 +135,7 @@ ValueObjectDynamicValue::UpdateValue ()
|
|||
if (!process)
|
||||
return false;
|
||||
|
||||
lldb::TypeSP dynamic_type_sp;
|
||||
TypeAndOrName class_type_or_name;
|
||||
Address dynamic_address;
|
||||
bool found_dynamic_type = false;
|
||||
|
||||
|
@ -144,22 +144,29 @@ ValueObjectDynamicValue::UpdateValue ()
|
|||
{
|
||||
LanguageRuntime *runtime = process->GetLanguageRuntime (known_type);
|
||||
if (runtime)
|
||||
found_dynamic_type = runtime->GetDynamicValue(*m_parent, dynamic_type_sp, dynamic_address);
|
||||
found_dynamic_type = runtime->GetDynamicTypeAndAddress (*m_parent, class_type_or_name, dynamic_address);
|
||||
}
|
||||
else
|
||||
{
|
||||
LanguageRuntime *cpp_runtime = process->GetLanguageRuntime (lldb::eLanguageTypeC_plus_plus);
|
||||
if (cpp_runtime)
|
||||
found_dynamic_type = cpp_runtime->GetDynamicValue(*m_parent, dynamic_type_sp, dynamic_address);
|
||||
found_dynamic_type = cpp_runtime->GetDynamicTypeAndAddress (*m_parent, class_type_or_name, dynamic_address);
|
||||
|
||||
if (!found_dynamic_type)
|
||||
{
|
||||
LanguageRuntime *objc_runtime = process->GetLanguageRuntime (lldb::eLanguageTypeObjC);
|
||||
if (objc_runtime)
|
||||
found_dynamic_type = cpp_runtime->GetDynamicValue(*m_parent, dynamic_type_sp, dynamic_address);
|
||||
found_dynamic_type = cpp_runtime->GetDynamicTypeAndAddress (*m_parent, class_type_or_name, dynamic_address);
|
||||
}
|
||||
}
|
||||
|
||||
lldb::TypeSP dynamic_type_sp = class_type_or_name.GetTypeSP();
|
||||
|
||||
// Getting the dynamic value may have run the program a bit, and so marked us as needing updating, but we really
|
||||
// don't...
|
||||
|
||||
m_update_point.SetUpdated();
|
||||
|
||||
// If we don't have a dynamic type, then make ourselves just a echo of our parent.
|
||||
// Or we could return false, and make ourselves an echo of our parent?
|
||||
if (!found_dynamic_type)
|
||||
|
|
|
@ -73,7 +73,8 @@ ClangExpressionDeclMap::WillParse(ExecutionContext &exe_ctx)
|
|||
m_parser_vars->m_sym_ctx = exe_ctx.frame->GetSymbolContext(lldb::eSymbolContextEverything);
|
||||
else if (exe_ctx.thread)
|
||||
m_parser_vars->m_sym_ctx = exe_ctx.thread->GetStackFrameAtIndex(0)->GetSymbolContext(lldb::eSymbolContextEverything);
|
||||
|
||||
else if (exe_ctx.process)
|
||||
m_parser_vars->m_sym_ctx = SymbolContext(exe_ctx.target->GetSP(), ModuleSP());
|
||||
if (exe_ctx.target)
|
||||
m_parser_vars->m_persistent_vars = &exe_ctx.target->GetPersistentVariables();
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ ItaniumABILanguageRuntime::CouldHaveDynamicValue (ValueObject &in_value)
|
|||
}
|
||||
|
||||
bool
|
||||
ItaniumABILanguageRuntime::GetDynamicValue (ValueObject &in_value, lldb::TypeSP &dynamic_type_sp, Address &dynamic_address)
|
||||
ItaniumABILanguageRuntime::GetDynamicTypeAndAddress (ValueObject &in_value, TypeAndOrName &class_type_or_name, Address &dynamic_address)
|
||||
{
|
||||
// For Itanium, if the type has a vtable pointer in the object, it will be at offset 0
|
||||
// in the object. That will point to the "address point" within the vtable (not the beginning of the
|
||||
|
@ -104,6 +104,7 @@ ItaniumABILanguageRuntime::GetDynamicValue (ValueObject &in_value, lldb::TypeSP
|
|||
{
|
||||
// We are a C++ class, that's good. Get the class name and look it up:
|
||||
const char *class_name = name + strlen(vtable_demangled_prefix);
|
||||
class_type_or_name.SetName (class_name);
|
||||
TypeList class_types;
|
||||
uint32_t num_matches = target->GetImages().FindTypes (sc,
|
||||
ConstString(class_name),
|
||||
|
@ -112,14 +113,40 @@ ItaniumABILanguageRuntime::GetDynamicValue (ValueObject &in_value, lldb::TypeSP
|
|||
class_types);
|
||||
if (num_matches == 1)
|
||||
{
|
||||
dynamic_type_sp = class_types.GetTypeAtIndex(0);
|
||||
class_type_or_name.SetTypeSP(class_types.GetTypeAtIndex(0));
|
||||
}
|
||||
else if (num_matches > 1)
|
||||
{
|
||||
// How to sort out which of the type matches to pick?
|
||||
for (size_t i = 0; i < num_matches; i++)
|
||||
{
|
||||
lldb::TypeSP this_type(class_types.GetTypeAtIndex(i));
|
||||
if (this_type)
|
||||
{
|
||||
if (ClangASTContext::IsCXXClassType(this_type->GetClangFullType()))
|
||||
{
|
||||
// There can only be one type with a given name,
|
||||
// so we've just found duplicate definitions, and this
|
||||
// one will do as well as any other.
|
||||
// We don't consider something to have a dynamic type if
|
||||
// it is the same as the static type. So compare against
|
||||
// the value we were handed:
|
||||
|
||||
clang::ASTContext *in_ast_ctx = in_value.GetClangAST ();
|
||||
clang::ASTContext *this_ast_ctx = this_type->GetClangAST ();
|
||||
if (in_ast_ctx != this_ast_ctx
|
||||
|| !ClangASTContext::AreTypesSame (in_ast_ctx,
|
||||
in_value.GetClangType(),
|
||||
this_type->GetClangFullType()))
|
||||
{
|
||||
class_type_or_name.SetTypeSP (this_type);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!dynamic_type_sp)
|
||||
else
|
||||
return false;
|
||||
|
||||
// The offset_to_top is two pointers above the address.
|
||||
|
|
|
@ -31,7 +31,7 @@ namespace lldb_private {
|
|||
IsVTableName (const char *name);
|
||||
|
||||
virtual bool
|
||||
GetDynamicValue (ValueObject &in_value, lldb::TypeSP &type_sp, Address &address);
|
||||
GetDynamicTypeAndAddress (ValueObject &in_value, TypeAndOrName &class_type_or_name, Address &address);
|
||||
|
||||
virtual bool
|
||||
CouldHaveDynamicValue (ValueObject &in_value);
|
||||
|
|
|
@ -198,7 +198,7 @@ AppleObjCRuntime::CouldHaveDynamicValue (ValueObject &in_value)
|
|||
}
|
||||
|
||||
bool
|
||||
AppleObjCRuntime::GetDynamicValue (ValueObject &in_value, lldb::TypeSP &type_sp, Address &address)
|
||||
AppleObjCRuntime::GetDynamicTypeAndAddress (ValueObject &in_value, TypeAndOrName &class_type_or_name, Address &address)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ public:
|
|||
CouldHaveDynamicValue (ValueObject &in_value);
|
||||
|
||||
virtual bool
|
||||
GetDynamicValue (ValueObject &in_value, lldb::TypeSP &type_sp, Address &address);
|
||||
GetDynamicTypeAndAddress (ValueObject &in_value, TypeAndOrName &class_type_or_name, Address &address);
|
||||
|
||||
// These are the ObjC specific functions.
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ static const char *pluginDesc = "Apple Objective C Language Runtime - Version 1"
|
|||
static const char *pluginShort = "language.apple.objc.v1";
|
||||
|
||||
bool
|
||||
AppleObjCRuntimeV1::GetDynamicValue (ValueObject &in_value, lldb::TypeSP &type_sp, Address &address)
|
||||
AppleObjCRuntimeV1::GetDynamicTypeAndAddress (ValueObject &in_value, TypeAndOrName &class_type_or_name, Address &address)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ public:
|
|||
|
||||
// These are generic runtime functions:
|
||||
virtual bool
|
||||
GetDynamicValue (ValueObject &in_value, lldb::TypeSP &type_sp, Address &address);
|
||||
GetDynamicTypeAndAddress (ValueObject &in_value, TypeAndOrName &class_type_or_name, Address &address);
|
||||
|
||||
virtual ClangUtilityFunction *
|
||||
CreateObjectChecker (const char *);
|
||||
|
|
|
@ -40,15 +40,359 @@ static const char *pluginName = "AppleObjCRuntimeV2";
|
|||
static const char *pluginDesc = "Apple Objective C Language Runtime - Version 2";
|
||||
static const char *pluginShort = "language.apple.objc.v2";
|
||||
|
||||
|
||||
const char *AppleObjCRuntimeV2::g_find_class_name_function_name = "__lldb_apple_objc_v2_find_class_name";
|
||||
const char *AppleObjCRuntimeV2::g_find_class_name_function_body = " \n\
|
||||
extern \"C\" \n\
|
||||
{ \n\
|
||||
extern void *gdb_class_getClass (void *objc_class); \n\
|
||||
extern void *class_getName(void *objc_class); \n\
|
||||
extern int printf(const char *format, ...); \n\
|
||||
} \n\
|
||||
\n\
|
||||
struct __lldb_objc_object { \n\
|
||||
void *isa; \n\
|
||||
}; \n\
|
||||
\n\
|
||||
extern \"C\" void *__lldb_apple_objc_v2_find_class_name ( \n\
|
||||
__lldb_objc_object *object_ptr, \n\
|
||||
int debug) \n\
|
||||
{ \n\
|
||||
void *name = 0; \n\
|
||||
if (debug) \n\
|
||||
printf (\"\\n*** Called in v2_find_class_name with object: 0x%p\\n\", object_ptr); \n\
|
||||
// Call gdb_class_getClass so we can tell if the class is good. \n\
|
||||
void *objc_class = gdb_class_getClass (object_ptr->isa); \n\
|
||||
if (objc_class) \n\
|
||||
{ \n\
|
||||
void *actual_class = (void *) [(id) object_ptr class]; \n\
|
||||
if (actual_class != 0) \n\
|
||||
name = class_getName((void *) actual_class); \n\
|
||||
if (debug) \n\
|
||||
printf (\"\\n*** Found name: %s\\n\", name ? name : \"<NOT FOUND>\"); \n\
|
||||
} \n\
|
||||
else if (debug) \n\
|
||||
printf (\"\\n*** gdb_class_getClass returned NULL\\n\"); \n\
|
||||
return name; \n\
|
||||
} \n\
|
||||
";
|
||||
|
||||
const char *AppleObjCRuntimeV2::g_objc_class_symbol_prefix = "OBJC_CLASS_$_";
|
||||
const char *AppleObjCRuntimeV2::g_objc_class_data_section_name = "__objc_data";
|
||||
|
||||
AppleObjCRuntimeV2::AppleObjCRuntimeV2 (Process *process, ModuleSP &objc_module_sp) :
|
||||
lldb_private::AppleObjCRuntime (process)
|
||||
lldb_private::AppleObjCRuntime (process),
|
||||
m_get_class_name_args(LLDB_INVALID_ADDRESS),
|
||||
m_get_class_name_args_mutex(Mutex::eMutexTypeNormal)
|
||||
{
|
||||
m_has_object_getClass = (objc_module_sp->FindFirstSymbolWithNameAndType(ConstString("gdb_object_getClass")) != NULL);
|
||||
}
|
||||
|
||||
bool
|
||||
AppleObjCRuntimeV2::GetDynamicValue (ValueObject &in_value, lldb::TypeSP &type_sp, Address &address)
|
||||
AppleObjCRuntimeV2::RunFunctionToFindClassName(lldb::addr_t object_addr, Thread *thread, char *name_dst, size_t max_name_len)
|
||||
{
|
||||
// Since we are going to run code we have to make sure only one thread at a time gets to try this.
|
||||
Mutex::Locker (m_get_class_name_args_mutex);
|
||||
|
||||
StreamString errors;
|
||||
|
||||
LogSP log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_STEP)); // FIXME - a more appropriate log channel?
|
||||
|
||||
int32_t debug;
|
||||
if (log)
|
||||
debug = 1;
|
||||
else
|
||||
debug = 0;
|
||||
|
||||
ValueList dispatch_values;
|
||||
|
||||
Value void_ptr_value;
|
||||
ClangASTContext *clang_ast_context = m_process->GetTarget().GetScratchClangASTContext();
|
||||
|
||||
lldb::clang_type_t clang_void_ptr_type = clang_ast_context->GetVoidPtrType(false);
|
||||
void_ptr_value.SetValueType (Value::eValueTypeScalar);
|
||||
void_ptr_value.SetContext (Value::eContextTypeClangType, clang_void_ptr_type);
|
||||
void_ptr_value.GetScalar() = object_addr;
|
||||
|
||||
dispatch_values.PushValue (void_ptr_value);
|
||||
|
||||
Value int_value;
|
||||
lldb::clang_type_t clang_int_type = clang_ast_context->GetBuiltinTypeForEncodingAndBitSize(lldb::eEncodingSint, 32);
|
||||
int_value.SetValueType (Value::eValueTypeScalar);
|
||||
int_value.SetContext (Value::eContextTypeClangType, clang_int_type);
|
||||
int_value.GetScalar() = debug;
|
||||
|
||||
dispatch_values.PushValue (int_value);
|
||||
|
||||
ExecutionContext exe_ctx;
|
||||
thread->CalculateExecutionContext(exe_ctx);
|
||||
|
||||
Address find_class_name_address;
|
||||
|
||||
if (!m_get_class_name_code.get())
|
||||
{
|
||||
m_get_class_name_code.reset (new ClangUtilityFunction (g_find_class_name_function_body,
|
||||
g_find_class_name_function_name));
|
||||
|
||||
if (!m_get_class_name_code->Install(errors, exe_ctx))
|
||||
{
|
||||
if (log)
|
||||
log->Printf ("Failed to install implementation lookup: %s.", errors.GetData());
|
||||
m_get_class_name_code.reset();
|
||||
return false;
|
||||
}
|
||||
find_class_name_address.Clear();
|
||||
find_class_name_address.SetOffset(m_get_class_name_code->StartAddress());
|
||||
}
|
||||
else
|
||||
{
|
||||
find_class_name_address.Clear();
|
||||
find_class_name_address.SetOffset(m_get_class_name_code->StartAddress());
|
||||
}
|
||||
|
||||
// Next make the runner function for our implementation utility function.
|
||||
if (!m_get_class_name_function.get())
|
||||
{
|
||||
m_get_class_name_function.reset(new ClangFunction (*m_process,
|
||||
clang_ast_context,
|
||||
clang_void_ptr_type,
|
||||
find_class_name_address,
|
||||
dispatch_values));
|
||||
|
||||
errors.Clear();
|
||||
unsigned num_errors = m_get_class_name_function->CompileFunction(errors);
|
||||
if (num_errors)
|
||||
{
|
||||
if (log)
|
||||
log->Printf ("Error compiling function: \"%s\".", errors.GetData());
|
||||
return false;
|
||||
}
|
||||
|
||||
errors.Clear();
|
||||
if (!m_get_class_name_function->WriteFunctionWrapper(exe_ctx, errors))
|
||||
{
|
||||
if (log)
|
||||
log->Printf ("Error Inserting function: \"%s\".", errors.GetData());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_get_class_name_code.get() == NULL || m_get_class_name_function.get() == NULL)
|
||||
return false;
|
||||
|
||||
// Finally, write down the arguments, and call the function. Note that we will re-use the same space in the target
|
||||
// for the args. We're locking this to ensure that only one thread at a time gets to call this function, so we don't
|
||||
// have to worry about overwriting the arguments.
|
||||
|
||||
if (!m_get_class_name_function->WriteFunctionArguments (exe_ctx, m_get_class_name_args, find_class_name_address, dispatch_values, errors))
|
||||
return false;
|
||||
|
||||
bool stop_others = true;
|
||||
bool try_all_threads = true;
|
||||
bool unwind_on_error = true;
|
||||
|
||||
ExecutionResults results = m_get_class_name_function->ExecuteFunction (exe_ctx,
|
||||
&m_get_class_name_args,
|
||||
errors,
|
||||
stop_others,
|
||||
1000000,
|
||||
try_all_threads,
|
||||
unwind_on_error,
|
||||
void_ptr_value);
|
||||
|
||||
if (results != eExecutionCompleted)
|
||||
{
|
||||
if (log)
|
||||
log->Printf("Error evaluating our find class name function: %d.\n", results);
|
||||
return false;
|
||||
}
|
||||
|
||||
lldb::addr_t result_ptr = void_ptr_value.GetScalar().ULongLong(LLDB_INVALID_ADDRESS);
|
||||
size_t chars_read = m_process->ReadCStringFromMemory (result_ptr, name_dst, max_name_len);
|
||||
|
||||
// If we exhausted our buffer before finding a NULL we're probably off in the weeds somewhere...
|
||||
if (chars_read == max_name_len)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
bool
|
||||
AppleObjCRuntimeV2::GetDynamicTypeAndAddress (ValueObject &in_value, TypeAndOrName &class_type_or_name, Address &address)
|
||||
{
|
||||
// The Runtime is attached to a particular process, you shouldn't pass in a value from another process.
|
||||
assert (in_value.GetUpdatePoint().GetProcess() == m_process);
|
||||
|
||||
// Make sure we can have a dynamic value before starting...
|
||||
if (CouldHaveDynamicValue (in_value))
|
||||
{
|
||||
// First job, pull out the address at 0 offset from the object That will be the ISA pointer.
|
||||
AddressType address_type;
|
||||
lldb::addr_t original_ptr = in_value.GetPointerValue(address_type, true);
|
||||
|
||||
// ObjC only has single inheritance, so the objects all start at the same pointer value.
|
||||
address.SetSection (NULL);
|
||||
address.SetOffset (original_ptr);
|
||||
|
||||
if (original_ptr == LLDB_INVALID_ADDRESS)
|
||||
return false;
|
||||
|
||||
Target *target = m_process->CalculateTarget();
|
||||
|
||||
char memory_buffer[16];
|
||||
DataExtractor data(memory_buffer, sizeof(memory_buffer),
|
||||
m_process->GetByteOrder(),
|
||||
m_process->GetAddressByteSize());
|
||||
size_t address_byte_size = m_process->GetAddressByteSize();
|
||||
Error error;
|
||||
size_t bytes_read = m_process->ReadMemory (original_ptr,
|
||||
memory_buffer,
|
||||
address_byte_size,
|
||||
error);
|
||||
if (!error.Success() || (bytes_read != address_byte_size))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t offset_ptr = 0;
|
||||
lldb::addr_t isa_addr = data.GetAddress (&offset_ptr);
|
||||
|
||||
if (offset_ptr == 0)
|
||||
return false;
|
||||
|
||||
// Make sure the class address is readable, otherwise this is not a good object:
|
||||
bytes_read = m_process->ReadMemory (isa_addr,
|
||||
memory_buffer,
|
||||
address_byte_size,
|
||||
error);
|
||||
if (bytes_read != address_byte_size)
|
||||
return false;
|
||||
|
||||
// First check the cache...
|
||||
|
||||
SymbolContext sc;
|
||||
|
||||
class_type_or_name = LookupInClassNameCache (isa_addr);
|
||||
|
||||
if (!class_type_or_name.IsEmpty())
|
||||
{
|
||||
if (class_type_or_name.GetTypeSP() != NULL)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *class_name = NULL;
|
||||
Address isa_address;
|
||||
target->GetSectionLoadList().ResolveLoadAddress (isa_addr, isa_address);
|
||||
|
||||
if (isa_address.IsValid())
|
||||
{
|
||||
// If the ISA pointer points to one of the sections in the binary, then see if we can
|
||||
// get the class name from the symbols.
|
||||
|
||||
const Section *section = isa_address.GetSection();
|
||||
|
||||
if (section)
|
||||
{
|
||||
// If this points to a section that we know about, then this is
|
||||
// some static class or nothing. See if it is in the right section
|
||||
// and if its name is the right form.
|
||||
ConstString section_name = section->GetName();
|
||||
if (section_name == ConstString(g_objc_class_data_section_name))
|
||||
{
|
||||
isa_address.CalculateSymbolContext(&sc);
|
||||
if (sc.symbol)
|
||||
{
|
||||
class_name = sc.symbol->GetName().AsCString();
|
||||
if (strstr (class_name, g_objc_class_symbol_prefix) == class_name)
|
||||
class_name += strlen (g_objc_class_symbol_prefix);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char class_buffer[1024];
|
||||
if (class_name == NULL)
|
||||
{
|
||||
// If the class address didn't point into the binary, or
|
||||
// it points into the right section but there wasn't a symbol
|
||||
// there, try to look it up by calling the class method in the target.
|
||||
ExecutionContextScope *exe_scope = in_value.GetUpdatePoint().GetExecutionContextScope();
|
||||
Thread *thread_to_use;
|
||||
if (exe_scope)
|
||||
thread_to_use = exe_scope->CalculateThread();
|
||||
|
||||
if (thread_to_use == NULL)
|
||||
thread_to_use = m_process->GetThreadList().GetSelectedThread().get();
|
||||
|
||||
if (thread_to_use == NULL)
|
||||
return false;
|
||||
|
||||
if (!RunFunctionToFindClassName (original_ptr, thread_to_use, class_buffer, 1024))
|
||||
return false;
|
||||
|
||||
class_name = class_buffer;
|
||||
|
||||
}
|
||||
|
||||
if (class_name != NULL && *class_name != '\0')
|
||||
{
|
||||
class_type_or_name.SetName (class_name);
|
||||
|
||||
TypeList class_types;
|
||||
uint32_t num_matches = target->GetImages().FindTypes (sc,
|
||||
class_type_or_name.GetName(),
|
||||
true,
|
||||
UINT32_MAX,
|
||||
class_types);
|
||||
if (num_matches == 1)
|
||||
{
|
||||
class_type_or_name.SetTypeSP (class_types.GetTypeAtIndex(0));
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < num_matches; i++)
|
||||
{
|
||||
lldb::TypeSP this_type(class_types.GetTypeAtIndex(i));
|
||||
if (this_type)
|
||||
{
|
||||
if (ClangASTContext::IsObjCClassType(this_type->GetClangFullType()))
|
||||
{
|
||||
// There can only be one type with a given name,
|
||||
// so we've just found duplicate definitions, and this
|
||||
// one will do as well as any other.
|
||||
// We don't consider something to have a dynamic type if
|
||||
// it is the same as the static type. So compare against
|
||||
// the value we were handed:
|
||||
|
||||
clang::ASTContext *in_ast_ctx = in_value.GetClangAST ();
|
||||
clang::ASTContext *this_ast_ctx = this_type->GetClangAST ();
|
||||
if (in_ast_ctx != this_ast_ctx
|
||||
|| !ClangASTContext::AreTypesSame (in_ast_ctx,
|
||||
in_value.GetClangType(),
|
||||
this_type->GetClangFullType()))
|
||||
{
|
||||
class_type_or_name.SetTypeSP (this_type);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AddToClassNameCache (isa_addr, class_type_or_name);
|
||||
if (class_type_or_name.GetTypeSP())
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
#include "AppleThreadPlanStepThroughObjCTrampoline.h"
|
||||
|
||||
namespace lldb_private {
|
||||
|
||||
|
||||
class AppleObjCRuntimeV2 :
|
||||
public AppleObjCRuntime
|
||||
{
|
||||
|
@ -32,7 +32,7 @@ public:
|
|||
|
||||
// These are generic runtime functions:
|
||||
virtual bool
|
||||
GetDynamicValue (ValueObject &in_value, lldb::TypeSP &type_sp, Address &address);
|
||||
GetDynamicTypeAndAddress (ValueObject &in_value, TypeAndOrName &class_type_or_name, Address &address);
|
||||
|
||||
virtual ClangUtilityFunction *
|
||||
CreateObjectChecker (const char *);
|
||||
|
@ -76,7 +76,18 @@ protected:
|
|||
private:
|
||||
AppleObjCRuntimeV2(Process *process, ModuleSP &objc_module_sp);
|
||||
|
||||
bool m_has_object_getClass;
|
||||
bool RunFunctionToFindClassName (lldb::addr_t class_addr, Thread *thread, char *name_dst, size_t max_name_len);
|
||||
|
||||
bool m_has_object_getClass;
|
||||
std::auto_ptr<ClangFunction> m_get_class_name_function;
|
||||
std::auto_ptr<ClangUtilityFunction> m_get_class_name_code;
|
||||
lldb::addr_t m_get_class_name_args;
|
||||
Mutex m_get_class_name_args_mutex;
|
||||
|
||||
static const char *g_find_class_name_function_name;
|
||||
static const char *g_find_class_name_function_body;
|
||||
static const char *g_objc_class_symbol_prefix;
|
||||
static const char *g_objc_class_data_section_name;
|
||||
};
|
||||
|
||||
} // namespace lldb_private
|
||||
|
|
|
@ -662,3 +662,75 @@ Type::CreateClangRValueReferenceType (Type *type)
|
|||
}
|
||||
|
||||
|
||||
TypeAndOrName::TypeAndOrName () : m_type_sp(), m_type_name()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
TypeAndOrName::TypeAndOrName (TypeSP &in_type_sp) : m_type_sp(in_type_sp)
|
||||
{
|
||||
if (in_type_sp)
|
||||
m_type_name = in_type_sp->GetName();
|
||||
}
|
||||
|
||||
TypeAndOrName::TypeAndOrName (const char *in_type_str) : m_type_name(in_type_str)
|
||||
{
|
||||
}
|
||||
|
||||
TypeAndOrName::TypeAndOrName (const TypeAndOrName &rhs) : m_type_sp (rhs.m_type_sp), m_type_name (rhs.m_type_name)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
TypeAndOrName::TypeAndOrName (ConstString &in_type_const_string) : m_type_name (in_type_const_string)
|
||||
{
|
||||
}
|
||||
|
||||
TypeAndOrName &
|
||||
TypeAndOrName::operator= (const TypeAndOrName &rhs)
|
||||
{
|
||||
if (this != &rhs)
|
||||
{
|
||||
m_type_name = rhs.m_type_name;
|
||||
m_type_sp = rhs.m_type_sp;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
ConstString
|
||||
TypeAndOrName::GetName () const
|
||||
{
|
||||
if (m_type_sp)
|
||||
return m_type_sp->GetName();
|
||||
else
|
||||
return m_type_name;
|
||||
}
|
||||
|
||||
void
|
||||
TypeAndOrName::SetName (ConstString &type_name_const_str)
|
||||
{
|
||||
m_type_name = type_name_const_str;
|
||||
}
|
||||
|
||||
void
|
||||
TypeAndOrName::SetName (const char *type_name_str)
|
||||
{
|
||||
m_type_name.SetCString (type_name_str);
|
||||
}
|
||||
|
||||
void
|
||||
TypeAndOrName::SetTypeSP (lldb::TypeSP type_sp)
|
||||
{
|
||||
m_type_sp = type_sp;
|
||||
if (type_sp)
|
||||
m_type_name = type_sp->GetName();
|
||||
}
|
||||
|
||||
bool
|
||||
TypeAndOrName::IsEmpty()
|
||||
{
|
||||
if (m_type_name || m_type_sp)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "lldb/Core/PluginManager.h"
|
||||
#include "lldb/Core/ValueObject.h"
|
||||
#include "lldb/Symbol/ClangASTContext.h"
|
||||
#include "lldb/Symbol/Type.h"
|
||||
#include "lldb/Target/ObjCLanguageRuntime.h"
|
||||
|
||||
using namespace lldb;
|
||||
|
@ -50,3 +51,45 @@ ObjCLanguageRuntime::LookupInMethodCache (lldb::addr_t class_addr, lldb::addr_t
|
|||
return (*pos).second;
|
||||
return LLDB_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
void
|
||||
ObjCLanguageRuntime::AddToClassNameCache (lldb::addr_t class_addr, const char *name, lldb::TypeSP type_sp)
|
||||
{
|
||||
LogSP log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_STEP));
|
||||
if (log)
|
||||
{
|
||||
log->Printf ("Caching: class 0x%llx name: %s.", class_addr, name);
|
||||
}
|
||||
|
||||
TypeAndOrName class_type_or_name;
|
||||
|
||||
if (type_sp != NULL)
|
||||
class_type_or_name.SetTypeSP (type_sp);
|
||||
else if (name && *name != '\0')
|
||||
class_type_or_name.SetName (name);
|
||||
else
|
||||
return;
|
||||
m_class_name_cache.insert (std::pair<lldb::addr_t,TypeAndOrName> (class_addr, class_type_or_name));
|
||||
}
|
||||
|
||||
void
|
||||
ObjCLanguageRuntime::AddToClassNameCache (lldb::addr_t class_addr, const TypeAndOrName &class_type_or_name)
|
||||
{
|
||||
LogSP log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_STEP));
|
||||
if (log)
|
||||
{
|
||||
log->Printf ("Caching: class 0x%llx name: %s.", class_addr, class_type_or_name.GetName().AsCString());
|
||||
}
|
||||
|
||||
m_class_name_cache.insert (std::pair<lldb::addr_t,TypeAndOrName> (class_addr, class_type_or_name));
|
||||
}
|
||||
|
||||
TypeAndOrName
|
||||
ObjCLanguageRuntime::LookupInClassNameCache (lldb::addr_t class_addr)
|
||||
{
|
||||
ClassNameMap::iterator pos, end = m_class_name_cache.end();
|
||||
pos = m_class_name_cache.find (class_addr);
|
||||
if (pos != end)
|
||||
return (*pos).second;
|
||||
return TypeAndOrName ();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
LEVEL = ../make
|
||||
|
||||
OBJC_SOURCES := dynamic-value.m
|
||||
LDFLAGS = $(CFLAGS) -lobjc -framework Foundation
|
||||
|
||||
include $(LEVEL)/Makefile.rules
|
|
@ -0,0 +1,165 @@
|
|||
"""
|
||||
Use lldb Python API to test dynamic values in ObjC
|
||||
"""
|
||||
|
||||
import os, time
|
||||
import re
|
||||
import unittest2
|
||||
import lldb, lldbutil
|
||||
from lldbtest import *
|
||||
|
||||
class ObjCDynamicValueTestCase(TestBase):
|
||||
|
||||
mydir = "objc-dynamic-value"
|
||||
|
||||
@unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin")
|
||||
@python_api_test
|
||||
def test_get_dynamic_objc_vals_with_dsym(self):
|
||||
"""Test fetching ObjC dynamic values."""
|
||||
self.buildDsym()
|
||||
self.do_get_dynamic_vals()
|
||||
|
||||
@python_api_test
|
||||
def test_get_objc_dynamic_vals_with_dwarf(self):
|
||||
"""Test fetching ObjC dynamic values."""
|
||||
self.buildDwarf()
|
||||
self.do_get_dynamic_vals()
|
||||
|
||||
def setUp(self):
|
||||
# Call super's setUp().
|
||||
TestBase.setUp(self)
|
||||
|
||||
# Find the line number to break for main.c.
|
||||
|
||||
self.source_name = 'dynamic-value.m'
|
||||
self.set_property_line = line_number(self.source_name, '// This is the line in setProperty, make sure we step to here.')
|
||||
self.handle_SourceBase = line_number(self.source_name,
|
||||
'// Break here to check dynamic values.')
|
||||
self.main_before_setProperty_line = line_number(self.source_name,
|
||||
'// Break here to see if we can step into real method.')
|
||||
|
||||
def examine_SourceDerived_ptr (self, object):
|
||||
self.assertTrue (object.IsValid())
|
||||
self.assertTrue (object.GetTypeName().find ('SourceDerived') != -1)
|
||||
derivedValue = object.GetChildMemberWithName ('_derivedValue')
|
||||
self.assertTrue (derivedValue.IsValid())
|
||||
self.assertTrue (int (derivedValue.GetValue(), 0) == 30)
|
||||
|
||||
def do_get_dynamic_vals(self):
|
||||
"""Make sure we get dynamic values correctly both for compiled in classes and dynamic ones"""
|
||||
exe = os.path.join(os.getcwd(), "a.out")
|
||||
|
||||
# Create a target from the debugger.
|
||||
|
||||
target = self.dbg.CreateTarget (exe)
|
||||
self.assertTrue(target.IsValid(), VALID_TARGET)
|
||||
|
||||
# Set up our breakpoints:
|
||||
|
||||
handle_SourceBase_bkpt = target.BreakpointCreateByLocation(self.source_name, self.handle_SourceBase)
|
||||
self.assertTrue(handle_SourceBase_bkpt.IsValid() and
|
||||
handle_SourceBase_bkpt.GetNumLocations() == 1,
|
||||
VALID_BREAKPOINT)
|
||||
|
||||
main_before_setProperty_bkpt = target.BreakpointCreateByLocation(self.source_name, self.main_before_setProperty_line)
|
||||
self.assertTrue(main_before_setProperty_bkpt.IsValid() and
|
||||
main_before_setProperty_bkpt.GetNumLocations() == 1,
|
||||
VALID_BREAKPOINT)
|
||||
|
||||
# Now launch the process, and do not stop at the entry point.
|
||||
self.process = target.LaunchSimple (None, None, os.getcwd())
|
||||
|
||||
self.assertTrue(self.process.GetState() == lldb.eStateStopped,
|
||||
PROCESS_STOPPED)
|
||||
|
||||
threads = lldbutil.get_threads_stopped_at_breakpoint (self.process, main_before_setProperty_bkpt)
|
||||
self.assertTrue (len(threads) == 1)
|
||||
thread = threads[0]
|
||||
|
||||
#
|
||||
# At this point, myObserver has a Source pointer that is actually a KVO swizzled SourceDerived
|
||||
# make sure we can get that properly:
|
||||
|
||||
frame = thread.GetFrameAtIndex(0)
|
||||
myObserver = frame.FindVariable('myObserver')
|
||||
self.assertTrue (myObserver.IsValid())
|
||||
myObserver_source = myObserver.GetChildMemberWithName ('_source')
|
||||
self.examine_SourceDerived_ptr (myObserver_source)
|
||||
|
||||
# The "frame var" code uses another path to get into children, so let's
|
||||
# make sure that works as well:
|
||||
|
||||
result = lldb.SBCommandReturnObject()
|
||||
|
||||
self.expect('frame var -d 1 myObserver->_source', 'frame var finds its way into a child member',
|
||||
patterns = ['\(SourceDerived \*\)'])
|
||||
|
||||
# This test is not entirely related to the main thrust of this test case, but since we're here,
|
||||
# try stepping into setProperty, and make sure we get into the version in Source:
|
||||
|
||||
thread.StepInto()
|
||||
|
||||
threads = lldbutil.get_stopped_threads (self.process, lldb.eStopReasonPlanComplete)
|
||||
self.assertTrue (len(threads) == 1)
|
||||
line_entry = threads[0].GetFrameAtIndex(0).GetLineEntry()
|
||||
self.assertTrue (line_entry.GetLine() == self.set_property_line)
|
||||
self.assertTrue (line_entry.GetFileSpec().GetFilename() == self.source_name)
|
||||
|
||||
# Okay, back to the main business. Continue to the handle_SourceBase and make sure we get the correct dynamic value.
|
||||
|
||||
threads = lldbutil.continue_to_breakpoint (self.process, handle_SourceBase_bkpt)
|
||||
self.assertTrue (len(threads) == 1)
|
||||
thread = threads[0]
|
||||
|
||||
frame = thread.GetFrameAtIndex(0)
|
||||
|
||||
# Get "object" using FindVariable:
|
||||
|
||||
noDynamic = False
|
||||
useDynamic = True
|
||||
|
||||
object_static = frame.FindVariable ('object', noDynamic)
|
||||
object_dynamic = frame.FindVariable ('object', useDynamic)
|
||||
|
||||
# Delete this object to make sure that this doesn't cause havoc with the dynamic object that depends on it.
|
||||
del (object_static)
|
||||
|
||||
self.examine_SourceDerived_ptr (object_dynamic)
|
||||
|
||||
# Get "this" using FindValue, make sure that works too:
|
||||
object_static = frame.FindValue ('object', lldb.eValueTypeVariableArgument, noDynamic)
|
||||
object_dynamic = frame.FindValue ('object', lldb.eValueTypeVariableArgument, useDynamic)
|
||||
del (object_static)
|
||||
self.examine_SourceDerived_ptr (object_dynamic)
|
||||
|
||||
# Get "this" using the EvaluateExpression:
|
||||
# These tests fail for now because EvaluateExpression doesn't currently support dynamic typing...
|
||||
#object_static = frame.EvaluateExpression ('object', False)
|
||||
#object_dynamic = frame.EvaluateExpression ('object', True)
|
||||
#self.examine_value_object_of_object_ptr (object_static, object_dynamic, myB_loc)
|
||||
|
||||
# Continue again to the handle_SourceBase and make sure we get the correct dynamic value.
|
||||
# This one looks exactly the same, but in fact this is an "un-KVO'ed" version of SourceBase, so
|
||||
# its isa pointer points to SourceBase not NSKVOSourceBase or whatever...
|
||||
|
||||
threads = lldbutil.continue_to_breakpoint (self.process, handle_SourceBase_bkpt)
|
||||
self.assertTrue (len(threads) == 1)
|
||||
thread = threads[0]
|
||||
|
||||
frame = thread.GetFrameAtIndex(0)
|
||||
|
||||
# Get "object" using FindVariable:
|
||||
|
||||
object_static = frame.FindVariable ('object', noDynamic)
|
||||
object_dynamic = frame.FindVariable ('object', useDynamic)
|
||||
|
||||
# Delete this object to make sure that this doesn't cause havoc with the dynamic object that depends on it.
|
||||
del (object_static)
|
||||
|
||||
self.examine_SourceDerived_ptr (object_dynamic)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import atexit
|
||||
lldb.SBDebugger.Initialize()
|
||||
atexit.register(lambda: lldb.SBDebugger.Terminate())
|
||||
unittest2.main()
|
|
@ -0,0 +1,147 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
// SourceBase will be the base class of Source. We'll pass a Source object into a
|
||||
// function as a SourceBase, and then see if the dynamic typing can get us through the KVO
|
||||
// goo and all the way back to Source.
|
||||
|
||||
@interface SourceBase: NSObject
|
||||
{
|
||||
uint32_t _value;
|
||||
}
|
||||
- (SourceBase *) init;
|
||||
- (uint32_t) getValue;
|
||||
@end
|
||||
|
||||
@implementation SourceBase
|
||||
- (SourceBase *) init
|
||||
{
|
||||
[super init];
|
||||
_value = 10;
|
||||
return self;
|
||||
}
|
||||
- (uint32_t) getValue
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
@end
|
||||
|
||||
// Source is a class that will be observed by the Observer class below.
|
||||
// When Observer sets itself up to observe this property (in initWithASource)
|
||||
// the KVO system will overwrite the "isa" pointer of the object with the "kvo'ed"
|
||||
// one.
|
||||
|
||||
@interface Source : SourceBase
|
||||
{
|
||||
int _property;
|
||||
}
|
||||
- (Source *) init;
|
||||
- (void) setProperty: (int) newValue;
|
||||
@end
|
||||
|
||||
@implementation Source
|
||||
- (Source *) init
|
||||
{
|
||||
[super init];
|
||||
_property = 20;
|
||||
return self;
|
||||
}
|
||||
- (void) setProperty: (int) newValue
|
||||
{
|
||||
_property = newValue; // This is the line in setProperty, make sure we step to here.
|
||||
}
|
||||
@end
|
||||
|
||||
@interface SourceDerived : Source
|
||||
{
|
||||
int _derivedValue;
|
||||
}
|
||||
- (SourceDerived *) init;
|
||||
- (uint32_t) getValue;
|
||||
@end
|
||||
|
||||
@implementation SourceDerived
|
||||
- (SourceDerived *) init
|
||||
{
|
||||
[super init];
|
||||
_derivedValue = 30;
|
||||
return self;
|
||||
}
|
||||
- (uint32_t) getValue
|
||||
{
|
||||
return _derivedValue;
|
||||
}
|
||||
@end
|
||||
|
||||
// Observer is the object that will watch Source and cause KVO to swizzle it...
|
||||
|
||||
@interface Observer : NSObject
|
||||
{
|
||||
Source *_source;
|
||||
}
|
||||
+ (Observer *) observerWithSource: (Source *) source;
|
||||
- (Observer *) initWithASource: (Source *) source;
|
||||
- (void) observeValueForKeyPath: (NSString *) path
|
||||
ofObject: (id) object
|
||||
change: (NSDictionary *) change
|
||||
context: (void *) context;
|
||||
@end
|
||||
|
||||
@implementation Observer
|
||||
|
||||
+ (Observer *) observerWithSource: (Source *) inSource;
|
||||
{
|
||||
Observer *retval;
|
||||
|
||||
retval = [[Observer alloc] initWithASource: inSource];
|
||||
return retval;
|
||||
}
|
||||
|
||||
- (Observer *) initWithASource: (Source *) source
|
||||
{
|
||||
[super init];
|
||||
_source = source;
|
||||
[_source addObserver: self
|
||||
forKeyPath: @"property"
|
||||
options: (NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
|
||||
context: NULL];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) observeValueForKeyPath: (NSString *) path
|
||||
ofObject: (id) object
|
||||
change: (NSDictionary *) change
|
||||
context: (void *) context
|
||||
{
|
||||
printf ("Observer function called.\n");
|
||||
return;
|
||||
}
|
||||
@end
|
||||
|
||||
uint32_t
|
||||
handle_SourceBase (SourceBase *object)
|
||||
{
|
||||
return [object getValue]; // Break here to check dynamic values.
|
||||
}
|
||||
|
||||
int main ()
|
||||
{
|
||||
SourceDerived *mySource;
|
||||
Observer *myObserver;
|
||||
|
||||
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
mySource = [[SourceDerived alloc] init];
|
||||
myObserver = [Observer observerWithSource: mySource];
|
||||
|
||||
[mySource setProperty: 5]; // Break here to see if we can step into real method.
|
||||
|
||||
uint32_t return_value = handle_SourceBase (mySource);
|
||||
|
||||
SourceDerived *unwatchedSource = [[SourceDerived alloc] init];
|
||||
|
||||
return_value = handle_SourceBase (unwatchedSource);
|
||||
|
||||
[pool release];
|
||||
return 0;
|
||||
|
||||
}
|
Loading…
Reference in New Issue