华域联盟 安全资讯 《Chrome V8源码》31.Ignition到底做了什么?(二)

《Chrome V8源码》31.Ignition到底做了什么?(二)

 

1 摘要

本篇文章是Builtin专题的第六篇,讲解Ignition中的Builtin::kInterpreterEntryTrampoline源码。包括InterpreterEntryTrampoline、Runtime_InterpreterTraceBytecodeEntry和Runtime_InterpreterTraceBytecodeExit源码。

 

2 InterpreterEntryTrampoline

提示: 本文使用的V8版本是7.9.10,CPU:x64,Builtins-x64.cc,样例代码参见上一篇。
InterpreterEntryTrampoline源码如下:

1.  void Builtins::Generate_InterpreterEntryTrampoline(MacroAssembler* masm) {
2.  Register closure = rdi;
3.  Register feedback_vector = rbx;
4.  __ LoadTaggedPointerField(
5.      rax, FieldOperand(closure, JSFunction::kSharedFunctionInfoOffset));
6.  __ LoadTaggedPointerField(
7.      kInterpreterBytecodeArrayRegister,
8.      FieldOperand(rax, SharedFunctionInfo::kFunctionDataOffset));
9.  GetSharedFunctionInfoBytecode(masm, kInterpreterBytecodeArrayRegister,
10.                                  kScratchRegister);
11.    Label compile_lazy;
12.    __ CmpObjectType(kInterpreterBytecodeArrayRegister, BYTECODE_ARRAY_TYPE, rax);
13.    __ j(not_equal, &compile_lazy);
14.    __ bind(&push_stack_frame);
15.    FrameScope frame_scope(masm, StackFrame::MANUAL);
16.    __ pushq(rbp);  // Caller's frame pointer.
17.    __ movq(rbp, rsp);
18.    __ Push(rsi);  // Callee's context.
19.    __ Push(rdi);  // Callee's JS function.
20.    __ movw(FieldOperand(kInterpreterBytecodeArrayRegister,
21.                         BytecodeArray::kOsrNestingLevelOffset),
22.            Immediate(0));
23.    __ movq(kInterpreterBytecodeOffsetRegister,
24.            Immediate(BytecodeArray::kHeaderSize - kHeapObjectTag));
25.    __ Push(kInterpreterBytecodeArrayRegister);
26.    __ SmiTag(rcx, kInterpreterBytecodeOffsetRegister);
27.    __ Push(rcx);
28.    {
29.      //Allocate the local and temporary register file on the stack.
30.      //省略...............
31.    }
32.    __ LoadRoot(kInterpreterAccumulatorRegister, RootIndex::kUndefinedValue);
33.    Label do_dispatch;
34.    __ bind(&do_dispatch);
35.    __ Move(
36.        kInterpreterDispatchTableRegister,
37.        ExternalReference::interpreter_dispatch_table_address(masm->isolate()));
38.    __ movzxbq(r11, Operand(kInterpreterBytecodeArrayRegister,
39.                            kInterpreterBytecodeOffsetRegister, times_1, 0));
40.    __ movq(kJavaScriptCallCodeStartRegister,
41.            Operand(kInterpreterDispatchTableRegister, r11,
42.                    times_system_pointer_size, 0));
43.    __ call(kJavaScriptCallCodeStartRegister);
44.    masm->isolate()->heap()->SetInterpreterEntryReturnPCOffset(masm->pc_offset());
45.    __ movq(kInterpreterBytecodeArrayRegister,
46.            Operand(rbp, InterpreterFrameConstants::kBytecodeArrayFromFp));
47.    __ movq(kInterpreterBytecodeOffsetRegister,
48.            Operand(rbp, InterpreterFrameConstants::kBytecodeOffsetFromFp));
49.    __ SmiUntag(kInterpreterBytecodeOffsetRegister,
50.                kInterpreterBytecodeOffsetRegister);
51.    Label do_return;
52.    __ movzxbq(rbx, Operand(kInterpreterBytecodeArrayRegister,
53.                            kInterpreterBytecodeOffsetRegister, times_1, 0));
54.    AdvanceBytecodeOffsetOrReturn(masm, kInterpreterBytecodeArrayRegister,
55.                                  kInterpreterBytecodeOffsetRegister, rbx, rcx,
56.                                  &do_return);
57.    __ jmp(&do_dispatch);
58.    __ bind(&do_return);
59.    LeaveInterpreterFrame(masm, rbx, rcx);
60.    __ ret(0);
61.    __ bind(&compile_lazy);
62.    GenerateTailCallToReturnedCode(masm, Runtime::kCompileLazy);
63.     __ int3(); 
64.   }

