提问人:Jorayen 提问时间:10/15/2023 最后编辑:Jorayen 更新时间:10/19/2023 访问量:71
磁盘上的 Objective-C ISA 指针与实例化对象时的指针
Objective-C ISA pointer on disk vs when object being instantiated
问:
Objective-C 运行时 ISA 指针定义如下:
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
uintptr_t bits;
private:
// Accessing the class requires custom ptrauth operations, so
// force clients to go through setClass/getClass by making this
// private.
Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
bool isDeallocating() {
return extra_rc == 0 && has_sidetable_rc == 0;
}
void setDeallocating() {
extra_rc = 0;
has_sidetable_rc = 0;
}
#endif
void setClass(Class cls, objc_object *obj);
Class getClass(bool authenticated);
Class getDecodedClass(bool authenticated);
};
位字段可以通过此处的定义读取。
当我从磁盘上读到一个男子气概并转到该部分并遵循定义如下的部分时:_objc_classlist
objc_class
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
...
并定义如下:objc_object
struct objc_object {
private:
isa_t isa;
public:
...
这意味着我应该能够将 的前 8 个字节解释为 的字段,但是当我这样做并尝试解释这些位时,我会得到随机和错误的信息,
另一方面,如果我将前 8 个字节解释为指针,它会将我引导到磁盘上的另一个实例,这通常是类的元类。那么我想知道为什么联合的定义来自 Objective-C 运行时及其字段。当我们实例化某种对象并且从磁盘读取时,它只是指向元类定义的指针时,将其解释为并集是否正确?objc_class
bits
isa
objc_class
isa
bits
isa
bits
编辑:
我从文件中读取结构的方式是使用 python:objc_class
ISA_MASK = 0x0000000ffffffff8
@dataclass
class Isa():
bits: ctypes.c_size_t
_cls: ctypes.c_size_t
def __init__(self, fp, addr):
fp.seek(addr)
self.bits = struct.unpack("<Q", fp.read(8))[0]
self._cls = self.bits
def nonpointer(self):
return self.bits & 1
def has_assoc(self):
return (self.bits >> 1) & 1
def has_cxx_dtor(self):
return (self.bits >> 2) & 1
def shiftcls(self):
return (self.bits >> 3) & 0x7ffffffff
def magic(self):
return (self.bits >> 36) & 0x3f
def weakly_referenced(self):
return (self.bits >> 42) & 1
def unused(self):
return (self.bits >> 43) & 1
def has_sidetable_rc(self):
return (self.bits >> 44) & 1
def extra_rc(self):
return (self.bits >> 45) & 0x7ffff
def get_class(self):
clsbits = self.bits
clsbits &= ISA_MASK
return clsbits
@dataclass
class ObjcObject:
isa: Isa
_addr: ctypes.c_size_t
def __init__(self, fp, addr, isa_class, external_block_addr):
self.isa = None
self._addr = addr
fp.seek(addr)
isa_addr = struct.unpack("<Q", fp.read(8))[0]
if isa_addr != 0 and isa_addr < external_block_addr:
self.isa = Isa(fp, isa_addr, external_block_addr)
@dataclass
class ObjcClass(ObjcObject):
super_class: ObjcClass
cache: Cache
class_ro: ClassRo
def __init__(self, fp, addr, external_block_addr):
super().__init__(fp, addr, ObjcClass, external_block_addr)
...
...
例如,我有一个类让我们调用它,在处理地址上的链式修复后,我有它,它的符号和该地址中的定义。A
0x0025eed0
_OBJC_CLASS_$_A
objc_class
结构的前 8 个字节是我们通过查看运行时的源代码建立的 ISA。将它作为指针而不是将其视为联合,我得到了符号的另一个结构,即该类的元类。isa_t
objc_class
_OBJC_METACLASS_$_A
现在,如果我没有将结构的前 8 个字节视为指向元类的指针,而是尝试将它们解释为联合的位,就像我在提供的代码中一样,例如使用我得到的方法,这是不正确的,因为我可以在结构上清楚地找到这个方法,所以它与我解析的内容不匹配,因此联合似乎不相关到磁盘上类的实际数据。objc_class
isa_t
has_cxx_dtor
False
method_list_t
class_ro
isa_t
请注意,从 的位中提取数据的方法是查看 isa.h
的源代码,并假设我读取了一个没有 ptr auth 的 ARM64 男子气概,而不是从模拟器读取的。isa_t
答:
在对运行时进行一些挖掘之后,似乎非指针 isa 是仅运行时的概念,并且所有磁盘上的 isa 将始终是常规指针。
对象文件中 Obj-C 类的加载过程:
dyld
调用 (/),传入对象标头以从中读取和加载类_objc_map_images
objc-internal.h
objc-runtime-new.mm
_objc_map_images
在调用之前做一些设置map_images
(objc-private.h
/objc-runtime-new.mm
)map_images
获取运行时锁,然后调用map_images_nolock
(objc-private.h
/objc-os.mm
)map_images_nolock
遍历 mach 标头,搜索 Obj-C 信息并执行一些验证。它将所有包含 Obj-C 类的标头传递给_read_images
(objc-private.h
/objc-runtime-new.mm
)_read_images
是我们真正到达有趣部分的地方。它首先设置对与运行时目标相关的非指针 isas 的支持,并设置一些用于存储类信息的表。在读取并修复选择器后,它开始读取类信息(OBJC_RUNTIME_DISCOVER_CLASSES_START()
)- 对于每个标头,它会循环访问标头中存储的原始数据,接收指向图像中每个类的直接指针
classlist
- 对于以这种方式读取的每个类,它都会调用 (),它解析了错误的类名、Swift 类等——但最终,读取(指向 dyld 类的原始指针)要么被强制转换为(类对象),要么被分配的实例替换
readClass
objc-runtime-new.mm
classref_t
Class
Class
- 对于每个标头,它会循环访问标头中存储的原始数据,接收指向图像中每个类的直接指针
那么,非指针 isas 在哪里发挥作用呢?仅当在运行时设置对象的类时:
- 当您通过 either () 创建对象时,或者通过 设置对象的类时,该对象已调用 either 或 () (并且只是调用 to )
objc_constructInstance
class_createInstance
runtime.h
object_setClass
objc_object::initInstanceIsa
objc_object::initIsa
objc-object.h
initInstanceIsa
initIsa
objc_object::initIsa
有两个实现(一个是 for,另一个是不支持的),但都调用到SUPPORT_NONPOINTER_ISA
isa_t::setClass
(objc-private.h
/objc-object.h
)isa_t::setClass
还有两个实现 — 当为 true 时,实现在 ISA 值本身中设置适当的位,根据需要进行设置;当为 false 时,它只是直接设置类SUPPORT_NONPOINTER_ISA
shiftcls
SUPPORT_NONPOINTER_ISA
(或者反过来,如果你愿意的话:只从 / 调用,而 / 本身也只从 // 调用。isa_t::setClass
objc_object::initIsa
objc_object::changeIsa
objc_constructInstance
class_createInstance
object_setClass
因此,当您在磁盘上读取这些对象文件时,您只会遇到对象和类的指针 isa;在 ISAS 中实际设置的位仅在运行时完成。如果您希望从这些位中读取详细信息,则需要从周围的 mach-o 数据中自行构建该信息。
评论
__objc_classlist
objc_class
objc_class
objc_class