일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 안티디버깅
- so inject
- apm
- Obfuscator
- initial-exec
- TLS
- linux debugging
- Android
- Linux packer
- 난독화
- OSR
- v8 tracing
- android inject
- v8 optimizing
- Injection
- LLVM 난독화
- anti debugging
- thread local storage
- uftrace
- tracerpid
- linux thread
- on stack replacement
- LLVM Obfuscator
- pthread
- LLVM
- tracing
- Linux custom packer
- pinpoint
- on-stack replacement
- custom packer
- Today
- Total
Why should I know this?
v8 tracing 지원을 위한 작업 기록 본문
======== 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 |