Глава 30. Загрузка JS-функции и выполнение байт-кода

Добро пожаловать в другие главы Давайте разберемся с Chrome V8

В этой статье я расскажу о зажигании и дам вам представление о том, как оно работает, как оно загружает JS-функцию и что она будет делать перед выполнением.

1. Вызов JS-функции

Для интерпретатора V8 все это начинается с загрузки JS-функции (термин V8 — invoke), поскольку он имеет массив байт-кода. Ниже приведена функция вызова.

1. InvokeParams InvokeParams::SetUpForCall(Isolate* isolate,
2. Handle<Object> callable,
3. Handle<Object> receiver, int argc,
4. Handle<Object>* argv) {
5. InvokeParams params;
6. params.target = callable;
7. params.receiver = NormalizeReceiver(isolate, receiver);
8. params.argc = argc;
9. params.argv = argv;
10. params.new_target = isolate->factory()->undefined_value();
11. //omit...................
12. return params;
13. }
14. //...............分隔线............................
15. V8_WARN_UNUSED_RESULT MaybeHandle<Object> Invoke(Isolate* isolate,
16. const InvokeParams& params) {
17. Handle<Code> code =
18. JSEntry(isolate, params.execution_target, params.is_construct);
19. {
20. if (params.execution_target == Execution::Target::kCallable) {
21. using JSEntryFunction = GeneratedCode<Address(
22. Address root_register_value, Address new_target, Address target,
23. Address receiver, intptr_t argc, Address** argv)>;
24. JSEntryFunction stub_entry =
25. JSEntryFunction::FromAddress(isolate, code->InstructionStart());
26. Address orig_func = params.new_target->ptr();
27. Address func = params.target->ptr();
28. Address recv = params.receiver->ptr();
29. Address** argv = reinterpret_cast<Address**>(params.argv);
30. RuntimeCallTimerScope timer(isolate, RuntimeCallCounterId::kJS_Execution);
31. value = Object(stub_entry.Call(isolate->isolate_data()->isolate_root(),
32. orig_func, func, recv, params.argc, argv));
33. } else {
34. }
35. }
36. }
view raw 30.1.cpp hosted with ❤ by GitHub

Перед вызовом JS-функции V8 оборачивает ее в invoke-params, но не добавляет другие новые вещи, поэтому нас это не волнует, но мы помним, какой член является в ней JSfcuntion. Я выкладываю важное содержание ниже.

(1) Строки 1–13, SetupForCall() — это функция-оболочка, о которой я упоминал.

(2) Строка 17, код представляет собой адрес функции Builtin::kJSEntry.

(3) Строка 24, stub_entry — это первая инструкция Builtin::kJSEntry.

(4) Строка 27, func — это JS-функция, которая будет выполняться.

(5) Строка 31, stub_entry.Call начинает переходить в Ignition для выполнения вашего JavaScript. Примечание.ваш код JavaScript еще не вызван, так как Ignition создает стек вызовов, а затем переходит к вашему коду.

2. Встроенный::kJSEntry

Прежде чем говорить о Builtin::kJSEntry, я хочу подчеркнуть некоторые члены JSFunction, которые часто используются в Ignition.

(1) SharedFunction является членом JSFunction, важно знать, что SharedFunction имеет массив байт-кода, который будет выполнять Ignition.

(2) код – это встроенный модуль::kInterpreterEntryTrampoline.

(3) контекст — это контекст, соответствующий текущей среде выполнения.

Перейдем к файлу Builtin::kJSEntry.

