iOS平台使用LLDB调试JS逻辑之——Structure在哪里?

2,141 阅读2分钟

我正在参加「掘金·启航计划」

补充说明

上一篇文章讲了如何在LLDB中查看JS对象的属性值,了解了Butterfly等概念。

有一个点之前没有提到,对于一个非array的JS对象,如果其属性数量较少,则JSC并不会为其分配相应的Butterfly对象,而是直接存在对象尾部(叫做inline property),存到Butterfly处的则称做out-of-line property。

:看起来属性offset在64以下的属性都是用inline存储(非array的情况):

 static constexpr PropertyOffset firstOutOfLineOffset = 64;

Structure

本节主要讲Structure。

在 JavaScriptCore 的概念中,Structure对应的就是JS对象的Prototype(原型)。每个JS对象都有一个Structure,Structure之间可以有继承关系,当JS对象的结构(也即prototype)发生变化时,JS对象会生成一个新的(或复用一个已有的)Stucture。一个对象中有哪些属性,以及这些属性对应到对象内存中的偏移都存在Structure及其相应ClassInfo对象中。

从上一篇对JS对象内存布局的描述可知,JS对象中存储了StructureID的值,是一个int值,那么如何从StructureID得到真正的Structure对象呢?

以下代码片段描述了StuctureID类的部分定义:

constexpr uintptr_t structureHeapAddressSize = 4 * GB;

class StructureID {
public:
    static constexpr uint32_t nukedStructureIDBit = 1;
    static constexpr CPURegister structureIDMask = structureHeapAddressSize - 1;
    ...
    StructureID decontaminate() const { return StructureID(m_bits & ~nukedStructureIDBit); }
    inline Structure* decode() const;
    ...
    
private:
    explicit StructureID(uint32_t bits) : m_bits(bits) { }

    uint32_t m_bits { 0 };
};

在StructureID内部存储了一个uint32_t,其代表Structure表内的偏移,真正的Structure数据是存在Structure表中。

StrctureID有个inline成员函数decode(),此方法即实现了从StructureID到Structure的转换:

ALWAYS_INLINE Structure* StructureID::decode() const
{
    // Take care to only use the bits from m_bits in the structure's address reservation.
    ASSERT(decontaminate());
    return reinterpret_cast<Structure*>((static_cast<uintptr_t>(decontaminate().m_bits) & structureIDMask) + g_jscConfig.startOfStructureHeap);
}

从上面的代码片段可以看出,Structure的具体位置是某个全局变量(g_jscConfig.startOfStructureHeap)加上 StructureID 作为偏移。

下一个问题就是:如何在运行时找到g_jscConfig?

看一下g_jscConfig及其相关的关键对象的定义:

#define g_jscConfig (*bitwise_cast<JSC::Config*>(&g_wtfConfig.spaceForExtensions))

//注:12 * sizeof(void *) == 12 * 8 == 0x60
#define g_wtfConfig (*bitwise_cast<WTF::Config*>(&WebConfig::g_config[WTF::startSlotOfWTFConfig]))  

其中:

constexpr size_t reservedSlotsForGigacageConfig = 12;
constexpr size_t startSlotOfWTFConfig = Gigacage::reservedSlotsForGigacageConfig;

也即,如果知道g_config,即可通过以下方式得到g_jscConfig及其startOfStructureHeap:

&g_wtfConfig == &g_config + 0x60   //g_config->wtfConfig
&g_jscConfig == &g_wtfConfig + 0x140  //g_wtfConfig->spaceForExtensions

g_jscConfig->startOfStructureHeap == *(&g_jscConfig + 0x30)  //g_jscConfig->startOfStructureHeap

看了一下JavasScriptCore的导出符号,其中包含了g_config,使用 dlsym(RTLD_DEFAULT, "g_config")即可取到。

在lldb中,也可以直接print全局变量g_config的地址:

(lldb) p &g_config

下一篇,我们接着看Structure的具体内容。