Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2
* vim: set ts=8 sts=2 et sw=2 tw=80:
3
*
4
* Copyright 2016 Mozilla Foundation
5
*
6
* Licensed under the Apache License, Version 2.0 (the "License");
7
* you may not use this file except in compliance with the License.
8
* You may obtain a copy of the License at
9
*
11
*
12
* Unless required by applicable law or agreed to in writing, software
13
* distributed under the License is distributed on an "AS IS" BASIS,
14
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
* See the License for the specific language governing permissions and
16
* limitations under the License.
17
*/
18
19
#include "wasm/WasmDebug.h"
20
21
#include "mozilla/BinarySearch.h"
22
23
#include "debugger/Debugger.h"
24
#include "ds/Sort.h"
25
#include "jit/ExecutableAllocator.h"
26
#include "jit/MacroAssembler.h"
27
#include "wasm/WasmInstance.h"
28
#include "wasm/WasmValidate.h"
29
30
#include "gc/FreeOp-inl.h"
31
32
using namespace js;
33
using namespace js::jit;
34
using namespace js::wasm;
35
36
using mozilla::BinarySearchIf;
37
38
DebugState::DebugState(const Code& code, const Module& module)
39
: code_(&code),
40
module_(&module),
41
enterFrameTrapsEnabled_(false),
42
enterAndLeaveFrameTrapsCounter_(0) {
43
MOZ_ASSERT(code.metadata().debugEnabled);
44
}
45
46
void DebugState::trace(JSTracer* trc) {
47
for (auto iter = breakpointSites_.iter(); !iter.done(); iter.next()) {
48
WasmBreakpointSite* site = iter.get().value();
49
site->trace(trc);
50
}
51
}
52
53
void DebugState::finalize(JSFreeOp* fop) {
54
for (auto iter = breakpointSites_.iter(); !iter.done(); iter.next()) {
55
WasmBreakpointSite* site = iter.get().value();
56
site->delete_(fop);
57
}
58
}
59
60
static const uint32_t DefaultBinarySourceColumnNumber = 1;
61
62
static const CallSite* SlowCallSiteSearchByOffset(const MetadataTier& metadata,
63
uint32_t offset) {
64
for (const CallSite& callSite : metadata.callSites) {
65
if (callSite.lineOrBytecode() == offset &&
66
callSite.kind() == CallSiteDesc::Breakpoint) {
67
return &callSite;
68
}
69
}
70
return nullptr;
71
}
72
73
bool DebugState::getLineOffsets(size_t lineno, Vector<uint32_t>* offsets) {
74
const CallSite* callsite =
75
SlowCallSiteSearchByOffset(metadata(Tier::Debug), lineno);
76
if (callsite && !offsets->append(lineno)) {
77
return false;
78
}
79
return true;
80
}
81
82
bool DebugState::getAllColumnOffsets(Vector<ExprLoc>* offsets) {
83
for (const CallSite& callSite : metadata(Tier::Debug).callSites) {
84
if (callSite.kind() != CallSite::Breakpoint) {
85
continue;
86
}
87
uint32_t offset = callSite.lineOrBytecode();
88
if (!offsets->emplaceBack(offset, DefaultBinarySourceColumnNumber,
89
offset)) {
90
return false;
91
}
92
}
93
return true;
94
}
95
96
bool DebugState::getOffsetLocation(uint32_t offset, size_t* lineno,
97
size_t* column) {
98
if (!SlowCallSiteSearchByOffset(metadata(Tier::Debug), offset)) {
99
return false;
100
}
101
*lineno = offset;
102
*column = DefaultBinarySourceColumnNumber;
103
return true;
104
}
105
106
bool DebugState::stepModeEnabled(uint32_t funcIndex) const {
107
return stepperCounters_.lookup(funcIndex).found();
108
}
109
110
bool DebugState::incrementStepperCount(JSContext* cx, uint32_t funcIndex) {
111
const CodeRange& codeRange =
112
codeRanges(Tier::Debug)[funcToCodeRangeIndex(funcIndex)];
113
MOZ_ASSERT(codeRange.isFunction());
114
115
StepperCounters::AddPtr p = stepperCounters_.lookupForAdd(funcIndex);
116
if (p) {
117
MOZ_ASSERT(p->value() > 0);
118
p->value()++;
119
return true;
120
}
121
if (!stepperCounters_.add(p, funcIndex, 1)) {
122
ReportOutOfMemory(cx);
123
return false;
124
}
125
126
AutoWritableJitCode awjc(
127
cx->runtime(), code_->segment(Tier::Debug).base() + codeRange.begin(),
128
codeRange.end() - codeRange.begin());
129
130
for (const CallSite& callSite : callSites(Tier::Debug)) {
131
if (callSite.kind() != CallSite::Breakpoint) {
132
continue;
133
}
134
uint32_t offset = callSite.returnAddressOffset();
135
if (codeRange.begin() <= offset && offset <= codeRange.end()) {
136
toggleDebugTrap(offset, true);
137
}
138
}
139
return true;
140
}
141
142
void DebugState::decrementStepperCount(JSFreeOp* fop, uint32_t funcIndex) {
143
const CodeRange& codeRange =
144
codeRanges(Tier::Debug)[funcToCodeRangeIndex(funcIndex)];
145
MOZ_ASSERT(codeRange.isFunction());
146
147
MOZ_ASSERT(!stepperCounters_.empty());
148
StepperCounters::Ptr p = stepperCounters_.lookup(funcIndex);
149
MOZ_ASSERT(p);
150
if (--p->value()) {
151
return;
152
}
153
154
stepperCounters_.remove(p);
155
156
AutoWritableJitCode awjc(
157
fop->runtime(), code_->segment(Tier::Debug).base() + codeRange.begin(),
158
codeRange.end() - codeRange.begin());
159
160
for (const CallSite& callSite : callSites(Tier::Debug)) {
161
if (callSite.kind() != CallSite::Breakpoint) {
162
continue;
163
}
164
uint32_t offset = callSite.returnAddressOffset();
165
if (codeRange.begin() <= offset && offset <= codeRange.end()) {
166
bool enabled = breakpointSites_.has(offset);
167
toggleDebugTrap(offset, enabled);
168
}
169
}
170
}
171
172
bool DebugState::hasBreakpointTrapAtOffset(uint32_t offset) {
173
return SlowCallSiteSearchByOffset(metadata(Tier::Debug), offset);
174
}
175
176
void DebugState::toggleBreakpointTrap(JSRuntime* rt, uint32_t offset,
177
bool enabled) {
178
const CallSite* callSite =
179
SlowCallSiteSearchByOffset(metadata(Tier::Debug), offset);
180
if (!callSite) {
181
return;
182
}
183
size_t debugTrapOffset = callSite->returnAddressOffset();
184
185
const ModuleSegment& codeSegment = code_->segment(Tier::Debug);
186
const CodeRange* codeRange =
187
code_->lookupFuncRange(codeSegment.base() + debugTrapOffset);
188
MOZ_ASSERT(codeRange);
189
190
if (stepperCounters_.lookup(codeRange->funcIndex())) {
191
return; // no need to toggle when step mode is enabled
192
}
193
194
AutoWritableJitCode awjc(rt, codeSegment.base(), codeSegment.length());
195
toggleDebugTrap(debugTrapOffset, enabled);
196
}
197
198
WasmBreakpointSite* DebugState::getBreakpointSite(uint32_t offset) const {
199
WasmBreakpointSiteMap::Ptr p = breakpointSites_.lookup(offset);
200
if (!p) {
201
return nullptr;
202
}
203
204
return p->value();
205
}
206
207
WasmBreakpointSite* DebugState::getOrCreateBreakpointSite(JSContext* cx,
208
Instance* instance,
209
uint32_t offset) {
210
WasmBreakpointSite* site;
211
212
WasmBreakpointSiteMap::AddPtr p = breakpointSites_.lookupForAdd(offset);
213
if (!p) {
214
site = cx->new_<WasmBreakpointSite>(instance->object(), offset);
215
if (!site) {
216
return nullptr;
217
}
218
219
if (!breakpointSites_.add(p, offset, site)) {
220
js_delete(site);
221
ReportOutOfMemory(cx);
222
return nullptr;
223
}
224
225
AddCellMemory(instance->object(), sizeof(WasmBreakpointSite),
226
MemoryUse::BreakpointSite);
227
228
toggleBreakpointTrap(cx->runtime(), offset, true);
229
} else {
230
site = p->value();
231
}
232
return site;
233
}
234
235
bool DebugState::hasBreakpointSite(uint32_t offset) {
236
return breakpointSites_.has(offset);
237
}
238
239
void DebugState::destroyBreakpointSite(JSFreeOp* fop, Instance* instance,
240
uint32_t offset) {
241
WasmBreakpointSiteMap::Ptr p = breakpointSites_.lookup(offset);
242
MOZ_ASSERT(p);
243
fop->delete_(instance->objectUnbarriered(), p->value(),
244
MemoryUse::BreakpointSite);
245
breakpointSites_.remove(p);
246
toggleBreakpointTrap(fop->runtime(), offset, false);
247
}
248
249
void DebugState::clearBreakpointsIn(JSFreeOp* fop, WasmInstanceObject* instance,
250
js::Debugger* dbg, JSObject* handler) {
251
MOZ_ASSERT(instance);
252
253
// Breakpoints hold wrappers in the instance's compartment for the handler.
254
// Make sure we don't try to search for the unwrapped handler.
255
MOZ_ASSERT_IF(handler, instance->compartment() == handler->compartment());
256
257
if (breakpointSites_.empty()) {
258
return;
259
}
260
for (WasmBreakpointSiteMap::Enum e(breakpointSites_); !e.empty();
261
e.popFront()) {
262
WasmBreakpointSite* site = e.front().value();
263
MOZ_ASSERT(site->instanceObject == instance);
264
265
Breakpoint* nextbp;
266
for (Breakpoint* bp = site->firstBreakpoint(); bp; bp = nextbp) {
267
nextbp = bp->nextInSite();
268
MOZ_ASSERT(bp->site == site);
269
if ((!dbg || bp->debugger == dbg) &&
270
(!handler || bp->getHandler() == handler)) {
271
bp->delete_(fop);
272
}
273
}
274
if (site->isEmpty()) {
275
fop->delete_(instance, site, MemoryUse::BreakpointSite);
276
e.removeFront();
277
}
278
}
279
}
280
281
void DebugState::clearAllBreakpoints(JSFreeOp* fop,
282
WasmInstanceObject* instance) {
283
clearBreakpointsIn(fop, instance, nullptr, nullptr);
284
}
285
286
void DebugState::toggleDebugTrap(uint32_t offset, bool enabled) {
287
MOZ_ASSERT(offset);
288
uint8_t* trap = code_->segment(Tier::Debug).base() + offset;
289
const Uint32Vector& farJumpOffsets =
290
metadata(Tier::Debug).debugTrapFarJumpOffsets;
291
if (enabled) {
292
MOZ_ASSERT(farJumpOffsets.length() > 0);
293
size_t i = 0;
294
while (i < farJumpOffsets.length() && offset < farJumpOffsets[i]) {
295
i++;
296
}
297
if (i >= farJumpOffsets.length() ||
298
(i > 0 && offset - farJumpOffsets[i - 1] < farJumpOffsets[i] - offset))
299
i--;
300
uint8_t* farJump = code_->segment(Tier::Debug).base() + farJumpOffsets[i];
301
MacroAssembler::patchNopToCall(trap, farJump);
302
} else {
303
MacroAssembler::patchCallToNop(trap);
304
}
305
}
306
307
void DebugState::adjustEnterAndLeaveFrameTrapsState(JSContext* cx,
308
bool enabled) {
309
MOZ_ASSERT_IF(!enabled, enterAndLeaveFrameTrapsCounter_ > 0);
310
311
bool wasEnabled = enterAndLeaveFrameTrapsCounter_ > 0;
312
if (enabled) {
313
++enterAndLeaveFrameTrapsCounter_;
314
} else {
315
--enterAndLeaveFrameTrapsCounter_;
316
}
317
bool stillEnabled = enterAndLeaveFrameTrapsCounter_ > 0;
318
if (wasEnabled == stillEnabled) {
319
return;
320
}
321
322
const ModuleSegment& codeSegment = code_->segment(Tier::Debug);
323
AutoWritableJitCode awjc(cx->runtime(), codeSegment.base(),
324
codeSegment.length());
325
for (const CallSite& callSite : callSites(Tier::Debug)) {
326
if (callSite.kind() != CallSite::EnterFrame &&
327
callSite.kind() != CallSite::LeaveFrame) {
328
continue;
329
}
330
toggleDebugTrap(callSite.returnAddressOffset(), stillEnabled);
331
}
332
}
333
334
void DebugState::ensureEnterFrameTrapsState(JSContext* cx, bool enabled) {
335
if (enterFrameTrapsEnabled_ == enabled) {
336
return;
337
}
338
339
adjustEnterAndLeaveFrameTrapsState(cx, enabled);
340
341
enterFrameTrapsEnabled_ = enabled;
342
}
343
344
bool DebugState::debugGetLocalTypes(uint32_t funcIndex, ValTypeVector* locals,
345
size_t* argsLength) {
346
const ValTypeVector& args = metadata().debugFuncArgTypes[funcIndex];
347
*argsLength = args.length();
348
if (!locals->appendAll(args)) {
349
return false;
350
}
351
352
// Decode local var types from wasm binary function body.
353
const CodeRange& range =
354
codeRanges(Tier::Debug)[funcToCodeRangeIndex(funcIndex)];
355
// In wasm, the Code points to the function start via funcLineOrBytecode.
356
size_t offsetInModule = range.funcLineOrBytecode();
357
Decoder d(bytecode().begin() + offsetInModule, bytecode().end(),
358
offsetInModule,
359
/* error = */ nullptr);
360
return DecodeValidatedLocalEntries(d, locals);
361
}
362
363
bool DebugState::debugGetResultTypes(uint32_t funcIndex,
364
ValTypeVector* results) {
365
return results->appendAll(metadata().debugFuncReturnTypes[funcIndex]);
366
}
367
368
bool DebugState::getGlobal(Instance& instance, uint32_t globalIndex,
369
MutableHandleValue vp) {
370
const GlobalDesc& global = metadata().globals[globalIndex];
371
372
if (global.isConstant()) {
373
LitVal value = global.constantValue();
374
switch (value.type().code()) {
375
case ValType::I32:
376
vp.set(Int32Value(value.i32()));
377
break;
378
case ValType::I64:
379
// Just display as a Number; it's ok if we lose some precision
380
vp.set(NumberValue((double)value.i64()));
381
break;
382
case ValType::F32:
383
vp.set(NumberValue(JS::CanonicalizeNaN(value.f32())));
384
break;
385
case ValType::F64:
386
vp.set(NumberValue(JS::CanonicalizeNaN(value.f64())));
387
break;
388
default:
389
MOZ_CRASH("Global constant type");
390
}
391
return true;
392
}
393
394
uint8_t* globalData = instance.globalData();
395
void* dataPtr = globalData + global.offset();
396
if (global.isIndirect()) {
397
dataPtr = *static_cast<void**>(dataPtr);
398
}
399
switch (global.type().code()) {
400
case ValType::I32: {
401
vp.set(Int32Value(*static_cast<int32_t*>(dataPtr)));
402
break;
403
}
404
case ValType::I64: {
405
// Just display as a Number; it's ok if we lose some precision
406
vp.set(NumberValue((double)*static_cast<int64_t*>(dataPtr)));
407
break;
408
}
409
case ValType::F32: {
410
vp.set(NumberValue(JS::CanonicalizeNaN(*static_cast<float*>(dataPtr))));
411
break;
412
}
413
case ValType::F64: {
414
vp.set(NumberValue(JS::CanonicalizeNaN(*static_cast<double*>(dataPtr))));
415
break;
416
}
417
default:
418
MOZ_CRASH("Global variable type");
419
break;
420
}
421
return true;
422
}
423
424
bool DebugState::getSourceMappingURL(JSContext* cx,
425
MutableHandleString result) const {
426
result.set(nullptr);
427
428
for (const CustomSection& customSection : module_->customSections()) {
429
const Bytes& sectionName = customSection.name;
430
if (strlen(SourceMappingURLSectionName) != sectionName.length() ||
431
memcmp(SourceMappingURLSectionName, sectionName.begin(),
432
sectionName.length()) != 0) {
433
continue;
434
}
435
436
// Parse found "SourceMappingURL" custom section.
437
Decoder d(customSection.payload->begin(), customSection.payload->end(), 0,
438
/* error = */ nullptr);
439
uint32_t nchars;
440
if (!d.readVarU32(&nchars)) {
441
return true; // ignoring invalid section data
442
}
443
const uint8_t* chars;
444
if (!d.readBytes(nchars, &chars) || d.currentPosition() != d.end()) {
445
return true; // ignoring invalid section data
446
}
447
448
UTF8Chars utf8Chars(reinterpret_cast<const char*>(chars), nchars);
449
JSString* str = JS_NewStringCopyUTF8N(cx, utf8Chars);
450
if (!str) {
451
return false;
452
}
453
result.set(str);
454
return true;
455
}
456
457
// Check presence of "SourceMap:" HTTP response header.
458
char* sourceMapURL = metadata().sourceMapURL.get();
459
if (sourceMapURL && strlen(sourceMapURL)) {
460
UTF8Chars utf8Chars(sourceMapURL, strlen(sourceMapURL));
461
JSString* str = JS_NewStringCopyUTF8N(cx, utf8Chars);
462
if (!str) {
463
return false;
464
}
465
result.set(str);
466
}
467
return true;
468
}
469
470
void DebugState::addSizeOfMisc(MallocSizeOf mallocSizeOf,
471
Metadata::SeenSet* seenMetadata,
472
Code::SeenSet* seenCode, size_t* code,
473
size_t* data) const {
474
code_->addSizeOfMiscIfNotSeen(mallocSizeOf, seenMetadata, seenCode, code,
475
data);
476
module_->addSizeOfMisc(mallocSizeOf, seenMetadata, seenCode, code, data);
477
}