ios 示例化对象时磁盘上的C伊萨指针与

wnrlj8wa  于 2023-10-21  发布在  iOS
关注(0)|答案(1)|浏览(87)

XNUMBER-C运行时伊萨指针是defined,如下所示:

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);
};

位字段可以通过定义here来读取。
当我从磁盘中读取一个macho并转到_objc_classlist部分并遵循objc_class,即defined

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_objectdefined

struct objc_object {
private:
    isa_t isa;

public:
    ...

这意味着我应该能够将objc_class的前8个字节解释为isabits字段,但是当我这样做并试图解释这些位时,我得到了随机和错误的信息,另一方面,如果我将前8个字节解释为指针,它会将我引导到磁盘上的另一个objc_class示例,这通常是该类的元类。我想知道为什么isa联合的定义来自于XNUMX-C运行时及其bits字段。当我们示例化一个某种类型的对象时,当从磁盘上阅读时,它只是一个指向Meta类定义的指针,这是否只正确地解释为isabits的联合?

编辑:

我从文件中读取objc_class结构体的方式是使用python:

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_t联合体,我得到了另一个objc_class结构体,它是这个类的元类,符号_OBJC_METACLASS_$_A
现在,如果不将objc_class结构体的前8个字节视为指向元类的指针,而是尝试将它们解释为isa_t联合体的位,就像我在提供的代码中所做的那样,例如,使用has_cxx_dtor方法,我得到False,这是不正确的,因为我可以清楚地在method_list_t结构上找到这个方法。class_ro,所以它与我解析的不匹配,因此isa_t联合似乎与磁盘上类的实际数据无关。
请注意,从isa_t的位提取数据的方法是查看isa.h的源代码,并假设我在没有ptr auth的情况下读取ARM 64 macho,而不是从模拟器中读取。

a14dhokn

a14dhokn1#

在深入研究了运行时之后,似乎非指针isas是一个仅在运行时的概念,并且所有磁盘上的isas将始终是常规指针。
对象文件中Obj-C类的加载过程:

  1. dyld调用_objc_map_imagesobjc-internal.h/objc-runtime-new.mm),传入对象头以从中读取和加载类
  2. _objc_map_images在调用map_imagesobjc-private.h/objc-runtime-new.mm)之前进行了一些设置
  3. map_images获取运行时锁,然后调用map_images_nolockobjc-private.h/objc-os.mm
  4. map_images_nolock迭代mach头,搜索Obj-C信息并执行一些验证。它将所有包含Obj-C类的头文件传递给_read_imagesobjc-private.h/objc-runtime-new.mm
  • _read_images是我们真正感兴趣的部分。它首先为运行时目标建立对非指针isas的支持,并建立一些表来存储类信息。在阅读并修复选择器之后,它开始阅读类信息(OBJC_RUNTIME_DISCOVER_CLASSES_START()
  • 对于每个头,它迭代存储在头中的原始classlist,接收指向图像中每个类的直接指针
  • 对于以这种方式读取的每个类,它都会调用readClassobjc-runtime-new.mm),这会解析损坏的类名、Swift类等--但在一天结束时,读取的classref_t(指向dyld类的原始指针)要么被转换为Class(类对象),要么被分配的Class示例替换

那么,非指针在哪里起作用呢?仅当在运行时设置对象的类时:
1.当你通过objc_constructInstanceclass_createInstanceruntime.h)创建一个对象,或者通过object_setClass设置一个对象的类时,这个对象被调用了objc_object::initInstanceIsaobjc_object::initIsaobjc-object.h)(而initInstanceIsa只是通过调用initIsa

  1. objc_object::initIsa有两个实现(一个用于SUPPORT_NONPOINTER_ISA,另一个用于不支持的),但都调用了isa_t::setClassobjc-private.h/objc-object.h
  2. isa_t::setClass也有两种实现--当SUPPORT_NONPOINTER_ISA为true时,该实现在伊萨值本身中设置适当的位,必要时设置shiftcls;当SUPPORT_NONPOINTER_ISA为false时,它直接设置类
    (Or如果你愿意,可以反过来说:isa_t::setClass * only * 从objc_object::initIsa/objc_object::changeIsa调用,它们本身 onlyobjc_constructInstance/class_createInstance/object_setClass调用。
    所以,当你在磁盘上读取这些对象文件时,你只会遇到对象和类的指针;在ISA内部实际设置的位是在运行时排他地完成的。如果您希望从这些位中读取一些细节,则需要自己从周围的mach-o数据中构建这些信息。

相关问题