Chrome V8을 이해해 봅시다 — 15장: V8을 더 쉽게 디버깅하는 방법

27394 단어 v8chromejavascriptcpp
원본 소스: https://medium.com/@huidou/lets-understand-chrome-v8-chapter-15-how-can-we-debug-v8-more-easily-85a6ae1d736d
Let’s Understand Chrome V8의 다른 장에 오신 것을 환영합니다.

디버깅은 의심할 여지 없이 V8을 분석하는 가장 효율적인 방법입니다. 불행하게도 대부분의 V8 코드는 디버깅하기 어렵습니다. V8 커널 코드의 대부분은 CodeStubAssembler, 즉 CSA에 의해 구현됩니다. 대충 CSA가 어셈블리 언어라고 생각하시면 됩니다. 거대한 프로젝트 V8에서 디버깅하는 것은 고사하고 어셈블리 디버깅도 허용되지 않습니다.

디버깅을 더 쉽게 할 수 있는 방법이 있습니까? 이 기사에서는 내장 디버깅 경험에 대해 이야기하겠습니다. 구체적으로 바이트코드를 디버그하는 방법에 대해 이야기하고 싶습니다. 바이트코드 실행에 대한 세부 정보를 보시겠습니까? 갑시다! 당연히 이러한 지루한 이론은 건너뛰고 섹션 3으로 이동할 수도 있습니다.

팁: V8은 주로 C++와 어셈블리의 두 가지 언어로 구현됩니다. 특히 바이트코드는 CSA에서 구현됩니다.

1. 런타임



런타임이란 무엇입니까? 바이트코드 디버깅에 도움이 됩니까? 물론 예!

V8의 대부분의 런타임은 C++로 작성되었으며 런타임과 바이트코드는 서로를 호출할 수 있습니다. 바이트코드 실행 중에 런타임을 수동으로 호출하여 C++ 환경으로 돌아갈 수 있으므로 세부 정보를 더 쉽게 볼 수 있습니다. 이것은 런타임이 제공하는 도움말입니다.

런타임에 대해 알아보고 런타임을 정의하는 방법을 알아보겠습니다.

1.  #define FOR_EACH_INTRINSIC_GENERATOR(F, I)    \
2.    I(AsyncFunctionAwaitCaught, 2, 1)           \
3.    I(AsyncFunctionAwaitUncaught, 2, 1)         \
4.    I(AsyncFunctionEnter, 2, 1)                 \
5.    I(AsyncFunctionReject, 3, 1)                \
6.    I(AsyncFunctionResolve, 3, 1)               \
7.    I(AsyncGeneratorAwaitCaught, 2, 1)          \
8.    I(GeneratorGetResumeMode, 1, 1)
9.  #define FOR_EACH_INTRINSIC_MODULE(F, I) \ //omit....................
10.    F(DynamicImportCall, 2, 1)            \
11.    I(GetImportMetaObject, 0, 1)          \
12.    F(GetModuleNamespace, 1, 1)
13.  #define FOR_EACH_INTRINSIC_NUMBERS(F, I) \
14.    F(GetHoleNaNLower, 0, 1)               \
15.    F(GetHoleNaNUpper, 0, 1)               \
16.    I(IsSmi, 1, 1)                         \
17.    F(IsValidSmi, 1, 1)                    \
18.    F(MaxSmi, 0, 1)                        \
19.    F(NumberToString, 1, 1)                \
20.    F(StringParseFloat, 1, 1)              \ //omit....................
21.    F(StringParseInt, 2, 1)                \
22.    F(StringToNumber, 1, 1)
23.  #define FOR_EACH_INTRINSIC_OBJECT(F, I)                         \
24.    F(AddDictionaryProperty, 3, 1)                                \
25.    F(NewObject, 2, 1)  


위의 코드는 Runtime 매크로 템플릿이고 각 줄은 Runtime 함수이며 runtime.h에서 모든 코드를 볼 수 있습니다.

