OC底层探索(十四): 类的加载(二)

所用版本:

  • 处理器: Intel Core i9
  • MacOS 12.3.1
  • Xcode 13.3.1
  • objc4-838

建议先看下 OC底层探索(十三): 类的加载(一)

还是先看下类的初始化方法_objc_init代码以及示意图

/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?

    // 环境变量初始化 
    environ_init();
    // 线程处理
    tls_init();
    // 运行C++静态构造函数。
    static_init();
    // runtime运行时初始化
    runtime_init();
    // objc异常处理系统初始化
    exception_init();
#if __OBJC2__
    // 缓存初始化
    cache_t::init();
#endif
    // 启动回调机制
    _imp_implementationWithBlock_init();
   // dyld通知注册
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}
_objc_init

以及_read_images的示意图. _dyld_objc_notify_register(&map_images, load_images, unmap_image); 第一个参数: &map_imagesmap_images_nolock_read_images

read_image

_read_images

read_image做了:

  • 条件控制进行一次加载
  • 修复预编译阶段的@selector混乱问题
  • 错误混乱的类处理
  • 修复重新映射的类
  • 修复消息
  • 协议读取
  • 分类处理
  • 类的加载处理
  • 优化类
_read_images

[修复重新映射的类]

修复重新映射的类

其中

// 类
GETSECT(_getObjc2ClassRefs,           Class,           "__objc_classrefs");  
// 父类
GETSECT(_getObjc2SuperRefs,           Class,           "__objc_superrefs");
remapClassRef
remapClass
image.png

循环判断, 如果找到未映射当前类 &父类, 进行修复。

[修复消息]

修复消息

一些特殊的消息处理, 正常消息已经被llvm给处理了, 这里主要修复一些特殊消息。例如: alloc的消息改为直接调用objc_alloc (而不是走alloc的实现) 这种。

fixupMessageRef

[协议读取]

协议读取
GETSECT(_getObjc2ProtocolList,        protocol_t * const,    "__objc_protolist");
  • 创建协议表 NXMapTable *protocol_map = protocols()
  • Mach-O中获取__objc_protolist中协议
  • 遍历读取插入表, 方法: readProtocol
readProtocol

稍微打印一下, 看一下:

printf("%s - 协议- %s\n",__func__,newproto->mangledName);
image.png

可发现系统的代理还是蛮多的 -_- !!!, 接下来我们自定义一个代理走下流程

// 自定义代理
@protocol SRTestDelegate <NSObject>

- (void)SRTestAction;

@end

很遗憾发现并没有出现


错误例子

为什么呢? 因为我并未对代理进行调用, 这里也体现了苹果严谨性。每调用还想让我给你个空床位, 做你大头梦 :)

正确例子

加个代理调用就行, 实现方法可以不写。, 接下来我们跟一下源码看下流程。

可看到, 直接走到这里


绑定isa指针
  • 先通过initIsa创建isa指针,与mach-o里面的协议相关结构体进行绑定
  • 把绑定协议名, 协议结构体, 插入新建的协议表里面

当然如果协议出现没加载情况, 也会有一步修复

修复协议

[分类处理]

分类处理
  • 只有在初始类完成后才能执行此操作。
  • 对于启动时存在的分类,需要推迟到+ load之后调用。

[类的加载处理]

[非懒加载类]
非懒加载类

只有非懒加载类才走, 正常添加的类不会走这里, 比如我加一个打印

            const char *mangledName = cls->nonlazyMangledName();
            
            if (strcmp(mangledName, "SRTest") == 0) {
                printf("%s 测试 %s\n",__func__,mangledName);
            }
正常类

可发现并没走。其中classref_t const *classlist = hi->nlclslist(&count);是从非懒加载类找数据。

nlclslist

其中_getObjc2NonlazyClassList是获取Mach-O的静态段__objc_nlclslist非懒加载类表

GETSECT(_getObjc2NonlazyClassList,    classref_t const,      "__objc_nlclslist");

这个里稍微提一下懒加载类与非懒加载类