1. void Builtins::Generate_JSEntry(MacroAssembler* masm) {
2. Generate_JSEntryVariant(masm, StackFrame::ENTRY,
3. Builtins::kJSEntryTrampoline);
4. }
5. //............................
6. void Generate_JSEntryVariant(MacroAssembler* masm, StackFrame::Type type,
7. Builtins::Name entry_trampoline) {
8. Label invoke, handler_entry, exit;
9. Label not_outermost_js, not_outermost_js_2;
10. { // NOLINT. Scope block confuses linter.
11. NoRootArrayScope uninitialized_root_register(masm);
12. //..............omit...........
13. // Initialize the root register.
14. // C calling convention. The first argument is passed in arg_reg_1.
15. __ movq(kRootRegister, arg_reg_1);
16. // Save copies of the top frame descriptor on the stack.
17. ExternalReference c_entry_fp = ExternalReference::Create(
18. IsolateAddressId::kCEntryFPAddress, masm->isolate());
19. {
20. Operand c_entry_fp_operand = masm->ExternalReferenceAsOperand(c_entry_fp);
21. __ Push(c_entry_fp_operand);
22. }
23. // Store the context address in the previously-reserved slot.
24. ExternalReference context_address = ExternalReference::Create(
25. IsolateAddressId::kContextAddress, masm->isolate());
26. __ Load(kScratchRegister, context_address);
27. static constexpr int kOffsetToContextSlot = -2 * kSystemPointerSize;
28. __ movq(Operand(rbp, kOffsetToContextSlot), kScratchRegister);
29. __ jmp(&invoke);
30. __ bind(&handler_entry);
31. // Store the current pc as the handler offset. It's used later to create the
32. // handler table.
33. masm->isolate()->builtins()->SetJSEntryHandlerOffset(handler_entry.pos());
34. // Caught exception: Store result (exception) in the pending exception
35. // field in the JSEnv and return a failure sentinel.
36. ExternalReference pending_exception = ExternalReference::Create(
37. IsolateAddressId::kPendingExceptionAddress, masm->isolate());
38. __ Store(pending_exception, rax);
39. __ LoadRoot(rax, RootIndex::kException);
40. __ jmp(&exit);
41. // Invoke: Link this frame into the handler chain.
42. __ bind(&invoke);
43. __ PushStackHandler();
44. // Invoke the function by calling through JS entry trampoline builtin and
45. // pop the faked function when we return.
46. Handle<Code> trampoline_code =
47. masm->isolate()->builtins()->builtin_handle(entry_trampoline);
48. __ Call(trampoline_code, RelocInfo::CODE_TARGET);
49. //.............omit............
50. }
view raw 30.2.cpp hosted with ❤ by GitHub

В приведенном выше коде строка 2 вызывает Generate_JSEntryVariant(), третий параметр которой — это батут, который в конечном итоге вызывает наш байт-код.

В строке 15 arg_reg_1 представляет собой isolate-›isolate_data()-›isolate_root(), причина, по которой я подчеркиваю это, заключается в том, что isolate_root() содержит все необходимые вещи, такие как встроенные модули и куча, которые полезны во время выполнения.

Строки 17–22, поместите верхний фрейм в стек, как мы это делаем в X86.

Строки 24–26 загружают текущий контекст в ScratchRegister.

Строки 46–48, выполните JSEntryTrampoline.

3. Встроенный::kJSEntryTrampoline

1. void Builtins::Generate_JSEntryTrampoline(MacroAssembler* masm) {
2. Generate_JSEntryTrampolineHelper(masm, false);
3. }
4. //.............分隔线..................
5. static void Generate_JSEntryTrampolineHelper(MacroAssembler* masm,
6. bool is_construct) {
7. // Expects six C++ function parameters.
8. // - Address root_register_value
9. // - Address new_target (tagged Object pointer)
10. // - Address function (tagged JSFunction pointer)
11. // - Address receiver (tagged Object pointer)
12. // - intptr_t argc
13. // - Address** argv (pointer to array of tagged Object pointers)
14. // (see Handle::Invoke in execution.cc).
15. // Open a C++ scope for the FrameScope.
16. {
17. // Platform specific argument handling. After this, the stack contains
18. // an internal frame and the pushed function and receiver, and
19. // register rax and rbx holds the argument count and argument array,
20. // while rdi holds the function pointer, rsi the context, and rdx the
21. // new.target.
22. // MSVC parameters in:
23. // rcx : root_register_value
24. // rdx : new_target
25. // r8 : function
26. // r9 : receiver
27. // [rsp+0x20] : argc
28. // [rsp+0x28] : argv
29. __ movq(rdi, arg_reg_3);
30. __ Move(rdx, arg_reg_2);
31. // rdi : function
32. // rdx : new_target
33. // Setup the context (we need to use the caller context from the isolate).
34. ExternalReference context_address = ExternalReference::Create(
35. IsolateAddressId::kContextAddress, masm->isolate());
36. __ movq(rsi, masm->ExternalReferenceAsOperand(context_address));
37. // Push the function and the receiver onto the stack.
38. __ Push(rdi);
39. __ Push(arg_reg_4);
40. // Current stack contents:
41. // [rsp + 2 * kSystemPointerSize ... ] : Internal frame
42. // [rsp + kSystemPointerSize] : function
43. // [rsp] : receiver
44. // Current register contents:
45. // rax : argc
46. // rbx : argv
47. // rsi : context
48. // rdi : function
49. // rdx : new.target
50. __ bind(&enough_stack_space);
51. // Copy arguments to the stack in a loop.
52. // Invoke the builtin code.
53. Handle<Code> builtin = is_construct
54. ? BUILTIN_CODE(masm->isolate(), Construct)
55. : masm->isolate()->builtins()->Call();
56. __ Call(builtin, RelocInfo::CODE_TARGET);
57. }
58. __ ret(0);
59. }
view raw 30.3.cpp hosted with ❤ by GitHub

В приведенном выше коде строки 7–28 помещают параметры в стек, в котором функция параметра представляет собой байт-код, полученный из нашего JavaScript. В строках 40–49 комментарии описывают структуру стекового фрейма. Строка 53, получите встроенный адрес, то есть Builtin::kCall_ReceiverIsAny.

4. Встроенный::kCall_ReceiverIsAny

1. void Builtins::Generate_Call_ReceiverIsAny(MacroAssembler* masm) {
2. Generate_Call(masm, ConvertReceiverMode::kAny);
3. }
4. //...........分隔线..................
5. void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode) {
6. // ----------- S t a t e -------------
7. // -- rax : the number of arguments (not including the receiver)
8. // -- rdi : the target to call (can be any Object)
9. // -----------------------------------
10. StackArgumentsAccessor args(rsp, rax);
11. Label non_callable;
12. __ JumpIfSmi(rdi, &non_callable);
13. __ CmpObjectType(rdi, JS_FUNCTION_TYPE, rcx);
14. __ Jump(masm->isolate()->builtins()->CallFunction(mode),
15. RelocInfo::CODE_TARGET, equal);
16. __ CmpInstanceType(rcx, JS_BOUND_FUNCTION_TYPE);
17. __ Jump(BUILTIN_CODE(masm->isolate(), CallBoundFunction),
18. RelocInfo::CODE_TARGET, equal);
19. // Check if target has a [[Call]] internal method.
20. __ testb(FieldOperand(rcx, Map::kBitFieldOffset),
21. Immediate(Map::IsCallableBit::kMask));
22. __ j(zero, &non_callable, Label::kNear);
23. // Check if target is a proxy and call CallProxy external builtin
24. __ CmpInstanceType(rcx, JS_PROXY_TYPE);
25. __ Jump(BUILTIN_CODE(masm->isolate(), CallProxy), RelocInfo::CODE_TARGET,
26. equal);
27. }
view raw 30.4.cpp hosted with ❤ by GitHub

