【技术分享】《Chrome V8源码》29.CallBuiltin()调用过程详解

浏览器安全 2年前 (2021) admin
581 0 0

【技术分享】《Chrome V8源码》29.CallBuiltin()调用过程详解


01
摘要

本篇文章是Builtin专题的第五篇,详细分析Builtin的调用过程。在Bytecode handler中使用CallBuiltin()调用Builtin是最常见的情况,本文将详解CallBuiltin()源码和相关的数据结构。本文内容组织方式:重要数据结构(章节2);CallBuiltin()源码(章节3)。

 

02
数据结构

提示:Just-In-Time Compiler是本文的前导知识,请读者自行查阅。
Builtin的调用过程主要分为两部分:查询Builtin表找到相应的入口函数、计算calldescriptor。下面解释相关的数据结构:
(1) Builtin名字(例如Builtin::kStoreGlobalIC),名字是枚举类型变量,CallBuiltin()使用该名字查询Builtin表,找到相应的入口函数,源码如下:

class Builtins {//...............省略....................  enum Name : int32_t {#define DEF_ENUM(Name, ...) k##Name,    BUILTIN_LIST(DEF_ENUM, DEF_ENUM, DEF_ENUM, DEF_ENUM, DEF_ENUM, DEF_ENUM,                 DEF_ENUM)#undef DEF_ENUM        builtin_count,  }

展开之后如下:

enum Name:int32_t{kRecordWrite, kEphemeronKeyBarrier, kAdaptorWithBuiltinExitFrame, kArgumentsAdaptorTrampoline,......}

(2) Builtin表存储Builtin的地址。Builtin表的位置是isoate->isolatedata->builtins_,源码如下:

class IsolateData final { public:  explicit IsolateData(Isolate* isolate) : stack_guard_(isolate) {}//............省略....................  Address* builtins() { return builtins_; }}

builtins_是Address类型的数组,与enum Name:int32_t{}配合使用可查询对应的Builtin地址(下面的第2行代码就完成了地址查询),源码如下:

1.Callable Builtins::CallableFor(Isolate* isolate, Name name) {2.  Handle<Code> code = isolate->builtins()->builtin_handle(name);3.  return Callable{code, CallInterfaceDescriptorFor(name)};4.}

上述代码第3行CallInterfaceDescriptorFor返回Builtin的调用信息,该信息与code共同组成了Callable。
(3) Code类,该类包括Builtin地址、指令的开始和结束以及填充信息,它的作用之一是创建snapshot文件,源码如下:

1.  class Code : public HeapObject {2.   public:3.  #define CODE_KIND_LIST(V)   4.    V(OPTIMIZED_FUNCTION) V(BYTECODE_HANDLER)       5.    V(STUB)  V(BUILTIN)  V(REGEXP)  V(WASM_FUNCTION)   V(WASM_TO_CAPI_FUNCTION)  6.     V(WASM_TO_JS_FUNCTION)   V(JS_TO_WASM_FUNCTION)   V(JS_TO_JS_FUNCTION)      7.     V(WASM_INTERPRETER_ENTRY)   V(C_WASM_ENTRY)8.     inline int builtin_index() const;9.     inline int handler_table_offset() const;10.     inline void set_handler_table_offset(int offset);11.     // The body of all code objects has the following layout.12.     //  +--------------------------+  <-- raw_instruction_start()13.     //  |       instructions       |14.     //  |           ...            |15.     //  +--------------------------+16.     //  |     embedded metadata    |  <-- safepoint_table_offset()17.     //  |           ...            |  <-- handler_table_offset()18.     //  |                          |  <-- constant_pool_offset()19.     //  |                          |  <-- code_comments_offset()20.     //  |                          |21.     //  +--------------------------+  <-- raw_instruction_end()22.     // If has_unwinding_info() is false, raw_instruction_end() points to the first23.     // memory location after the end of the code object. Otherwise, the body24.     // continues as follows:25.     //  +--------------------------+26.     //  |    padding to the next   |27.     //  |  8-byte aligned address  |28.     //  +--------------------------+  <-- raw_instruction_end()29.     //  |   [unwinding_info_size]  |30.     //  |        as uint64_t       |31.     //  +--------------------------+  <-- unwinding_info_start()32.     //  |       unwinding info     |33.     //  |            ...           |34.     //  +--------------------------+  <-- unwinding_info_end()35.     // and unwinding_info_end() points to the first memory location after the end36.     // of the code object.37.   };

上述代码第3-7行说明了当前Code是哪种指令类型;第9-10代码是异常处理程序;第11-36行注释说明了Code的内存布局。写snapshot文件时内存布局会有细微变化,详情请参考mksnapshot.exe源码。
(4) CallInterfaceDescriptor描述了Builtin入口函数的寄存器参数、堆栈参数和返回值等信息,调用Builtin时会使用这些信息,源码如下:

1.  class V8_EXPORT_PRIVATE CallInterfaceDescriptor {2.   public:3.    Flags flags() const { return data()->flags(); }4.    bool HasContextParameter() const {return (flags() & CallInterfaceDescriptorData::kNoContext) == 0;}5.    int GetReturnCount() const { return data()->return_count(); }6.    MachineType GetReturnType(int index) const {return data()->return_type(index);}7.    int GetParameterCount() const { return data()->param_count(); }8.    int GetRegisterParameterCount() const {return data()->register_param_count();}9.    int GetStackParameterCount() const {return data()->param_count() - data()->register_param_count();}10.    Register GetRegisterParameter(int index) const {return data()->register_param(index);}11.    MachineType GetParameterType(int index) const {return data()->param_type(index);}12.    RegList allocatable_registers() const {return data()->allocatable_registers();}13.  //..............省略...................14.  private:15.  const CallInterfaceDescriptorData* data_;16.  }

上述代码第5行是Builtin的返回值数量;第6行是返回值的类型;第7行是参数数量;第8代是寄存器参数的数量;第9行是栈参数的数量;第10-12行是获取参数;第15行代码CallInterfaceDescriptorData存储上述代码中所需的信息,即返回值数量、类型等信息,源码如下:

1.  class V8_EXPORT_PRIVATE CallInterfaceDescriptorData {2.   private:3.    bool IsInitializedPlatformSpecific() const {4.  //.........省略..............5.    }6.    bool IsInitializedPlatformIndependent() const {7.  //.........省略..............8.    }9.    int register_param_count_ = -1;10.    int return_count_ = -1;11.    int param_count_ = -1;12.    Flags flags_ = kNoFlags;13.    RegList allocatable_registers_ = 0;14.    Register* register_params_ = nullptr;15.    MachineType* machine_types_ = nullptr;16.    DISALLOW_COPY_AND_ASSIGN(CallInterfaceDescriptorData);17.  };

上述代码第9-15行定义的变量就是CallInterfaceDescriptor中提到的返回值、参数等信息。
以上内容是CallBuiltin()使用的主要数据结构。

 

03
CallBuiltin()

来看下面的使用场景:

IGNITION_HANDLER(LdaNamedPropertyNoFeedback, InterpreterAssembler) {  TNode<Object> object = LoadRegisterAtOperandIndex(0);  TNode<Name> name = CAST(LoadConstantPoolEntryAtOperandIndex(1));  TNode<Context> context = GetContext();  TNode<Object> result = CallBuiltin(Builtins::kGetProperty, context, object, name);  SetAccumulator(result);  Dispatch();}

LdaNamedPropertyNoFeedback的作用是获取属性,例如从document属性中获取getelementbyID方法,该方法的获取由CallBuiltin调用Builtins::kGetProperty实现,源码如下:

  template <class... TArgs>  TNode<Object> CallBuiltin(Builtins::Name id, SloppyTNode<Object> context,                            TArgs... args) {    return CallStub<Object>(Builtins::CallableFor(isolate(), id), context,                            args...);  }

上述代码中id代表Builtin的名字,即前面提到的枚举值;args有两个成员:args[0]代表object(上述例子中的document),args[1]代表name(getelementbyID方法)。CallStub()源码如下:

1.    template <class T = Object, class... TArgs>2.    TNode<T> CallStub(Callable const& callable, SloppyTNode<Object> context,3.                      TArgs... args) {4.      TNode<Code> target = HeapConstant(callable.code());5.      return CallStub<T>(callable.descriptor(), target, context, args...);6.    }7.  //..............分隔线..................8.    template <class T = Object, class... TArgs>9.    TNode<T> CallStub(const CallInterfaceDescriptor& descriptor,10.                      SloppyTNode<Code> target, SloppyTNode<Object> context,11.                      TArgs... args) {12.      return UncheckedCast<T>(CallStubR(StubCallMode::kCallCodeObject, descriptor,13.                                        1, target, context, args...));14.    }15.  //..............分隔线..................16.    template <class... TArgs>17.    Node* CallStubR(StubCallMode call_mode,18.                    const CallInterfaceDescriptor& descriptor, size_t result_size,19.                    SloppyTNode<Object> target, SloppyTNode<Object> context,20.                    TArgs... args) {21.      return CallStubRImpl(call_mode, descriptor, result_size, target, context,22.                           {args...});23.    }

上述代码第4行创建target对象,该对象是Builtin的入口地址;第5行代码调用CallStub()方法(第9行),最终进入CallStubR()。在CallStubR()中调用CallStubRImpl(),源码如下:

1.  Node* CodeAssembler::CallStubRImpl( ) {2.    DCHECK(call_mode == StubCallMode::kCallCodeObject ||3.           call_mode == StubCallMode::kCallBuiltinPointer);4.    constexpr size_t kMaxNumArgs = 10;5.    DCHECK_GE(kMaxNumArgs, args.size());6.     NodeArray<kMaxNumArgs + 2> inputs;7.     inputs.Add(target);8.     for (auto arg : args) inputs.Add(arg);9.     if (descriptor.HasContextParameter()) {10.       inputs.Add(context);11.     }12.     return CallStubN(call_mode, descriptor, result_size, inputs.size(),13.                      inputs.data());14.   }

上述代码第7-10行将所有参数添加到数组inputs中,inputs内容依次为:Builtin的入口地址(code类型)、object、name、context。进入第12行代码,CallStubN()源码如下:

1.  Node* CodeAssembler::CallStubN() {2.    // implicit nodes are target and optionally context.3.    int implicit_nodes = descriptor.HasContextParameter() ? 2 : 1;4.    int argc = input_count - implicit_nodes;5.    // Extra arguments not mentioned in the descriptor are passed on the stack.6.    int stack_parameter_count = argc - descriptor.GetRegisterParameterCount();7.    auto call_descriptor = Linkage::GetStubCallDescriptor(8.        zone(), descriptor, stack_parameter_count, CallDescriptor::kNoFlags,9.        Operator::kNoProperties, call_mode);10.    CallPrologue();11.    Node* return_value =12.        raw_assembler()->CallN(call_descriptor, input_count, inputs);13.    HandleException(return_value);14.    CallEpilogue();15.    return return_value;16.  }

上述第7行代码call_descriptor的返回值类型如下:

CallDescriptor(          // --      kind,                                  // kind      target_type,                           // target MachineType      target_loc,                            // target location      locations.Build(),                     // location_sig      stack_parameter_count,                 // stack_parameter_count      properties,                            // properties      kNoCalleeSaved,                        // callee-saved registers      kNoCalleeSaved,                        // callee-saved fp      CallDescriptor::kCanUseRoots | flags,  // flags      descriptor.DebugName(),                // debug name      descriptor.allocatable_registers())

上述信息为调用Builtin做准备工作。CallStubN()中第11行代码:完成Builtin的调用,第13行代码:异常处理。图1给出了CodeAssembler()的调用堆栈,此时正在建立Builtin,Builtin的建立发生在Isolate初始化阶段。

【技术分享】《Chrome V8源码》29.CallBuiltin()调用过程详解


技术总结
(1) Builtin名字与Builtin的入口之间存在一一对应的关系,这种关系由isoate->isolatedata->builtins表示,builtins是在Isolate初始化过程中创建的Address数组;
(2) inputs数组除了包括参数之外还有target和context;
(3) Builtin的参数、返回值等信息的详细说明可在BUILTIN_LIST宏中查看。

好了,今天到这里,下次见。

个人能力有限,有不足与纰漏,欢迎批评指正
微信:qq9123013 备注:v8交流 邮箱:
[email protected]

【技术分享】《Chrome V8源码》29.CallBuiltin()调用过程详解

- 结尾 -
精彩推荐
gitlab未授权RCE(CVE-2021-22205)漏洞复现及深入利用
【技术分享】自删除技术详解
【技术分享】mimikatz源码学习-Kerberos模块
【技术分享】《Chrome V8源码》29.CallBuiltin()调用过程详解
戳“阅读原文”查看更多内容

原文始发于微信公众号(安全客):【技术分享】《Chrome V8源码》29.CallBuiltin()调用过程详解

版权声明:admin 发表于 2021年12月2日 上午10:00。
转载请注明:【技术分享】《Chrome V8源码》29.CallBuiltin()调用过程详解 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...