懒加载类与非懒加载类

  • 懒加载:
    • 并未实现+load方法
    • 数据加载推迟到第一次消息发送objc_msgSend, lookUpImpOrForward消息慢速查找的时候进行初始化的。
  • 非懒加载:
    • 自己实现+load方法 (会出现在__objc_nlclslist中)

    • 子类实现了+ load方法。(父类不会出现在__objc_nlclslist中, 但是子类在)

    • 分类实现了+load方法。(自己以及子类的分类,不是在map_images而是在prepare_load_methods中实例化类的,自己类/子类/分类 都不会出现在__objc_nlclslist中)

    • map_images的时候加载所有类数据(提前加载),为+ load的调用做准备。

两者调用流程也有区别

懒加载类:
懒加载类

执行顺序:

  • dyld start
  • main
  • objc_alloc
  • objc_alloc
  • lookUpImpOrForward → realizeAndInitializeIfNeeded_locked → realizeClassMaybeSwiftAndLeaveLocked
  • realizeClassWithoutSwift
  • methodizeClass
    ......
非懒加载类:
非懒加载类

执行顺序:

  • dyld`start
  • dyld4::prepare
  • dyld4::Loader::findAndRunAllInitializers
  • _objc_init
  • dyld4::APIs::_dyld_objc_notify_register
  • dyld4::RuntimeState::setObjCNotifiers
  • map_images
  • _read_images
  • realizeClassWithoutSwift
  • methodizeClass
    ...
[子类 +load]

子类的+load方法稍微看下, SRTestSubSRTest子类, 子类有load方法, 父类没有, 运行看一下

子类+load方法

可验证: 父类不会出现在__objc_nlclslist中, 但是子类在__objc_nlclslist

[分类 +load]

看下分类的+load, 分类如果忘记创建参考: OC分类创建

分类添加+load方法

分类添加+load方法
非懒加载并没有断住
分类

运行, 可发现

  • 非懒加载并没有断住, 所以不在__objc_nlclslist,
  • 在main中可看出分类的+load` 在加载当前类之前就运行了
    // Category discovery MUST BE Late to avoid potential races
    // when other threads call the new category code before
    // this thread finishes its fixups.

    // +load handled by prepare_load_methods()

再回头看下官方注释, 可发现分类 +load也是在prepare_load_methods()执行的

prepare load methods


回到之前看一下, 其实我们是想看下非懒加载类的执行顺序, 那么我们在普通类写一个+load方法, 把它变成非懒加载类看源码流程.

  • addClassTableEntry: 将非懒加载类插入类表,存储到内存,如果已经添加就不会载添加,需要确保整个结构都被添加.
addClassTableEntry

接着往下走了 realizeClassWithoutSwift

realizeClassWithoutSwift

类的初始化核心方法: realizeClassWithoutSwift

realizeClassWithoutSwift

实现类方法realizeClassWithoutSwift

