| 1 | //===-- AppleObjCRuntimeV1.cpp --------------------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "AppleObjCRuntimeV1.h" |
| 10 | #include "AppleObjCDeclVendor.h" |
| 11 | #include "AppleObjCTrampolineHandler.h" |
| 12 | |
| 13 | #include "clang/AST/Type.h" |
| 14 | |
| 15 | #include "Plugins/TypeSystem/Clang/TypeSystemClang.h" |
| 16 | #include "lldb/Breakpoint/BreakpointLocation.h" |
| 17 | #include "lldb/Core/Module.h" |
| 18 | #include "lldb/Core/PluginManager.h" |
| 19 | #include "lldb/Expression/FunctionCaller.h" |
| 20 | #include "lldb/Expression/UtilityFunction.h" |
| 21 | #include "lldb/Symbol/Symbol.h" |
| 22 | #include "lldb/Target/ExecutionContext.h" |
| 23 | #include "lldb/Target/Process.h" |
| 24 | #include "lldb/Target/RegisterContext.h" |
| 25 | #include "lldb/Target/Target.h" |
| 26 | #include "lldb/Target/Thread.h" |
| 27 | #include "lldb/Utility/ConstString.h" |
| 28 | #include "lldb/Utility/LLDBLog.h" |
| 29 | #include "lldb/Utility/Log.h" |
| 30 | #include "lldb/Utility/Scalar.h" |
| 31 | #include "lldb/Utility/Status.h" |
| 32 | #include "lldb/Utility/StreamString.h" |
| 33 | |
| 34 | #include <memory> |
| 35 | #include <vector> |
| 36 | |
| 37 | using namespace lldb; |
| 38 | using namespace lldb_private; |
| 39 | |
| 40 | char AppleObjCRuntimeV1::ID = 0; |
| 41 | |
| 42 | AppleObjCRuntimeV1::AppleObjCRuntimeV1(Process *process) |
| 43 | : AppleObjCRuntime(process), m_hash_signature(), |
| 44 | m_isa_hash_table_ptr(LLDB_INVALID_ADDRESS) {} |
| 45 | |
| 46 | // for V1 runtime we just try to return a class name as that is the minimum |
| 47 | // level of support required for the data formatters to work |
| 48 | bool AppleObjCRuntimeV1::GetDynamicTypeAndAddress( |
| 49 | ValueObject &in_value, lldb::DynamicValueType use_dynamic, |
| 50 | TypeAndOrName &class_type_or_name, Address &address, |
| 51 | Value::ValueType &value_type, llvm::ArrayRef<uint8_t> &local_buffer) { |
| 52 | class_type_or_name.Clear(); |
| 53 | value_type = Value::ValueType::Scalar; |
| 54 | if (CouldHaveDynamicValue(in_value)) { |
| 55 | auto class_descriptor(GetClassDescriptor(in_value)); |
| 56 | if (class_descriptor && class_descriptor->IsValid() && |
| 57 | class_descriptor->GetClassName()) { |
| 58 | const addr_t object_ptr = in_value.GetPointerValue().address; |
| 59 | address.SetRawAddress(object_ptr); |
| 60 | class_type_or_name.SetName(class_descriptor->GetClassName()); |
| 61 | } |
| 62 | } |
| 63 | return !class_type_or_name.IsEmpty(); |
| 64 | } |
| 65 | |
| 66 | // Static Functions |
| 67 | lldb_private::LanguageRuntime * |
| 68 | AppleObjCRuntimeV1::CreateInstance(Process *process, |
| 69 | lldb::LanguageType language) { |
| 70 | // FIXME: This should be a MacOS or iOS process, and we need to look for the |
| 71 | // OBJC section to make |
| 72 | // sure we aren't using the V1 runtime. |
| 73 | if (language == eLanguageTypeObjC) { |
| 74 | ModuleSP objc_module_sp; |
| 75 | |
| 76 | if (AppleObjCRuntime::GetObjCVersion(process, objc_module_sp) == |
| 77 | ObjCRuntimeVersions::eAppleObjC_V1) |
| 78 | return new AppleObjCRuntimeV1(process); |
| 79 | else |
| 80 | return nullptr; |
| 81 | } else |
| 82 | return nullptr; |
| 83 | } |
| 84 | |
| 85 | void AppleObjCRuntimeV1::Initialize() { |
| 86 | PluginManager::RegisterPlugin( |
| 87 | name: GetPluginNameStatic(), description: "Apple Objective-C Language Runtime - Version 1" , |
| 88 | create_callback: CreateInstance, |
| 89 | /*command_callback = */ nullptr, precondition_callback: GetBreakpointExceptionPrecondition); |
| 90 | } |
| 91 | |
| 92 | void AppleObjCRuntimeV1::Terminate() { |
| 93 | PluginManager::UnregisterPlugin(create_callback: CreateInstance); |
| 94 | } |
| 95 | |
| 96 | BreakpointResolverSP |
| 97 | AppleObjCRuntimeV1::CreateExceptionResolver(const BreakpointSP &bkpt, |
| 98 | bool catch_bp, bool throw_bp) { |
| 99 | BreakpointResolverSP resolver_sp; |
| 100 | |
| 101 | if (throw_bp) |
| 102 | resolver_sp = std::make_shared<BreakpointResolverName>( |
| 103 | args: bkpt, args: std::get<1>(t: GetExceptionThrowLocation()).AsCString(), |
| 104 | args: eFunctionNameTypeBase, args: eLanguageTypeUnknown, args: Breakpoint::Exact, args: 0, |
| 105 | args: eLazyBoolNo); |
| 106 | // FIXME: don't do catch yet. |
| 107 | return resolver_sp; |
| 108 | } |
| 109 | |
| 110 | struct BufStruct { |
| 111 | char contents[2048]; |
| 112 | }; |
| 113 | |
| 114 | llvm::Expected<std::unique_ptr<UtilityFunction>> |
| 115 | AppleObjCRuntimeV1::CreateObjectChecker(std::string name, |
| 116 | ExecutionContext &exe_ctx) { |
| 117 | std::unique_ptr<BufStruct> buf(new BufStruct); |
| 118 | |
| 119 | int strformatsize = |
| 120 | snprintf(s: &buf->contents[0], maxlen: sizeof(buf->contents), |
| 121 | format: "struct __objc_class " |
| 122 | " \n" |
| 123 | "{ " |
| 124 | " \n" |
| 125 | " struct __objc_class *isa; " |
| 126 | " \n" |
| 127 | " struct __objc_class *super_class; " |
| 128 | " \n" |
| 129 | " const char *name; " |
| 130 | " \n" |
| 131 | " // rest of struct elided because unused " |
| 132 | " \n" |
| 133 | "}; " |
| 134 | " \n" |
| 135 | " " |
| 136 | " \n" |
| 137 | "struct __objc_object " |
| 138 | " \n" |
| 139 | "{ " |
| 140 | " \n" |
| 141 | " struct __objc_class *isa; " |
| 142 | " \n" |
| 143 | "}; " |
| 144 | " \n" |
| 145 | " " |
| 146 | " \n" |
| 147 | "extern \"C\" void " |
| 148 | " \n" |
| 149 | "%s(void *$__lldb_arg_obj, void *$__lldb_arg_selector) " |
| 150 | " \n" |
| 151 | "{ " |
| 152 | " \n" |
| 153 | " struct __objc_object *obj = (struct " |
| 154 | "__objc_object*)$__lldb_arg_obj; \n" |
| 155 | " if ($__lldb_arg_obj == (void *)0) " |
| 156 | " \n" |
| 157 | " return; // nil is ok " |
| 158 | " (int)strlen(obj->isa->name); " |
| 159 | " \n" |
| 160 | "} " |
| 161 | " \n" , |
| 162 | name.c_str()); |
| 163 | assert(strformatsize < (int)sizeof(buf->contents)); |
| 164 | UNUSED_IF_ASSERT_DISABLED(strformatsize); |
| 165 | |
| 166 | return GetTargetRef().CreateUtilityFunction(expression: buf->contents, name: std::move(name), |
| 167 | language: eLanguageTypeC, exe_ctx); |
| 168 | } |
| 169 | |
| 170 | AppleObjCRuntimeV1::ClassDescriptorV1::ClassDescriptorV1( |
| 171 | ValueObject &isa_pointer) { |
| 172 | Initialize(isa: isa_pointer.GetValueAsUnsigned(fail_value: 0), process_sp: isa_pointer.GetProcessSP()); |
| 173 | } |
| 174 | |
| 175 | AppleObjCRuntimeV1::ClassDescriptorV1::ClassDescriptorV1( |
| 176 | ObjCISA isa, lldb::ProcessSP process_sp) { |
| 177 | Initialize(isa, process_sp); |
| 178 | } |
| 179 | |
| 180 | void AppleObjCRuntimeV1::ClassDescriptorV1::Initialize( |
| 181 | ObjCISA isa, lldb::ProcessSP process_sp) { |
| 182 | if (!isa || !process_sp) { |
| 183 | m_valid = false; |
| 184 | return; |
| 185 | } |
| 186 | |
| 187 | m_valid = true; |
| 188 | |
| 189 | Status error; |
| 190 | |
| 191 | m_isa = process_sp->ReadPointerFromMemory(vm_addr: isa, error); |
| 192 | |
| 193 | if (error.Fail()) { |
| 194 | m_valid = false; |
| 195 | return; |
| 196 | } |
| 197 | |
| 198 | uint32_t ptr_size = process_sp->GetAddressByteSize(); |
| 199 | |
| 200 | if (!IsPointerValid(value: m_isa, ptr_size)) { |
| 201 | m_valid = false; |
| 202 | return; |
| 203 | } |
| 204 | |
| 205 | m_parent_isa = process_sp->ReadPointerFromMemory(vm_addr: m_isa + ptr_size, error); |
| 206 | |
| 207 | if (error.Fail()) { |
| 208 | m_valid = false; |
| 209 | return; |
| 210 | } |
| 211 | |
| 212 | if (!IsPointerValid(value: m_parent_isa, ptr_size, allow_NULLs: true)) { |
| 213 | m_valid = false; |
| 214 | return; |
| 215 | } |
| 216 | |
| 217 | lldb::addr_t name_ptr = |
| 218 | process_sp->ReadPointerFromMemory(vm_addr: m_isa + 2 * ptr_size, error); |
| 219 | |
| 220 | if (error.Fail()) { |
| 221 | m_valid = false; |
| 222 | return; |
| 223 | } |
| 224 | |
| 225 | lldb::WritableDataBufferSP buffer_sp(new DataBufferHeap(1024, 0)); |
| 226 | |
| 227 | size_t count = process_sp->ReadCStringFromMemory( |
| 228 | vm_addr: name_ptr, cstr: (char *)buffer_sp->GetBytes(), cstr_max_len: 1024, error); |
| 229 | |
| 230 | if (error.Fail()) { |
| 231 | m_valid = false; |
| 232 | return; |
| 233 | } |
| 234 | |
| 235 | if (count) |
| 236 | m_name = ConstString(reinterpret_cast<const char *>(buffer_sp->GetBytes())); |
| 237 | else |
| 238 | m_name = ConstString(); |
| 239 | |
| 240 | m_instance_size = process_sp->ReadUnsignedIntegerFromMemory( |
| 241 | load_addr: m_isa + 5 * ptr_size, byte_size: ptr_size, fail_value: 0, error); |
| 242 | |
| 243 | if (error.Fail()) { |
| 244 | m_valid = false; |
| 245 | return; |
| 246 | } |
| 247 | |
| 248 | m_process_wp = lldb::ProcessWP(process_sp); |
| 249 | } |
| 250 | |
| 251 | AppleObjCRuntime::ClassDescriptorSP |
| 252 | AppleObjCRuntimeV1::ClassDescriptorV1::GetSuperclass() { |
| 253 | if (!m_valid) |
| 254 | return AppleObjCRuntime::ClassDescriptorSP(); |
| 255 | ProcessSP process_sp = m_process_wp.lock(); |
| 256 | if (!process_sp) |
| 257 | return AppleObjCRuntime::ClassDescriptorSP(); |
| 258 | return ObjCLanguageRuntime::ClassDescriptorSP( |
| 259 | new AppleObjCRuntimeV1::ClassDescriptorV1(m_parent_isa, process_sp)); |
| 260 | } |
| 261 | |
| 262 | AppleObjCRuntime::ClassDescriptorSP |
| 263 | AppleObjCRuntimeV1::ClassDescriptorV1::GetMetaclass() const { |
| 264 | return ClassDescriptorSP(); |
| 265 | } |
| 266 | |
| 267 | bool AppleObjCRuntimeV1::ClassDescriptorV1::Describe( |
| 268 | std::function<void(ObjCLanguageRuntime::ObjCISA)> const &superclass_func, |
| 269 | std::function<bool(const char *, const char *)> const &instance_method_func, |
| 270 | std::function<bool(const char *, const char *)> const &class_method_func, |
| 271 | std::function<bool(const char *, const char *, lldb::addr_t, |
| 272 | uint64_t)> const &ivar_func) const { |
| 273 | return false; |
| 274 | } |
| 275 | |
| 276 | lldb::addr_t AppleObjCRuntimeV1::GetTaggedPointerObfuscator() { |
| 277 | return 0; |
| 278 | } |
| 279 | |
| 280 | lldb::addr_t AppleObjCRuntimeV1::GetISAHashTablePointer() { |
| 281 | if (m_isa_hash_table_ptr == LLDB_INVALID_ADDRESS) { |
| 282 | ModuleSP objc_module_sp(GetObjCModule()); |
| 283 | |
| 284 | if (!objc_module_sp) |
| 285 | return LLDB_INVALID_ADDRESS; |
| 286 | |
| 287 | static ConstString g_objc_debug_class_hash("_objc_debug_class_hash" ); |
| 288 | |
| 289 | const Symbol *symbol = objc_module_sp->FindFirstSymbolWithNameAndType( |
| 290 | name: g_objc_debug_class_hash, symbol_type: lldb::eSymbolTypeData); |
| 291 | if (symbol && symbol->ValueIsAddress()) { |
| 292 | Process *process = GetProcess(); |
| 293 | if (process) { |
| 294 | |
| 295 | lldb::addr_t objc_debug_class_hash_addr = |
| 296 | symbol->GetAddressRef().GetLoadAddress(target: &process->GetTarget()); |
| 297 | |
| 298 | if (objc_debug_class_hash_addr != LLDB_INVALID_ADDRESS) { |
| 299 | Status error; |
| 300 | lldb::addr_t objc_debug_class_hash_ptr = |
| 301 | process->ReadPointerFromMemory(vm_addr: objc_debug_class_hash_addr, error); |
| 302 | if (objc_debug_class_hash_ptr != 0 && |
| 303 | objc_debug_class_hash_ptr != LLDB_INVALID_ADDRESS) { |
| 304 | m_isa_hash_table_ptr = objc_debug_class_hash_ptr; |
| 305 | } |
| 306 | } |
| 307 | } |
| 308 | } |
| 309 | } |
| 310 | return m_isa_hash_table_ptr; |
| 311 | } |
| 312 | |
| 313 | void AppleObjCRuntimeV1::UpdateISAToDescriptorMapIfNeeded() { |
| 314 | // TODO: implement HashTableSignature... |
| 315 | Process *process = GetProcess(); |
| 316 | |
| 317 | if (process) { |
| 318 | // Update the process stop ID that indicates the last time we updated the |
| 319 | // map, whether it was successful or not. |
| 320 | m_isa_to_descriptor_stop_id = process->GetStopID(); |
| 321 | |
| 322 | Log *log = GetLog(mask: LLDBLog::Process); |
| 323 | |
| 324 | ProcessSP process_sp = process->shared_from_this(); |
| 325 | |
| 326 | ModuleSP objc_module_sp(GetObjCModule()); |
| 327 | |
| 328 | if (!objc_module_sp) |
| 329 | return; |
| 330 | |
| 331 | lldb::addr_t hash_table_ptr = GetISAHashTablePointer(); |
| 332 | if (hash_table_ptr != LLDB_INVALID_ADDRESS) { |
| 333 | // Read the NXHashTable struct: |
| 334 | // |
| 335 | // typedef struct { |
| 336 | // const NXHashTablePrototype *prototype; |
| 337 | // unsigned count; |
| 338 | // unsigned nbBuckets; |
| 339 | // void *buckets; |
| 340 | // const void *info; |
| 341 | // } NXHashTable; |
| 342 | |
| 343 | Status error; |
| 344 | DataBufferHeap buffer(1024, 0); |
| 345 | if (process->ReadMemory(vm_addr: hash_table_ptr, buf: buffer.GetBytes(), size: 20, error) == |
| 346 | 20) { |
| 347 | const uint32_t addr_size = m_process->GetAddressByteSize(); |
| 348 | const ByteOrder byte_order = m_process->GetByteOrder(); |
| 349 | DataExtractor data(buffer.GetBytes(), buffer.GetByteSize(), byte_order, |
| 350 | addr_size); |
| 351 | lldb::offset_t offset = addr_size; // Skip prototype |
| 352 | const uint32_t count = data.GetU32(offset_ptr: &offset); |
| 353 | const uint32_t num_buckets = data.GetU32(offset_ptr: &offset); |
| 354 | const addr_t buckets_ptr = data.GetAddress(offset_ptr: &offset); |
| 355 | if (m_hash_signature.NeedsUpdate(count, num_buckets, buckets_ptr)) { |
| 356 | m_hash_signature.UpdateSignature(count, num_buckets, buckets_ptr); |
| 357 | |
| 358 | const uint32_t data_size = num_buckets * 2 * sizeof(uint32_t); |
| 359 | buffer.SetByteSize(data_size); |
| 360 | |
| 361 | if (process->ReadMemory(vm_addr: buckets_ptr, buf: buffer.GetBytes(), size: data_size, |
| 362 | error) == data_size) { |
| 363 | data.SetData(bytes: buffer.GetBytes(), length: buffer.GetByteSize(), byte_order); |
| 364 | offset = 0; |
| 365 | for (uint32_t bucket_idx = 0; bucket_idx < num_buckets; |
| 366 | ++bucket_idx) { |
| 367 | const uint32_t bucket_isa_count = data.GetU32(offset_ptr: &offset); |
| 368 | const lldb::addr_t bucket_data = data.GetU32(offset_ptr: &offset); |
| 369 | |
| 370 | if (bucket_isa_count == 0) |
| 371 | continue; |
| 372 | |
| 373 | ObjCISA isa; |
| 374 | if (bucket_isa_count == 1) { |
| 375 | // When we only have one entry in the bucket, the bucket data |
| 376 | // is the "isa" |
| 377 | isa = bucket_data; |
| 378 | if (isa) { |
| 379 | if (!ISAIsCached(isa)) { |
| 380 | ClassDescriptorSP descriptor_sp( |
| 381 | new ClassDescriptorV1(isa, process_sp)); |
| 382 | |
| 383 | if (log && log->GetVerbose()) |
| 384 | LLDB_LOGF(log, |
| 385 | "AppleObjCRuntimeV1 added (ObjCISA)0x%" PRIx64 |
| 386 | " from _objc_debug_class_hash to " |
| 387 | "isa->descriptor cache" , |
| 388 | isa); |
| 389 | |
| 390 | AddClass(isa, descriptor_sp); |
| 391 | } |
| 392 | } |
| 393 | } else { |
| 394 | // When we have more than one entry in the bucket, the bucket |
| 395 | // data is a pointer to an array of "isa" values |
| 396 | addr_t isa_addr = bucket_data; |
| 397 | for (uint32_t isa_idx = 0; isa_idx < bucket_isa_count; |
| 398 | ++isa_idx, isa_addr += addr_size) { |
| 399 | isa = m_process->ReadPointerFromMemory(vm_addr: isa_addr, error); |
| 400 | |
| 401 | if (isa && isa != LLDB_INVALID_ADDRESS) { |
| 402 | if (!ISAIsCached(isa)) { |
| 403 | ClassDescriptorSP descriptor_sp( |
| 404 | new ClassDescriptorV1(isa, process_sp)); |
| 405 | |
| 406 | if (log && log->GetVerbose()) |
| 407 | LLDB_LOGF( |
| 408 | log, |
| 409 | "AppleObjCRuntimeV1 added (ObjCISA)0x%" PRIx64 |
| 410 | " from _objc_debug_class_hash to isa->descriptor " |
| 411 | "cache" , |
| 412 | isa); |
| 413 | |
| 414 | AddClass(isa, descriptor_sp); |
| 415 | } |
| 416 | } |
| 417 | } |
| 418 | } |
| 419 | } |
| 420 | } |
| 421 | } |
| 422 | } |
| 423 | } |
| 424 | } else { |
| 425 | m_isa_to_descriptor_stop_id = UINT32_MAX; |
| 426 | } |
| 427 | } |
| 428 | |
| 429 | DeclVendor *AppleObjCRuntimeV1::GetDeclVendor() { |
| 430 | return nullptr; |
| 431 | } |
| 432 | |