В приведенном выше коде, строка 13, rdi — это выполняемая функция JSF, а rcx — ее карта. Как правило, CmpObjectType имеет значение true и переходит к строке 14 и в конечном итоге вызывает kCallFunction_ReceiverIsAny.

5. Встроенный::kCallFunction_ReceiverIsAny

1. void Builtins::Generate_CallFunction_ReceiverIsAny(MacroAssembler* masm) {
2. Generate_CallFunction(masm, ConvertReceiverMode::kAny);
3. }
4. //.............分隔线..................
5. void Builtins::Generate_CallFunction(MacroAssembler* masm,
6. ConvertReceiverMode mode) {
7. StackArgumentsAccessor args(rsp, rax);
8. __ AssertFunction(rdi);
9. {
10. // ----------- S t a t e -------------
11. // -- rax : the number of arguments (not including the receiver)
12. // -- rdx : the shared function info.
13. // -- rdi : the function to call (checked to be a JSFunction)
14. // -- rsi : the function context.
15. // -----------------------------------
16. __ movzxwq(
17. rbx, FieldOperand(rdx, SharedFunctionInfo::kFormalParameterCountOffset));
18. ParameterCount actual(rax);
19. ParameterCount expected(rbx);
20. __ InvokeFunctionCode(rdi, no_reg, expected, actual, JUMP_FUNCTION);
21. // The function is a "classConstructor", need to raise an exception.
22. __ bind(&class_constructor);
23. {
24. FrameScope frame(masm, StackFrame::INTERNAL);
25. __ Push(rdi);
26. __ CallRuntime(Runtime::kThrowConstructorNonCallableError);
27. }
28. }
view raw 30.5.cpp hosted with ❤ by GitHub

В приведенном выше коде строки 7–19 помещают параметры в стек, регистр rax имеет решающее значение для обеспечения того, чтобы аргументы JavaScript всегда совпадали, поскольку V8 заполняет Null, если аргумент отсутствует, или V8 отбрасывает лишнее. аргументы. Строки 10–14, rdi — это JSFuncion, а rdx — его SharedFunction. Завершите выполнение строки 20.

6. InvokeFunctionCode

1. void MacroAssembler::InvokeFunctionCode(Register function, Register new_target,
2. const ParameterCount& expected,
3. const ParameterCount& actual,
4. InvokeFlag flag) {
5. // On function call, call into the debugger if necessary.
6. CheckDebugHook(function, new_target, expected, actual);
7. // Clear the new.target register if not given.
8. if (!new_target.is_valid()) {
9. LoadRoot(rdx, RootIndex::kUndefinedValue);
10. }
11. Label done;
12. bool definitely_mismatches = false;
13. InvokePrologue(expected, actual, &done, &definitely_mismatches, flag,
14. Label::kNear);
15. if (!definitely_mismatches) {
16. // We call indirectly through the code field in the function to
17. // allow recompilation to take effect without changing any of the
18. // call sites.
19. static_assert(kJavaScriptCallCodeStartRegister == rcx, "ABI mismatch");
20. LoadTaggedPointerField(rcx,
21. FieldOperand(function, JSFunction::kCodeOffset));
22. if (flag == CALL_FUNCTION) {
23. CallCodeObject(rcx);
24. } else {
25. DCHECK(flag == JUMP_FUNCTION);
26. JumpCodeObject(rcx);
27. }
28. bind(&done);
29. }
30. }
view raw 30.6.cpp hosted with ❤ by GitHub

В приведенном выше коде строка 23 вызывает rcx, rcx — это просто Builtins::kInterpreterEntryTrampoline, который является прологом нашего JavaScript. Другими словами, если вам интересно, как выполняется ваш JavaScript, отладка отсюда — самый простой способ.

Следующее описание взято из v8.dev.

«Когда функция вызывается во время выполнения, вводится заглушка InterpreterEntryTrampoline. Эта заглушка устанавливает соответствующий кадр стека, а затем отправляет обработчику байт-кода интерпретатора первый байт-код функции, чтобы начать выполнение функции в интерпретаторе. Конец каждого обработчика байт-кода напрямую отправляет следующему обработчику через индекс в глобальной таблице интерпретатора на основе байт-кода».

Хорошо, на этом мы закончили. Увидимся в следующий раз, берегите себя!

Пожалуйста, свяжитесь со мной, если у вас есть какие-либо проблемы. WeChat: qq9123013 Электронная почта: v8blink@outlook.com

Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord . Заинтересованы в хакинге роста? Ознакомьтесь с разделом Схема.