Line data Source code
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/inspector/wasm-translation.h"
6 :
7 : #include <algorithm>
8 :
9 : #include "src/debug/debug-interface.h"
10 : #include "src/inspector/protocol/Debugger.h"
11 : #include "src/inspector/script-breakpoint.h"
12 : #include "src/inspector/string-util.h"
13 : #include "src/inspector/v8-debugger-agent-impl.h"
14 : #include "src/inspector/v8-debugger-script.h"
15 : #include "src/inspector/v8-debugger.h"
16 : #include "src/inspector/v8-inspector-impl.h"
17 :
18 : using namespace v8_inspector;
19 : using namespace v8;
20 :
21 56 : class WasmTranslation::TranslatorImpl {
22 : public:
23 : struct TransLocation {
24 : WasmTranslation* translation;
25 : String16 script_id;
26 : int line;
27 : int column;
28 : TransLocation(WasmTranslation* translation, String16 script_id, int line,
29 : int column)
30 : : translation(translation),
31 : script_id(script_id),
32 : line(line),
33 1688 : column(column) {}
34 : };
35 :
36 : virtual void Init(Isolate*, WasmTranslation*, V8DebuggerAgentImpl*) = 0;
37 : virtual void Translate(TransLocation*) = 0;
38 : virtual void TranslateBack(TransLocation*) = 0;
39 56 : virtual ~TranslatorImpl() {}
40 :
41 : class RawTranslator;
42 : class DisassemblingTranslator;
43 : };
44 :
45 0 : class WasmTranslation::TranslatorImpl::RawTranslator
46 : : public WasmTranslation::TranslatorImpl {
47 : public:
48 0 : void Init(Isolate*, WasmTranslation*, V8DebuggerAgentImpl*) {}
49 0 : void Translate(TransLocation*) {}
50 0 : void TranslateBack(TransLocation*) {}
51 : };
52 :
53 168 : class WasmTranslation::TranslatorImpl::DisassemblingTranslator
54 : : public WasmTranslation::TranslatorImpl {
55 : using OffsetTable = debug::WasmDisassembly::OffsetTable;
56 :
57 : public:
58 56 : DisassemblingTranslator(Isolate* isolate, Local<debug::WasmScript> script)
59 168 : : script_(isolate, script) {}
60 :
61 56 : void Init(Isolate* isolate, WasmTranslation* translation,
62 : V8DebuggerAgentImpl* agent) override {
63 : // Register fake scripts for each function in this wasm module/script.
64 : Local<debug::WasmScript> script = script_.Get(isolate);
65 56 : int num_functions = script->NumFunctions();
66 56 : int num_imported_functions = script->NumImportedFunctions();
67 : DCHECK_LE(0, num_imported_functions);
68 : DCHECK_LE(0, num_functions);
69 : DCHECK_GE(num_functions, num_imported_functions);
70 56 : String16 script_id = String16::fromInteger(script->Id());
71 156 : for (int func_idx = num_imported_functions; func_idx < num_functions;
72 : ++func_idx) {
73 100 : AddFakeScript(isolate, script_id, func_idx, translation, agent);
74 : }
75 56 : }
76 :
77 706 : void Translate(TransLocation* loc) override {
78 1412 : const OffsetTable& offset_table = GetOffsetTable(loc);
79 : DCHECK(!offset_table.empty());
80 706 : uint32_t byte_offset = static_cast<uint32_t>(loc->column);
81 :
82 : // Binary search for the given offset.
83 : unsigned left = 0; // inclusive
84 706 : unsigned right = static_cast<unsigned>(offset_table.size()); // exclusive
85 3308 : while (right - left > 1) {
86 1896 : unsigned mid = (left + right) / 2;
87 3792 : if (offset_table[mid].byte_offset <= byte_offset) {
88 : left = mid;
89 : } else {
90 : right = mid;
91 : }
92 : }
93 :
94 1412 : loc->script_id = GetFakeScriptId(loc);
95 1412 : if (offset_table[left].byte_offset == byte_offset) {
96 706 : loc->line = offset_table[left].line;
97 706 : loc->column = offset_table[left].column;
98 : } else {
99 0 : loc->line = 0;
100 0 : loc->column = 0;
101 : }
102 706 : }
103 :
104 138 : void TranslateBack(TransLocation* loc) override {
105 138 : int func_index = GetFunctionIndexFromFakeScriptId(loc->script_id);
106 276 : const OffsetTable* reverse_table = GetReverseTable(func_index);
107 276 : if (!reverse_table) return;
108 : DCHECK(!reverse_table->empty());
109 138 : v8::Isolate* isolate = loc->translation->isolate_;
110 :
111 : // Binary search for the given line and column.
112 : unsigned left = 0; // inclusive
113 138 : unsigned right = static_cast<unsigned>(reverse_table->size()); // exclusive
114 612 : while (right - left > 1) {
115 336 : unsigned mid = (left + right) / 2;
116 336 : auto& entry = (*reverse_table)[mid];
117 336 : if (entry.line < loc->line ||
118 90 : (entry.line == loc->line && entry.column <= loc->column)) {
119 : left = mid;
120 : } else {
121 : right = mid;
122 : }
123 : }
124 :
125 : int found_byte_offset = 0;
126 : // [left] is <= <line,column>, or left==0 and [0] > <line,column>.
127 : // We are searching for the smallest entry >= <line,column> which is still
128 : // on the same line. This must be either [left] or [left + 1].
129 : // If we don't find such an entry, we might have hit the special case of
130 : // pointing after the last line, which is translated to the end of the
131 : // function (one byte after the last function byte).
132 348 : if ((*reverse_table)[left].line == loc->line &&
133 72 : (*reverse_table)[left].column >= loc->column) {
134 72 : found_byte_offset = (*reverse_table)[left].byte_offset;
135 192 : } else if (left + 1 < reverse_table->size() &&
136 162 : (*reverse_table)[left + 1].line == loc->line &&
137 36 : (*reverse_table)[left + 1].column >= loc->column) {
138 36 : found_byte_offset = (*reverse_table)[left + 1].byte_offset;
139 66 : } else if (left == reverse_table->size() - 1 &&
140 36 : (*reverse_table)[left].line == loc->line - 1 &&
141 6 : loc->column == 0) {
142 : std::pair<int, int> func_range =
143 6 : script_.Get(isolate)->GetFunctionRange(func_index);
144 : DCHECK_LE(func_range.first, func_range.second);
145 6 : found_byte_offset = func_range.second - func_range.first;
146 : }
147 :
148 276 : loc->script_id = String16::fromInteger(script_.Get(isolate)->Id());
149 138 : loc->line = func_index;
150 138 : loc->column = found_byte_offset;
151 : }
152 :
153 : private:
154 100 : String16 GetFakeScriptUrl(v8::Isolate* isolate, int func_index) {
155 : Local<debug::WasmScript> script = script_.Get(isolate);
156 200 : String16 script_name = toProtocolString(script->Name().ToLocalChecked());
157 100 : int numFunctions = script->NumFunctions();
158 100 : int numImported = script->NumImportedFunctions();
159 100 : String16Builder builder;
160 200 : builder.appendAll("wasm://wasm/", script_name, '/');
161 100 : if (numFunctions - numImported > 300) {
162 0 : size_t digits = String16::fromInteger(numFunctions - 1).length();
163 0 : String16 thisCategory = String16::fromInteger((func_index / 100) * 100);
164 : DCHECK_LE(thisCategory.length(), digits);
165 0 : for (size_t i = thisCategory.length(); i < digits; ++i)
166 0 : builder.append('0');
167 0 : builder.appendAll(thisCategory, '/');
168 : }
169 100 : builder.appendAll(script_name, '-');
170 100 : builder.appendNumber(func_index);
171 200 : return builder.toString();
172 : }
173 :
174 806 : String16 GetFakeScriptId(const String16 script_id, int func_index) {
175 3224 : return String16::concat(script_id, '-', String16::fromInteger(func_index));
176 : }
177 706 : String16 GetFakeScriptId(const TransLocation* loc) {
178 2118 : return GetFakeScriptId(loc->script_id, loc->line);
179 : }
180 :
181 100 : void AddFakeScript(v8::Isolate* isolate, const String16& underlyingScriptId,
182 : int func_idx, WasmTranslation* translation,
183 : V8DebuggerAgentImpl* agent) {
184 200 : String16 fake_script_id = GetFakeScriptId(underlyingScriptId, func_idx);
185 100 : String16 fake_script_url = GetFakeScriptUrl(isolate, func_idx);
186 :
187 : v8::Local<debug::WasmScript> script = script_.Get(isolate);
188 : // TODO(clemensh): Generate disassembly lazily when queried by the frontend.
189 200 : debug::WasmDisassembly disassembly = script->DisassembleFunction(func_idx);
190 :
191 : DCHECK_EQ(0, offset_tables_.count(func_idx));
192 : offset_tables_.insert(
193 100 : std::make_pair(func_idx, std::move(disassembly.offset_table)));
194 : String16 source(disassembly.disassembly.data(),
195 100 : disassembly.disassembly.length());
196 : std::unique_ptr<V8DebuggerScript> fake_script =
197 : V8DebuggerScript::CreateWasm(isolate, translation, script,
198 : fake_script_id, std::move(fake_script_url),
199 400 : source);
200 :
201 200 : translation->AddFakeScript(fake_script->scriptId(), this);
202 200 : agent->didParseSource(std::move(fake_script), true);
203 100 : }
204 :
205 138 : int GetFunctionIndexFromFakeScriptId(const String16& fake_script_id) {
206 : size_t last_dash_pos = fake_script_id.reverseFind('-');
207 : DCHECK_GT(fake_script_id.length(), last_dash_pos);
208 138 : bool ok = true;
209 276 : int func_index = fake_script_id.substring(last_dash_pos + 1).toInteger(&ok);
210 : DCHECK(ok);
211 138 : return func_index;
212 : }
213 :
214 : const OffsetTable& GetOffsetTable(const TransLocation* loc) {
215 706 : int func_index = loc->line;
216 : auto it = offset_tables_.find(func_index);
217 : // TODO(clemensh): Once we load disassembly lazily, the offset table
218 : // might not be there yet. Load it lazily then.
219 : DCHECK(it != offset_tables_.end());
220 : return it->second;
221 : }
222 :
223 138 : const OffsetTable* GetReverseTable(int func_index) {
224 : auto it = reverse_tables_.find(func_index);
225 138 : if (it != reverse_tables_.end()) return &it->second;
226 :
227 : // Find offset table, copy and sort it to get reverse table.
228 : it = offset_tables_.find(func_index);
229 24 : if (it == offset_tables_.end()) return nullptr;
230 :
231 24 : OffsetTable reverse_table = it->second;
232 : // Order by line, column, then byte offset.
233 : auto cmp = [](OffsetTable::value_type el1, OffsetTable::value_type el2) {
234 252 : if (el1.line != el2.line) return el1.line < el2.line;
235 0 : if (el1.column != el2.column) return el1.column < el2.column;
236 0 : return el1.byte_offset < el2.byte_offset;
237 : };
238 24 : std::sort(reverse_table.begin(), reverse_table.end(), cmp);
239 :
240 : auto inserted = reverse_tables_.insert(
241 24 : std::make_pair(func_index, std::move(reverse_table)));
242 : DCHECK(inserted.second);
243 24 : return &inserted.first->second;
244 : }
245 :
246 : Global<debug::WasmScript> script_;
247 :
248 : // We assume to only disassemble a subset of the functions, so store them in a
249 : // map instead of an array.
250 : std::unordered_map<int, const OffsetTable> offset_tables_;
251 : std::unordered_map<int, const OffsetTable> reverse_tables_;
252 : };
253 :
254 4543 : WasmTranslation::WasmTranslation(v8::Isolate* isolate)
255 13629 : : isolate_(isolate), mode_(Disassemble) {}
256 :
257 9086 : WasmTranslation::~WasmTranslation() { Clear(); }
258 :
259 56 : void WasmTranslation::AddScript(Local<debug::WasmScript> script,
260 : V8DebuggerAgentImpl* agent) {
261 : std::unique_ptr<TranslatorImpl> impl;
262 56 : switch (mode_) {
263 : case Raw:
264 0 : impl.reset(new TranslatorImpl::RawTranslator());
265 : break;
266 : case Disassemble:
267 56 : impl.reset(new TranslatorImpl::DisassemblingTranslator(isolate_, script));
268 56 : break;
269 : }
270 : DCHECK(impl);
271 : auto inserted =
272 112 : wasm_translators_.insert(std::make_pair(script->Id(), std::move(impl)));
273 : // Check that no mapping for this script id existed before.
274 : DCHECK(inserted.second);
275 : // impl has been moved, use the returned iterator to call Init.
276 56 : inserted.first->second->Init(isolate_, this, agent);
277 56 : }
278 :
279 4309 : void WasmTranslation::Clear() {
280 : wasm_translators_.clear();
281 : fake_scripts_.clear();
282 4309 : }
283 :
284 : // Translation "forward" (to artificial scripts).
285 194048 : bool WasmTranslation::TranslateWasmScriptLocationToProtocolLocation(
286 : String16* script_id, int* line_number, int* column_number) {
287 : DCHECK(script_id && line_number && column_number);
288 194048 : bool ok = true;
289 194048 : int script_id_int = script_id->toInteger(&ok);
290 194048 : if (!ok) return false;
291 :
292 : auto it = wasm_translators_.find(script_id_int);
293 194048 : if (it == wasm_translators_.end()) return false;
294 : TranslatorImpl* translator = it->second.get();
295 :
296 : TranslatorImpl::TransLocation trans_loc(this, std::move(*script_id),
297 706 : *line_number, *column_number);
298 706 : translator->Translate(&trans_loc);
299 :
300 : *script_id = std::move(trans_loc.script_id);
301 706 : *line_number = trans_loc.line;
302 706 : *column_number = trans_loc.column;
303 :
304 : return true;
305 : }
306 :
307 : // Translation "backward" (from artificial to real scripts).
308 2482 : bool WasmTranslation::TranslateProtocolLocationToWasmScriptLocation(
309 : String16* script_id, int* line_number, int* column_number) {
310 : auto it = fake_scripts_.find(*script_id);
311 2482 : if (it == fake_scripts_.end()) return false;
312 138 : TranslatorImpl* translator = it->second;
313 :
314 : TranslatorImpl::TransLocation trans_loc(this, std::move(*script_id),
315 138 : *line_number, *column_number);
316 138 : translator->TranslateBack(&trans_loc);
317 :
318 : *script_id = std::move(trans_loc.script_id);
319 138 : *line_number = trans_loc.line;
320 138 : *column_number = trans_loc.column;
321 :
322 : return true;
323 : }
324 :
325 100 : void WasmTranslation::AddFakeScript(const String16& scriptId,
326 : TranslatorImpl* translator) {
327 : DCHECK_EQ(0, fake_scripts_.count(scriptId));
328 100 : fake_scripts_.insert(std::make_pair(scriptId, translator));
329 100 : }
|