25행의 예에 대해 자세히 설명합니다. 키워드 F는 매크로이고 숫자 2는 매개변수 카운터이며 숫자 1은 반환 값 카운터입니다. 키워드 I 역시 매크로입니다(11행 참조).

NewObject의 코드는 아래와 같습니다.

RUNTIME_FUNCTION(Runtime_NewObject) {
  HandleScope scope(isolate);
  DCHECK_EQ(2, args.length());
  CONVERT_ARG_HANDLE_CHECKED(JSFunction, target, 0);
  CONVERT_ARG_HANDLE_CHECKED(JSReceiver, new_target, 1);
  RETURN_RESULT_OR_FAILURE(
      isolate,
      JSObject::New(target, new_target, Handle<AllocationSite>::null()));
}


위의 코드에서 RUNTIME_FUNCTION은 여전히 ​​매크로입니다. 이를 확장하여 아래에 있는 완전한 NewObject 코드를 얻습니다.

1.  static V8_INLINE Object __RT_impl_Runtime_NewObject(Arguments args,              
2.                                                 Isolate* isolate);           
3.  V8_NOINLINE static Address Stats_Runtime_NewObject(int args_length, Address* args_object, 
4.                                       Isolate* isolate) {                    
5.    RuntimeCallTimerScope timer(isolate, RuntimeCallCounterId::kRuntime_NewObject);      
6.    TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.runtime"),                     
7.                 "V8.Runtime_" "Runtime_NewObject");                                        
8.    Arguments args(args_length, args_object);                                 
9.    return (__RT_impl_Runtime_NewObject(args, isolate)).ptr();                          
10.   }                                                                           
11.   Address Name(int args_length, Address* args_object, Isolate* isolate) {        
12.     DCHECK(isolate->context().is_null() || isolate->context().IsContext());   
13.     CLOBBER_DOUBLE_REGISTERS();                                               
14.     if (V8_UNLIKELY(TracingFlags::is_runtime_stats_enabled())) {              
15.       return Stats_Runtime_NewObject(args_length, args_object, isolate);                 
16.     }                                                                         
17.     Arguments args(args_length, args_object);                                 
18.     return (__RT_impl_Runtime_NewObject(args, isolate)).ptr();                          
19.   }                                                                          
20.  //................................ 
21.   static Object __RT_impl_Runtime_NewObject(Arguments args, Isolate* isolate)
22.  {
23.    HandleScope scope(isolate);
24.    DCHECK_EQ(2, args.length());
25.    CONVERT_ARG_HANDLE_CHECKED(JSFunction, target, 0);
26.    CONVERT_ARG_HANDLE_CHECKED(JSReceiver, new_target, 1);
27.    RETURN_RESULT_OR_FAILURE(
28.        isolate,
29.        JSObject::New(target, new_target, Handle<AllocationSite>::null()));
30.  }


위의 코드는 C++ 환경에서 실행되므로 C++ 코드로 디버깅할 수 있습니다.

2. 런타임 생성



