| 1 | //===-- AppleObjCClassDescriptorV2.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 "AppleObjCClassDescriptorV2.h" |
| 10 | |
| 11 | #include "lldb/Expression/FunctionCaller.h" |
| 12 | #include "lldb/Target/ABI.h" |
| 13 | #include "lldb/Target/Language.h" |
| 14 | #include "lldb/Utility/LLDBLog.h" |
| 15 | #include "lldb/Utility/Log.h" |
| 16 | #include "lldb/lldb-enumerations.h" |
| 17 | |
| 18 | using namespace lldb; |
| 19 | using namespace lldb_private; |
| 20 | |
| 21 | bool ClassDescriptorV2::Read_objc_class( |
| 22 | Process *process, std::unique_ptr<objc_class_t> &objc_class) const { |
| 23 | objc_class = std::make_unique<objc_class_t>(); |
| 24 | |
| 25 | bool ret = objc_class->Read(process, addr: m_objc_class_ptr); |
| 26 | |
| 27 | if (!ret) |
| 28 | objc_class.reset(); |
| 29 | |
| 30 | return ret; |
| 31 | } |
| 32 | |
| 33 | static lldb::addr_t GetClassDataMask(Process *process) { |
| 34 | switch (process->GetAddressByteSize()) { |
| 35 | case 4: |
| 36 | return 0xfffffffcUL; |
| 37 | case 8: |
| 38 | return 0x00007ffffffffff8UL; |
| 39 | default: |
| 40 | break; |
| 41 | } |
| 42 | |
| 43 | return LLDB_INVALID_ADDRESS; |
| 44 | } |
| 45 | |
| 46 | bool ClassDescriptorV2::objc_class_t::Read(Process *process, |
| 47 | lldb::addr_t addr) { |
| 48 | size_t ptr_size = process->GetAddressByteSize(); |
| 49 | |
| 50 | size_t objc_class_size = ptr_size // uintptr_t isa; |
| 51 | + ptr_size // Class superclass; |
| 52 | + ptr_size // void *cache; |
| 53 | + ptr_size // IMP *vtable; |
| 54 | + ptr_size; // uintptr_t data_NEVER_USE; |
| 55 | |
| 56 | DataBufferHeap objc_class_buf(objc_class_size, '\0'); |
| 57 | Status error; |
| 58 | |
| 59 | process->ReadMemory(vm_addr: addr, buf: objc_class_buf.GetBytes(), size: objc_class_size, error); |
| 60 | if (error.Fail()) { |
| 61 | return false; |
| 62 | } |
| 63 | |
| 64 | DataExtractor (objc_class_buf.GetBytes(), objc_class_size, |
| 65 | process->GetByteOrder(), |
| 66 | process->GetAddressByteSize()); |
| 67 | |
| 68 | lldb::offset_t cursor = 0; |
| 69 | |
| 70 | m_isa = extractor.GetAddress_unchecked(offset_ptr: &cursor); // uintptr_t isa; |
| 71 | m_superclass = extractor.GetAddress_unchecked(offset_ptr: &cursor); // Class superclass; |
| 72 | m_cache_ptr = extractor.GetAddress_unchecked(offset_ptr: &cursor); // void *cache; |
| 73 | m_vtable_ptr = extractor.GetAddress_unchecked(offset_ptr: &cursor); // IMP *vtable; |
| 74 | lldb::addr_t data_NEVER_USE = |
| 75 | extractor.GetAddress_unchecked(offset_ptr: &cursor); // uintptr_t data_NEVER_USE; |
| 76 | |
| 77 | m_flags = (uint8_t)(data_NEVER_USE & (lldb::addr_t)3); |
| 78 | m_data_ptr = data_NEVER_USE & GetClassDataMask(process); |
| 79 | |
| 80 | if (ABISP abi_sp = process->GetABI()) { |
| 81 | m_isa = abi_sp->FixCodeAddress(pc: m_isa); |
| 82 | m_superclass = abi_sp->FixCodeAddress(pc: m_superclass); |
| 83 | m_data_ptr = abi_sp->FixCodeAddress(pc: m_data_ptr); |
| 84 | } |
| 85 | return true; |
| 86 | } |
| 87 | |
| 88 | bool ClassDescriptorV2::class_rw_t::Read(Process *process, lldb::addr_t addr) { |
| 89 | size_t ptr_size = process->GetAddressByteSize(); |
| 90 | |
| 91 | size_t size = sizeof(uint32_t) // uint32_t flags; |
| 92 | + sizeof(uint32_t) // uint32_t version; |
| 93 | + ptr_size // const class_ro_t *ro; |
| 94 | + ptr_size // union { method_list_t **method_lists; |
| 95 | // method_list_t *method_list; }; |
| 96 | + ptr_size // struct chained_property_list *properties; |
| 97 | + ptr_size // const protocol_list_t **protocols; |
| 98 | + ptr_size // Class firstSubclass; |
| 99 | + ptr_size; // Class nextSiblingClass; |
| 100 | |
| 101 | DataBufferHeap buffer(size, '\0'); |
| 102 | Status error; |
| 103 | |
| 104 | process->ReadMemory(vm_addr: addr, buf: buffer.GetBytes(), size, error); |
| 105 | if (error.Fail()) { |
| 106 | return false; |
| 107 | } |
| 108 | |
| 109 | DataExtractor (buffer.GetBytes(), size, process->GetByteOrder(), |
| 110 | process->GetAddressByteSize()); |
| 111 | |
| 112 | lldb::offset_t cursor = 0; |
| 113 | |
| 114 | m_flags = extractor.GetU32_unchecked(offset_ptr: &cursor); |
| 115 | m_version = extractor.GetU32_unchecked(offset_ptr: &cursor); |
| 116 | m_ro_ptr = extractor.GetAddress_unchecked(offset_ptr: &cursor); |
| 117 | if (ABISP abi_sp = process->GetABI()) |
| 118 | m_ro_ptr = abi_sp->FixCodeAddress(pc: m_ro_ptr); |
| 119 | m_method_list_ptr = extractor.GetAddress_unchecked(offset_ptr: &cursor); |
| 120 | m_properties_ptr = extractor.GetAddress_unchecked(offset_ptr: &cursor); |
| 121 | m_firstSubclass = extractor.GetAddress_unchecked(offset_ptr: &cursor); |
| 122 | m_nextSiblingClass = extractor.GetAddress_unchecked(offset_ptr: &cursor); |
| 123 | |
| 124 | if (m_ro_ptr & 1) { |
| 125 | DataBufferHeap buffer(ptr_size, '\0'); |
| 126 | process->ReadMemory(vm_addr: m_ro_ptr ^ 1, buf: buffer.GetBytes(), size: ptr_size, error); |
| 127 | if (error.Fail()) |
| 128 | return false; |
| 129 | cursor = 0; |
| 130 | DataExtractor (buffer.GetBytes(), ptr_size, |
| 131 | process->GetByteOrder(), |
| 132 | process->GetAddressByteSize()); |
| 133 | m_ro_ptr = extractor.GetAddress_unchecked(offset_ptr: &cursor); |
| 134 | if (ABISP abi_sp = process->GetABI()) |
| 135 | m_ro_ptr = abi_sp->FixCodeAddress(pc: m_ro_ptr); |
| 136 | } |
| 137 | |
| 138 | return true; |
| 139 | } |
| 140 | |
| 141 | bool ClassDescriptorV2::class_ro_t::Read(Process *process, lldb::addr_t addr) { |
| 142 | size_t ptr_size = process->GetAddressByteSize(); |
| 143 | |
| 144 | size_t size = sizeof(uint32_t) // uint32_t flags; |
| 145 | + sizeof(uint32_t) // uint32_t instanceStart; |
| 146 | + sizeof(uint32_t) // uint32_t instanceSize; |
| 147 | + (ptr_size == 8 ? sizeof(uint32_t) |
| 148 | : 0) // uint32_t reserved; // __LP64__ only |
| 149 | + ptr_size // const uint8_t *ivarLayout; |
| 150 | + ptr_size // const char *name; |
| 151 | + ptr_size // const method_list_t *baseMethods; |
| 152 | + ptr_size // const protocol_list_t *baseProtocols; |
| 153 | + ptr_size // const ivar_list_t *ivars; |
| 154 | + ptr_size // const uint8_t *weakIvarLayout; |
| 155 | + ptr_size; // const property_list_t *baseProperties; |
| 156 | |
| 157 | DataBufferHeap buffer(size, '\0'); |
| 158 | Status error; |
| 159 | |
| 160 | process->ReadMemory(vm_addr: addr, buf: buffer.GetBytes(), size, error); |
| 161 | if (error.Fail()) { |
| 162 | return false; |
| 163 | } |
| 164 | |
| 165 | DataExtractor (buffer.GetBytes(), size, process->GetByteOrder(), |
| 166 | process->GetAddressByteSize()); |
| 167 | |
| 168 | lldb::offset_t cursor = 0; |
| 169 | |
| 170 | m_flags = extractor.GetU32_unchecked(offset_ptr: &cursor); |
| 171 | m_instanceStart = extractor.GetU32_unchecked(offset_ptr: &cursor); |
| 172 | m_instanceSize = extractor.GetU32_unchecked(offset_ptr: &cursor); |
| 173 | if (ptr_size == 8) |
| 174 | m_reserved = extractor.GetU32_unchecked(offset_ptr: &cursor); |
| 175 | else |
| 176 | m_reserved = 0; |
| 177 | m_ivarLayout_ptr = extractor.GetAddress_unchecked(offset_ptr: &cursor); |
| 178 | m_name_ptr = extractor.GetAddress_unchecked(offset_ptr: &cursor); |
| 179 | m_baseMethods_ptr = extractor.GetAddress_unchecked(offset_ptr: &cursor); |
| 180 | m_baseProtocols_ptr = extractor.GetAddress_unchecked(offset_ptr: &cursor); |
| 181 | m_ivars_ptr = extractor.GetAddress_unchecked(offset_ptr: &cursor); |
| 182 | m_weakIvarLayout_ptr = extractor.GetAddress_unchecked(offset_ptr: &cursor); |
| 183 | m_baseProperties_ptr = extractor.GetAddress_unchecked(offset_ptr: &cursor); |
| 184 | |
| 185 | DataBufferHeap name_buf(1024, '\0'); |
| 186 | |
| 187 | process->ReadCStringFromMemory(vm_addr: m_name_ptr, cstr: (char *)name_buf.GetBytes(), |
| 188 | cstr_max_len: name_buf.GetByteSize(), error); |
| 189 | |
| 190 | if (error.Fail()) { |
| 191 | return false; |
| 192 | } |
| 193 | |
| 194 | m_name.assign(s: (char *)name_buf.GetBytes()); |
| 195 | |
| 196 | return true; |
| 197 | } |
| 198 | |
| 199 | bool ClassDescriptorV2::Read_class_row( |
| 200 | Process *process, const objc_class_t &objc_class, |
| 201 | std::unique_ptr<class_ro_t> &class_ro, |
| 202 | std::unique_ptr<class_rw_t> &class_rw) const { |
| 203 | class_ro.reset(); |
| 204 | class_rw.reset(); |
| 205 | |
| 206 | Status error; |
| 207 | uint32_t class_row_t_flags = process->ReadUnsignedIntegerFromMemory( |
| 208 | load_addr: objc_class.m_data_ptr, byte_size: sizeof(uint32_t), fail_value: 0, error); |
| 209 | if (!error.Success()) |
| 210 | return false; |
| 211 | |
| 212 | if (class_row_t_flags & RW_REALIZED) { |
| 213 | class_rw = std::make_unique<class_rw_t>(); |
| 214 | |
| 215 | if (!class_rw->Read(process, addr: objc_class.m_data_ptr)) { |
| 216 | class_rw.reset(); |
| 217 | return false; |
| 218 | } |
| 219 | |
| 220 | class_ro = std::make_unique<class_ro_t>(); |
| 221 | |
| 222 | if (!class_ro->Read(process, addr: class_rw->m_ro_ptr)) { |
| 223 | class_rw.reset(); |
| 224 | class_ro.reset(); |
| 225 | return false; |
| 226 | } |
| 227 | } else { |
| 228 | class_ro = std::make_unique<class_ro_t>(); |
| 229 | |
| 230 | if (!class_ro->Read(process, addr: objc_class.m_data_ptr)) { |
| 231 | class_ro.reset(); |
| 232 | return false; |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | return true; |
| 237 | } |
| 238 | |
| 239 | bool ClassDescriptorV2::method_list_t::Read(Process *process, |
| 240 | lldb::addr_t addr) { |
| 241 | size_t size = sizeof(uint32_t) // uint32_t entsize_NEVER_USE; |
| 242 | + sizeof(uint32_t); // uint32_t count; |
| 243 | |
| 244 | DataBufferHeap buffer(size, '\0'); |
| 245 | Status error; |
| 246 | |
| 247 | if (ABISP abi_sp = process->GetABI()) |
| 248 | addr = abi_sp->FixCodeAddress(pc: addr); |
| 249 | process->ReadMemory(vm_addr: addr, buf: buffer.GetBytes(), size, error); |
| 250 | if (error.Fail()) { |
| 251 | return false; |
| 252 | } |
| 253 | |
| 254 | DataExtractor (buffer.GetBytes(), size, process->GetByteOrder(), |
| 255 | process->GetAddressByteSize()); |
| 256 | |
| 257 | lldb::offset_t cursor = 0; |
| 258 | |
| 259 | uint32_t entsize = extractor.GetU32_unchecked(offset_ptr: &cursor); |
| 260 | m_is_small = (entsize & 0x80000000) != 0; |
| 261 | m_has_direct_selector = (entsize & 0x40000000) != 0; |
| 262 | m_entsize = entsize & 0xfffc; |
| 263 | m_count = extractor.GetU32_unchecked(offset_ptr: &cursor); |
| 264 | m_first_ptr = addr + cursor; |
| 265 | |
| 266 | return true; |
| 267 | } |
| 268 | |
| 269 | bool ClassDescriptorV2::method_t::Read(Process *process, lldb::addr_t addr, |
| 270 | lldb::addr_t relative_selector_base_addr, |
| 271 | bool is_small, bool has_direct_sel) { |
| 272 | size_t ptr_size = process->GetAddressByteSize(); |
| 273 | size_t size = GetSize(process, is_small); |
| 274 | |
| 275 | DataBufferHeap buffer(size, '\0'); |
| 276 | Status error; |
| 277 | |
| 278 | process->ReadMemory(vm_addr: addr, buf: buffer.GetBytes(), size, error); |
| 279 | if (error.Fail()) { |
| 280 | return false; |
| 281 | } |
| 282 | |
| 283 | DataExtractor (buffer.GetBytes(), size, process->GetByteOrder(), |
| 284 | ptr_size); |
| 285 | lldb::offset_t cursor = 0; |
| 286 | |
| 287 | if (is_small) { |
| 288 | uint32_t nameref_offset = extractor.GetU32_unchecked(offset_ptr: &cursor); |
| 289 | uint32_t types_offset = extractor.GetU32_unchecked(offset_ptr: &cursor); |
| 290 | uint32_t imp_offset = extractor.GetU32_unchecked(offset_ptr: &cursor); |
| 291 | |
| 292 | m_name_ptr = addr + nameref_offset; |
| 293 | |
| 294 | if (!has_direct_sel) { |
| 295 | // The SEL offset points to a SELRef. We need to dereference twice. |
| 296 | m_name_ptr = process->ReadUnsignedIntegerFromMemory(load_addr: m_name_ptr, byte_size: ptr_size, |
| 297 | fail_value: 0, error); |
| 298 | if (!error.Success()) |
| 299 | return false; |
| 300 | } else if (relative_selector_base_addr != LLDB_INVALID_ADDRESS) { |
| 301 | m_name_ptr = relative_selector_base_addr + nameref_offset; |
| 302 | } |
| 303 | m_types_ptr = addr + 4 + types_offset; |
| 304 | m_imp_ptr = addr + 8 + imp_offset; |
| 305 | } else { |
| 306 | m_name_ptr = extractor.GetAddress_unchecked(offset_ptr: &cursor); |
| 307 | m_types_ptr = extractor.GetAddress_unchecked(offset_ptr: &cursor); |
| 308 | m_imp_ptr = extractor.GetAddress_unchecked(offset_ptr: &cursor); |
| 309 | } |
| 310 | |
| 311 | process->ReadCStringFromMemory(vm_addr: m_name_ptr, out_str&: m_name, error); |
| 312 | if (error.Fail()) { |
| 313 | return false; |
| 314 | } |
| 315 | |
| 316 | process->ReadCStringFromMemory(vm_addr: m_types_ptr, out_str&: m_types, error); |
| 317 | return !error.Fail(); |
| 318 | } |
| 319 | |
| 320 | bool ClassDescriptorV2::ivar_list_t::Read(Process *process, lldb::addr_t addr) { |
| 321 | size_t size = sizeof(uint32_t) // uint32_t entsize; |
| 322 | + sizeof(uint32_t); // uint32_t count; |
| 323 | |
| 324 | DataBufferHeap buffer(size, '\0'); |
| 325 | Status error; |
| 326 | |
| 327 | process->ReadMemory(vm_addr: addr, buf: buffer.GetBytes(), size, error); |
| 328 | if (error.Fail()) { |
| 329 | return false; |
| 330 | } |
| 331 | |
| 332 | DataExtractor (buffer.GetBytes(), size, process->GetByteOrder(), |
| 333 | process->GetAddressByteSize()); |
| 334 | |
| 335 | lldb::offset_t cursor = 0; |
| 336 | |
| 337 | m_entsize = extractor.GetU32_unchecked(offset_ptr: &cursor); |
| 338 | m_count = extractor.GetU32_unchecked(offset_ptr: &cursor); |
| 339 | m_first_ptr = addr + cursor; |
| 340 | |
| 341 | return true; |
| 342 | } |
| 343 | |
| 344 | bool ClassDescriptorV2::ivar_t::Read(Process *process, lldb::addr_t addr) { |
| 345 | size_t size = GetSize(process); |
| 346 | |
| 347 | DataBufferHeap buffer(size, '\0'); |
| 348 | Status error; |
| 349 | |
| 350 | process->ReadMemory(vm_addr: addr, buf: buffer.GetBytes(), size, error); |
| 351 | if (error.Fail()) { |
| 352 | return false; |
| 353 | } |
| 354 | |
| 355 | DataExtractor (buffer.GetBytes(), size, process->GetByteOrder(), |
| 356 | process->GetAddressByteSize()); |
| 357 | |
| 358 | lldb::offset_t cursor = 0; |
| 359 | |
| 360 | m_offset_ptr = extractor.GetAddress_unchecked(offset_ptr: &cursor); |
| 361 | m_name_ptr = extractor.GetAddress_unchecked(offset_ptr: &cursor); |
| 362 | m_type_ptr = extractor.GetAddress_unchecked(offset_ptr: &cursor); |
| 363 | m_alignment = extractor.GetU32_unchecked(offset_ptr: &cursor); |
| 364 | m_size = extractor.GetU32_unchecked(offset_ptr: &cursor); |
| 365 | |
| 366 | process->ReadCStringFromMemory(vm_addr: m_name_ptr, out_str&: m_name, error); |
| 367 | if (error.Fail()) { |
| 368 | return false; |
| 369 | } |
| 370 | |
| 371 | process->ReadCStringFromMemory(vm_addr: m_type_ptr, out_str&: m_type, error); |
| 372 | return !error.Fail(); |
| 373 | } |
| 374 | |
| 375 | bool ClassDescriptorV2::relative_list_entry_t::Read(Process *process, |
| 376 | lldb::addr_t addr) { |
| 377 | Log *log = GetLog(mask: LLDBLog::Types); |
| 378 | size_t size = sizeof(uint64_t); // m_image_index : 16 |
| 379 | // m_list_offset : 48 |
| 380 | |
| 381 | DataBufferHeap buffer(size, '\0'); |
| 382 | Status error; |
| 383 | |
| 384 | process->ReadMemory(vm_addr: addr, buf: buffer.GetBytes(), size, error); |
| 385 | // FIXME: Propagate this error up |
| 386 | if (error.Fail()) { |
| 387 | LLDB_LOG(log, "Failed to read relative_list_entry_t at address {0:x}" , |
| 388 | addr); |
| 389 | return false; |
| 390 | } |
| 391 | |
| 392 | DataExtractor (buffer.GetBytes(), size, process->GetByteOrder(), |
| 393 | process->GetAddressByteSize()); |
| 394 | lldb::offset_t cursor = 0; |
| 395 | uint64_t raw_entry = extractor.GetU64_unchecked(offset_ptr: &cursor); |
| 396 | m_image_index = raw_entry & 0xFFFF; |
| 397 | m_list_offset = llvm::SignExtend64<48>(x: raw_entry >> 16); |
| 398 | return true; |
| 399 | } |
| 400 | |
| 401 | bool ClassDescriptorV2::relative_list_list_t::Read(Process *process, |
| 402 | lldb::addr_t addr) { |
| 403 | Log *log = GetLog(mask: LLDBLog::Types); |
| 404 | size_t size = sizeof(uint32_t) // m_entsize |
| 405 | + sizeof(uint32_t); // m_count |
| 406 | |
| 407 | DataBufferHeap buffer(size, '\0'); |
| 408 | Status error; |
| 409 | |
| 410 | // FIXME: Propagate this error up |
| 411 | process->ReadMemory(vm_addr: addr, buf: buffer.GetBytes(), size, error); |
| 412 | if (error.Fail()) { |
| 413 | LLDB_LOG(log, "Failed to read relative_list_list_t at address 0x" PRIx64, |
| 414 | addr); |
| 415 | return false; |
| 416 | } |
| 417 | |
| 418 | DataExtractor (buffer.GetBytes(), size, process->GetByteOrder(), |
| 419 | process->GetAddressByteSize()); |
| 420 | lldb::offset_t cursor = 0; |
| 421 | m_entsize = extractor.GetU32_unchecked(offset_ptr: &cursor); |
| 422 | m_count = extractor.GetU32_unchecked(offset_ptr: &cursor); |
| 423 | m_first_ptr = addr + cursor; |
| 424 | return true; |
| 425 | } |
| 426 | |
| 427 | std::optional<ClassDescriptorV2::method_list_t> |
| 428 | ClassDescriptorV2::GetMethodList(Process *process, |
| 429 | lldb::addr_t method_list_ptr) const { |
| 430 | Log *log = GetLog(mask: LLDBLog::Types); |
| 431 | ClassDescriptorV2::method_list_t method_list; |
| 432 | if (!method_list.Read(process, addr: method_list_ptr)) |
| 433 | return std::nullopt; |
| 434 | |
| 435 | const size_t method_size = method_t::GetSize(process, is_small: method_list.m_is_small); |
| 436 | if (method_list.m_entsize != method_size) { |
| 437 | LLDB_LOG(log, |
| 438 | "method_list_t at address 0x" PRIx64 " has an entsize of " PRIu16 |
| 439 | " but method size should be " PRIu64, |
| 440 | method_list_ptr, method_list.m_entsize, method_size); |
| 441 | return std::nullopt; |
| 442 | } |
| 443 | |
| 444 | return method_list; |
| 445 | } |
| 446 | |
| 447 | bool ClassDescriptorV2::ProcessMethodList( |
| 448 | std::function<bool(const char *, const char *)> const &instance_method_func, |
| 449 | ClassDescriptorV2::method_list_t &method_list) const { |
| 450 | lldb_private::Process *process = m_runtime.GetProcess(); |
| 451 | auto method = std::make_unique<method_t>(); |
| 452 | lldb::addr_t relative_selector_base_addr = |
| 453 | m_runtime.GetRelativeSelectorBaseAddr(); |
| 454 | for (uint32_t i = 0, e = method_list.m_count; i < e; ++i) { |
| 455 | method->Read(process, addr: method_list.m_first_ptr + (i * method_list.m_entsize), |
| 456 | relative_selector_base_addr, is_small: method_list.m_is_small, |
| 457 | has_direct_sel: method_list.m_has_direct_selector); |
| 458 | if (instance_method_func(method->m_name.c_str(), method->m_types.c_str())) |
| 459 | break; |
| 460 | } |
| 461 | return true; |
| 462 | } |
| 463 | |
| 464 | // The relevant data structures: |
| 465 | // - relative_list_list_t |
| 466 | // - uint32_t count |
| 467 | // - uint32_t entsize |
| 468 | // - Followed by <count> number of relative_list_entry_t of size <entsize> |
| 469 | // |
| 470 | // - relative_list_entry_t |
| 471 | // - uint64_t image_index : 16 |
| 472 | // - int64_t list_offset : 48 |
| 473 | // - Note: The above 2 fit into 8 bytes always |
| 474 | // |
| 475 | // image_index corresponds to an image in the shared cache |
| 476 | // list_offset is used to calculate the address of the method_list_t we want |
| 477 | bool ClassDescriptorV2::ProcessRelativeMethodLists( |
| 478 | std::function<bool(const char *, const char *)> const &instance_method_func, |
| 479 | lldb::addr_t relative_method_list_ptr) const { |
| 480 | lldb_private::Process *process = m_runtime.GetProcess(); |
| 481 | auto relative_method_lists = std::make_unique<relative_list_list_t>(); |
| 482 | |
| 483 | // 1. Process the count and entsize of the relative_list_list_t |
| 484 | if (!relative_method_lists->Read(process, addr: relative_method_list_ptr)) |
| 485 | return false; |
| 486 | |
| 487 | auto entry = std::make_unique<relative_list_entry_t>(); |
| 488 | for (uint32_t i = 0; i < relative_method_lists->m_count; i++) { |
| 489 | // 2. Extract the image index and the list offset from the |
| 490 | // relative_list_entry_t |
| 491 | const lldb::addr_t entry_addr = relative_method_lists->m_first_ptr + |
| 492 | (i * relative_method_lists->m_entsize); |
| 493 | if (!entry->Read(process, addr: entry_addr)) |
| 494 | return false; |
| 495 | |
| 496 | // 3. Calculate the pointer to the method_list_t from the |
| 497 | // relative_list_entry_t |
| 498 | const lldb::addr_t method_list_addr = entry_addr + entry->m_list_offset; |
| 499 | |
| 500 | // 4. Get the method_list_t from the pointer |
| 501 | std::optional<method_list_t> method_list = |
| 502 | GetMethodList(process, method_list_ptr: method_list_addr); |
| 503 | if (!method_list) |
| 504 | return false; |
| 505 | |
| 506 | // 5. Cache the result so we don't need to reconstruct it later. |
| 507 | m_image_to_method_lists[entry->m_image_index].emplace_back(args&: *method_list); |
| 508 | |
| 509 | // 6. If the relevant image is loaded, add the methods to the Decl |
| 510 | if (!m_runtime.IsSharedCacheImageLoaded(image_index: entry->m_image_index)) |
| 511 | continue; |
| 512 | |
| 513 | if (!ProcessMethodList(instance_method_func, method_list&: *method_list)) |
| 514 | return false; |
| 515 | } |
| 516 | |
| 517 | // We need to keep track of the last time we updated so we can re-update the |
| 518 | // type information in the future |
| 519 | m_last_version_updated = m_runtime.GetSharedCacheImageHeaderVersion(); |
| 520 | |
| 521 | return true; |
| 522 | } |
| 523 | |
| 524 | bool ClassDescriptorV2::Describe( |
| 525 | std::function<void(ObjCLanguageRuntime::ObjCISA)> const &superclass_func, |
| 526 | std::function<bool(const char *, const char *)> const &instance_method_func, |
| 527 | std::function<bool(const char *, const char *)> const &class_method_func, |
| 528 | std::function<bool(const char *, const char *, lldb::addr_t, |
| 529 | uint64_t)> const &ivar_func) const { |
| 530 | lldb_private::Process *process = m_runtime.GetProcess(); |
| 531 | |
| 532 | std::unique_ptr<objc_class_t> objc_class; |
| 533 | std::unique_ptr<class_ro_t> class_ro; |
| 534 | std::unique_ptr<class_rw_t> class_rw; |
| 535 | |
| 536 | if (!Read_objc_class(process, objc_class)) |
| 537 | return false; |
| 538 | if (!Read_class_row(process, objc_class: *objc_class, class_ro, class_rw)) |
| 539 | return false; |
| 540 | |
| 541 | static ConstString NSObject_name("NSObject" ); |
| 542 | |
| 543 | if (m_name != NSObject_name && superclass_func) |
| 544 | superclass_func(objc_class->m_superclass); |
| 545 | |
| 546 | if (instance_method_func) { |
| 547 | // This is a relative list of lists |
| 548 | if (class_ro->m_baseMethods_ptr & 1) { |
| 549 | if (!ProcessRelativeMethodLists(instance_method_func, |
| 550 | relative_method_list_ptr: class_ro->m_baseMethods_ptr ^ 1)) |
| 551 | return false; |
| 552 | } else { |
| 553 | std::optional<method_list_t> base_method_list = |
| 554 | GetMethodList(process, method_list_ptr: class_ro->m_baseMethods_ptr); |
| 555 | if (!base_method_list) |
| 556 | return false; |
| 557 | if (!ProcessMethodList(instance_method_func, method_list&: *base_method_list)) |
| 558 | return false; |
| 559 | } |
| 560 | } |
| 561 | |
| 562 | if (class_method_func) { |
| 563 | AppleObjCRuntime::ClassDescriptorSP metaclass(GetMetaclass()); |
| 564 | |
| 565 | // We don't care about the metaclass's superclass, or its class methods. |
| 566 | // Its instance methods are our class methods. |
| 567 | |
| 568 | if (metaclass) { |
| 569 | metaclass->Describe( |
| 570 | superclass_func: std::function<void(ObjCLanguageRuntime::ObjCISA)>(nullptr), |
| 571 | instance_method_func: class_method_func, |
| 572 | class_method_func: std::function<bool(const char *, const char *)>(nullptr), |
| 573 | ivar_func: std::function<bool(const char *, const char *, lldb::addr_t, |
| 574 | uint64_t)>(nullptr)); |
| 575 | } |
| 576 | } |
| 577 | |
| 578 | if (ivar_func) { |
| 579 | if (class_ro->m_ivars_ptr != 0) { |
| 580 | ivar_list_t ivar_list; |
| 581 | if (!ivar_list.Read(process, addr: class_ro->m_ivars_ptr)) |
| 582 | return false; |
| 583 | |
| 584 | if (ivar_list.m_entsize != ivar_t::GetSize(process)) |
| 585 | return false; |
| 586 | |
| 587 | ivar_t ivar; |
| 588 | |
| 589 | for (uint32_t i = 0, e = ivar_list.m_count; i < e; ++i) { |
| 590 | ivar.Read(process, addr: ivar_list.m_first_ptr + (i * ivar_list.m_entsize)); |
| 591 | |
| 592 | if (ivar_func(ivar.m_name.c_str(), ivar.m_type.c_str(), |
| 593 | ivar.m_offset_ptr, ivar.m_size)) |
| 594 | break; |
| 595 | } |
| 596 | } |
| 597 | } |
| 598 | |
| 599 | return true; |
| 600 | } |
| 601 | |
| 602 | ConstString ClassDescriptorV2::GetClassName() { |
| 603 | if (!m_name) { |
| 604 | lldb_private::Process *process = m_runtime.GetProcess(); |
| 605 | |
| 606 | if (process) { |
| 607 | std::unique_ptr<objc_class_t> objc_class; |
| 608 | std::unique_ptr<class_ro_t> class_ro; |
| 609 | std::unique_ptr<class_rw_t> class_rw; |
| 610 | |
| 611 | if (!Read_objc_class(process, objc_class)) |
| 612 | return m_name; |
| 613 | if (!Read_class_row(process, objc_class: *objc_class, class_ro, class_rw)) |
| 614 | return m_name; |
| 615 | |
| 616 | m_name = ConstString(class_ro->m_name.c_str()); |
| 617 | } |
| 618 | } |
| 619 | return m_name; |
| 620 | } |
| 621 | |
| 622 | ObjCLanguageRuntime::ClassDescriptorSP ClassDescriptorV2::GetSuperclass() { |
| 623 | lldb_private::Process *process = m_runtime.GetProcess(); |
| 624 | |
| 625 | if (!process) |
| 626 | return ObjCLanguageRuntime::ClassDescriptorSP(); |
| 627 | |
| 628 | std::unique_ptr<objc_class_t> objc_class; |
| 629 | |
| 630 | if (!Read_objc_class(process, objc_class)) |
| 631 | return ObjCLanguageRuntime::ClassDescriptorSP(); |
| 632 | |
| 633 | return m_runtime.ObjCLanguageRuntime::GetClassDescriptorFromISA( |
| 634 | isa: objc_class->m_superclass); |
| 635 | } |
| 636 | |
| 637 | ObjCLanguageRuntime::ClassDescriptorSP ClassDescriptorV2::GetMetaclass() const { |
| 638 | lldb_private::Process *process = m_runtime.GetProcess(); |
| 639 | |
| 640 | if (!process) |
| 641 | return ObjCLanguageRuntime::ClassDescriptorSP(); |
| 642 | |
| 643 | std::unique_ptr<objc_class_t> objc_class; |
| 644 | |
| 645 | if (!Read_objc_class(process, objc_class)) |
| 646 | return ObjCLanguageRuntime::ClassDescriptorSP(); |
| 647 | |
| 648 | lldb::addr_t candidate_isa = m_runtime.GetPointerISA(isa: objc_class->m_isa); |
| 649 | |
| 650 | return ObjCLanguageRuntime::ClassDescriptorSP( |
| 651 | new ClassDescriptorV2(m_runtime, candidate_isa, nullptr)); |
| 652 | } |
| 653 | |
| 654 | uint64_t ClassDescriptorV2::GetInstanceSize() { |
| 655 | lldb_private::Process *process = m_runtime.GetProcess(); |
| 656 | |
| 657 | if (process) { |
| 658 | std::unique_ptr<objc_class_t> objc_class; |
| 659 | std::unique_ptr<class_ro_t> class_ro; |
| 660 | std::unique_ptr<class_rw_t> class_rw; |
| 661 | |
| 662 | if (!Read_objc_class(process, objc_class)) |
| 663 | return 0; |
| 664 | if (!Read_class_row(process, objc_class: *objc_class, class_ro, class_rw)) |
| 665 | return 0; |
| 666 | |
| 667 | return class_ro->m_instanceSize; |
| 668 | } |
| 669 | |
| 670 | return 0; |
| 671 | } |
| 672 | |
| 673 | // From the ObjC runtime. |
| 674 | static uint8_t IS_SWIFT_STABLE = 1U << 1; |
| 675 | |
| 676 | LanguageType ClassDescriptorV2::GetImplementationLanguage() const { |
| 677 | std::unique_ptr<objc_class_t> objc_class; |
| 678 | if (auto *process = m_runtime.GetProcess()) |
| 679 | if (Read_objc_class(process, objc_class)) |
| 680 | if (objc_class->m_flags & IS_SWIFT_STABLE) |
| 681 | return lldb::eLanguageTypeSwift; |
| 682 | |
| 683 | return lldb::eLanguageTypeObjC; |
| 684 | } |
| 685 | |
| 686 | ClassDescriptorV2::iVarsStorage::() : m_ivars(), m_mutex() {} |
| 687 | |
| 688 | size_t ClassDescriptorV2::iVarsStorage::() { return m_ivars.size(); } |
| 689 | |
| 690 | ClassDescriptorV2::iVarDescriptor &ClassDescriptorV2::iVarsStorage:: |
| 691 | (size_t idx) { |
| 692 | return m_ivars[idx]; |
| 693 | } |
| 694 | |
| 695 | void ClassDescriptorV2::iVarsStorage::(AppleObjCRuntimeV2 &runtime, |
| 696 | ClassDescriptorV2 &descriptor) { |
| 697 | if (m_filled) |
| 698 | return; |
| 699 | std::lock_guard<std::recursive_mutex> guard(m_mutex); |
| 700 | Log *log = GetLog(mask: LLDBLog::Types); |
| 701 | LLDB_LOGV(log, "class_name = {0}" , descriptor.GetClassName()); |
| 702 | m_filled = true; |
| 703 | ObjCLanguageRuntime::EncodingToTypeSP encoding_to_type_sp( |
| 704 | runtime.GetEncodingToType()); |
| 705 | Process *process(runtime.GetProcess()); |
| 706 | if (!encoding_to_type_sp) |
| 707 | return; |
| 708 | descriptor.Describe(superclass_func: nullptr, instance_method_func: nullptr, class_method_func: nullptr, ivar_func: [this, process, |
| 709 | encoding_to_type_sp, |
| 710 | log](const char *name, |
| 711 | const char *type, |
| 712 | lldb::addr_t offset_ptr, |
| 713 | uint64_t size) -> bool { |
| 714 | const bool for_expression = false; |
| 715 | const bool stop_loop = false; |
| 716 | LLDB_LOGV(log, "name = {0}, encoding = {1}, offset_ptr = {2:x}, size = {3}" , |
| 717 | name, type, offset_ptr, size); |
| 718 | CompilerType ivar_type = |
| 719 | encoding_to_type_sp->RealizeType(name: type, for_expression); |
| 720 | if (ivar_type) { |
| 721 | LLDB_LOGV(log, |
| 722 | "name = {0}, encoding = {1}, offset_ptr = {2:x}, size = " |
| 723 | "{3}, type_size = {4}" , |
| 724 | name, type, offset_ptr, size, |
| 725 | expectedToOptional(ivar_type.GetByteSize(nullptr)).value_or(0)); |
| 726 | Scalar offset_scalar; |
| 727 | Status error; |
| 728 | const int offset_ptr_size = 4; |
| 729 | const bool is_signed = false; |
| 730 | size_t read = process->ReadScalarIntegerFromMemory( |
| 731 | addr: offset_ptr, byte_size: offset_ptr_size, is_signed, scalar&: offset_scalar, error); |
| 732 | if (error.Success() && 4 == read) { |
| 733 | LLDB_LOGV(log, "offset_ptr = {0:x} --> {1}" , offset_ptr, |
| 734 | offset_scalar.SInt()); |
| 735 | m_ivars.push_back( |
| 736 | x: {.m_name: ConstString(name), .m_type: ivar_type, .m_size: size, .m_offset: offset_scalar.SInt()}); |
| 737 | } else |
| 738 | LLDB_LOGV(log, "offset_ptr = {0:x} --> read fail, read = %{1}" , |
| 739 | offset_ptr, read); |
| 740 | } |
| 741 | return stop_loop; |
| 742 | }); |
| 743 | } |
| 744 | |
| 745 | void ClassDescriptorV2::GetIVarInformation() { |
| 746 | m_ivars_storage.fill(runtime&: m_runtime, descriptor&: *this); |
| 747 | } |
| 748 | |