isa深入解析

前一篇介紹過(guò)isa的優(yōu)化方式以及從被優(yōu)化過(guò)的isa中獲取真正的struct objc_class指針。然而我們對(duì)知識(shí)的渴望,并不允許自己僅僅只是知道它、了解它而已,還想進(jìn)一步分析struct objc_class結(jié)構(gòu)體,以及用它來(lái)做點(diǎn)什么。
接下來(lái),從isa指針中到底有哪些信息獲取到這些信息可以用來(lái)做什么兩個(gè)方面,來(lái)進(jìn)一步揭開(kāi)isa的面紗。

isa指針中到底有哪些信息?

既然已經(jīng)可以從一個(gè)實(shí)例對(duì)象中獲取到isa指針,那么我們就直接用這個(gè)指針來(lái)struct objc_class中的數(shù)據(jù)。
要解析指針指向的地址中存放的數(shù)據(jù),肯定需要知道struct objc_class的定義(中間有很多方法直接忽略掉,只看其數(shù)據(jù)結(jié)構(gòu)):

struct objc_class : objc_object {
    // 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
...
}

因?yàn)橹羔樥加玫膬?nèi)存空間是8字節(jié),所以通過(guò)看其結(jié)構(gòu)體的構(gòu)成,可以知道前16個(gè)字節(jié)里存放的是struct objc_class *類型的指針ISAsuperclass。
這兩個(gè)指針,實(shí)際上存儲(chǔ)的是類對(duì)象的父類以及元類的指針。父類和元類的解析,因?yàn)轭愋鸵粯?,所以和自身的解析是一樣的?br> 第17個(gè)字節(jié)開(kāi)始,存放的是cache_t類型的數(shù)據(jù),但是這個(gè)數(shù)據(jù)并沒(méi)有使用指針,所以它里面的數(shù)據(jù),會(huì)有序并字節(jié)對(duì)齊的存放在第17個(gè)字節(jié)開(kāi)始的位置??梢酝ㄟ^(guò)cache_t結(jié)構(gòu)體來(lái)查看其數(shù)據(jù)類型:

struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
}

#if __LP64__
typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits

struct cache_t里面有一個(gè)指針,占8字節(jié)內(nèi)存;有兩個(gè)mask_t類型(即uint32_t類型)的數(shù)據(jù),每個(gè)占4字節(jié)的內(nèi)存,所以cache_t cache一共占16個(gè)字節(jié)的內(nèi)存。
這個(gè)字段里,存放的是方法緩存相關(guān)的信息。
最后一個(gè)結(jié)構(gòu)體class_data_bits_t bits

struct class_data_bits_t {
    // Values are the FAST_ flags above.
    uintptr_t bits;
}

里面就一個(gè)占8字節(jié)的數(shù)據(jù)。
從這個(gè)結(jié)構(gòu)體的名字可以看出,類的主要信息都存儲(chǔ)在這個(gè)字段里。這個(gè)字段里存儲(chǔ)的是class_rw_t類型的指針,但是也不是直接將指針的值存進(jìn)去的,和被優(yōu)化的isa一樣被優(yōu)化過(guò)的,所以不能直接取出來(lái)用。那么要怎么獲取到這個(gè)指針呢?
在源碼中往下看struct class_data_bits_t這個(gè)結(jié)構(gòu)體,會(huì)發(fā)現(xiàn)有一個(gè)data()方法,返回一個(gè)class_rw_t*

// data pointer
#define FAST_DATA_MASK          0x00007ffffffffff8UL

class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }

所以其實(shí)我們也可以將獲取的bits的值,和0x00007ffffffffff8UL進(jìn)行&運(yùn)算,得到class_rw_t*指針。
寫(xiě)個(gè)簡(jiǎn)單的代碼,可以走一下這個(gè)順序,驗(yàn)證一下:

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;

    appDelegateClassName = NSStringFromClass([AppDelegate class]);
    
    CustomClass *c = [CustomClass new];
    c.i = 10;
    c.obj = [NSObject new];
    c.str = @"i love code";
    NSLog(@"c:%p",c);
    
    //1、通過(guò)實(shí)例對(duì)象`c`,獲取 isa 指針
    uintptr_t c_isa = (*(uintptr_t *)c) & 0x0000000FFFFFFFF8ULL;
    
    //2、獲取 `class_data_bits_t bits` 的值
    uintptr_t c_bits = *(uintptr_t *)((unsigned long long)c_isa + 4*8);
    
    //3、獲取 `class_rw_t *`指針
    uintptr_t rw_t = c_bits & 0x00007ffffffffff8UL;
    
    
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    
}

雖說(shuō)是驗(yàn)證,但是取到這個(gè)值,也不知道是不是正確的,反正值是取到了!
要想驗(yàn)證取的值是否正確,還得看看class_rw_t *里面的數(shù)據(jù),是否是這個(gè)類的信息。
所以還是要跟進(jìn)去看這個(gè)結(jié)構(gòu)體:


struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;

還是按照剛才的思路,挨個(gè)字段去解析:

  • flags
  • version
  • ro
    const 修飾的 class_ro_t * 指針,儲(chǔ)了當(dāng)前類在編譯期就已經(jīng)確定的屬性、方法以及遵循的協(xié)議,不可修改。
  • methods
    實(shí)例方法列表(元類中存儲(chǔ)的是類方法列表),是一個(gè)指向method_t的二級(jí)指針。
  • properties
    屬性列表。
  • protocols
    協(xié)議列表
    這里主要關(guān)注romethods、properties以及protocols字段。
    先看一下ro的類型class_ro_t的結(jié)構(gòu):

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

剛剛的代碼中,我們以及獲取到了class_rw_t指針的值,我們可以通過(guò)讀取class_rw_tro字段的值,然后獲取class_ro_t里面name,來(lái)判斷我們獲取的指針是否正確:

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;

    appDelegateClassName = NSStringFromClass([AppDelegate class]);
    
    CustomClass *c = [CustomClass new];
    c.i = 10;
    c.obj = [NSObject new];
    c.str = @"i love code";
    NSLog(@"c:%p",c);
    
    //1、通過(guò)實(shí)例對(duì)象`c`,獲取 isa 指針
    uintptr_t c_isa = (*(uintptr_t *)c) & 0x0000000FFFFFFFF8ULL;
    
    //2、獲取 `class_data_bits_t bits` 的值
    uintptr_t c_bits = *(uintptr_t *)((unsigned long long)c_isa + 4*8);
    
    //3、獲取 `class_rw_t *`指針
    uintptr_t rw_t = c_bits & 0x00007ffffffffff8UL;
    
    //4、獲取 `class_rw_t` 中 ro 指針
    uintptr_t ro = *(uintptr_t *)((unsigned long long)rw_t+4+4);
    
    //5、獲取 `class_ro_t` 中的 `name`
    
    const char *name = (const char *)(*(uintptr_t *)((unsigned long long)ro + 4 +4 + 4 +4 + 8));
    
    
   /*這里下斷點(diǎn)*/ return UIApplicationMain(argc, argv, nil, appDelegateClassName);     
    
}
2019-11-25 00:00:30.956322+0800 IvarDemo[11902:3499502] c:0x1740261c0
(lldb) p/s name
(const char *) $0 = "CustomClass" "CustomClass"
(lldb) 

看log,可以看出,取出的值都是正確的。
既然name可以取出,那么其他的字段,也是可以取出的,例如在 8byte~12byte 存儲(chǔ)的instanceSize

    
    //6、獲取 `class_ro_t` 中的 `instanceSize`
    uint32_t instanceSize = *(uint32_t *)((unsigned long long)ro + 4 +4);