런타임 생성을 두 가지로 요약합니다.
  • 모든 런타임 함수 이름을 포함하는 런타임 클래스입니다.

  • class Runtime : public AllStatic {
     public:
      enum FunctionId : int32_t {
    #define F(name, nargs, ressize) k##name,
    #define I(name, nargs, ressize) kInline##name,
        FOR_EACH_INTRINSIC(F) FOR_EACH_INLINE_INTRINSIC(I)
    #undef I
    #undef F
            kNumFunctions,
      };
    //omit.......................
    


    새 함수를 정의하면 이름도 이 열거형에 나타납니다.
  • runtime_table은 모든 런타임 함수의 주소를 보유하는 포인터 배열입니다. 이 어레이는 v8::i::islolate에서 관리합니다. 런타임을 추가할 위치를 선택해야 합니다. 아래는 내 런타임입니다.

  • #define FOR_EACH_INTRINSIC_TEST(F, I)         \
      F(Abort, 1, 1)                              \
      F(AbortJS, 1, 1)                            \
      F(AbortCSAAssert, 1, 1)                     \
      F(ArraySpeciesProtector, 0, 1)              \
      F(ClearFunctionFeedback, 1, 1)              \
      F(ClearMegamorphicStubCache, 0, 1)          \
      F(CloneWasmModule, 1, 1)                    \
      F(CompleteInobjectSlackTracking, 1, 1)      \
      F(ConstructConsString, 2, 1)                \
      F(ConstructDouble, 2, 1)                    \
      F(ConstructSlicedString, 2, 1)              \
      F(MyRuntime,1,1)                            //Here is my runtime
    


    마지막 줄은 내 함수이고, 이름은 MyRuntime이고, 하나의 매개변수와 하나의 반환 값이 있으며 코드는 아래와 같습니다.

    1.RUNTIME_FUNCTION(Runtime_MyRuntime) {
    1.  SealHandleScope shs(isolate);
    2.  DCHECK_EQ(1, args.length());
    3. //function body,
    4.  return ReadOnlyRoots(isolate).undefined_value();
    6.}
    


    위의 코드에서 라인 1 RUNTIME_FUNCTION은 V8 매크로입니다. 두 번째 줄은 SealHandleScope입니다(이후 문서에서 설명함). 4행에서 코드, 즉 함수 본문을 작성할 수 있습니다.

    다음은 바이트코드 — LdaConstant에서 MyRuntime을 호출하는 방법입니다.

    IGNITION_HANDLER(LdaConstant, InterpreterAssembler) {
      TNode<Object> constant = LoadConstantPoolEntryAtOperandIndex(0);
      TNode<Context> context = GetContext();
      SetAccumulator(constant);
      CallRuntime(Runtime::kMyRuntime, context, constant);//Here is calling MyRuntime
      Dispatch();
    }
    


    LdaConstant가 실행되는 동안 MyRuntime이 호출됩니다. 그림 1은 MyRuntime의 호출 스택입니다.



    3. 부활절 달걀



    실제로 V8에는 Runtime_DebugPrint와 같은 바이트코드와 같은 빌트인을 디버그하는 데 사용할 수 있는 여러 함수가 있습니다.

    1.  RUNTIME_FUNCTION(Runtime_DebugPrint) {
    2.    SealHandleScope shs(isolate);
    3.    DCHECK_EQ(1, args.length());
    4.    MaybeObject maybe_object(*args.address_of_arg_at(0));
    5.    DebugPrintImpl(maybe_object);
    6.    return args[0];
    7.  }
    8.  //...........separation......................
    9.  void CodeStubAssembler::Print(const char* prefix,
    10.                                TNode<MaybeObject> tagged_value) {
    11.    if (prefix != nullptr) {
    12.      std::string formatted(prefix);
    13.      formatted += ": ";
    14.      Handle<String> string = isolate()->factory()->NewStringFromAsciiChecked(
    15.          formatted.c_str(), AllocationType::kOld);
    16.      CallRuntime(Runtime::kGlobalPrint, NoContextConstant(),
    17.                  HeapConstant(string));
    18.    }
    19.    // CallRuntime only accepts Objects, so do an UncheckedCast to object.
    20.    // DebugPrint explicitly checks whether the tagged value is a MaybeObject.
    21.    TNode<Object> arg = UncheckedCast<Object>(tagged_value);
    22.    CallRuntime(Runtime::kDebugPrint, NoContextConstant(), arg);
    23.  }
    


    위의 코드에서 void Print(const char* s)는 code-stub-assembler.h에 정의되어 있습니다. 인쇄는 내가 말한 부활절 달걀입니다. Print를 사용하면 바이트코드 실행에서 정보를 출력할 수 있습니다. Print의 이면에 있는 기술은 위에서 언급한 Runtime과 Bytecode 사이의 상호 호출입니다. 왜냐하면 22번째 라인은 첫 번째 라인에서 Runtime_DebugPrint를 호출하기 때문입니다.


    자, 이것으로 이번 공유를 마치겠습니다. 다음에 뵙겠습니다 여러분 건강하세요!

    문제가 있으면 저에게 연락해 주세요. 위챗: qq9123013 이메일: [email protected]

    좋은 웹페이지 즐겨찾기