上述代码中,第4行代码:从JSFunction中取出SharedFunction并存储到rax中;第6行代码:从SharedFunctionInfo获取kFunctionDataOffset的数据并存储到kInterpreterBytecodeArrayRegister中;第9行代码:加载Bytecodearray到kInterpreterBytecodeArrayRegister。
细节说明:
(1) FieldOperand(x,y)方法中x是基址,y是偏移量,该方法用于返回x+y的位置的数据;
(2) 因为SharedFunction::kFunctionDataOffset可能存储Bytecodearray或Builtin,所以执行完第6行代码后需要用第9行代码判断kInterpreterBytecodeArrayRegister中的数据是否是Bytecodearray。
上述第10-13行代码:判断kInterpreterBytecodeArrayRegister的值是Bytecodarray还是Builtins::kCompileLazy,根据判断结果跳转到相应的Label;第15-19行代码存储caller的栈帧并把callee的信息压入堆栈。第20-27行代码获取Bytecodearray中第一条Bytecode的偏移量并压入堆栈。BytecodeArray类继承自FixedArrayBase,FixedArrayBase又继承自HeapObject,所以获取第一条Bytecode时需要使用刚刚获取的偏移量。
上述第32行初始化kInterpreterAccumulatorRegister;第35行代码加载dispatch到kInterpreterDispatchTableRegister;第38-40行代码加载第一条Bytecode到kJavaScriptCallCodeStartRegister;第43行代码开始执行Bytecode。所有Bytecode都执行完成后会跳转到第44行代码以设置返回地址。
两种情况下会执行上述第45-63行代码,(1)当全部Bytecode执行完后,Bytecode的结尾会调用Dispatch(),所以只有全部执行完时才会返回;(2)在Bytecode执行过程中调用了其它Builtin,因为调用其它Builtin要重新构建堆栈,所以还要用InterpreterEntryTrampoline。
至此,InterpreterEntryTrampoline分析完毕。

 

3 Register

InterpreterEntryTrampoline中使用了很多Register,列表如下:

constexpr Register kReturnRegister0 = rax;
constexpr Register kReturnRegister1 = rdx;
constexpr Register kReturnRegister2 = r8;
constexpr Register kJSFunctionRegister = rdi;
constexpr Register kContextRegister = rsi;
constexpr Register kAllocateSizeRegister = rdx;
constexpr Register kSpeculationPoisonRegister = r12;
constexpr Register kInterpreterAccumulatorRegister = rax;
constexpr Register kInterpreterBytecodeOffsetRegister = r9;
constexpr Register kInterpreterBytecodeArrayRegister = r14;
constexpr Register kInterpreterDispatchTableRegister = r15;
//省略.................

InterpreterEntryTrampoline中常用到的寄存器是rax、rdi、rdx和r15,其中被多次提及的r15负责Bytecode的调度。我在汇编中调试Byteocde时,r15寄存器常被用作“入口标记”,即看到r15就说明一条Bytecode开始了,再次看到r15就说明这条Bytecode结束了。

 

4 InterpreterTraceBytecodeEntry和InterpreterTraceBytecodeExit

这两个方法用于跟踪Bytecode的解释过程,InterpreterTraceBytecodeEntry可以查看寄存器状态;Bytecode执行后调用InterpreterTraceBytecodeExit。源码如下:

1.  RUNTIME_FUNCTION(Runtime_InterpreterTraceBytecodeEntry) {
2.    if (!FLAG_trace_ignition) {
3.      return ReadOnlyRoots(isolate).undefined_value();
4.    }
5.    SealHandleScope shs(isolate);
6.    DCHECK_EQ(3, args.length());
7.    CONVERT_ARG_HANDLE_CHECKED(BytecodeArray, bytecode_array, 0);
8.    CONVERT_SMI_ARG_CHECKED(bytecode_offset, 1);
9.    CONVERT_ARG_HANDLE_CHECKED(Object, accumulator, 2);
10.    int offset = bytecode_offset - BytecodeArray::kHeaderSize + kHeapObjectTag;
11.    interpreter::BytecodeArrayIterator bytecode_iterator(bytecode_array);
12.    AdvanceToOffsetForTracing(bytecode_iterator, offset);
13.    if (offset == bytecode_iterator.current_offset()) {
14.      StdoutStream os;
15.      // Print bytecode.
16.      const uint8_t* base_address = reinterpret_cast<const uint8_t*>(
17.          bytecode_array->GetFirstBytecodeAddress());
18.      const uint8_t* bytecode_address = base_address + offset;
19.      os << " -> " << static_cast<const void*>(bytecode_address) << " @ "
20.         << std::setw(4) << offset << " : ";
21.      interpreter::BytecodeDecoder::Decode(os, bytecode_address,
22.                                           bytecode_array->parameter_count());
23.      os << std::endl;
24.      // Print all input registers and accumulator.
25.      PrintRegisters(isolate, os, true, bytecode_iterator, accumulator);
26.      os << std::flush;
27.    }
28.    return ReadOnlyRoots(isolate).undefined_value();
29.  }
30.  //分隔线...............................
31.  RUNTIME_FUNCTION(Runtime_InterpreterTraceBytecodeExit) {
32.    if (!FLAG_trace_ignition) {
33.      return ReadOnlyRoots(isolate).undefined_value();
34.    }
35.    SealHandleScope shs(isolate);
36.    DCHECK_EQ(3, args.length());
37.    CONVERT_ARG_HANDLE_CHECKED(BytecodeArray, bytecode_array, 0);
38.    CONVERT_SMI_ARG_CHECKED(bytecode_offset, 1);
39.    CONVERT_ARG_HANDLE_CHECKED(Object, accumulator, 2);
40.    int offset = bytecode_offset - BytecodeArray::kHeaderSize + kHeapObjectTag;
41.    interpreter::BytecodeArrayIterator bytecode_iterator(bytecode_array);
42.    AdvanceToOffsetForTracing(bytecode_iterator, offset);
43.    if (bytecode_iterator.current_operand_scale() ==
44.            interpreter::OperandScale::kSingle ||
45.        offset > bytecode_iterator.current_offset()) {
46.      StdoutStream os;
47.      // Print all output registers and accumulator.
48.      PrintRegisters(isolate, os, false, bytecode_iterator, accumulator);
49.      os << std::flush;
50.    }
51.    return ReadOnlyRoots(isolate).undefined_value();
52.  }

