OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 the V8 project authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "src/wasm/wasm-debug.h" |
| 6 |
| 7 #include "src/assert-scope.h" |
| 8 #include "src/debug/debug.h" |
| 9 #include "src/factory.h" |
| 10 #include "src/isolate.h" |
| 11 #include "src/wasm/module-decoder.h" |
| 12 #include "src/wasm/wasm-module.h" |
| 13 |
| 14 using namespace v8::internal; |
| 15 using namespace v8::internal::wasm; |
| 16 |
| 17 namespace { |
| 18 |
| 19 enum { |
| 20 kWasmDebugInfoWasmObj, |
| 21 kWasmDebugInfoWasmBytesHash, |
| 22 kWasmDebugInfoFunctionOffsets, |
| 23 kWasmDebugInfoNumInternalFields |
| 24 }; |
| 25 |
| 26 ByteArray *GetOrCreateFunctionOffsetTable(WasmDebugInfo *debug_info) { |
| 27 Object *offset_table = |
| 28 debug_info->GetInternalField(kWasmDebugInfoFunctionOffsets); |
| 29 if (!offset_table->IsUndefined()) return ByteArray::cast(offset_table); |
| 30 |
| 31 FunctionOffsetsResult function_offsets; |
| 32 { |
| 33 DisallowHeapAllocation no_gc; |
| 34 SeqOneByteString *wasm_bytes = |
| 35 wasm::GetWasmBytes(debug_info->wasm_object()); |
| 36 const byte *bytes_start = wasm_bytes->GetChars(); |
| 37 const byte *bytes_end = bytes_start + wasm_bytes->length(); |
| 38 function_offsets = wasm::DecodeWasmFunctionOffsets(bytes_start, bytes_end); |
| 39 } |
| 40 DCHECK(function_offsets.ok()); |
| 41 Isolate *isolate = debug_info->GetIsolate(); |
| 42 size_t array_size = |
| 43 2 * sizeof(int) * static_cast<int>(function_offsets.val.size()); |
| 44 CHECK_LE(array_size, std::numeric_limits<int>::max()); |
| 45 ByteArray *arr = |
| 46 *isolate->factory()->NewByteArray(static_cast<int>(array_size)); |
| 47 int idx = 0; |
| 48 for (std::pair<int, int> p : function_offsets.val) { |
| 49 arr->set_int(idx++, p.first); |
| 50 arr->set_int(idx++, p.second); |
| 51 } |
| 52 DCHECK_EQ(arr->length(), idx * sizeof(int)); |
| 53 debug_info->SetInternalField(kWasmDebugInfoFunctionOffsets, arr); |
| 54 |
| 55 return arr; |
| 56 } |
| 57 |
| 58 } // namespace |
| 59 |
| 60 Handle<WasmDebugInfo> WasmDebugInfo::New(Handle<JSObject> wasm) { |
| 61 Isolate *isolate = wasm->GetIsolate(); |
| 62 Factory *factory = isolate->factory(); |
| 63 Handle<Map> map = factory->NewMap( |
| 64 JS_OBJECT_TYPE, |
| 65 JSObject::kHeaderSize + kWasmDebugInfoNumInternalFields * kPointerSize); |
| 66 Handle<JSObject> info = factory->NewJSObjectFromMap(map, TENURED); |
| 67 info->SetInternalField(kWasmDebugInfoWasmObj, *wasm); |
| 68 int hash = 0; |
| 69 Handle<SeqOneByteString> wasm_bytes(GetWasmBytes(*wasm), isolate); |
| 70 { |
| 71 DisallowHeapAllocation no_gc; |
| 72 hash = StringHasher::HashSequentialString(wasm_bytes->GetChars(), |
| 73 wasm_bytes->length(), 0); |
| 74 } |
| 75 info->SetInternalField(kWasmDebugInfoWasmBytesHash, |
| 76 *factory->NewNumberFromInt(hash, TENURED)); |
| 77 |
| 78 // TODO(clemensh): Register the wasm module at the debugger |
| 79 |
| 80 return Handle<WasmDebugInfo>::cast(info); |
| 81 } |
| 82 |
| 83 bool WasmDebugInfo::IsDebugInfo(Object *object) { |
| 84 if (!object->IsJSObject()) return false; |
| 85 JSObject *obj = JSObject::cast(object); |
| 86 return obj->GetInternalFieldCount() == kWasmDebugInfoNumInternalFields && |
| 87 IsWasmObject(obj->GetInternalField(kWasmDebugInfoWasmObj)) && |
| 88 obj->GetInternalField(kWasmDebugInfoWasmBytesHash)->IsNumber() && |
| 89 (obj->GetInternalField(kWasmDebugInfoFunctionOffsets)->IsUndefined() || |
| 90 obj->GetInternalField(kWasmDebugInfoFunctionOffsets)->IsByteArray()); |
| 91 } |
| 92 |
| 93 WasmDebugInfo *WasmDebugInfo::cast(Object *object) { |
| 94 DCHECK(IsDebugInfo(object)); |
| 95 return static_cast<WasmDebugInfo *>(object); |
| 96 } |
| 97 |
| 98 JSObject *WasmDebugInfo::wasm_object() { |
| 99 return JSObject::cast(GetInternalField(kWasmDebugInfoWasmObj)); |
| 100 } |
| 101 |
| 102 bool WasmDebugInfo::SetBreakPoint(int byte_offset) { return false; } |
| 103 |
| 104 std::pair<int, int> WasmDebugInfo::GetFunctionOffsetAndLength(int func_index) { |
| 105 ByteArray *arr = GetOrCreateFunctionOffsetTable(this); |
| 106 |
| 107 if (func_index < 0 || func_index >= arr->length() / sizeof(int) / 2) { |
| 108 return {0, 0}; |
| 109 } |
| 110 int offset = arr->get_int(2 * func_index); |
| 111 int length = arr->get_int(2 * func_index + 1); |
| 112 // Assert that it's distinguishable from the "illegal function index" return. |
| 113 DCHECK(offset > 0 && length > 0); |
| 114 return {offset, length}; |
| 115 } |
| 116 |
| 117 Handle<Object> wasm::GetFunctionOffsetTable(Isolate *isolate, |
| 118 Handle<String> module_bytes) { |
| 119 FunctionOffsetsResult function_offsets; |
| 120 { |
| 121 DisallowHeapAllocation no_gc; |
| 122 DCHECK(module_bytes->IsSeqOneByteString()); |
| 123 Vector<const uint8_t> bytes_vec = |
| 124 module_bytes->GetFlatContent().ToOneByteVector(); |
| 125 function_offsets = |
| 126 wasm::DecodeWasmFunctionOffsets(bytes_vec.start(), bytes_vec.end()); |
| 127 } |
| 128 if (!function_offsets.ok()) return isolate->factory()->undefined_value(); |
| 129 |
| 130 size_t array_size = 2 * function_offsets.val.size(); |
| 131 CHECK_LE(array_size, std::numeric_limits<int>::max()); |
| 132 Handle<FixedArray> arr = |
| 133 isolate->factory()->NewFixedArray(static_cast<int>(array_size)); |
| 134 int idx = 0; |
| 135 for (std::pair<int, int> p : function_offsets.val) { |
| 136 arr->set(idx++, Smi::FromInt(p.first)); |
| 137 arr->set(idx++, Smi::FromInt(p.second)); |
| 138 } |
| 139 DCHECK_EQ(arr->length(), idx); |
| 140 return isolate->factory()->NewJSArrayWithElements(arr); |
| 141 } |
| 142 |
| 143 Handle<Object> wasm::DisassembleFunction(Isolate *isolate, |
| 144 Handle<String> function_bytes, |
| 145 bool get_code, bool get_table) { |
| 146 struct NullBuf : public std::streambuf {}; |
| 147 struct LineCountingStream : public std::streambuf { |
| 148 std::streambuf *buf; |
| 149 uint32_t line_nr = 0; |
| 150 uint32_t col_nr = 0; |
| 151 |
| 152 explicit LineCountingStream(std::streambuf *buf) : buf(buf) {} |
| 153 inline void nextChar(int_type c) { |
| 154 ++col_nr; |
| 155 if (c == '\n') ++line_nr, col_nr = 0; |
| 156 } |
| 157 int_type overflow(int_type c) override { |
| 158 if (c != EOF) { |
| 159 nextChar(c); |
| 160 buf->sputc(c); |
| 161 } |
| 162 return c; |
| 163 } |
| 164 std::streamsize xsputn(const char *s, std::streamsize n) override { |
| 165 for (const char *p = s, *e = s + n; p != e; ++p) nextChar(*p); |
| 166 buf->sputn(s, n); |
| 167 return n; |
| 168 } |
| 169 }; |
| 170 |
| 171 // The result will be put here. |
| 172 std::stringbuf code_buf; |
| 173 std::vector<uint32_t> offset_table; |
| 174 |
| 175 // If get_code is set, use ostringstream. If get_table is set, use |
| 176 // LineCountingStream. Both can be combined. |
| 177 NullBuf nullbuf; |
| 178 std::streambuf *selected_streambuf = |
| 179 get_code ? &code_buf : static_cast<std::streambuf *>(&nullbuf); |
| 180 LineCountingStream line_counting_stream(selected_streambuf); |
| 181 std::ostream line_counting_ostream(&line_counting_stream); |
| 182 std::function<void(int)> instruction_callback; |
| 183 if (get_table) { |
| 184 instruction_callback = [&](uint32_t offset) { |
| 185 offset_table.push_back(offset); |
| 186 offset_table.push_back(line_counting_stream.line_nr); |
| 187 offset_table.push_back(line_counting_stream.col_nr); |
| 188 }; |
| 189 selected_streambuf = &line_counting_stream; |
| 190 } |
| 191 |
| 192 { |
| 193 DisallowHeapAllocation no_gc; |
| 194 DCHECK(function_bytes->IsSeqOneByteString()); |
| 195 Vector<const uint8_t> bytes_vec = |
| 196 function_bytes->GetFlatContent().ToOneByteVector(); |
| 197 |
| 198 base::AccountingAllocator allocator; |
| 199 std::ostream os(selected_streambuf); |
| 200 if (!PrintAst(&allocator, |
| 201 FunctionBody::ForTesting(bytes_vec.start(), bytes_vec.end()), |
| 202 os, instruction_callback)) { |
| 203 return isolate->factory()->undefined_value(); |
| 204 } |
| 205 } |
| 206 |
| 207 Handle<FixedArray> return_arr = isolate->factory()->NewFixedArray(2); |
| 208 |
| 209 if (get_code) { |
| 210 // Unfortunately, we have to copy the string here. |
| 211 std::string code_str = code_buf.str(); |
| 212 CHECK_LE(code_str.length(), std::numeric_limits<int>::max()); |
| 213 return_arr->set( |
| 214 0, *isolate->factory() |
| 215 ->NewStringFromAscii(Vector<const char>( |
| 216 code_str.data(), static_cast<int>(code_str.length()))) |
| 217 .ToHandleChecked()); |
| 218 } |
| 219 |
| 220 if (get_table) { |
| 221 DCHECK_LE(offset_table.size(), std::numeric_limits<int>::max()); |
| 222 Handle<FixedArray> offset_arr = isolate->factory()->NewFixedArray( |
| 223 static_cast<int>(offset_table.size())); |
| 224 int idx = 0; |
| 225 for (uint32_t elem : offset_table) { |
| 226 offset_arr->set(idx++, Smi::FromInt(elem)); |
| 227 } |
| 228 DCHECK(idx % 3 == 0); |
| 229 DCHECK_EQ(idx, offset_arr->length()); |
| 230 return_arr->set(1, *isolate->factory()->NewJSArrayWithElements(offset_arr)); |
| 231 } |
| 232 |
| 233 return isolate->factory()->NewJSArrayWithElements(return_arr); |
| 234 } |
OLD | NEW |