├── README.md └── v8_doare-helpers ├── README.md ├── runtime-doare-helpers.cc ├── runtime.diff └── samples └── sample_wasm.js /README.md: -------------------------------------------------------------------------------- 1 | # debugging-tools 2 | 3 | - [v8-doar-helpers](https://github.com/JeremyFetiveau/debugging-tools/tree/master/v8_doare-helpers) adds a `%DumpObjects` command to `v8` that you can use from Javascript. It will display an annotated memory dump starting from a given JS object allowing you to easily see what kind of objects are located in memory. 4 | -------------------------------------------------------------------------------- /v8_doare-helpers/README.md: -------------------------------------------------------------------------------- 1 | # v8 runtime helper 2 | 3 | The file `runtime-doare-helpers.cc` is a C++ runtime function for [v8](https://v8.dev/) that exposes custom debugging functions to javascript. 4 | 5 | Runtime functions can be called using the [native syntax `%FunctionName`](https://v8.dev/docs/builtin-functions). 6 | 7 | You need to enable this syntax using the flag `--allow-natives-syntax` in order to make it work in the [d8](https://cs.chromium.org/chromium/src/v8/src/d8.cc) shell. 8 | 9 | If you want to pass flags to v8 from the browser, you also need to use the flag `--js-flags`. 10 | 11 | Be aware that the code is not safe to use and is only a debugging helper. 12 | Many runtime functions are not safe to call directly from javascript. 13 | 14 | ## Build 15 | 16 | The helpers are implemented in `runtime-doare-helpers.cc`. 17 | 18 | You need to add it to `BUILD.gn` so that it will build and also declare your runtime function (implemented using the `RUNTIME_FUNCTION` macro) in the file `src/runtime/runtime.h`. 19 | 20 | Long story short : 21 | - Apply `runtime.diff` 22 | - Copy `runtime-doare-helpers.cc` to the `src/runtime/` folder. 23 | 24 | ## Examples 25 | 26 | ### `%DumpObjects()` 27 | 28 | Usage is the following : 29 | 30 | * `%DumpObjects(, )` 31 | * `%DumpObjects(, )` 32 | * `%DumpObjects(, )` 33 | 34 | This example shows how to examine memory of a WebAssembly exported function and explore it so as to find a [pointer to WASM RWX memory](https://abiondo.me/2019/01/02/exploiting-math-expm1-v8/#code-execution). 35 | 36 | ``` 37 | d8> load("sample_wasm.js") 38 | d8> %DumpObjects(global_test,10) 39 | ----- [ JS_FUNCTION_TYPE : 0x38 ] ----- 40 | 0x00002fac7911ed10 0x00001024ebc84191 MAP_TYPE 41 | 0x00002fac7911ed18 0x00000cdfc0080c19 FIXED_ARRAY_TYPE 42 | 0x00002fac7911ed20 0x00000cdfc0080c19 FIXED_ARRAY_TYPE 43 | 0x00002fac7911ed28 0x00002fac7911ecd9 SHARED_FUNCTION_INFO_TYPE 44 | 0x00002fac7911ed30 0x00002fac79101741 NATIVE_CONTEXT_TYPE 45 | 0x00002fac7911ed38 0x00000d1caca00691 FEEDBACK_CELL_TYPE 46 | 0x00002fac7911ed40 0x00002dc28a002001 CODE_TYPE 47 | ----- [ TRANSITION_ARRAY_TYPE : 0x30 ] ----- 48 | 0x00002fac7911ed48 0x00000cdfc0080b69 MAP_TYPE 49 | 0x00002fac7911ed50 0x0000000400000000 50 | 0x00002fac7911ed58 0x0000000000000000 51 | function 1() { [native code] } 52 | d8> %DumpObjects(0x00002fac7911ecd9,11) 53 | ----- [ SHARED_FUNCTION_INFO_TYPE : 0x38 ] ----- 54 | 0x00002fac7911ecd8 0x00000cdfc0080989 MAP_TYPE 55 | 0x00002fac7911ece0 0x00002fac7911ecb1 WASM_EXPORTED_FUNCTION_DATA_TYPE 56 | 0x00002fac7911ece8 0x00000cdfc00842c1 ONE_BYTE_INTERNALIZED_STRING_TYPE 57 | 0x00002fac7911ecf0 0x00000cdfc0082ad1 FEEDBACK_METADATA_TYPE 58 | 0x00002fac7911ecf8 0x00000cdfc00804c9 ODDBALL_TYPE 59 | 0x00002fac7911ed00 0x000000000000004f 60 | 0x00002fac7911ed08 0x000000000000ff00 61 | ----- [ JS_FUNCTION_TYPE : 0x38 ] ----- 62 | 0x00002fac7911ed10 0x00001024ebc84191 MAP_TYPE 63 | 0x00002fac7911ed18 0x00000cdfc0080c19 FIXED_ARRAY_TYPE 64 | 0x00002fac7911ed20 0x00000cdfc0080c19 FIXED_ARRAY_TYPE 65 | 0x00002fac7911ed28 0x00002fac7911ecd9 SHARED_FUNCTION_INFO_TYPE 66 | 52417812098265 67 | d8> %DumpObjects(0x00002fac7911ecb1,11) 68 | ----- [ WASM_EXPORTED_FUNCTION_DATA_TYPE : 0x28 ] ----- 69 | 0x00002fac7911ecb0 0x00000cdfc00857a9 MAP_TYPE 70 | 0x00002fac7911ecb8 0x00002dc28a002001 CODE_TYPE 71 | 0x00002fac7911ecc0 0x00002fac7911eb29 WASM_INSTANCE_TYPE 72 | 0x00002fac7911ecc8 0x0000000000000000 73 | 0x00002fac7911ecd0 0x0000000100000000 74 | ----- [ SHARED_FUNCTION_INFO_TYPE : 0x38 ] ----- 75 | 0x00002fac7911ecd8 0x00000cdfc0080989 MAP_TYPE 76 | 0x00002fac7911ece0 0x00002fac7911ecb1 WASM_EXPORTED_FUNCTION_DATA_TYPE 77 | 0x00002fac7911ece8 0x00000cdfc00842c1 ONE_BYTE_INTERNALIZED_STRING_TYPE 78 | 0x00002fac7911ecf0 0x00000cdfc0082ad1 FEEDBACK_METADATA_TYPE 79 | 0x00002fac7911ecf8 0x00000cdfc00804c9 ODDBALL_TYPE 80 | 0x00002fac7911ed00 0x000000000000004f 81 | 52417812098225 82 | d8> %DumpObjects(0x00002fac7911eb29,41) 83 | ----- [ WASM_INSTANCE_TYPE : 0x118 : REFERENCES RWX MEMORY] ----- 84 | 0x00002fac7911eb28 0x00001024ebc89411 MAP_TYPE 85 | 0x00002fac7911eb30 0x00000cdfc0080c19 FIXED_ARRAY_TYPE 86 | 0x00002fac7911eb38 0x00000cdfc0080c19 FIXED_ARRAY_TYPE 87 | 0x00002fac7911eb40 0x00002073d820bac1 WASM_MODULE_TYPE 88 | 0x00002fac7911eb48 0x00002073d820bcf1 JS_OBJECT_TYPE 89 | 0x00002fac7911eb50 0x00002fac79101741 NATIVE_CONTEXT_TYPE 90 | 0x00002fac7911eb58 0x00002fac7911ec59 WASM_MEMORY_TYPE 91 | 0x00002fac7911eb60 0x00000cdfc00804c9 ODDBALL_TYPE 92 | 0x00002fac7911eb68 0x00000cdfc00804c9 ODDBALL_TYPE 93 | 0x00002fac7911eb70 0x00000cdfc00804c9 ODDBALL_TYPE 94 | 0x00002fac7911eb78 0x00000cdfc00804c9 ODDBALL_TYPE 95 | 0x00002fac7911eb80 0x00000cdfc00804c9 ODDBALL_TYPE 96 | 0x00002fac7911eb88 0x00002073d820bc79 FIXED_ARRAY_TYPE 97 | 0x00002fac7911eb90 0x00000cdfc00804c9 ODDBALL_TYPE 98 | 0x00002fac7911eb98 0x00002073d820bc69 FOREIGN_TYPE 99 | 0x00002fac7911eba0 0x00000cdfc00804c9 ODDBALL_TYPE 100 | 0x00002fac7911eba8 0x00000cdfc00804c9 ODDBALL_TYPE 101 | 0x00002fac7911ebb0 0x00000cdfc00801d1 ODDBALL_TYPE 102 | 0x00002fac7911ebb8 0x00002dc289f94d21 CODE_TYPE 103 | 0x00002fac7911ebc0 0x0000000000000000 104 | 0x00002fac7911ebc8 0x00007f9f9cf60000 105 | 0x00002fac7911ebd0 0x0000000000010000 106 | 0x00002fac7911ebd8 0x000000000000ffff 107 | 0x00002fac7911ebe0 0x0000556b3a3e0c00 108 | 0x00002fac7911ebe8 0x0000556b3a3ea630 109 | 0x00002fac7911ebf0 0x0000556b3a3ea620 110 | 0x00002fac7911ebf8 0x0000556b3a47c210 111 | 0x00002fac7911ec00 0x0000000000000000 112 | 0x00002fac7911ec08 0x0000556b3a47c230 113 | 0x00002fac7911ec10 0x0000000000000000 114 | 0x00002fac7911ec18 0x0000000000000000 115 | 0x00002fac7911ec20 0x0000087e7c50a000 JumpTableStart [RWX] 116 | 0x00002fac7911ec28 0x0000556b3a47c250 117 | 0x00002fac7911ec30 0x0000556b3a47afa0 118 | 0x00002fac7911ec38 0x0000556b3a47afc0 119 | ----- [ TUPLE2_TYPE : 0x18 ] ----- 120 | 0x00002fac7911ec40 0x00000cdfc00827c9 MAP_TYPE 121 | 0x00002fac7911ec48 0x00002fac7911eb29 WASM_INSTANCE_TYPE 122 | 0x00002fac7911ec50 0x00002073d820b849 JS_FUNCTION_TYPE 123 | ----- [ WASM_MEMORY_TYPE : 0x30 ] ----- 124 | 0x00002fac7911ec58 0x00001024ebc89e11 MAP_TYPE 125 | 0x00002fac7911ec60 0x00000cdfc0080c19 FIXED_ARRAY_TYPE 126 | 0x00002fac7911ec68 0x00000cdfc0080c19 FIXED_ARRAY_TYPE 127 | 52417812097833 128 | ``` 129 | -------------------------------------------------------------------------------- /v8_doare-helpers/runtime-doare-helpers.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Jeremy '@__x86' Fetiveau 3 | // 4 | 5 | #include "src/runtime/runtime-utils.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "src/api-inl.h" 12 | #include "src/arguments-inl.h" 13 | #include "src/assembler-inl.h" 14 | #include "src/counters.h" 15 | 16 | #include "src/wasm/wasm-objects.h" 17 | #include "src/wasm/wasm-objects-inl.h" 18 | 19 | namespace v8 { 20 | namespace internal { 21 | 22 | #define MIN_PTR_ADDR 0x0000010000000000 23 | #define MAX_PTR_ADDR 0x00007fffffffffff 24 | #define PTR_HI_MASK 0xFFFFFFFF00000000 25 | 26 | #define CONVERT_NUMBER_OPT(type, name, Type, obj, default_value) \ 27 | type name = default_value; \ 28 | if(obj->IsNumber()) name = NumberTo##Type(obj); \ 29 | 30 | bool isTaggedPointer(unsigned long ptr) { 31 | return (ptr & 1) && (ptr > MIN_PTR_ADDR) && (ptr < MAX_PTR_ADDR) && IsAligned(ptr-1, kTaggedSize); 32 | } 33 | 34 | enum DumpFlag { 35 | OBJECT_MODE = 0, 36 | ADDRESS_MODE = 1, 37 | ELEMENTS_MODE = 2 38 | }; 39 | 40 | void usage() { 41 | StdoutStream os; 42 | os << "Object mode" << std::endl; 43 | os << "\%DumpObjects(js_object, lines|0);" << std::endl; 44 | os << "\%DumpObjects(address, lines|1);" << std::endl; 45 | os << "Address mode" << std::endl; 46 | os << "\%DumpObjects(taggedAddress, lines|1);" << std::endl; 47 | os << "\%DumpObjects(address, lines|1);" << std::endl; 48 | os << "Elements mode" << std::endl; 49 | os << "\%DumpObjects(taggedAddress, lines|3);" << std::endl; 50 | os << "\%DumpObjects(address, lines|3);" << std::endl; 51 | } 52 | 53 | RUNTIME_FUNCTION(Runtime_DumpObjects) { 54 | SealHandleScope shs(isolate); 55 | 56 | StdoutStream os; 57 | 58 | CONVERT_NUMBER_OPT(uint32_t, pvoid_display_count, Uint32, args[1],10) 59 | 60 | DumpFlag mode = OBJECT_MODE; 61 | 62 | if (pvoid_display_count & 1) { 63 | mode = ADDRESS_MODE; 64 | } 65 | if ((pvoid_display_count & 3) == 3) { 66 | mode = ELEMENTS_MODE; 67 | } 68 | 69 | Address pobj; 70 | 71 | if (mode == OBJECT_MODE) { 72 | MaybeObject maybe_object(*args.address_of_arg_at(0)); 73 | Object obj = maybe_object.GetHeapObjectOrSmi(); 74 | pobj = HeapObject::cast(obj)->address(); 75 | if (obj->IsSmi()) { 76 | os << "[!] error using OBJECT_MODE with smi parameter" << std::endl; 77 | usage(); 78 | return args[0]; 79 | } 80 | } 81 | else if (mode == ADDRESS_MODE) { 82 | CONVERT_NUMBER_OPT(int64_t, obj_addr, Int64, args[0], 0); 83 | obj_addr &= ~1; // untag if tagged 84 | if (obj_addr == 0) { 85 | os << "[!] error using ADDRESS_MODE with object parameter" << std::endl; 86 | usage(); 87 | return args[0]; 88 | } 89 | pobj = static_cast
(obj_addr); 90 | } 91 | else if (mode == ELEMENTS_MODE) { 92 | MaybeObject maybe_object(*args.address_of_arg_at(0)); 93 | Object obj = maybe_object.GetHeapObjectOrSmi(); 94 | if (!obj->IsJSObject()) { 95 | os << "[!] error using ELEMENTS_MODE with non jsobject parameter" << std::endl; 96 | usage(); 97 | return args[0]; 98 | } 99 | pobj = HeapObject::cast(JSObject::cast(obj)->elements())->address(); 100 | } 101 | 102 | bool dumping_wasm_instance = false; 103 | bool dumping_string = false; 104 | 105 | for (unsigned int i = 0; i < pvoid_display_count; ++i) { 106 | uintptr_t ptr = pobj + i * sizeof(uintptr_t); 107 | unsigned long val = *reinterpret_cast(ptr); 108 | // this is totally unsafe and might crash sometimes 109 | // if val is incorrectly considered as a pointer 110 | if (isTaggedPointer(val)) { 111 | HeapObject* heapobj = reinterpret_cast(ptr) ; 112 | InstanceType heapobj_instance_type = heapobj->map()->instance_type(); 113 | if (heapobj_instance_type == MAP_TYPE) { 114 | MaybeObject maybe_tmp_object(reinterpret_cast
(ptr+1)); 115 | Object tmp_obj = maybe_tmp_object.GetHeapObjectOrSmi(); 116 | if (!tmp_obj->IsSmi()) { 117 | os << "----- [ "; 118 | os << HeapObject::cast(tmp_obj)->map()->instance_type(); 119 | os << " : 0x" << std::hex << HeapObject::cast(tmp_obj)->Size(); 120 | dumping_wasm_instance = false; 121 | dumping_string = false; 122 | switch (HeapObject::cast(tmp_obj)->map()->instance_type()) { 123 | case WASM_INSTANCE_TYPE: 124 | dumping_wasm_instance = true; 125 | break; 126 | case ONE_BYTE_STRING_TYPE: 127 | case ONE_BYTE_INTERNALIZED_STRING_TYPE: 128 | dumping_string = true; 129 | break; 130 | default: 131 | break; 132 | } 133 | os << ((dumping_wasm_instance) ? " : REFERENCES RWX MEMORY]" : " ]"); 134 | os << " -----"; 135 | os << std::endl; 136 | } 137 | } 138 | os << std::hex << "0x" << std::setfill('0') << std::setw(sizeof(uintptr_t)*2) << ptr; 139 | os << " "; 140 | os << std::hex << "0x" << std::setfill('0') << std::setw(sizeof(uintptr_t)*2) << val; 141 | os << " "; 142 | os << heapobj_instance_type; 143 | os << " "; 144 | os << std::endl; 145 | } 146 | else { 147 | os << std::hex << "0x" << std::setfill('0') << std::setw(sizeof(uintptr_t)*2) << ptr; 148 | os << " "; 149 | os << std::hex << "0x" << std::setfill('0') << std::setw(sizeof(uintptr_t)*2) << val; 150 | os << " "; 151 | if (dumping_wasm_instance) { 152 | if (i == WasmInstanceObject::kJumpTableStartOffset / sizeof(uintptr_t)) 153 | os << "JumpTableStart [RWX]"; 154 | } 155 | else if (dumping_string) { 156 | std::string str = std::string((const char*)&val).substr(0, sizeof(uintptr_t)); 157 | str.erase(remove_if(str.begin(),str.end(), [](char c) { return c < '!'; }), str.end()); 158 | os << str; 159 | } 160 | os << std::endl; 161 | } 162 | } 163 | 164 | 165 | return args[0]; 166 | } 167 | 168 | RUNTIME_FUNCTION(Runtime_DescribeObjectLayout) { 169 | return args[0]; 170 | } 171 | 172 | } // namespace internal 173 | } // namespace v8 174 | -------------------------------------------------------------------------------- /v8_doare-helpers/runtime.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h 2 | index 74bf642..b6e0f4f 100644 3 | --- a/src/runtime/runtime.h 4 | +++ b/src/runtime/runtime.h 5 | @@ -445,10 +445,12 @@ namespace internal { 6 | F(DebugTrackRetainingPath, -1, 1) \ 7 | F(DeoptimizeFunction, 1, 1) \ 8 | I(DeoptimizeNow, 0, 1) \ 9 | + F(DescribeObjectLayout, 1, 1) \ 10 | F(DeserializeWasmModule, 2, 1) \ 11 | F(DisallowCodegenFromStrings, 1, 1) \ 12 | F(DisallowWasmCodegen, 1, 1) \ 13 | F(DisassembleFunction, 1, 1) \ 14 | + F(DumpObjects, 2, 1) \ 15 | F(FreezeWasmLazyCompilation, 1, 1) \ 16 | F(GetCallable, 0, 1) \ 17 | F(GetDeoptCount, 1, 1) \ 18 | 19 | diff --git a/BUILD.gn b/BUILD.gn 20 | index a00dcd7..089d6f4 100644 21 | --- a/BUILD.gn 22 | +++ b/BUILD.gn 23 | @@ -2442,6 +2444,7 @@ v8_source_set("v8_base") { 24 | "src/runtime/runtime-strings.cc", 25 | "src/runtime/runtime-symbol.cc", 26 | "src/runtime/runtime-test.cc", 27 | + "src/runtime/runtime-doare-helpers.cc", 28 | "src/runtime/runtime-typedarray.cc", 29 | "src/runtime/runtime-utils.h", 30 | "src/runtime/runtime-wasm.cc", 31 | -------------------------------------------------------------------------------- /v8_doare-helpers/samples/sample_wasm.js: -------------------------------------------------------------------------------- 1 | function utf8ToString(h, p) { 2 | let s = ""; 3 | for (i = p; h[i]; i++) { 4 | s += String.fromCharCode(h[i]); 5 | } 6 | return s; 7 | } 8 | 9 | function test() { 10 | var wasmImports = { 11 | env: { 12 | puts: function puts (index) { 13 | print(utf8ToString(h, index)); 14 | } 15 | } 16 | }; 17 | var buffer = new Uint8Array([0,97,115,109,1,0,0,0,1,137,128,128,128,0,2, 18 | 96,1,127,1,127,96,0,0,2,140,128,128,128,0,1,3,101,110,118,4,112,117, 19 | 116,115,0,0,3,130,128,128,128,0,1,1,4,132,128,128,128,0,1,112,0,0,5, 20 | 131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,146,128,128,128,0,2,6, 21 | 109,101,109,111,114,121,2,0,5,104,101,108,108,111,0,1,10,141,128,128, 22 | 128,0,1,135,128,128,128,0,0,65,16,16,0,26,11,11,146,128,128,128,0,1,0, 23 | 65,16,11,12,72,101,108,108,111,32,87,111,114,108,100,0]); 24 | let m = new WebAssembly.Instance(new WebAssembly.Module(buffer),wasmImports); 25 | let h = new Uint8Array(m.exports.memory.buffer); 26 | return m.exports.hello; 27 | } 28 | 29 | global_test = test(); 30 | --------------------------------------------------------------------------------