/*
使用 class_getInstanceSize()   函數(shù)驗(yàn)證
*/
(lldb) p/d instanceSize
(uint32_t) $0 = 32
Printing description of c:
<CustomClass: 0x17002f340>
(lldb) p/d (uint32_t)class_getInstanceSize([c class])    
(uint32_t) $3 = 32
(lldb) 

其他字段就不一一獲取。
回過(guò)頭去看看class_rw_t中的methods字段。
還是先看其定義:

class method_array_t : 
    public list_array_tt<method_t, method_list_t> 
{
    typedef list_array_tt<method_t, method_list_t> Super;

 public:
    method_list_t **beginCategoryMethodLists() {
        return beginLists();
    }
    
    method_list_t **endCategoryMethodLists(Class cls);

    method_array_t duplicate() {
        return Super::duplicate<method_array_t>();
    }
};

method_array_t是一個(gè)C++類,且其繼承自模板類list_array_tt,所以我們還得了解一下list_array_tt這個(gè)模板類:

template <typename Element, typename List>
class list_array_tt {
...
 private:
    union {
        List* list;
        uintptr_t arrayAndFlag;
    };
...
}

從結(jié)構(gòu)中可以看出,method_array_t類只占8字節(jié)的內(nèi)存空間,成員用union聯(lián)合,說(shuō)明listarrayAndFlag公用8字節(jié)的空間。
我們知道List在類method_array_t中是method_array_t,但是既然定義成聯(lián)合,那么肯定不是簡(jiǎn)單的將method_array_t指針存入這8個(gè)字節(jié)的,所以現(xiàn)在問(wèn)題就變成了:怎么獲取真正的method_array_t指針的值。
觀察這個(gè)模板類,發(fā)現(xiàn)里面有一個(gè)List** beginLists()方法:

List** beginLists() {
        if (hasArray()) {
            return array()->lists;
        } else {
            return &list;
        }
    }

bool hasArray() const {
        return arrayAndFlag & 1;
    }

array_t *array() {
        return (array_t *)(arrayAndFlag & ~1);
    }

以上三個(gè)的函數(shù)告訴了咱們?cè)趺赐ㄟ^(guò)arrayAndFlag獲取真正的List**
1、判斷arrayAndFlag最后一位是否為1;
2、通過(guò)判斷結(jié)果,分別獲取List**;

  • 判斷正確:array()->lists;
  • 否則:&list;

接下來(lái),我們也可以書(shū)寫(xiě)代碼,嘗試著獲取List**:

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;

    appDelegateClassName = NSStringFromClass([AppDelegate class]);
    
    CustomClass *c = [CustomClass new];
    c.i = 10;
    c.obj = [NSObject new];
    c.str = @"i love code";
    NSLog(@"c:%p",c);
    
    //1、通過(guò)實(shí)例對(duì)象`c`,獲取 isa 指針
    uintptr_t c_isa = (*(uintptr_t *)c) & 0x0000000FFFFFFFF8ULL;
    
    //2、獲取 `class_data_bits_t bits` 的值
    uintptr_t c_bits = *(uintptr_t *)((unsigned long long)c_isa + 4*8);
    
    //3、獲取 `class_rw_t *`指針
    uintptr_t rw_t = c_bits & 0x00007ffffffffff8UL;
    
    //4、獲取 `class_rw_t` 中 ro 指針
    uintptr_t ro = *(uintptr_t *)((unsigned long long)rw_t+4+4);
    
    //5、獲取 `class_ro_t` 中的 `name`
    
    const char *name = (const char *)(*(uintptr_t *)((unsigned long long)ro + 4 +4 + 4 +4 + 8));
    
    //6、獲取 `class_ro_t` 中的 `instanceSize`
    uint32_t instanceSize = *(uint32_t *)((unsigned long long)ro + 4 +4);
    
    //7、獲取  `class_rw_t` 中 `method`
    
    uintptr_t method = *((uintptr_t *)(((unsigned long long)rw_t)+4+4+8));
    
    /*注:因?yàn)閌method`成員沒(méi)有用指針修飾,所以`method`的值即為
     union
     {List *list;
     uintptr_t arrayAndFlag;
     
     }
     的值。
     */
    //8、獲取 `arrayAndFlag`的值
    uintptr_t arrayAndFlag = method;
    
    //9、//獲取 `List **`
    uintptr_t *list;
    //判斷 `arrayAndFlag` 標(biāo)志位
    if (arrayAndFlag & 1) {
        //獲取 `array_t *`
        uintptr_t array_t = arrayAndFlag & ~1;
        list = &(*((uintptr_t *)((unsigned long long)array_t+8)));
    }else {
        //獲取 `List **`
        list = &arrayAndFlag;
    }
    
    /*這里下斷點(diǎn)*/ return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    
}