/***********************************************************************
* realizeClassWithoutSwift
* Performs first-time initialization on class cls, 
* including allocating its read-write data.
* Does not perform any Swift-side initialization.
* Returns the real class structure for the class. 
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if (!cls) return nil;
    if (cls->isRealized()) {
        validateAlreadyRealizedClass(cls);
        return cls;
    }
    ASSERT(cls == remapClass(cls));

    // fixme verify class is not in an un-dlopened part of the shared cache?

    auto ro = (const class_ro_t *)cls->data();
    auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. Allocate writeable class data.
        rw = objc::zalloc<class_rw_t>();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }

    cls->cache.initializeToEmptyOrPreoptimizedInDisguise();

#if FAST_CACHE_META
    if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif

    // Choose an index for this class.
    // Sets cls->instancesRequireRawIsa if indexes no more indexes are available
    cls->chooseClassArrayIndex();

    if (PrintConnecting) {
        _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                     cls->nameForLogging(), isMeta ? " (meta)" : "", 
                     (void*)cls, ro, cls->classArrayIndex(),
                     cls->isSwiftStable() ? "(swift)" : "",
                     cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
    }

    // Realize superclass and metaclass, if they aren't already.
    // This needs to be done after RW_REALIZED is set above, for root classes.
    // This needs to be done after class index is chosen, for root metaclasses.
    // This assumes that none of those classes have Swift contents,
    //   or that Swift's initializers have already been called.
    //   fixme that assumption will be wrong if we add support
    //   for ObjC subclasses of Swift classes.
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        // Metaclasses do not need any features from non pointer ISA
        // This allows for a faspath for classes in objc_retain/objc_release.
        cls->setInstancesRequireRawIsa();
    } else {
        // Disable non-pointer isa for some classes and/or platforms.
        // Set instancesRequireRawIsa.
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;

        if (DisableNonpointerIsa) {
            // Non-pointer isa disabled by environment or app SDK version
            instancesRequireRawIsa = true;
        }
        else if (!hackedDispatch  &&  0 == strcmp(ro->getName(), "OS_object"))
        {
            // hack for libdispatch et al - isa also acts as vtable pointer
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->getSuperclass()  &&
                 supercls->instancesRequireRawIsa())
        {
            // This is also propagated by addSubclass()
            // but nonpointer isa setup needs it earlier.
            // Special case: instancesRequireRawIsa does not propagate
            // from root class to root metaclass
            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }

        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }
// SUPPORT_NONPOINTER_ISA
#endif

    // Update superclass and metaclass in case of remapping
    cls->setSuperclass(supercls);
    cls->initClassIsa(metacls);

    // Reconcile instance variable offsets / layout.
    // This may reallocate class_ro_t, updating our ro variable.
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // Set fastInstanceSize if it wasn't set already.
    cls->setInstanceSize(ro->instanceSize);

    // Copy some flags from ro to rw
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }
    
    // Propagate the associated objects forbidden flag from ro or from
    // the superclass.
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    // Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // Attach categories
    methodizeClass(cls, previously);

    return cls;
}

继续探索前, 先了解下2个概念



clean memory 和 dirty memory

  • clean memory: 加载后不会发生更改的内存, 例如: class_ro_t, 因为它是只读的
  • dirty memory: 进程运行时会发生更改的内存,

类结构一经使用就会变成dirty memory, 因为runtime会向它写入新的数据。例如: 创建一个新的方法缓存并从类中指向它。dirty memoryclean memory 要"昂贵"的多, 只要进程运行, 它就必须在。比如手机64G, 升级到128G是不是要添"马内", 侧面也表示了有多昂贵 (^ - ^)。同时clean memory可以进行移除从而节省更多的内存空间。因为如果你需要clean memory, 系统可以从磁盘从新加载。同时因为IOS不使用swap, 所以dirty memory在ios中代价是很大的。苹果更建议保持"清洁"的数据越多越好, 通过分类出那些永远不会更改的数据, 可以把大部分数据储存为clean memory.



realizeClassWithoutSwift会操作ro, rw信息。其中

  • ro : Read Only只读 -- clean memory -- 结构体, 包含类的名称, 方法, 协议, 实例变量等编译期的信息。
  • rw: Read Write读写 -- dirty memory-- 结构体, 复制ro, 目的是对于方法, 属性等进行操作。
  • rwe: Read Write Extra, 额外读写 -- dirty memory-- 结构体, ro部分复制, 只操作需要改变的信息。

class_ro_t, class_rw_t , class_rw_ext_t 结构体详细内容看末尾附录 (class_rw_ext_t 真是真滴少 :) )

因为ro 是沙盒路径加载, 只读不能操作, 那么当需要操作的时候会将ro 复制一份存放到rw上。

rw

这也体现了 Objective-C 是一门动态语言, 可以在运行时更改方法, 属性等。分类可以在不改变类设计的前提下, 将新方法添加到类中。

但是对于一个类, 我们需要大部分改变的就是methods, properties, protocols等 , 其他的例如父类关系, Flags等不会进行更改, 所以这部分"dirty memory" 很浪费的。由此苹果对class_rw_t进行进一步拆分出class_rw_ext_t用来存储这部分可能被修改的methods, properties, protocols等,class_rw_ext_t只有在需要的时候,才被创建, 相对于旧版runtime可以节省下的内存空间。

rwe

熟悉完下一章继续看 realizeClassWithoutSwift

附: class_ro_t, class_rw_t , class_rw_ext_t 详细

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

    union {
        const uint8_t * ivarLayout;
        Class nonMetaclass;
    };

    explicit_atomic<const char *> name;
    WrappedPtr<method_list_t, method_list_t::Ptrauth> baseMethods;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    // This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
    _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];

    _objc_swiftMetadataInitializer swiftMetadataInitializer() const {
        if (flags & RO_HAS_SWIFT_INITIALIZER) {
            return _swiftMetadataInitializer_NEVER_USE[0];
        } else {
            return nil;
        }
    }

    const char *getName() const {
        return name.load(std::memory_order_acquire);
    }

    class_ro_t *duplicate() const {
        bool hasSwiftInitializer = flags & RO_HAS_SWIFT_INITIALIZER;

        size_t size = sizeof(*this);
        if (hasSwiftInitializer)
            size += sizeof(_swiftMetadataInitializer_NEVER_USE[0]);

        class_ro_t *ro = (class_ro_t *)memdup(this, size);

        if (hasSwiftInitializer)
            ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];

#if __has_feature(ptrauth_calls)
        // Re-sign the method list pointer.
        ro->baseMethods = baseMethods;
#endif

        return ro;
    }

    Class getNonMetaclass() const {
        ASSERT(flags & RO_META);
        return nonMetaclass;
    }

    const uint8_t *getIvarLayout() const {
        if (flags & RO_META)
            return nullptr;
        return ivarLayout;
    }
};

[class_rw_ext_t]

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;

private:
    using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t, class_rw_ext_t, PTRAUTH_STR("class_ro_t"), PTRAUTH_STR("class_rw_ext_t")>;

    const ro_or_rw_ext_t get_ro_or_rwe() const {
        return ro_or_rw_ext_t{ro_or_rw_ext};
    }

    void set_ro_or_rwe(const class_ro_t *ro) {
        ro_or_rw_ext_t{ro, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_relaxed);
    }

    void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
        // the release barrier is so that the class_rw_ext_t::ro initialization
        // is visible to lockless readers
        rwe->ro = ro;
        ro_or_rw_ext_t{rwe, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_release);
    }

    class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);

public:
    void setFlags(uint32_t set)
    {
        __c11_atomic_fetch_or((_Atomic(uint32_t) *)&flags, set, __ATOMIC_RELAXED);
    }

    void clearFlags(uint32_t clear) 
    {
        __c11_atomic_fetch_and((_Atomic(uint32_t) *)&flags, ~clear, __ATOMIC_RELAXED);
    }

    // set and clear must not overlap
    void changeFlags(uint32_t set, uint32_t clear) 
    {
        ASSERT((set & clear) == 0);

        uint32_t oldf, newf;
        do {
            oldf = flags;
            newf = (oldf | set) & ~clear;
        } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
    }

    class_rw_ext_t *ext() const {
        return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
    }

    class_rw_ext_t *extAllocIfNeeded() {
        auto v = get_ro_or_rwe();
        if (fastpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
        } else {
            return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
        }
    }

    class_rw_ext_t *deepCopy(const class_ro_t *ro) {
        return extAlloc(ro, true);
    }

    const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        if (slowpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
        }
        return v.get<const class_ro_t *>(&ro_or_rw_ext);
    }

    void set_ro(const class_ro_t *ro) {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
        } else {
            set_ro_or_rwe(ro);
        }
    }

    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods};
        }
    }

    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
        }
    }

    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
        }
    }
};

[class_rw_ext_t]

struct class_rw_ext_t {
    DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t)
    class_ro_t_authed_ptr<const class_ro_t> ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    char *demangledName;
    uint32_t version;
};

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容