Why should I know this?

v8 tracing 지원을 위한 작업 기록 본문

v8

v8 tracing 지원을 위한 작업 기록

die4taoam 2018. 11. 20. 15:58

======== gdb 지원 메소드 컴파일 ===========

v8에는 GDB에서의 디버깅을 지원하기 위한 메소드들이 있다. 

이를 사용하고자 한다면, 컴파일 할 때 옵션을 설정해야 한다.


nodejs 를 예로 들면, 

node$ ./configure --help

...

--gdb                 add gdb support

...


node$ ./configure --gdb 

node$ make -j 6


이렇게 컴파일 하면 gdb 지원 메소드들을 사용할 수 있다. 

관련 내용은 deps/v8/tools/gdbinit 에 있다. 


========== v8 Tracing ==========

  TOTAL TIME : FUNCTION

    2.522  s : (1) node

    2.522  s : (4) v8::Function::Call                                                                                                                            2.586 us :  ├─(8) v8::Utils::OpenHandle

    0.389 us :  │ (8) v8::internal::Object::IsHeapObject

             :  │

    3.615 us :  ├▶(1) v8::internal::tracing::TraceEventHelper::GetTracingController

             :  │

    2.454 us :  ├▶(1) v8::platform::tracing::TracingController::GetCategoryGroupEnabled

             :  │

    0.302 us :  ├─(4) v8::IsExecutionTerminatingCheck

             :  │

    8.502 us :  ├▶(4) v8::EscapableHandleScope::EscapableHandleScope

             :  │

    5.398 us :  ├▶(4) v8::_GLOBAL__N_1::CallDepthScope::CallDepthScope

             :  │

    0.230 us :  ├─(4) v8::internal::RuntimeCallTimerScope::RuntimeCallTimerScope

             :  │

    0.999 us :  ├─(8) v8::internal::TimerEventScope::LogTimerEvent

             :  │

    2.522  s :  ├─(4) v8::internal::Execution::Call

    2.522  s :  │ (4) v8::internal::_GLOBAL__N_1::Invoke

=======================================================


v8 tracing 기록에 따라 v8::internal::Execution::Call을 찾아보면, 


// static

MaybeHandle<Object> Execution::Call(Isolate* isolate, Handle<Object> callable,

                                    Handle<Object> receiver, int argc,

                                    Handle<Object> argv[]) {

  return CallInternal(isolate, callable, receiver, argc, argv,

                      MessageHandling::kReport, Execution::Target::kCallable);

}


이전에는 gdb로 이곳을 breakpoint로 잡아도 별 소득을 거둘 수 없었지만, 

--gdb 옵션으로 컴파일 한 뒤에 다음과 같은 호출로 gdb에서 Object들의 내용을 파악할 수 있다.


====================================================

(gdb) list __gdb_print_v8_object

1842      // uninitialized descriptor.

1843      JITDescriptor __jit_debug_descriptor = { 1, 0, 0, 0 };

1844

1845    #ifdef OBJECT_PRINT

1846      void __gdb_print_v8_object(Object* object) {

1847        StdoutStream os;

1848        object->Print(os);

1849        os << std::flush;

1850      }

1851    #endif

(gdb) call __gdb_print_v8_object(receiver)

Cannot resolve function __gdb_print_v8_object to any overloaded instance

(gdb) p receiver

$1 = {<v8::internal::HandleBase> = {location_ = 0x338cd18}, <No data fields>}

(gdb) p receiver.location_

$2 = (v8::internal::Object **) 0x338cd18

(gdb) p __gdb_print_v8_object(receiver.location_)

Cannot resolve function __gdb_print_v8_object to any overloaded instance

(gdb) p __gdb_print_v8_object(*((v8::internal::Object **)receiver.location_))

0x38c197402239: [JSGlobalProxy]

 - map: 0x3919a4507111 <Map(HOLEY_ELEMENTS)> [FastProperties]

 - prototype: 0x34cebf19d969 <JSObject>

 - elements: 0x26c9a6702d29 <FixedArray[0]> [HOLEY_ELEMENTS]

 - native context: 0x34cebf183dc1 <NativeContext[252]>

 - properties: 0x26c9a6702d29 <FixedArray[0]> {}

$3 = void

(gdb) p __gdb_print_v8_object(*((v8::internal::Object **)callable.location_))

0x34cebf1a1479: [Function] in OldSpace

 - map: 0x3919a45025c1 <Map(HOLEY_ELEMENTS)> [FastProperties]

 - prototype: 0x34cebf184689 <JSFunction (sfi = 0x37ead3904df1)>

 - elements: 0x26c9a6702d29 <FixedArray[0]> [HOLEY_ELEMENTS]

 - function prototype:

 - initial_map:

 - shared_info: 0x34cebf1a0f69 <SharedFunctionInfo>

 - name: 0x26c9a6702851 <String[0]: >                                                                                                               [38/1863]

 - formal_parameter_count: 0

 - kind: NormalFunction

 - context: 0x34cebf183dc1 <NativeContext[252]>

 - code: 0x0f9044da0141 <Code BUILTIN InterpreterEntryTrampoline>

 - interpreted

 - bytecode: 0x34cebf1a1111

 - source code: 'use strict';


// node::NewContext calls this script