雖然到這里,已經(jīng)獲取到了List **(也就是method_list_t**),但是我們要找的方法列表,還只是初見(jiàn)端倪,并沒(méi)有完全浮出水面。所以我們還是要進(jìn)一步分析一下method_list_t

struct method_list_t : entsize_list_tt<method_t, method_list_t, 0x3> {
    bool isFixedUp() const;
    void setFixedUp();

    uint32_t indexOfMethod(const method_t *meth) const {
        uint32_t i = 
            (uint32_t)(((uintptr_t)meth - (uintptr_t)this) / entsize());
        assert(i < count);
        return i;
    }
};
template <typename Element, typename List, uint32_t FlagMask>
struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
    Element first;
...
}

method_list_t結(jié)構(gòu)體繼承自模板結(jié)構(gòu)體entsize_list_tt,所以method_list_t的成員變量有3個(gè):

    uint32_t entsizeAndFlags;
    uint32_t count;
    Element first;

我們要關(guān)注的,是countfirstcount中存儲(chǔ)的是方法個(gè)數(shù),而first則是方法列表的其實(shí)地址,也就是方法數(shù)組的第一個(gè)元素。(至于第一個(gè)entsizeAndFlags,存儲(chǔ)的是數(shù)組中每個(gè)元素的大小。)
既然可以看到其內(nèi)存布局,也就意味著我們可以獲取到數(shù)據(jù):

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;

    appDelegateClassName = NSStringFromClass([AppDelegate class]);
    
    CustomClass *c = [CustomClass new];
    NSLog(@"c:%p",c);
    
    //1、通過(guò)實(shí)例對(duì)象`c`,獲取 isa 指針
    uintptr_t c_isa = (*(uintptr_t *)c) & 0x0000000FFFFFFFF8ULL;
    
    //2、獲取 `class_data_bits_t bits` 的值
    uintptr_t c_bits = *(uintptr_t *)((unsigned long long)c_isa + 4*8);
    
    //3、獲取 `class_rw_t *`指針
    uintptr_t rw_t = c_bits & 0x00007ffffffffff8UL;
    
    //4、獲取 `class_rw_t` 中 ro 指針
    uintptr_t ro = *(uintptr_t *)((unsigned long long)rw_t+4+4);
    
    //5、獲取 `class_ro_t` 中的 `name`
    
    const char *name = (const char *)(*(uintptr_t *)((unsigned long long)ro + 4 +4 + 4 +4 + 8));
    
    //6、獲取 `class_ro_t` 中的 `instanceSize`
    uint32_t instanceSize = *(uint32_t *)((unsigned long long)ro + 4 +4);
    
    //7、獲取  `class_rw_t` 中 `method`
    
    uintptr_t method = *((uintptr_t *)(((unsigned long long)rw_t)+4+4+8));
    
    /*注:因?yàn)閌method`成員沒(méi)有用指針修飾,所以`method`的值即為
     union
     {List *list;
     uintptr_t arrayAndFlag;
     
     }
     的值。
     */
    //8、獲取 `arrayAndFlag`的值
    uintptr_t arrayAndFlag = method;
    
    //9、//獲取 `List **`
    uintptr_t *list;
    //判斷 `arrayAndFlag` 標(biāo)志位
    if (arrayAndFlag & 1) {
        //獲取 `array_t *`
        uintptr_t array_t = arrayAndFlag & ~1;
        list = &(*((uintptr_t *)((unsigned long long)array_t+8)));
    }else {
        //獲取 `List **`
        list = &arrayAndFlag;
    }
    
    //10、獲取`method_list_t` 中的 `count`
        //獲取`method_list_t *`指針
    uintptr_t method_list_t = *list;
        //獲取`count`
    uint32_t count = *(uint32_t *)((unsigned long long)method_list_t + 4);
    
    //11、獲取 `method_list_t`中的`first`
    /* 這里為了更好的獲取方法信息,定義結(jié)構(gòu)體struct method_t */
    struct sl_method_t {
        SEL name;
        const char *types;
        IMP imp;
    };
    struct sl_method_t first =*((struct sl_method_t *)((unsigned long long)method_list_t + 4 + 4));
    
    //12、打印所有方法的信息
        //獲取數(shù)組中每個(gè)元素的大小
    uint32_t entsizeAndFlags = *(uint32_t *)((unsigned long long)method_list_t);
    /*
     uint32_t entsize() const {
         return entsizeAndFlags & ~FlagMask;
     }
     */
    uint32_t entsize = entsizeAndFlags & ~0x3;
        
    for (int i=0; i<count; i++) {
        
        struct sl_method_t method = *((struct sl_method_t *)((unsigned long long)method_list_t + 4 + 4 + i*entsize));
        
        printf("method_name:%s \t method_types:%s \t method_imp:0x%llx",method.name,method.types,(unsigned long long)(method.imp));
        
        printf("\n");
    }
       
    /*這里下斷點(diǎn)*/ return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    
}
method_name:setStr:      method_types:v24@0:8@16     method_imp:0x1000a8120
method_name:setObj:      method_types:v24@0:8@16     method_imp:0x1000a8174
method_name:.cxx_destruct    method_types:v16@0:8    method_imp:0x1000a81b4
method_name:str      method_types:@16@0:8    method_imp:0x1000a80e8
method_name:setI:    method_types:v20@0:8i16     method_imp:0x1000a80c4
method_name:i    method_types:i16@0:8    method_imp:0x1000a80a8
method_name:obj      method_types:@16@0:8    method_imp:0x1000a8158
(lldb) 

