Chrome V8을 이해해 봅시다 — 15장: V8을 더 쉽게 디버깅하는 방법
27394 단어 v8chromejavascriptcpp
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.......................
새 함수를 정의하면 이름도 이 열거형에 나타납니다.
#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]
Reference
이 문제에 관하여(Chrome V8을 이해해 봅시다 — 15장: V8을 더 쉽게 디버깅하는 방법), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/v8blink/lets-understand-chrome-v8-chapter-15-how-can-we-debug-v8-more-easily-2lnk텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)