(function(global) {

  // https://github.com/nodejs/node/issues/14909

  if (global.Intl) delete global.Intl.v8BreakIterator;


  // https://github.com/nodejs/node/issues/21219

  // Adds Atomics.notify and warns on first usage of Atomics.wake

  // https://github.com/v8/v8/commit/c79206b363 adds Atomics.notify so

  // now we alias Atomics.wake to notify so that we can remove it

  // semver major without worrying about V8.

===================================================


v8의 tools/gdbinit 에는 다음과 같이 정의되어 있다.


==================================================

call _v8_internal_Print_Object(*((v8::internal::Object**)($arg0).val_))   

==================================================


이 함수는 Print를 호출하는데,

==========================================

2432 //

2433 // The following functions are used by our gdb macros.

2434 //

2435 V8_EXPORT_PRIVATE extern void _v8_internal_Print_Object(void* object) {

2436   reinterpret_cast<i::Object*>(object)->Print();

2437 }

==========================================

  53 void Object::Print() {

  54   StdoutStream os;

  55   this->Print(os);

  56   os << std::flush;

  57 }


  59 void Object::Print(std::ostream& os) {  // NOLINT

  60   if (IsSmi()) {

  61     os << "Smi: " << std::hex << "0x" << Smi::ToInt(this);

  62     os << std::dec << " (" << Smi::ToInt(this) << ")\n";

  63   } else {

  64     HeapObject::cast(this)->HeapObjectPrint(os);

  65   }

  66 }

==========================================

내부에서는 HeapObject로 캐스팅하고 그의 HeapObjectPrint를 호출한다.

v8/src/objects.h를 참고하면, v8에서 root가 Object 클래스이고, 

모든 클래스가 이를 상속함을 알 수 있다. 


특히, 객체화 되는 모든 Object들은 HeapObject를 상속하므로 

runtime에 caller와 callee는 모두 HeapObject를 상속하게 될 것. 

===========================================

//

// Most object types in the V8 JavaScript are described in this file.

//

// Inheritance hierarchy:

// - Object

//   - Smi          (immediate small integer)

//   - HeapObject   (superclass for everything allocated in the heap)

//     - JSReceiver  (suitable for property access)

//       - JSObject

================================================


이후 HeapObjectPrint는 instance의 타입에 따라 출력을 결정하는데

==============================================

 82 void HeapObject::HeapObjectPrint(std::ostream& os) {  // NOLINT

  83   InstanceType instance_type = map()->instance_type();

  91   switch (instance_type) {

 222     case JS_FUNCTION_TYPE:

 223       JSFunction::cast(this)->JSFunctionPrint(os);

 224       break;

============================


출력되는 값을 토대로 추적해보면 instance_type은 JSFunction이라는 것을 알 수 있다. 


=======================================================

1326 void JSFunction::JSFunctionPrint(std::ostream& os) {  // NOLINT                                                                                         1327   Isolate* isolate = GetIsolate();

 1328   JSObjectPrintHeader(os, this, "Function");         

=======================================================


여기까지 추적됐으면 내용을 찍어볼 수 있다.

============================================================

(gdb) br deps/v8/src/execution.cc:202

(gdb) command

p ((v8::internal::JSFunction *)(*((v8::internal::Object**)(callable.location_))))->PrintName(stdout)

c

end

(gdb) r test.js

=============================================================



이런 결과를 얻을 수 있다.

=============================================================

Thread 1 "node_g" hit Breakpoint 17, v8::internal::Execution::Call (isolate=isolate@entry=0x33091a0, callable=callable@entry=...,                 [1482/1980]

    receiver=receiver@entry=..., argc=argc@entry=5, argv=argv@entry=0x7fffffffd5a0) at ../deps/v8/src/execution.cc:202

202       return CallInternal(isolate, callable, receiver, argc, argv,

bootstrapInternalLoaders$666 = void


Thread 1 "node_g" hit Breakpoint 17, v8::internal::Execution::Call (isolate=isolate@entry=0x33091a0, callable=callable@entry=...,

    receiver=receiver@entry=..., argc=argc@entry=4, argv=argv@entry=0x7fffffffd560) at ../deps/v8/src/execution.cc:202

202       return CallInternal(isolate, callable, receiver, argc, argv,

bootstrapNodeJSCore$667 = void


Thread 1 "node_g" hit Breakpoint 17, v8::internal::Execution::Call (isolate=isolate@entry=0x33091a0, callable=callable@entry=..., receiver=...,

    argc=argc@entry=0, argv=argv@entry=0x0) at ../deps/v8/src/execution.cc:202

202       return CallInternal(isolate, callable, receiver, argc, argv,

$668 = void


=============================================================


그리 만족스럽지 않은 결과인데, 

node --log-api의 기록과 비교해보자.


===========================================================

$ node --log-api test.js


api,call,global

api,accessor-getter,Array,length

api,v8::String::NewFromOneByte

api,v8::Object::Get

api,v8::Object::ToInteger

api,v8::String::NewFromUtf8

api,v8::Object::Get

api,v8::Function::Call

api,v8::String::NewFromOneByte

api,v8::Object::Set

api,v8::Object::Get

api,v8::Value::Int32Value

api,v8::String::NewFromOneByte

api,v8::String::NewFromUtf8

api,v8::Object::Get

api,v8::Function::Call

api,v8::Object::Get

api,v8::Value::Int32Value

api,v8::Object::Has

api,v8::Object::Has

===========================================================


node --log-api 옵션은 v8에 내장된 API를 호출할 때 호출된 함수의 이름을 기록한다. 

v8::Function::Call과 같은 함수는 v8에 내장된 함수들이며, 

이는 v8이 interpreting하기 위해 필요한 함수들이란 의미이다. 


즉, 이런 API 함수의 경우에는 interpreting을 할 때 v8::internal::Call을 통할 이유가 딱히 없다는것.

동시에 심지어 JSFunction이나 HeapObject일 필요도 없다. 

=======================================

$ cat test.js


test = Object();

name = test.toString();


console.log(name);

=======================================

위의 예제에서, test라는 Object의 toString 함수 호출이나 

console이라는 객체의 log 함수 호출 등은 v8 혹은 node의 내장 객체라는 뜻.



때문에 위의 GDB command 에

============================================================

(gdb) br deps/v8/src/execution.cc:202

(gdb) command

p ((v8::internal::JSFunction *)(*((v8::internal::Object**)(callable.location_))))->PrintName(stdout)

p ((v8::internal::JSFunction *)(*((v8::internal::Object**)(receiver.location_))))->PrintName(stdout)

c

end

(gdb) r test.js

=============================================================


receiver를 추가하면 

=============================================================

Thread 1 "node_g" hit Breakpoint 1, v8::internal::Execution::Call (isolate=isolate@entry=0x33091a0, callable=callable@entry=...,

    receiver=receiver@entry=..., argc=argc@entry=5, argv=argv@entry=0x7fffffffd5a0) at ../deps/v8/src/execution.cc:202

202       return CallInternal(isolate, callable, receiver, argc, argv,

bootstrapInternalLoaders$7 = void


Thread 1 "node_g" received signal SIGSEGV, Segmentation fault.

v8::internal::SharedFunctionInfo::DebugName (this=0x0) at ../deps/v8/src/objects.cc:13994

13994     String* function_name = Name();

================================================================


SIGSEGV를 맞게 된다.

여기서 SIGSEGV를 맞는 이유는 this=0x0이라서고, this가 0x0인 이유는, 

SharedFunctionInfo를 갖지 않기 때문이다.


PrintName() 메소드는 SharedFunctionInfo의 메소드를 호출하게 된다.

========================================

#2  0x0000000001e93ec3 in v8::base::(anonymous namespace)::DefaultDcheckHandler (file=<optimized out>, line=<optimized out>, message=<optimized out>)

    at ../deps/v8/src/base/logging.cc:56

#3  0x0000000000fcc00b in v8::internal::Object::IsHeapObject (this=<optimized out>) at /usr/local/include/c++/8.2.0/bits/basic_string.h:2281

#4  0x00000000016862ad in v8::internal::Object::IsScopeInfo (this=0x82003ff) at ../deps/v8/src/objects-inl.h:163

#5  v8::internal::SharedFunctionInfo::HasSharedName (this=0x1914f3382201) at ../deps/v8/src/objects/shared-function-info-inl.h:105

#6  v8::internal::SharedFunctionInfo::Name (this=0x1914f3382201) at ../deps/v8/src/objects/shared-function-info-inl.h:112

#7  v8::internal::SharedFunctionInfo::DebugName (this=0x1914f3382201) at ../deps/v8/src/objects.cc:13994

#8  0x0000000001686461 in v8::internal::JSFunction::PrintName (this=<optimized out>, out=0x7ffff6e61760 <_IO_2_1_stdout_>)

    at ../deps/v8/src/objects-inl.h:2201

#9  <function called from gdb>

=========================================


그래서 아마도 v8 내장 객체들을 초기화 하는 시점에는 

(여기서 내장 객체라고 하는 것은 Java로 치면 java.lang 클래스들처럼, vm을 구성하는데 필요한 최소한의 클래스를 뜻한다.)

receiver는 대상이 없거나 JSFunction이 아닌 어떤 것이기 때문이다. 


이렇게 확인해 볼 수 있다.

====================================================

(gdb) p __gdb_print_v8_object(*((v8::internal::Object **)receiver.location_))

0xfe0d1502889: [JS_API_OBJECT_TYPE] in OldSpace

 - map: 0x2551e3ecd751 <Map(HOLEY_ELEMENTS)> [FastProperties]

 - prototype: 0x0fe0d155dcd9 <Object map = 0x2551e3ea2691>

 - elements: 0x3ecfd5282d29 <FixedArray[0]> [HOLEY_ELEMENTS]

 - properties: 0x2bce5f6cbe39 <PropertyArray[23]> {



#1  0x0000000001e93ea8 in V8_Fatal (file=0x2250579 "../deps/v8/src/objects.h", line=1516, format=format@entry=0x241d2b2 "Debug check failed: %s.")

    at ../deps/v8/src/base/logging.cc:171

#2  0x0000000001e93ec3 in v8::base::(anonymous namespace)::DefaultDcheckHandler (file=<optimized out>, line=<optimized out>, message=<optimized out>)

    at ../deps/v8/src/base/logging.cc:56

#3  0x0000000000fcc00b in v8::internal::Object::IsHeapObject (this=<optimized out>) at /usr/local/include/c++/8.2.0/bits/basic_string.h:2281

#4  0x00000000016862ad in v8::internal::Object::IsScopeInfo (this=0x82003ff) at ../deps/v8/src/objects-inl.h:163

#5  v8::internal::SharedFunctionInfo::HasSharedName (this=0x1914f3382201) at ../deps/v8/src/objects/shared-function-info-inl.h:105

#6  v8::internal::SharedFunctionInfo::Name (this=0x1914f3382201) at ../deps/v8/src/objects/shared-function-info-inl.h:112

#7  v8::internal::SharedFunctionInfo::DebugName (this=0x1914f3382201) at ../deps/v8/src/objects.cc:13994

#8  0x0000000001686461 in v8::internal::JSFunction::PrintName (this=<optimized out>, out=0x7ffff6e61760 <_IO_2_1_stdout_>)

    at ../deps/v8/src/objects-inl.h:2201

#9  <function called from gdb>

====================================================


이처럼 JS_API_OBJECT_TYPE 같은 것이 올 수 있다.


=================================================================

(gdb) list

13989     return CoverageInfo::cast(GetDebugInfo()->coverage_info());

13990   }

13991

13992   String* SharedFunctionInfo::DebugName() {

13993     DisallowHeapAllocation no_gc;

13994     String* function_name = Name();

13995     if (function_name->length() > 0) return function_name;

13996     return inferred_name();

13997   }

13998

(gdb) frame 1

#1  0x0000000001686461 in v8::internal::JSFunction::PrintName (this=<optimized out>, out=0x7ffff6e61760 <_IO_2_1_stdout_>)

    at ../deps/v8/src/objects-inl.h:2201

2201    ACCESSORS(JSFunction, shared, SharedFunctionInfo, kSharedFunctionInfoOffset)

(gdb) list

2196    ACCESSORS(JSBoundFunction, bound_target_function, JSReceiver,

2197              kBoundTargetFunctionOffset)

2198    ACCESSORS(JSBoundFunction, bound_this, Object, kBoundThisOffset)

2199    ACCESSORS(JSBoundFunction, bound_arguments, FixedArray, kBoundArgumentsOffset)

2200

2201    ACCESSORS(JSFunction, shared, SharedFunctionInfo, kSharedFunctionInfoOffset)


(gdb) bt

#0  v8::internal::SharedFunctionInfo::DebugName (this=0x0) at ../deps/v8/src/objects.cc:13994

#1  0x0000000001686461 in v8::internal::JSFunction::PrintName (this=<optimized out>, out=0x7ffff6e61760 <_IO_2_1_stdout_>)

    at ../deps/v8/src/objects-inl.h:2201

#2  <function called from gdb>

=================================================================

(아... 오랜만에 사람 빡치게 만드는 추적하기 타입의 소스를 만남. )


재미있는 것은, 상위 클래스에서 하위 클래스로 캐스팅 했을 때, 

this 가 0으로 넘어가는 것, 존재하지 않는 공간을 참조하여 this 포인터로 넘겨주는데,

랜덤한 값이 나와야 하지만 최초 할당 시 오버되는 공간까지 0으로 초기화하는 것 같다.



어쨌든 정리하자면, v8에서 Call이 interpreting 되는 과정에서 callable = 호출자는 무조건 JSFunction이겠지만, 

피호출자인 receiver는 항상 JSFunction이 아니다. 



interpreter의 기본적인 구조에 대해서 조금 부연설명을 덧붙이자면, 

Java에서는 Java.lang 이하 클래스처럼, Javascript도 내장 class들이 있다. console.log('xxx')가 대표적.

Java는 내장클래스를 C/C++코드로 아예 박아넣어놨으며, 이는 Javascript도 마찬가지일 것이다. 

왜냐면 Java에서 String이란 클래스 없이는 Java코드를 읽어들이는 것 자체가 안되기 때문,

Javascript도 마찬가지 interpreter가 javascript를 실행할 수 있는 최소한의 클래스들을 내장하고 있다. 

아마 JS_API_OBJECT_TYPE이 이런 클래스들이 아닌가 싶기도 하다....


어쨌든 함수 호출을 트레이싱하려고 하는 입장에서 신경 쓸 것은 아니지만, 

조건 검사를 해야하니까 더 까다로워진건 사실이다....




현재 과제는 : 

Tracing 중에 v8::internal::Execution::Call에 도달했을때, 

-. callable과 receiver의 이름을 알아내 함께 기록하는 것이다. 

이를 위해서는 다음의 사항이 요구된다.

1. callable과 receiver의 Name을 읽어들이는 방법.

2. 1에 앞서 name을 읽을 수 있는지 확인하기 위해 callable과 receiver의 instance_type을 확인



2의 진행 과정. 

HeapObject를 출력해주기 위해 먼저 instance_type을 확인한다. 

=================================================

void HeapObject::HeapObjectPrint(std::ostream& os) {

  InstanceType instance_type = map()->instance_type();

  switch (instance_type) {

...

===================================================


map()->instance_type(); 을 통해 얻은 결과 값이 JS_FUNCTION_TYPE인지 확인해야 한다.


==================================================

(gdb) list v8::internal::HeapObject

1630    };

1631

1632

1633    // HeapObject is the superclass for all classes describing heap allocated

1634    // objects.

1635    class HeapObject: public Object {

1636     public:

1637      // [map]: Contains a map which contains the object's reflective

1638      // information.

1639      inline Map* map() const;


deps/v8/src/objects-inl.h

 885 Map* HeapObject::map() const {

 886   return map_word().ToMap();

 887 }


 948 MapWord HeapObject::map_word() const {

 949   return MapWord(

 950       reinterpret_cast<uintptr_t>(RELAXED_READ_FIELD(this, kMapOffset)));

 951 }

==================================================


비교적 map()은 쉽게 확인할 수 있다.


==================================================

179 #define FIELD_ADDR(p, offset) \

180   (reinterpret_cast<Address>(p) + offset - kHeapObjectTag)


192 #define RELAXED_READ_FIELD(p, offset)           \

193   reinterpret_cast<Object*>(base::Relaxed_Load( \

194       reinterpret_cast<const base::AtomicWord*>(FIELD_ADDR(p, offset))))

=====================================================


(gdb) p ((v8::internal::Map *)*(callable.location_ - 0x1))->kHeaderSize

$72 = 8

(gdb) p ((v8::internal::Map *)*(callable.location_ - 0x1))->kMaxFastProperties

$73 = 128



여기서 실수가 있었다.

(gdb) p ((v8::internal::Map *)*(callable.location_ - 0x1))->kMaxFastProperties  (X)

(gdb) p ((v8::internal::Map *)*(*(callable.location_)-0x1))->instance_type()    (O)

이렇게 제대로 참조하면, 

(gdb) p ((v8::internal::Map *)*(*(callable.location_)-0x1))->instance_type()

$85 = v8::internal::JS_FUNCTION_TYPE


응답이 된다! 이것으로 instance_type을 체크하기도 쉬워졌다.



먼저 instance_size를 한번 확인해보자.


========================================================

207 int Map::instance_size_in_words() const {

208   return RELAXED_READ_BYTE_FIELD(this, kInstanceSizeInWordsOffset);

209 }

210

211 void Map::set_instance_size_in_words(int value) {

212   RELAXED_WRITE_BYTE_FIELD(this, kInstanceSizeInWordsOffset,

213                            static_cast<byte>(value));

214 }

215

216 int Map::instance_size() const {

217   return instance_size_in_words() << kPointerSizeLog2;

218 }


(gdb) ptype v8::internal::Map::kInstanceSizeInWordsOffset

type = enum : unsigned int {v8::internal::Map::MAP_FIELDS_StartOffset = 7, v8::internal::Map::kInstanceSizeInWordsOffset,

    v8::internal::Map::kInstanceSizeInWordsOffsetEnd = 8, v8::internal::Map::kInObjectPropertiesStartOrConstructorFunctionIndexOffset,

    v8::internal::Map::kInObjectPropertiesStartOrConstructorFunctionIndexOffsetEnd = 9, v8::internal::Map::kUsedOrUnusedInstanceSizeInWordsOffset,

    v8::internal::Map::kUsedOrUnusedInstanceSizeInWordsOffsetEnd = 10, v8::internal::Map::kVisitorIdOffset, v8::internal::Map::kVisitorIdOffsetEnd = 11,

    v8::internal::Map::kInstanceTypeOffset, v8::internal::Map::kInstanceTypeOffsetEnd, v8::internal::Map::kBitFieldOffset,

    v8::internal::Map::kBitFieldOffsetEnd = 14, v8::internal::Map::kBitField2Offset, v8::internal::Map::kBitField2OffsetEnd = 15,

    v8::internal::Map::kBitField3Offset, v8::internal::Map::kBitField3OffsetEnd = 19, v8::internal::Map::k64BitArchPaddingOffset,

    v8::internal::Map::k64BitArchPaddingOffsetEnd = 23, v8::internal::Map::kPointerFieldsBeginOffset, v8::internal::Map::kPointerFieldsBeginOffsetEnd = 23,

    v8::internal::Map::kPrototypeOffset, v8::internal::Map::kPrototypeOffsetEnd = 31, v8::internal::Map::kConstructorOrBackPointerOffset,

    v8::internal::Map::kConstructorOrBackPointerOffsetEnd = 39, v8::internal::Map::kTransitionsOrPrototypeInfoOffset,

    v8::internal::Map::kTransitionsOrPrototypeInfoOffsetEnd = 47, v8::internal::Map::kDescriptorsOffset, v8::internal::Map::kDescriptorsOffsetEnd = 55,

    v8::internal::Map::kLayoutDescriptorOffset, v8::internal::Map::kLayoutDescriptorOffsetEnd = 63, v8::internal::Map::kDependentCodeOffset,

    v8::internal::Map::kDependentCodeOffsetEnd = 71, v8::internal::Map::kPrototypeValidityCellOffset,

    v8::internal::Map::kPrototypeValidityCellOffsetEnd = 79, v8::internal::Map::kPointerFieldsEndOffset,

    v8::internal::Map::kPointerFieldsEndOffsetEnd = 79, v8::internal::Map::kSize, v8::internal::Map::kSizeEnd = 79}


(gdb) p kPointerSizeLog2

$90 = 3


(gdb) x/bx ((v8::internal::Map *)*(*(callable.location_)-0x1))+0x7

0x1fa40f2025c8: 0x08

(gdb) p 0x08 <<3

$88 = 64

(gdb) call ((v8::internal::Map *)*(*(callable.location_)-0x1))->instance_size()

$89 = 64

=============================================================

어떤가? 제대로 읽어올 수 있는 듯 하다. 


(gdb) call ((v8::internal::Map *)*(*(callable.location_)-0x1))->instance_type()

$91 = v8::internal::JS_FUNCTION_TYPE


(gdb) x/2bx (short *)(((v8::internal::Map *)*(*(callable.location_)-0x1))+12)

0x1fa40f2025cd: 0x04    0xc2


여기서 또 삽질... 망할 리틀엔디안........


(gdb) x/2bx (((v8::internal::Map *)*(*(callable.location_)-0x1))+11)

0x1fa40f2025cc: 0x47    0x04


(gdb) p (InstanceType)0x400

$103 = v8::internal::JS_PROXY_TYPE

(gdb) p (InstanceType)0x4c2

$104 = 1218

(gdb) p (InstanceType)0x447

$105 = v8::internal::JS_FUNCTION_TYPE


좋아 제대로 읽었다.


(gdb) x/2bx (((v8::internal::Map *)*(*(receiver.location_)-0x1))+11)

0x1fa40f20711c: 0x02    0x04

(gdb) p (InstanceType)0x402

$109 = v8::internal::JS_GLOBAL_PROXY_TYPE


receiver도 제대로 읽을 수 있다.



# 결론

v8::internal::Execution::Call의 인자로 넘어오는 callable과 receiver의 타입을 다음과 같은 방법으로 확인 할 수 있다.

(gdb) x/gx &callable

0x7fffffffd470: 0x00000000048f8120


(gdb) x/2bx ((uintptr_t)(*(*(callable.location_)-0x1)))+11

0x318d33e025cc: 0x47    0x04


<< (v8::internal::Object *) >>

(gdb) p/x (uintptr_t)(*((long *)(*0x7fffffffd470)))

$189 = 0x94b1ce21479


<< v8::internal::Map * >>

(gdb) p/x *(uintptr_t *)((uintptr_t)(*((long *)(*0x7fffffffd470)))-1)

$198 = 0x318d33e025c1


<< (v8::internal::Map *)->instance_type() >>

(gdb) x/2bx *(uintptr_t *)((uintptr_t)(*((long *)(*0x7fffffffd470)))-1)+11

0x318d33e025cc: 0x47    0x04



이제 남은 과제는 callable, receiver가 JS_FUNCTION 타입일 경우 Name을 읽어들이는 것이다.

gdb를 사용한다면 다음과 같은 방법으로 참조할 수 있다.


==========================================

#if defined(DEBUG) || defined(OBJECT_PRINT)

  char* ToAsciiArray();

#endif

==========================================

(gdb) call ((v8::internal::String *)((((v8::internal::JSFunction *)(*(callable.location_)))->shared())->Name()))->ToAsciiArray()

$239 = 0x48f3850 "bootstrapInternalLoaders"

===========================================

ACCESSORS(JSFunction, shared, SharedFunctionInfo, kSharedFunctionInfoOffset)

#define ACCESSORS_CHECKED2(holder, name, type, offset, get_condition, \

                           set_condition)                             \

  type* holder::name() const {                                        \

    type* value = type::cast(READ_FIELD(this, offset));               \

    DCHECK(get_condition);                                            \

    return value;                                                     \

  }                                                                   \

  void holder::set_##name(type* value, WriteBarrierMode mode) {       \

    DCHECK(set_condition);                                            \

    WRITE_FIELD(this, offset, value);                                 \

    CONDITIONAL_WRITE_BARRIER(this, offset, value, mode);             \

  }

#define ACCESSORS_CHECKED(holder, name, type, offset, condition) \

  ACCESSORS_CHECKED2(holder, name, type, offset, condition, condition)

                                                                                                                                                             

#define ACCESSORS(holder, name, type, offset) \

  ACCESSORS_CHECKED(holder, name, type, offset, true)


===========================================


SharedFunctionInfo::Name()

111     String* SharedFunctionInfo::Name() const {

112       if (!HasSharedName()) return GetReadOnlyRoots().empty_string();

113       Object* value = name_or_scope_info();

114       if (value->IsScopeInfo()) {

115         if (ScopeInfo::cast(value)->HasFunctionName()) {

116           return String::cast(ScopeInfo::cast(value)->FunctionName());

117         }

118         return GetReadOnlyRoots().empty_string();

119       }

120       return String::cast(value);

121     }



84      ACCESSORS(SharedFunctionInfo, name_or_scope_info, Object,

85                kNameOrScopeInfoOffset)



Dump of assembler code for function v8::internal::SharedFunctionInfo::name_or_scope_info() const:

   0x0000000001cbabb6 <+0>:     push   %rbp

   0x0000000001cbabb7 <+1>:     mov    %rsp,%rbp

   0x0000000001cbabba <+4>:     sub    $0x20,%rsp

   0x0000000001cbabbe <+8>:     mov    %rdi,-0x18(%rbp)

   0x0000000001cbabc2 <+12>:    mov    -0x18(%rbp),%rax

   0x0000000001cbabc6 <+16>:    add    $0xf,%rax

   0x0000000001cbabca <+20>:    mov    (%rax),%rax

   0x0000000001cbabcd <+23>:    mov    %rax,%rdi

   0x0000000001cbabd0 <+26>:    callq  0x1cafcca <v8::internal::Object::cast(v8::internal::Object*)>

   0x0000000001cbabd5 <+31>:    mov    %rax,-0x8(%rbp)

   0x0000000001cbabd9 <+35>:    mov    -0x8(%rbp),%rax

   0x0000000001cbabdd <+39>:    leaveq

   0x0000000001cbabde <+40>:    retq


v8::internal::Object의 시작 1byte가 특정한 체크 값이므로 계속 1씩 빼주고 있다.

*((v8::internal::SharedFunctionInfo *)v +0x15) = name or scope_info 이다.



(gdb) disas HasSharedName

Dump of assembler code for function v8::internal::SharedFunctionInfo::HasSharedName() const:

   0x0000000001cbac0a <+0>:     push   %rbp

   0x0000000001cbac0b <+1>:     mov    %rsp,%rbp

   0x0000000001cbac0e <+4>:     sub    $0x20,%rsp

   0x0000000001cbac12 <+8>:     mov    %rdi,-0x18(%rbp)

   0x0000000001cbac16 <+12>:    mov    -0x18(%rbp),%rax

   0x0000000001cbac1a <+16>:    mov    %rax,%rdi

   0x0000000001cbac1d <+19>:    callq  0x1cbabb6 <v8::internal::SharedFunctionInfo::name_or_scope_info() const>

   0x0000000001cbac22 <+24>:    mov    %rax,-0x8(%rbp)

   0x0000000001cbac26 <+28>:    mov    -0x8(%rbp),%rax

   0x0000000001cbac2a <+32>:    mov    %rax,%rdi

   0x0000000001cbac2d <+35>:    callq  0x1cbc568 <v8::internal::Object::IsScopeInfo() const>

   0x0000000001cbac32 <+40>:    test   %al,%al

   0x0000000001cbac34 <+42>:    je     0x1cbac4c <v8::internal::SharedFunctionInfo::HasSharedName() const+66>

   0x0000000001cbac36 <+44>:    mov    -0x8(%rbp),%rax

   0x0000000001cbac3a <+48>:    mov    %rax,%rdi

   0x0000000001cbac3d <+51>:    callq  0x1cbc7c2 <v8::internal::ScopeInfo::cast(v8::internal::Object*)>

   0x0000000001cbac42 <+56>:    mov    %rax,%rdi

   0x0000000001cbac45 <+59>:    callq  0x262630e <v8::internal::ScopeInfo::HasSharedFunctionName() const>

   0x0000000001cbac4a <+64>:    jmp    0x1cbac54 <v8::internal::SharedFunctionInfo::HasSharedName() const+74>

   0x0000000001cbac4c <+66>:    cmpq   $0x0,-0x8(%rbp)

   0x0000000001cbac51 <+71>:    setne  %al

   0x0000000001cbac54 <+74>:    leaveq

   0x0000000001cbac55 <+75>:    retq


골때린다... HasSharedName에 따르면 

*((v8::internal::SharedFunctionInfo *)v +0x15) = name 일수도 ScopeInfo *일 수도 있기 때문에, 

그걸 IsScopeInfo로 체크를 해야 한다. 대체 뭐지? 잘 이해가 되지는 않는 방식이다. 

왜냐면 코드로는 이렇게 되어 있기 때문ㅇ...


111 String* SharedFunctionInfo::Name() const {

112   if (!HasSharedName()) return GetReadOnlyRoots().empty_string();

113   Object* value = name_or_scope_info();

114   if (value->IsScopeInfo()) {

115     if (ScopeInfo::cast(value)->HasFunctionName()) {

116       return String::cast(ScopeInfo::cast(value)->FunctionName());

117     }

118     return GetReadOnlyRoots().empty_string();

119   }

120   return String::cast(value);

121 }


로직에 따르면 이 부분은 중복 호출이 들어가 있다. 


112   if (!HasSharedName()) return GetReadOnlyRoots().empty_string();

이 라인은 불필요하다. 리폿한다!

https://groups.google.com/forum/#!topic/v8-dev/sG5CPMKsFeQ


어쨌든 해야 하는 작업이 3배 이상 늘었다.

1. *((v8::internal::SharedFunctionInfo *)v +0x15) 이 name일지 ScopeInfo일지 모르기 때문에 참조한 후

2. IsScopeInfo()를 때려봐야 한다. 

3-1. ScopeInfo이면 HasFunctionName()을 호출하고 True면 FunctionName()을 호출한ㄷ....

3-2. ScopeInfo가 아니면 String으로 타입 변환을 한다.

4. 3에서 얻은 String class의 문자열이 저장된 주소를 얻어낸다. 



#define FOR_EACH_SCOPE_INFO_NUMERIC_FIELD(V) \

  V(Flags)                                   \

  V(ParameterCount)                          \

  V(ContextLocalCount)



근데 FunctionDebugName을 보니까 이런 방법도 있는 것 같다.


585     String* ScopeInfo::FunctionDebugName() const {

586       Object* name = FunctionName();

587       if (name->IsString() && String::cast(name)->length() > 0) {

588         return String::cast(name);

589       }

590       if (HasInferredFunctionName()) {

591         name = InferredFunctionName();

592         if (name->IsString()) return String::cast(name);

593       }

594       return GetReadOnlyRoots().empty_string();

595     }


575     Object* ScopeInfo::FunctionName() const {

576       DCHECK(HasFunctionName());

577       return get(FunctionNameInfoIndex());

578     }


790     int ScopeInfo::ReceiverInfoIndex() const {

791       return ContextLocalInfosIndex() + ContextLocalCount();

792     }

793

794     int ScopeInfo::FunctionNameInfoIndex() const {

795       return ReceiverInfoIndex() + (HasAllocatedReceiver() ? 1 : 0);

796     }

797


781     int ScopeInfo::ContextLocalNamesIndex() const {

782       DCHECK_LT(0, length());

783       return kVariablePartIndex;

784     }

785



# 부분 해결 

3-2. ScopeInfo가 아니면 String으로 타입 변환을 한다. 


(gdb) call ((v8::internal::Object *)(*(long *)(*((long *)((uintptr_t)(*((long *)(*0x7fffffff9320)))-1) + 0x3)+0xf)))->IsScopeInfo()

$304 = false

(gdb) call ((v8::internal::String *)(*(long *)(*((long *)((uintptr_t)(*((long *)(*0x7fffffff9320)))-1) + 0x3)+0xf)))->ToAsciiArray()

$305 = 0x48f7520 "set"


(gdb) call _v8_internal_Print_Object(*((v8::internal::Object**)receiver.location_))

0x17d958e41291: [JSMap]

 - map: 0x116fd7203ec1 <Map(HOLEY_ELEMENTS)> [FastProperties]

 - prototype: 0x1f4a07290bc1 <Object map = 0x116fd7203f11>

 - elements: 0x2254f3002d29 <FixedArray[0]> [HOLEY_ELEMENTS] - table: 0x17d958e412b1 <OrderedHashMap[17]>

 - properties: 0x2254f3002d29 <FixedArray[0]> {}


(gdb) call _v8_internal_Print_Object(*((v8::internal::Object**)callable.location_))

0x1f4a07290dc1: [Function] in OldSpace

 - map: 0x116fd72024d1 <Map(HOLEY_ELEMENTS)> [FastProperties]

 - prototype: 0x1f4a07284689 <JSFunction (sfi = 0x2e4c6d784df1)>

 - elements: 0x2254f3002d29 <FixedArray[0]> [HOLEY_ELEMENTS]

 - function prototype: <no-prototype-slot>

 - shared_info: 0x2e4c6d796141 <SharedFunctionInfo set>

 - name: 0x2254f3006da9 <String[3]: set>

 - builtin: MapPrototypeSet

 - formal_parameter_count: 2

 - kind: NormalFunction

 - context: 0x1f4a07283dc1 <NativeContext[252]>

 - code: 0x36df3d4cb681 <Code BUILTIN MapPrototypeSet>

 - properties: 0x2254f3002d29 <FixedArray[0]> {

    #length: 0x2e4c6d798079 <AccessorInfo> (const accessor descriptor)

    #name: 0x2e4c6d798009 <AccessorInfo> (const accessor descriptor)

 }



이제 3-1을 한번 보자.


(gdb) p ((v8::internal::Object *)(*(long *)(*((long *)((uintptr_t)(*((long *)(*&callable)))-1) + 0x3)+0xf)))->IsScopeInfo()

$318 = true


(gdb) p ((v8::internal::ScopeInfo *)(*(long *)(*((long *)((uintptr_t)(*((long *)(*&callable)))-1) + 0x3)+0xf)))->HasSharedFunctionName()

$320 = true


(gdb) p ((v8::internal::String *)((v8::internal::ScopeInfo *)(*(long *)(*((long *)((uintptr_t)(*((long *)(*&callable)))-1) + 0x3)+0xf)))->FunctionName())->ToAsciiArray()

$324 = 0x48f70c0 "bootstrapNodeJSCore"




지금까지 모은 정보를 검증해보자.


$ cat poc.cmd


file node_g


br v8::internal::Execution::Call

commands

  set $target = *&callable

  set $instanceType = *(uint16_t *)(*(uintptr_t *)((uintptr_t)(*((long *)$target))-1)+11)

    if $instanceType == 0x447

    set $isscope = ((v8::internal::Object *)(*(long *)(*((long *)((uintptr_t)(*((long *)$target))-1) + 0x3)+0xf)))->IsScopeInfo()

    if $isscope == 1

      set $hasFn = ((v8::internal::ScopeInfo *)(*(long *)(*((long *)((uintptr_t)(*((long *)$target))-1) + 0x3)+0xf)))->HasSharedFunctionName()

      if $hasFn == 1

        p ((v8::internal::String *)((v8::internal::ScopeInfo *)(*(long *)(*((long *)((uintptr_t)(*((long *)$target))-1) + 0x3)+0xf)))->FunctionName())->ToAsciiArray()

      end

    else

      p ((v8::internal::String *)(*(long *)(*((long *)((uintptr_t)(*((long *)$target))-1) + 0x3)+0xf)))->ToAsciiArray()

    end

  end

  set $target = *&receiver

  set $instanceType = *(uint16_t *)(*(uintptr_t *)((uintptr_t)(*((long *)$target))-1)+11)

    if $instanceType == 0x447

    set $isscope = ((v8::internal::Object *)(*(long *)(*((long *)((uintptr_t)(*((long *)$target))-1) + 0x3)+0xf)))->IsScopeInfo()

    if $isscope == 1

      set $hasFn = ((v8::internal::ScopeInfo *)(*(long *)(*((long *)((uintptr_t)(*((long *)$target))-1) + 0x3)+0xf)))->HasSharedFunctionName()

      if $hasFn == 1

        p ((v8::internal::String *)((v8::internal::ScopeInfo *)(*(long *)(*((long *)((uintptr_t)(*((long *)$target))-1) + 0x3)+0xf)))->FunctionName())->ToAsciiArray()

      end

    else

      p ((v8::internal::String *)(*(long *)(*((long *)((uintptr_t)(*((long *)$target))-1) + 0x3)+0xf)))->ToAsciiArray()

    end

  end


  continue

end


r test3.js



$ cat test3.js

function c() {

    console.log("Hello C");

}

function b() {

    c();

    console.log("Hello B");

}

function a() {

    b();

    console.log("Hello A");

}


a();

b();

c();



$ gdb -x poc.cmd 


..

(gdb) bt

#0  0x0000000002323f0f in v8::internal::Execution::Call (isolate=0x7fffffffc1d8, callable=..., receiver=..., argc=32512,

    argv=0x1cb07a5 <v8::Utils::OpenHandle(v8::Data const*, bool)+165>) at ../deps/v8/src/execution.cc:201

#1  0x0000000001ceda20 in v8::Function::Call (this=0x7fffffffc940, context=..., recv=..., argc=1, argv=0x7fffffffc280) at ../deps/v8/src/api.cc:5053


(gdb) call _v8_internal_Print_Object(*((v8::internal::Object**)callable.location_))

0xb4adab4c6e9: [JSBoundFunction]

 - map: 0x0fc021805451 <Map(HOLEY_ELEMENTS)> [FastProperties]

 - prototype: 0x2de499a04689 <JSFunction (sfi = 0x16d21d284df1)>

 - elements: 0x37c157d82d29 <FixedArray[0]> [HOLEY_ELEMENTS]

 - bound_target_function: 0x31127917cb59 <JSFunction log (sfi = 0x31127917a929)>



JSBoundFunction이 뭔지는 모르겠지만, bound_target_function을 출력해보면 

(gdb) p ((v8::internal::JSFunction *)0x31127917cb59)->Print()

0x31127917cb59: [Function] in OldSpace

 - map: 0x0fc0218025c1 <Map(HOLEY_ELEMENTS)> [FastProperties]

 - prototype: 0x2de499a04689 <JSFunction (sfi = 0x16d21d284df1)>

 - elements: 0x37c157d82d29 <FixedArray[0]> [HOLEY_ELEMENTS]

 - function prototype:

 - initial_map:

 - shared_info: 0x31127917a929 <SharedFunctionInfo log>


log라는 이름의 함수인걸 알 수 있다.



(gdb) disas v8::internal::JSBoundFunction::bound_target_function

Dump of assembler code for function v8::internal::JSBoundFunction::bound_target_function() const:

   0x0000000001ccff9a <+0>:     push   %rbp

   0x0000000001ccff9b <+1>:     mov    %rsp,%rbp

   0x0000000001ccff9e <+4>:     sub    $0x20,%rsp

   0x0000000001ccffa2 <+8>:     mov    %rdi,-0x18(%rbp)

   0x0000000001ccffa6 <+12>:    mov    -0x18(%rbp),%rax

   0x0000000001ccffaa <+16>:    add    $0x17,%rax

   0x0000000001ccffae <+20>:    mov    (%rax),%rax

   0x0000000001ccffb1 <+23>:    mov    %rax,%rdi

   0x0000000001ccffb4 <+26>:    callq  0x1cbc75c <v8::internal::JSReceiver::cast(v8::internal::Object*)>

   0x0000000001ccffb9 <+31>:    mov    %rax,-0x8(%rbp)

   0x0000000001ccffbd <+35>:    mov    -0x8(%rbp),%rax

   0x0000000001ccffc1 <+39>:    leaveq

   0x0000000001ccffc2 <+40>:    retq


(gdb) p (((v8::internal::JSReceiver *)0x000031127917cb59)->class_name())->ToAsciiArray()

$1489 = 0x48ff6e0 "Function"


bound_target_function은 현 JSBoundFunction의 JSReceiver를 리턴해줄텐데, 


// Inheritance hierarchy:

// - Object

//   - Smi          (immediate small integer)

//   - HeapObject   (superclass for everything allocated in the heap)

//     - JSReceiver  (suitable for property access)

//       - JSObject

//         - JSArray

//         - JSArrayBuffer

//         - JSArrayBufferView

//           - JSTypedArray

//           - JSDataView

//         - JSBoundFunction

//         - JSCollection

//           - JSSet

//           - JSMap

//         - JSStringIterator

//         - JSSetIterator

//         - JSMapIterator

//         - JSWeakCollection

//           - JSWeakMap

//           - JSWeakSet

//         - JSRegExp

//         - JSFunction


 JSFunction은 JSReceiver에 기록되어 있다 뜻.


3691    String* JSReceiver::class_name() {

3692      ReadOnlyRoots roots = GetReadOnlyRoots();

3693      if (IsFunction()) return roots.Function_string();

3694      if (IsJSArgumentsObject()) return roots.Arguments_string();

3695      if (IsJSArray()) return roots.Array_string();

3696      if (IsJSArrayBuffer()) {

3697        if (JSArrayBuffer::cast(this)->is_shared()) {


214     bool HeapObject::IsFunction() const {

215       STATIC_ASSERT(LAST_FUNCTION_TYPE == LAST_TYPE);

216       return map()->instance_type() >= FIRST_FUNCTION_TYPE;

217     }


JSReceiver는 property를 갖는 모든 것들의 부모였던것, 

v8과 같은 interpreter들은 정형화되지 않은 Javascript들을 실행하더라도 안전성을 보장해야 하기 때문에

C++의 추상화로 갖을 수 있는 장점들이 모두 타입체크로 상쇄되어 버리는 느낌이 없지 않아 있다.


deps/v8/src/objects-definitions.h

#define INSTANCE_TYPE_LIST_AFTER_INTL(V) \

  V(WASM_GLOBAL_TYPE)                    \

  V(WASM_INSTANCE_TYPE)                  \

  V(WASM_MEMORY_TYPE)                    \

  V(WASM_MODULE_TYPE)                    \

  V(WASM_TABLE_TYPE)                     \

  V(JS_BOUND_FUNCTION_TYPE)              \

  V(JS_FUNCTION_TYPE)


(gdb) p FIRST_FUNCTION_TYPE

$1501 = v8::internal::JS_BOUND_FUNCTION_TYPE


(gdb) p LAST_FUNCTION_TYPE

$1502 = v8::internal::JS_FUNCTION_TYPE


이것으로 v8에서 Function 타입은 JS_BOUND_FUNCTION_TYPE과 JS_FUNCTION_TYPE 둘 뿐이라는걸 

확인할 수 있으므로, 조금은 마음이 놓인다.


지금까지 했던 작업으로 JS_FUNCTION_TYPE의 경우는 이름을 출력할 수 있으므로, 

JS_BOUND_FUNCTION_TYPE인 경우 0x17에 존재하는 JSReceiver를 참조하고, 

이게 JS_FUNCTION_TYPE인지 체크하고, JS_FUNCTION_TYPE인 경우

동일한 로직으로 이름을 출력하면 될 것이다.


한 스텝씩 진행해보자.

(gdb) call _v8_internal_Print_Object(*(v8::internal::Object **)callable.location_)

0xb4adab4c6e9: [JSBoundFunction]

 - map: 0x0fc021805451 <Map(HOLEY_ELEMENTS)> [FastProperties]

 - prototype: 0x2de499a04689 <JSFunction (sfi = 0x16d21d284df1)>

 - elements: 0x37c157d82d29 <FixedArray[0]> [HOLEY_ELEMENTS]

 - bound_target_function: 0x31127917cb59 <JSFunction log (sfi = 0x31127917a929)>

 - bound_this: 0x0b4adab0ab59 <Object map = 0xfc021852021>

 - bound_arguments: 0x37c157d82d29 <FixedArray[0]>

 - properties: 0x37c157d82d29 <FixedArray[0]> {

    #length: 0x16d21d297dd9 <AccessorInfo> (const accessor descriptor)

    #name: 0x16d21d297e49 <AccessorInfo> (const accessor descriptor)

 }


# v8::internal::JSBoundFunction

(gdb) x/10gx *(long *)(*&callable.location_)-0x1

0xb4adab4c6e8:  0x00000fc021805451      0x000037c157d82d29

0xb4adab4c6f8:  0x000037c157d82d29      0x000031127917cb59


# v8::internal::JSBoundFunction bound_target_function

(gdb) x/10gx *(long *)(*&callable.location_)+0x17

0xb4adab4c700:  0x000031127917cb59      0x00000b4adab0ab59

0xb4adab4c710:  0x000037c157d82d29      0x000037c157d838f9


# bound_target_function

(gdb) x/10gx (*(uintptr_t *)(*(uintptr_t *)(*&callable.location_)+0x17))

0x31127917cb59: 0x2900000fc0218025      0x29000037c157d82d

0x31127917cb69: 0x29000037c157d82d      0xc9000031127917a9


(gdb) set $bound_target_fn = (*(uintptr_t *)(*(uintptr_t *)(*&callable.location_)+0x17))

(gdb) p/x $bound_target_fn

$1506 = 0x31127917cb59


(gdb) p/x *(uint16_t *)(*(uintptr_t *)($bound_target_fn-1)+11)

$1522 = 0x447



(gdb) call _v8_internal_Print_Object(((v8::internal::Object **)$target))

0x2314b4ffcb59: [Function] in OldSpace

 - map: 0x3e3cc03025c1 <Map(HOLEY_ELEMENTS)> [FastProperties]

 - prototype: 0x2b08f9004689 <JSFunction (sfi = 0x3a16f5a84df1)>

 - elements: 0x03d03fa82d29 <FixedArray[0]> [HOLEY_ELEMENTS]

 - function prototype:

 - initial_map:

 - shared_info: 0x2314b4ffa929 <SharedFunctionInfo log>

 - name: 0x3a16f5a852b9 <String[3]: log>




요시! 조각은 모두 갖춰졌다.

이제 퍼즐을 맞춰보자.


==================================================================

# poc.cmd

file node_g


br v8::internal::Execution::Call

commands

  set $target = *(uintptr_t *)callable

  set $instanceType = *(uint16_t *)(*(uintptr_t *)((uintptr_t)$target-1)+11)

    # JS_BOUND_FUNCTION_TYPE

    if $instanceType == 0x446

      p "JS_BOUND_FUNCTION_TYPE"

      set $target = *((uintptr_t *)($target+0x17))

      set $instanceType = *(uint16_t *)((*(uintptr_t *)($target-1))+11)

    end


    # JS_FUNCTION_TYPE case

    if $instanceType == 0x447

      p "JS_FUNCTION_TYPE"

      set $isscope = ((v8::internal::Object *)(*(long *)(*((long *)((uintptr_t)($target)-1) + 0x3)+0xf)))->IsScopeInfo()

      if $isscope == 1

        p "Is Scope"

        set $hasFn = ((v8::internal::ScopeInfo *)(*(long *)(*((long *)((uintptr_t)($target)-1) + 0x3)+0xf)))->HasSharedFunctionName()

        if $hasFn == 1

          p "has fn"

          p ((v8::internal::String *)((v8::internal::ScopeInfo *)(*(long *)(*((long *)((uintptr_t)($target)-1) + 0x3)+0xf)))->FunctionName())->ToAsciiArray()        end

      else

        p "Not Scope just Name"

        p ((v8::internal::String *)(*(long *)(*((long *)((uintptr_t)($target)-1) + 0x3)+0xf)))->ToAsciiArray()

      end

    end

  continue

end


r test3.js


==================================================================

# gdb -x poc.cmd

203                           MessageHandling::kReport, Execution::Target::kCallable);

$995 = "JS_BOUND_FUNCTION_TYPE"

$996 = "JS_FUNCTION_TYPE"

$997 = "Is Scope"

$998 = "has fn"

$999 = 0x48ff6e0 "log"

Hello A


Thread 1 "node_g" hit Breakpoint 1, v8::internal::Execution::Call (isolate=0x48711b0, callable=..., receiver=..., argc=1, argv=0x7fffffffc2d0)

    at ../deps/v8/src/execution.cc:203

203                           MessageHandling::kReport, Execution::Target::kCallable);

$1000 = "JS_BOUND_FUNCTION_TYPE"

$1001 = "JS_FUNCTION_TYPE"

$1002 = "Is Scope"

$1003 = "has fn"

$1004 = 0x48ff6e0 "log"

Hello C


Thread 1 "node_g" hit Breakpoint 1, v8::internal::Execution::Call (isolate=0x48711b0, callable=..., receiver=..., argc=1, argv=0x7fffffffc320)

    at ../deps/v8/src/execution.cc:203

203                           MessageHandling::kReport, Execution::Target::kCallable);

$1005 = "JS_BOUND_FUNCTION_TYPE"

$1006 = "JS_FUNCTION_TYPE"

$1007 = "Is Scope"

$1008 = "has fn"

$1009 = 0x48ff6e0 "log"

Hello B

....

==================================================================

console.log를 호출할 때의 함수 이름 log는 나오는걸 볼 수 있다.



(gdb) call _v8_internal_Print_Object(*(v8::internal::Object **)callable)

0x1d7eccecc6e9: [JSBoundFunction]

 - map: 0x37a4b8885451 <Map(HOLEY_ELEMENTS)> [FastProperties]

 - prototype: 0x290aec904689 <JSFunction (sfi = 0x2caf77b84df1)>

 - elements: 0x087cc1282d29 <FixedArray[0]> [HOLEY_ELEMENTS]

 - bound_target_function: 0x10dd5effcb59 <JSFunction log (sfi = 0x10dd5effa929)>

 - bound_this: 0x1d7ecce8ab59 <Object map = 0x37a4b88d2021>

 - bound_arguments: 0x087cc1282d29 <FixedArray[0]>

 - properties: 0x087cc1282d29 <FixedArray[0]> {

    #length: 0x2caf77b97dd9 <AccessorInfo> (const accessor descriptor)

    #name: 0x2caf77b97e49 <AccessorInfo> (const accessor descriptor)


(gdb) call _v8_internal_Print_Object(*(v8::internal::Object **)receiver)

0x1d7ecce8ab59: [JS_OBJECT_TYPE]

 - map: 0x37a4b88d2021 <Map(HOLEY_ELEMENTS)> [FastProperties]

 - prototype: 0x1d7ecce8aa89 <Object map = 0x37a4b88d0ea1>

 - elements: 0x087cc1282d29 <FixedArray[0]> [HOLEY_ELEMENTS]

 - properties: 0x1d7eccece6c9 <PropertyArray[24]> {

    0x10dd5effc999 <Symbol: kGetInspectOptions>: 0x10dd5effca99 <JSFunction Console.(anonymous function) (sfi = 0x10dd5effa821)> (const data descriptor)

    0x10dd5effc979 <Symbol: kFormatForStdout>: 0x10dd5effcad9 <JSFunction Console.(anonymous function) (sfi = 0x10dd5effa879)> (const data descriptor)

    0x10dd5effc959 <Symbol: kFormatForStderr>: 0x10dd5effcb19 <JSFunction Console.(anonymous function) (sfi = 0x10dd5effa8d1)> (const data descriptor)

    #_stdout: 0x1d7eccec3f61 <WriteStream map = 0x37a4b88d0131> (data field 0)

    #_stderr: 0x1d7eccec8ad9 <WriteStream map = 0x37a4b88d00e1> (data field 1)

    #_ignoreErrors: 0x087cc1282741 <true> (data field 2)

    #_times: 0x1d7eccec9aa9 <Map map = 0x37a4b8883ec1> (data field 3)

    #_stdoutErrorHandler: 0x1d7eccec9c31 <JSFunction (sfi = 0x236fce8b72f9)> (const data descriptor)

    #_stderrErrorHandler: 0x1d7eccec9d51 <JSFunction (sfi = 0x236fce8b72f9)> (const data descriptor)

    #log: 0x1d7eccecfab1 <JSBoundFunction (BoundTargetFunction 0x10dd5efe5d71)> (data field 4) properties[0]


JS_BOUND_FUNCTION_TYPE log를 호출할 때, callable과 receiver를 찍어보면, 

callable과 receiver가 같은 JSBoundFunction 주소를 갖고 있는걸 알 수 있다. 


그리고 아직 scopeinfo이고 hasfunctionname일때 제대로 찍히지가 않고 있다.

왜인지 추적하기 위해 JS_BOUND_FUNCTION_TYPE의 호출 때 continue 시키지 않고 멈추고

동시에 진행 과정에 _v8_internal_Print_Object를 사용해 로깅을 하도록 해보자.


$981 = 0x48e75d0 ""


Thread 1 "node_g" hit Breakpoint 1, v8::internal::Execution::Call (isolate=0x48711b0, callable=..., receiver=..., argc=1, argv=0x7fffffffc280)

    at ../deps/v8/src/execution.cc:203

203                           MessageHandling::kReport, Execution::Target::kCallable);

$982 = "JS_BOUND_FUNCTION_TYPE"

$983 = "JS_FUNCTION_TYPE"

$984 = "Not Scope just Name"

$985 = 0x48e75d0 "log"


JS_BOUND_FUNCTION_TYPE 바로 전 타임에 이런 로그가 찍힌다.



Thread 1 "node_g" hit Breakpoint 1, v8::internal::Execution::Call (isolate=0x48711b0, callable=..., receiver=..., argc=0, argv=0x0)                              at ../deps/v8/src/execution.cc:203

203                           MessageHandling::kReport, Execution::Target::kCallable);

$978 = "JS_FUNCTION_TYPE"

$979 = "Is Scope"

$980 = "has fn"

0x2942200c8f09: [Function] in OldSpace

 - map: 0x110252b02481 <Map(HOLEY_ELEMENTS)> [FastProperties]

 - prototype: 0x31130eb84689 <JSFunction (sfi = 0x23771a84df1)>

 - elements: 0x14a0fd102d29 <FixedArray[0]> [HOLEY_ELEMENTS]

 - function prototype:

 - initial_map:

 - shared_info: 0x2942200c8261 <SharedFunctionInfo>

 - name: 0x14a0fd102851 <String[0]: >

 - formal_parameter_count: 0

 - kind: NormalFunction

 - context: 0x31130eb83dc1 <NativeContext[252]>

 - code: 0x2da2e6932781 <Code BUILTIN InterpreterEntryTrampoline>

 - interpreted

 - bytecode: 0x2942200c83c9

 - source code: (function (exports, require, module, __filename, __dirname) { function c() {

    console.log("Hello C");

}

function b() {

    c();

    console.log("Hello B");

}

function a() {

    b();

    console.log("Hello A");

}


a();

b();

c();

});


이건 receiver.......


0x2dddb1d84de9: [JSGlobalProxy] in OldSpace

 - map: 0x01c44b687111 <Map(HOLEY_ELEMENTS)> [FastProperties]

 - prototype: 0x391d6269d969 <JSObject>

 - elements: 0x1045f8502d29 <FixedArray[0]> [HOLEY_ELEMENTS]

 - native context: 0x391d62683dc1 <NativeContext[252]>

 - properties: 0x1045f8502d29 <FixedArray[0]> {}

$981 = 0x48f0770 ""



# 주목할 점. 


- source code: 

(function (exports, require, module, __filename, __dirname) { 

function find_me_c() {

    console.log("Hello C");

}

function find_me_b() {

    find_me_c();

    console.log("Hello B");

}

function find_me_a() {

    find_me_b();

    console.log("Hello A");

}

find_me_a();

find_me_b();

find_me_c();

});


다수의 익명의 JS_FUNCTION_TYPE이 호출되는 것은, 위의 소스코드처럼, 

함수 자체가 호출되는 것이 아니라 함수가 포함된 module이 호출되는 것. 

이 때 마치 생성자처럼 각 함수들이 자리잡게 된다. 


그리고 --log-api 옵션을 통해 호출 기록을 보면, 

api,call,Object

api,v8::Function::Call

api,interceptor-named-getter,Object,NODE_DISABLE_COLORS

api,v8::String::WriteUtf8

api,interceptor-named-getter,Object,TERM

api,v8::String::WriteUtf8

api,v8::String::NewFromUtf8

api,interceptor-named-getter,Object,TMUX

api,v8::String::WriteUtf8

api,v8::String::NewFromUtf8

api,call,WriteWrap  

api,call,TTY 

api,v8::String::WriteUtf8


이 구간이고 미세하게 아래 함수들이 호출되고 안되고의 차이가 있다.

api,accessor-getter,Function,prototype

api,accessor-getter,Array,length  

api,accessor-getter,String,length 

api,accessor-getter,String,length   


다음은 --trace 옵션을 주고 실행했을 때이며, 


  10:          ~find_me_A+0(this=0x14ef22786819 <JSGlobal Object>) {



Thread 1 "node_g" hit Breakpoint 1, v8::internal::Logger::ApiObjectAccess (this=0x487beb0,

    tag=0x31ef773 "call", object=0x2d6939e83ba1) at ../deps/v8/src/log.cc:1146

(gdb) bt


#0  v8::internal::Logger::ApiObjectAccess (this=0x487beb0, tag=0x31ef773 "call", object=0x2d6939e83ba1)    at ../deps/v8/src/log.cc:1146

#1  0x0000000001dca1d8 in v8::internal::FunctionCallbackArguments::Call (this=0x7fffffffc6c0,    handler=0x35aea7ae6039) at ../deps/v8/src/api-arguments-inl.h:107

#2  0x0000000001dcc81a in v8::internal::(anonymous namespace)::HandleApiCallHelper<false> (    isolate=0x4871230, function=..., new_target=..., fun_data=..., receiver=..., args=...)    at ../deps/v8/src/builtins/builtins-api.cc:109

#3  0x0000000001dcab69 in v8::internal::Builtin_Impl_HandleApiCall (args=..., isolate=0x4871230)    at ../deps/v8/src/builtins/builtins-api.cc:139

#4  0x0000000001dca902 in v8::internal::Builtin_HandleApiCall (args_length=9, args_object=0x7fffffffc9b8,

...


여기서 find_me_A 함수 호출이 이뤄지지 않을까 했지만, 


timer-event-start,V8.CompileCode,1471483

function,full-parse,45,4346,4625,0.105,1471646,runInThisContext

function,parse-function,45,4346,4625,0.135,1471684,runInThisContext

code-creation,LazyCompile,10,1471891,0xd7b4ffcf87a,149,runInThisContext vm.js:117:19,0x3552695f32b0,~

function,compile-lazy,45,4346,4625,0.123,1471913,runInThisContext

timer-event-end,V8.CompileCode,1471930

function,first-execution,45,4346,4625,0,1471962,runInThisContext

timer-event-start,V8.CompileCode,1471981

function,full-parse,45,6080,6882,0.182,1472324,getRunInContextArgs

function,parse-function,45,6080,6882,0.348,1472402,getRunInContextArgs

code-creation,LazyCompile,10,1472831,0xd7b4ffcfc42,252,getRunInContextArgs vm.js:168:29,0x3552695f2ee8,~

function,compile-lazy,45,6080,6882,0.325,1472872,getRunInContextArgs

timer-event-end,V8.CompileCode,1472910

function,first-execution,45,6080,6882,0,1472954,getRunInContextArgs

api,call,ContextifyScript

timer-event-start,V8.External,1473607

api,v8::Script::Run

timer-event-start,V8.Execute,1473660

timer-event-start,V8.Execute,1473663

function,first-execution,66,0,830,0,1473681,

timer-event-end,V8.Execute,1473722

timer-event-end,V8.Execute,1473726

timer-event-end,V8.External,1473731

function,first-execution,66,10,828,0,1474927,

timer-event-start,V8.CompileCode,1474956

function,full-parse,66,313,419,0.081,1475087,find_me_a

function,parse-function,66,313,419,0.116,1475130,find_me_a

code-creation,LazyCompile,10,1475265,0xd7b4ffd0382,30,find_me_a /home/m/git/node/test3.js:8:19,0xd7b4ffcf4d8,~

function,compile-lazy,66,313,419,0.062,1475296,find_me_a

timer-event-end,V8.CompileCode,1475312

function,first-execution,66,313,419,0,1475337,find_me_a

timer-event-start,V8.CompileCode,1475352

function,full-parse,66,188,294,0.024,1475406,find_me_b

function,parse-function,66,188,294,0.044,1475434,find_me_b

code-creation,LazyCompile,10,1475583,0xd7b4ffd0562,30,find_me_b /home/m/git/node/test3.js:4:19,0xd7b4ffcf480,~

function,compile-lazy,66,188,294,0.044,1475627,find_me_b

timer-event-end,V8.CompileCode,1475642

function,first-execution,66,188,294,0,1475668,find_me_b

timer-event-start,V8.CompileCode,1475683

function,full-parse,66,80,169,0.02,1475734,find_me_c

function,parse-function,66,80,169,0.041,1475760,find_me_c

code-creation,LazyCompile,10,1475850,0xd7b4ffd0742,23,find_me_c /home/m/git/node/test3.js:1:81,0xd7b4ffcf428,~

function,compile-lazy,66,80,169,0.039,1475879,find_me_c

timer-event-end,V8.CompileCode,1475891

function,first-execution,66,80,169,0,1475913,find_me_c

api,call,Object

timer-event-start,V8.External,1475974

api,v8::Function::Call

timer-event-start,V8.Execute,1475994

timer-event-start,V8.CompileCode,1476005

function,full-parse,48,7311,7475,0.05,1476087,log

function,parse-function,48,7311,7475,0.07,1476113,log

code-creation,LazyCompile,10,1476254,0xd7b4ffd08da,60,log console.js:211:37,0x3552695fa928,~

function,compile-lazy,48,7311,7475,0.066,1476271,log

timer-event-end,V8.CompileCode,1476282

function,first-execution,48,7311,7475,0,1476307,log

timer-event-start,V8.CompileCode,1476367

function,full-parse,48,6997,7110,0.041,1476440,

function,parse-function,48,6997,7110,0.06,1476467,Console.(anonymous function)

code-creation,LazyCompile,10,1476614,0xd7b4ffd0aca,47,Console.(anonymous function) console.js:201:47,0x3552695fa878,~



vm.js에서 실행하는 거여따.

마치 함수를 호출하는 것 같은 부분이었던 요기는


api,call,Object

api,v8::Function::Call

code-creation,LazyCompile,10,1476254,0xd7b4ffd08da,60,log console.js:211:37,0x3552695fa928,~

function,compile-lazy,48,7311,7475,0.066,1476271,log

function,first-execution,48,7311,7475,0,1476307,log

function,full-parse,48,6997,7110,0.041,1476440,

function,parse-function,48,6997,7110,0.06,1476467,Console.(anonymous function)


그렇타....

v8::internal::Builtin_HandleApiCall 로, builtin 된 함수들이 호출되는 순서였따.

interpreting 과정에서 Builtin된 함수를 호출하는 "console.log"를 만나게 되면, 

위의 함수가 호출되 v8::Function::Call의 순서로 API함수를 호출하게 된다. 

물론 위의 API 함수들은 Native module javascript를 로드하는 과정에서 builtin되게 된다.


여기까지 분석 내용으로는 마치 GOT/PLT 후킹하여 native API 호출을 기록하는 것 처럼.

v8의 API들에 대한 기록만이 가능하다는 결론이 나왔다.


Javascript의 함수 호출을 기록하려면, 

--trace 옵션을 사용하면 다음 처럼 출력해주는데 


  10:          ~find_me_A+0(this=0x3718cefd9091 <Object map = 0xac5138d3b01>) {


이걸 기록할 수 있어야 한다. 



'v8' 카테고리의 다른 글

[졸간분] 최적화 기법 on-stack replacement  (0) 2019.11.14
Nodejs 최적화 Tip?  (0) 2019.11.13
Javascript Interpreting 분석  (0) 2019.06.08
Build and Test  (0) 2019.05.31
v8 인터프리터 분석 - 함수 호출  (0) 2019.03.15
Comments