Bytecode执行前后会分别调用上述两个方法,但需要把FLAG_trace_ignition(第2行代码)的值设置为True,其声明在flags-definitions.h中,具体位置是DEFINE_BOOL(trace_ignition, false,"trace the bytecodes executed by the ignition interpreter")。第21行代码输出Bytecode到终端,阅读BytecodeDecoder::Decode()源码可以看明白Bytecode和operand的编码方式,这有助于理解dispatch和JS调用堆栈。
下面给出PrintRegisters源码:

1.  void PrintRegisters(Isolate* isolate, std::ostream& os, bool is_input,
2.                      interpreter::BytecodeArrayIterator&
3.                          bytecode_iterator,  // NOLINT(runtime/references)
4.                      Handle<Object> accumulator) {
5.    interpreter::Bytecode bytecode = bytecode_iterator.current_bytecode();
6.    // Print accumulator.
7.    if ((is_input && interpreter::Bytecodes::ReadsAccumulator(bytecode)) ||
8.        (!is_input && interpreter::Bytecodes::WritesAccumulator(bytecode))) {
9.      os << "      [ " << kAccumulator << kArrowDirection;
10.      accumulator->ShortPrint();
11.      os << " ]" << std::endl;
12.    }
13.    // Print the registers.
14.    JavaScriptFrameIterator frame_iterator(isolate);
15.    InterpretedFrame* frame =
16.        reinterpret_cast<InterpretedFrame*>(frame_iterator.frame());
17.    int operand_count = interpreter::Bytecodes::NumberOfOperands(bytecode);
18.    for (int operand_index = 0; operand_index < operand_count; operand_index++) {
19.      interpreter::OperandType operand_type =
20.          interpreter::Bytecodes::GetOperandType(bytecode, operand_index);
21.      bool should_print =
22.          is_input
23.              ? interpreter::Bytecodes::IsRegisterInputOperandType(operand_type)
24.              : interpreter::Bytecodes::IsRegisterOutputOperandType(operand_type);
25.      if (should_print) {
26.        interpreter::Register first_reg =
27.            bytecode_iterator.GetRegisterOperand(operand_index);
28.        int range = bytecode_iterator.GetRegisterOperandRange(operand_index);
29.        for (int reg_index = first_reg.index();
30.             reg_index < first_reg.index() + range; reg_index++) {
31.          Object reg_object = frame->ReadInterpreterRegister(reg_index);
32.          os << "      [ " << std::setw(kRegFieldWidth)
33.             << interpreter::Register(reg_index).ToString(
34.                    bytecode_iterator.bytecode_array()->parameter_count())
35.             << kArrowDirection;
36.          reg_object.ShortPrint(os);
37.          os << " ]" << std::endl;
38.        }
39.      }
40.    }
41.  }

上述第14-17行代码计算操作数的数量。第20-37行代码输出寄存器的值。通过阅读PrintRegisters()方法,我们可以学到三个有用的知识点:
(1)读取寄存器的方法;
(2)V8中打印数据的方法;
(3)InterpretedFrame的数据结构。
这三点可以帮助我们更好地了解Bytecode的执行过程。提示V8中功能全面的打印方法是logger。

技术总结
(1) SharedFunction::kFunctionDataOffset位置存储的内容可能是Bytecodearray也可能是Builtins::kCompileLazy;
(2) BytecodeDecoder::Decode()PrintRegisters()很重要,可以帮助我们理解Bytecode的执行过程。
好了,今天到这里,下次见。

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

本文由 华域联盟 原创撰写:华域联盟 » 《Chrome V8源码》31.Ignition到底做了什么?(二)

转载请保留出处和原文链接:https://www.cnhackhy.com/129231.htm

本文来自网络,不代表华域联盟立场,转载请注明出处。

作者: sterben

CVE-2021-22205 GitLab RCE之未授权访问深入分析(一)

loT设备如何进行固件分析系列一

发表回复

联系我们

联系我们

2551209778

在线咨询: QQ交谈

邮箱: [email protected]

工作时间:周一至周五,9:00-17:30,节假日休息

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

关注微博
返回顶部