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