到這里,就意味著struct class_rw_t 中的method_array_t methods已經(jīng)完全被解析出來(lái)了。
那么同樣的,其他幾個(gè)字段的內(nèi)容,也是可以獲取到的。(例如:property_array_t properties; protocol_array_t protocols)。這里就不一一讀取。
我們接下來(lái)的問(wèn)題是:

獲取到這些信息可以用來(lái)做什么?

最簡(jiǎn)單的,我們既然可以獲取到類中每個(gè)方法的IMP,那么我們是否可以替換掉其指針,到達(dá)Hook的效果呢?
簡(jiǎn)單修改一下代碼:

NSString * sl_str(id self,SEL sel){
    return @"hooked!!!";
}

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;

    appDelegateClassName = NSStringFromClass([AppDelegate class]);
    
    CustomClass *c = [CustomClass new];
    NSLog(@"c:%p",c);
    
    //1、通過(guò)實(shí)例對(duì)象`c`,獲取 isa 指針
    uintptr_t c_isa = (*(uintptr_t *)c) & 0x0000000FFFFFFFF8ULL;
    
    //2、獲取 `class_data_bits_t bits` 的值
    uintptr_t c_bits = *(uintptr_t *)((unsigned long long)c_isa + 4*8);
    
    //3、獲取 `class_rw_t *`指針
    uintptr_t rw_t = c_bits & 0x00007ffffffffff8UL;
    
    //4、獲取 `class_rw_t` 中 ro 指針
    uintptr_t ro = *(uintptr_t *)((unsigned long long)rw_t+4+4);
    
    //5、獲取 `class_ro_t` 中的 `name`
    
    const char *name = (const char *)(*(uintptr_t *)((unsigned long long)ro + 4 +4 + 4 +4 + 8));
    
    //6、獲取 `class_ro_t` 中的 `instanceSize`
    uint32_t instanceSize = *(uint32_t *)((unsigned long long)ro + 4 +4);
    
    //7、獲取  `class_rw_t` 中 `method`
    
    uintptr_t method = *((uintptr_t *)(((unsigned long long)rw_t)+4+4+8));
    
    /*注:因?yàn)閌method`成員沒(méi)有用指針修飾,所以`method`的值即為
     union
     {List *list;
     uintptr_t arrayAndFlag;
     
     }
     的值。
     */
    //8、獲取 `arrayAndFlag`的值
    uintptr_t arrayAndFlag = method;
    
    //9、//獲取 `List **`
    uintptr_t *list;
    //判斷 `arrayAndFlag` 標(biāo)志位
    if (arrayAndFlag & 1) {
        //獲取 `array_t *`
        uintptr_t array_t = arrayAndFlag & ~1;
        list = &(*((uintptr_t *)((unsigned long long)array_t+8)));
    }else {
        //獲取 `List **`
        list = &arrayAndFlag;
    }
    
    //10、獲取`method_list_t` 中的 `count`
        //獲取`method_list_t *`指針
    uintptr_t method_list_t = *list;
        //獲取`count`
    uint32_t count = *(uint32_t *)((unsigned long long)method_list_t + 4);
    
    //11、獲取 `method_list_t`中的`first`
    /* 這里為了更好的獲取方法信息,定義結(jié)構(gòu)體struct method_t */
    struct sl_method_t {
        SEL name;
        const char *types;
        IMP imp;
    };
    struct sl_method_t first =*((struct sl_method_t *)((unsigned long long)method_list_t + 4 + 4));
    
    //12、打印所有方法的信息
        //獲取數(shù)組中每個(gè)元素的大小
    uint32_t entsizeAndFlags = *(uint32_t *)((unsigned long long)method_list_t);
    /*
     uint32_t entsize() const {
         return entsizeAndFlags & ~FlagMask;
     }
     */
    uint32_t entsize = entsizeAndFlags & ~0x3;
        
    for (int i=0; i<count; i++) {
        
        struct sl_method_t method = *((struct sl_method_t *)((unsigned long long)method_list_t + 4 + 4 + i*entsize));
        
        printf("method_name:%s \t method_types:%s \t method_imp:0x%llx",method.name,method.types,(unsigned long long)(method.imp));
        
        /*Hook `str` 方法*/
        if (!strcmp((char *)(method.name), "str")) {
            //修改IMP的值
            *(uintptr_t *)((unsigned long long)method_list_t + 4 + 4 + i*entsize+8+8) = (uintptr_t)&sl_str;
        }
        
        printf("\n");
    }
    
    //調(diào)用,檢查是否被hook
    NSString *str = [c str];
    
    /*這里下斷點(diǎn)*/ return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    
}
method_name:setStr:      method_types:v24@0:8@16     method_imp:0x1000880fc
method_name:setObj:      method_types:v24@0:8@16     method_imp:0x100088150
method_name:.cxx_destruct    method_types:v16@0:8    method_imp:0x100088190
method_name:str      method_types:@16@0:8    method_imp:0x1000880c4
method_name:setI:    method_types:v20@0:8i16     method_imp:0x1000880a0
method_name:i    method_types:i16@0:8    method_imp:0x100088084
method_name:obj      method_types:@16@0:8    method_imp:0x100088134
(lldb) po str
hooked!!!

(lldb) 

看到最后的hooked!!!說(shuō)明,hook成功了!
當(dāng)然,完整的hook,并不是單純的替換函數(shù)指針即可,還需要考慮很多問(wèn)題,這里只是簡(jiǎn)單的測(cè)試一下可行性。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容