Chrome V8을 이해합시다--18장: 바이트코드 이해 및 디버그 방법
25073 단어 cppjavascriptchromewebdev
Let’s Understand Chrome V8의 다른 장에 오신 것을 환영합니다.
바이트코드는 대략 어셈블리라고 볼 수 있는 CSA(CodeStubAssembler)를 사용하여 구현됩니다. 바이트코드는 기호 테이블 없이 V8 시작 중에 역직렬화 방식으로 로드됩니다. 그래서 저는 여러분에게 말하고 싶습니다. CAS는 정적 분석에서 모호합니다. 또한 디버깅에서 바이트 코드의 소스 코드를 얻을 수 없습니다. 이 문서에서는 어셈블리 수준에서 바이트코드를 디버그하는 방법과 실행 세부 정보를 확인하는 방법에 대해 설명합니다.
그림 1은 바이트코드의 인터프리터를 보여줍니다. 그림은 JavaScript 개발자에게 충분히 상세합니다. 그러나 V8 학습자의 경우 그림 1에는 점화 시작, 바이트코드 로드 및 발송과 같은 많은 세부 정보가 부족합니다. 바이트코드를 디버깅하여 세부 정보를 살펴보겠습니다.
1. 준비
참고: 디버깅하기 전에 스택 프레임, 바이트코드 및 레지스터의 인코딩see here을 이해하는 것이 좋습니다.
아래는 디버깅 전 마지막 C++ 함수인 Invoke입니다.
1. V8_WARN_UNUSED_RESULT MaybeHandle<Object> Invoke(Isolate* isolate,
2. const InvokeParams& params) {
3. if (params.target->IsJSFunction()) {
4. //............omit...............
5. }
6. Object value;
7. Handle<Code> code =
8. JSEntry(isolate, params.execution_target, params.is_construct);
9. {
10. SaveContext save(isolate);
11. SealHandleScope shs(isolate);
12. if (FLAG_clear_exceptions_on_js_entry) isolate->clear_pending_exception();
13. if (params.execution_target == Execution::Target::kCallable) {
14. using JSEntryFunction = GeneratedCode<Address(
15. Address root_register_value, Address new_target, Address target,
16. Address receiver, intptr_t argc, Address** argv)>;
17. // clang-format on
18. JSEntryFunction stub_entry =
19. JSEntryFunction::FromAddress(isolate, code->InstructionStart());
20. Address orig_func = params.new_target->ptr();
21. Address func = params.target->ptr();
22. Address recv = params.receiver->ptr();
23. Address** argv = reinterpret_cast<Address**>(params.argv);
24. RuntimeCallTimerScope timer(isolate, RuntimeCallCounterId::kJS_Execution);
25. value = Object(stub_entry.Call(isolate->isolate_data()->isolate_root(),
26. orig_func, func, recv, params.argc, argv));
27. } else {
28. //............omit...............
29. }
30. }
31. //............omit...............
32. return Handle<Object>(value, isolate);
33. }
14~23행은 실행될 JSFunction, 즉 JavaScript 코드를 초기화합니다. 다음은 기억해야 할 다섯 가지 중요한 구성원입니다. 이러한 구성원은 디버깅에서 위치를 찾는 데 도움이 될 수 있습니다. 하나의 디버그 컨텍스트에서는 일정하지만 다른 디버그에서는 유사하지 않습니다.
(1) 라인 7, 코드, 그것은 Builtin::JSEntry 포인터입니다. 이제 값은 1FA 0E06 ED30입니다(내 디버그 컨텍스트에서).
(2) 18행, stub_entry, 이그니션의 입구입니다. 다음은 Builtin::JSEntry에서 stub_entry를 가져오는 함수입니다.
Address Code::OffHeapInstructionStart() const {
DCHECK(is_off_heap_trampoline());
if (Isolate::CurrentEmbeddedBlob() == nullptr) return raw_instruction_start();
EmbeddedData d = EmbeddedData::FromBlob();
return d.InstructionStartOfBuiltin(builtin_index());
}
항목은 1FA 1326 1840입니다.
(3) Line21, func, JSFunction의 주소, 즉 JavaScript 코드입니다. 값은 16 2BD8 15A9입니다.
(4) Builtin::InterpreterEntryTrampoline의 주소는 52 61C0 8A41입니다.
(5) dispatch_table은 1FA 0E08 CFB0입니다.
그림 2는 바이트코드로 들어가는 호출 스택입니다.
2. 디버그 바이트코드
stub_entry.Call()을 단계별로 실행하여 바이트 코드 디버깅을 시작하겠습니다. 어셈블리 코드에 컨텍스트가 없기 때문에 한 줄씩 살펴볼 수 없어서 죄송합니다. Ignition 실행 중에 중요한 상태를 제공하는 것이 가장 좋은 방법이라고 생각합니다.
그림 3에서 레지스터 RCX는 1FA 1326 1840(stub_entry)입니다. 내가 언급했듯이 stub_entry는 Ignition의 입구입니다.
그림 4에서 레지스터 RBX는 stub_entry.Call()의 첫 번째 인수인 1FA0E068A00, 즉 isolate->isolate_data()->isolate_root()입니다.
그림 5는 R8을 이동하고 RSI를 호출하는 것입니다. 레지스터 R8은 func이고 RSI는 stub_entry입니다. 아래는 stub_entry가 가리키는 함수입니다.
1. void Builtins::Generate_JSEntry(MacroAssembler* masm) {
2. Generate_JSEntryVariant(masm, StackFrame::ENTRY,
3. Builtins::kJSEntryTrampoline);
4. }
5. //==================separation===========================
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. {
11. NoRootArrayScope uninitialized_root_register(masm);
12. __ pushq(rbp);
13. __ movq(rbp, rsp);
14. __ Push(Immediate(StackFrame::TypeToMarker(type)));
15. __ AllocateStackSpace(kSystemPointerSize);
16. __ pushq(r12);
17. __ pushq(r13);
18. __ pushq(r14);
19. __ pushq(r15);
20. #ifdef _WIN64
21. __ pushq(rdi); // Only callee save in Win64 ABI, argument in AMD64 ABI.
22. __ pushq(rsi); // Only callee save in Win64 ABI, argument in AMD64 ABI.
23. #endif
24. __ pushq(rbx);
25. #ifdef _WIN64 //Here is True, my OS is Win10!!!!!!!!!!!!!!!!!!
26. // On Win64 XMM6-XMM15 are callee-save.
27. __ AllocateStackSpace(EntryFrameConstants::kXMMRegistersBlockSize);
28. __ movdqu(Operand(rsp, EntryFrameConstants::kXMMRegisterSize * 0), xmm6);
29. __ movdqu(Operand(rsp, EntryFrameConstants::kXMMRegisterSize * 1), xmm7);
30. __ movdqu(Operand(rsp, EntryFrameConstants::kXMMRegisterSize * 2), xmm8);
31. __ movdqu(Operand(rsp, EntryFrameConstants::kXMMRegisterSize * 3), xmm9);
32. __ movdqu(Operand(rsp, EntryFrameConstants::kXMMRegisterSize * 4), xmm10);
33. __ movdqu(Operand(rsp, EntryFrameConstants::kXMMRegisterSize * 5), xmm11);
34. __ movdqu(Operand(rsp, EntryFrameConstants::kXMMRegisterSize * 6), xmm12);
35. __ movdqu(Operand(rsp, EntryFrameConstants::kXMMRegisterSize * 7), xmm13);
36. __ movdqu(Operand(rsp, EntryFrameConstants::kXMMRegisterSize * 8), xmm14);
37. __ movdqu(Operand(rsp, EntryFrameConstants::kXMMRegisterSize * 9), xmm15);
38. STATIC_ASSERT(EntryFrameConstants::kCalleeSaveXMMRegisters == 10);
39. STATIC_ASSERT(EntryFrameConstants::kXMMRegistersBlockSize ==
40. EntryFrameConstants::kXMMRegisterSize *
41. EntryFrameConstants::kCalleeSaveXMMRegisters);
42. #endif
43. //............omit...........................
44. }
12~37행에서 그림 6의 어셈블리 코드와 동일함을 알 수 있습니다. 실제로 Generate_JSEntryVariant가 실행 중입니다. Builtin:JSEntry는 std::call 표준에 따라 인수를 구성하는 역할을 합니다. 나중에 Builtin::InterpreterEntryTrampoline 함수는 이 인수를 사용합니다.
Builtin::InterpreterEntryTrampoline은 dispatch_table에서 조회하여 대상 바이트코드를 찾아 호출합니다(그림 7 참조).
디스패치를 좀 더 자세히 살펴보겠습니다(그림 7 참조).
내 경우 대상 바이트 코드는 LdaConstant입니다.
1. IGNITION_HANDLER(LdaConstant, InterpreterAssembler) {
2. TNode<Object> constant = LoadConstantPoolEntryAtOperandIndex(0);
3. SetAccumulator(constant);
4. Dispatch();
5. }
LdaConstant가 종료되면 dispatch_table을 조회하여 그림 8과 같이 다음 바이트코드를 찾습니다.
어셈블리 레벨에서 우리는 V8 인터프리터를 더 잘 이해하는 데 도움이 되는 Ignition 시작 및 바이트코드 실행을 볼 수 있습니다.
자, 이것으로 이번 공유를 마치겠습니다. 다음에 뵙겠습니다 여러분 건강하세요!
문제가 있으면 저에게 연락해 주세요. 위챗: qq9123013 이메일: [email protected]
Reference
이 문제에 관하여(Chrome V8을 이해합시다--18장: 바이트코드 이해 및 디버그 방법), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/v8blink/lets-understand-chrome-v8-chapter-18-understanding-bytecode-and-how-to-debug-it-1nch텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)