Quelle WasmDebug.cpp
Sprache: C
/*
* Copyright 2016 Mozilla Foundation
*
* Licensed under the Apache License , Version 2 . 0 ( the " License " ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an " AS IS " BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
*/
#include "wasm/WasmDebug.h"
#include "debugger/Debugger.h"
#include "ds/Sort.h"
#include "jit/MacroAssembler.h"
#include "js/ColumnNumber.h" // JS::WasmFunctionIndex
#include "wasm/WasmJS.h"
#include "wasm/WasmStubs.h"
#include "wasm/WasmValidate.h"
#include "gc/GCContext-inl.h"
#include "wasm/WasmInstance-inl.h"
using namespace js;
using namespace js::jit;
using namespace js::wasm;
DebugState::DebugState(const Code& code, const Module& module)
: code_(&code),
module_(&module),
enterFrameTrapsEnabled_(false ),
enterAndLeaveFrameTrapsCounter_(0 ) {
MOZ_RELEASE_ASSERT(code.debugEnabled());
}
void DebugState::trace(JSTracer* trc) {
for (auto iter = breakpointSites_.iter(); !iter.done(); iter.next()) {
WasmBreakpointSite* site = iter.get().value();
site->trace(trc);
}
}
void DebugState::finalize(JS::GCContext* gcx) {
for (auto iter = breakpointSites_.iter(); !iter.done(); iter.next()) {
WasmBreakpointSite* site = iter.get().value();
site->delete_(gcx);
}
}
static bool SlowCallSiteSearchByOffset(const CodeBlock& code, uint32_t offset,
CallSite* callSite) {
for (uint32_t callSiteIndex = 0 ; callSiteIndex < code.callSites.length();
callSiteIndex++) {
if (code.callSites.kind(callSiteIndex) == CallSiteKind::Breakpoint &&
code.callSites.bytecodeOffset(callSiteIndex).offset() == offset) {
*callSite = code.callSites.get(callSiteIndex, code.inliningContext);
return true ;
}
}
return false ;
}
bool DebugState::getLineOffsets(size_t lineno, Vector<uint32_t>* offsets) {
CallSite callSite;
return !SlowCallSiteSearchByOffset(debugCode(), lineno, &callSite) ||
offsets->append(lineno);
}
bool DebugState::getAllColumnOffsets(Vector<ExprLoc>* offsets) {
for (uint32_t callSiteIndex = 0 ;
callSiteIndex < debugCode().callSites.length(); callSiteIndex++) {
if (debugCode().callSites.kind(callSiteIndex) != CallSiteKind::Breakpoint) {
continue ;
}
uint32_t offset =
debugCode().callSites.bytecodeOffset(callSiteIndex).offset();
if (!offsets->emplaceBack(
offset,
JS::WasmFunctionIndex::DefaultBinarySourceColumnNumberOneOrigin,
offset)) {
return false ;
}
}
return true ;
}
bool DebugState::getOffsetLocation(uint32_t offset, uint32_t* lineno,
JS::LimitedColumnNumberOneOrigin* column) {
CallSite callSite;
if (!SlowCallSiteSearchByOffset(debugCode(), offset, &callSite)) {
return false ;
}
*lineno = offset;
*column = JS::LimitedColumnNumberOneOrigin(
JS::WasmFunctionIndex::DefaultBinarySourceColumnNumberOneOrigin);
return true ;
}
bool DebugState::stepModeEnabled(uint32_t funcIndex) const {
return stepperCounters_.lookup(funcIndex).found();
}
bool DebugState::incrementStepperCount(JSContext* cx, Instance* instance,
uint32_t funcIndex) {
StepperCounters::AddPtr p = stepperCounters_.lookupForAdd(funcIndex);
if (p) {
MOZ_ASSERT(p->value() > 0 );
p->value()++;
return true ;
}
if (!stepperCounters_.add(p, funcIndex, 1 )) {
ReportOutOfMemory(cx);
return false ;
}
enableDebuggingForFunction(instance, funcIndex);
enableDebugTrapping(instance);
return true ;
}
void DebugState::decrementStepperCount(JS::GCContext* gcx, Instance* instance,
uint32_t funcIndex) {
const CodeRange& codeRange =
debugCode().codeRanges[funcToCodeRangeIndex(funcIndex)];
MOZ_ASSERT(codeRange.isFunction());
MOZ_ASSERT(!stepperCounters_.empty());
StepperCounters::Ptr p = stepperCounters_.lookup(funcIndex);
MOZ_ASSERT(p);
if (--p->value()) {
return ;
}
stepperCounters_.remove(p);
bool anyStepping = !stepperCounters_.empty();
bool anyBreakpoints = !breakpointSites_.empty();
bool anyEnterAndLeave = enterAndLeaveFrameTrapsCounter_ > 0 ;
bool keepDebugging = false ;
for (uint32_t callSiteIndex = 0 ;
callSiteIndex < debugCode().callSites.length(); callSiteIndex++) {
if (debugCode().callSites.kind(callSiteIndex) != CallSiteKind::Breakpoint) {
continue ;
}
uint32_t offset = debugCode().callSites.returnAddressOffset(callSiteIndex);
if (codeRange.begin() <= offset && offset <= codeRange.end()) {
keepDebugging = keepDebugging || breakpointSites_.has(offset);
}
}
if (!keepDebugging && !anyEnterAndLeave) {
disableDebuggingForFunction(instance, funcIndex);
if (!anyStepping && !anyBreakpoints) {
disableDebugTrapping(instance);
}
}
}
bool DebugState::hasBreakpointTrapAtOffset(uint32_t offset) {
CallSite callSite;
return SlowCallSiteSearchByOffset(debugCode(), offset, &callSite);
}
void DebugState::toggleBreakpointTrap(JSRuntime* rt, Instance* instance,
uint32_t offset, bool enabled) {
CallSite callSite;
if (!SlowCallSiteSearchByOffset(debugCode(), offset, &callSite)) {
return ;
}
size_t debugTrapOffset = callSite.returnAddressOffset();
const CodeRange* codeRange =
code_->lookupFuncRange(debugCode().base() + debugTrapOffset);
MOZ_ASSERT(codeRange);
uint32_t funcIndex = codeRange->funcIndex();
if (stepperCounters_.lookup(funcIndex)) {
return ; // no need to toggle when step mode is enabled
}
bool anyEnterAndLeave = enterAndLeaveFrameTrapsCounter_ > 0 ;
bool anyStepping = !stepperCounters_.empty();
bool anyBreakpoints = !breakpointSites_.empty();
if (enabled) {
enableDebuggingForFunction(instance, funcIndex);
enableDebugTrapping(instance);
} else if (!anyEnterAndLeave) {
disableDebuggingForFunction(instance, funcIndex);
if (!anyStepping && !anyBreakpoints) {
disableDebugTrapping(instance);
}
}
}
WasmBreakpointSite* DebugState::getBreakpointSite(uint32_t offset) const {
WasmBreakpointSiteMap::Ptr p = breakpointSites_.lookup(offset);
if (!p) {
return nullptr;
}
return p->value();
}
WasmBreakpointSite* DebugState::getOrCreateBreakpointSite(JSContext* cx,
Instance* instance,
uint32_t offset) {
WasmBreakpointSite* site;
WasmBreakpointSiteMap::AddPtr p = breakpointSites_.lookupForAdd(offset);
if (!p) {
site = cx->new_<WasmBreakpointSite>(instance->object(), offset);
if (!site) {
return nullptr;
}
if (!breakpointSites_.add(p, offset, site)) {
js_delete(site);
ReportOutOfMemory(cx);
return nullptr;
}
AddCellMemory(instance->object(), sizeof (WasmBreakpointSite),
MemoryUse::BreakpointSite);
toggleBreakpointTrap(cx->runtime(), instance, offset, true );
} else {
site = p->value();
}
return site;
}
bool DebugState::hasBreakpointSite(uint32_t offset) {
return breakpointSites_.has(offset);
}
void DebugState::destroyBreakpointSite(JS::GCContext* gcx, Instance* instance,
uint32_t offset) {
WasmBreakpointSiteMap::Ptr p = breakpointSites_.lookup(offset);
MOZ_ASSERT(p);
gcx->delete_(instance->objectUnbarriered(), p->value(),
MemoryUse::BreakpointSite);
breakpointSites_.remove(p);
toggleBreakpointTrap(gcx->runtime(), instance, offset, false );
}
void DebugState::clearBreakpointsIn(JS::GCContext* gcx,
WasmInstanceObject* instance,
js::Debugger* dbg, JSObject* handler) {
MOZ_ASSERT(instance);
// Breakpoints hold wrappers in the instance's compartment for the handler.
// Make sure we don't try to search for the unwrapped handler.
MOZ_ASSERT_IF(handler, instance->compartment() == handler->compartment());
if (breakpointSites_.empty()) {
return ;
}
for (auto iter = breakpointSites_.modIter(); !iter.done(); iter.next()) {
WasmBreakpointSite* site = iter.get().value();
MOZ_ASSERT(site->instanceObject == instance);
Breakpoint* nextbp;
for (Breakpoint* bp = site->firstBreakpoint(); bp; bp = nextbp) {
nextbp = bp->nextInSite();
MOZ_ASSERT(bp->site == site);
if ((!dbg || bp->debugger == dbg) &&
(!handler || bp->getHandler() == handler)) {
bp->delete_(gcx);
}
}
if (site->isEmpty()) {
gcx->delete_(instance, site, MemoryUse::BreakpointSite);
iter.remove();
}
}
}
void DebugState::enableDebuggingForFunction(Instance* instance,
uint32_t funcIndex) {
instance->setDebugFilter(funcIndex, true );
}
void DebugState::disableDebuggingForFunction(Instance* instance,
uint32_t funcIndex) {
instance->setDebugFilter(funcIndex, false );
}
void DebugState::enableDebugTrapping(Instance* instance) {
instance->setDebugStub(code_->sharedStubs().base() +
code_->debugStubOffset());
}
void DebugState::disableDebugTrapping(Instance* instance) {
instance->setDebugStub(nullptr);
}
void DebugState::adjustEnterAndLeaveFrameTrapsState(JSContext* cx,
Instance* instance,
bool enabled) {
MOZ_ASSERT_IF(!enabled, enterAndLeaveFrameTrapsCounter_ > 0 );
bool wasEnabled = enterAndLeaveFrameTrapsCounter_ > 0 ;
enterAndLeaveFrameTrapsCounter_ += enabled ? 1 : -1 ;
bool stillEnabled = enterAndLeaveFrameTrapsCounter_ > 0 ;
if (wasEnabled == stillEnabled) {
return ;
}
MOZ_RELEASE_ASSERT(&instance->codeMeta() == &codeMeta());
MOZ_RELEASE_ASSERT(instance->codeMetaForAsmJS() == codeMetaForAsmJS());
uint32_t numFuncs = codeMeta().numFuncs();
if (enabled) {
MOZ_ASSERT(enterAndLeaveFrameTrapsCounter_ > 0 );
for (uint32_t funcIdx = 0 ; funcIdx < numFuncs; funcIdx++) {
enableDebuggingForFunction(instance, funcIdx);
}
enableDebugTrapping(instance);
} else {
MOZ_ASSERT(enterAndLeaveFrameTrapsCounter_ == 0 );
bool anyEnabled = false ;
for (uint32_t funcIdx = 0 ; funcIdx < numFuncs; funcIdx++) {
// For each function, disable the bit if nothing else is going on. This
// means determining if there's stepping or breakpoints.
bool mustLeaveEnabled = stepperCounters_.lookup(funcIdx).found();
for (auto iter = breakpointSites_.iter();
!iter.done() && !mustLeaveEnabled; iter.next()) {
WasmBreakpointSite* site = iter.get().value();
CallSite callSite;
const CodeBlock& codeBlock = debugCode();
if (SlowCallSiteSearchByOffset(codeBlock, site->offset, &callSite)) {
size_t debugTrapOffset = callSite.returnAddressOffset();
const CodeRange* codeRange =
code_->lookupFuncRange(codeBlock.base() + debugTrapOffset);
MOZ_ASSERT(codeRange);
mustLeaveEnabled = codeRange->funcIndex() == funcIdx;
}
}
if (mustLeaveEnabled) {
anyEnabled = true ;
} else {
disableDebuggingForFunction(instance, funcIdx);
}
}
if (!anyEnabled) {
disableDebugTrapping(instance);
}
}
}
void DebugState::ensureEnterFrameTrapsState(JSContext* cx, Instance* instance,
bool enabled) {
if (enterFrameTrapsEnabled_ == enabled) {
return ;
}
adjustEnterAndLeaveFrameTrapsState(cx, instance, enabled);
enterFrameTrapsEnabled_ = enabled;
}
bool DebugState::debugGetLocalTypes(uint32_t funcIndex, ValTypeVector* locals,
size_t* argsLength,
StackResults* stackResults) {
const TypeContext& types = *codeMeta().types;
const FuncType& funcType = codeMeta().getFuncType(funcIndex);
const ValTypeVector& args = funcType.args();
const ValTypeVector& results = funcType.results();
ResultType resultType(ResultType::Vector(results));
*argsLength = args.length();
*stackResults = ABIResultIter::HasStackResults(resultType)
? StackResults::HasStackResults
: StackResults::NoStackResults;
if (!locals->appendAll(args)) {
return false ;
}
// Decode local var types from wasm binary function body.
const BytecodeRange& funcRange = codeTailMeta().funcDefRange(funcIndex);
BytecodeSpan funcBytecode = codeTailMeta().funcDefBody(funcIndex);
Decoder d(funcBytecode.data(), funcBytecode.data() + funcBytecode.size(),
funcRange.start,
/* error = */ nullptr);
return DecodeValidatedLocalEntries(types, d, locals);
}
bool DebugState::getGlobal(Instance& instance, uint32_t globalIndex,
MutableHandleValue vp) {
const GlobalDesc& global = codeMeta().globals[globalIndex];
if (global.isConstant()) {
LitVal value = global.constantValue();
switch (value.type().kind()) {
case ValType::I32:
vp.set(Int32Value(value.i32()));
break ;
case ValType::I64:
// Just display as a Number; it's ok if we lose some precision
vp.set(NumberValue((double )value.i64()));
break ;
case ValType::F32:
vp.set(NumberValue(value.f32()));
break ;
case ValType::F64:
vp.set(NumberValue(value.f64()));
break ;
case ValType::Ref:
// It's possible to do better. We could try some kind of hashing
// scheme, to make the pointer recognizable without revealing it.
vp.set(MagicValue(JS_OPTIMIZED_OUT));
break ;
case ValType::V128:
// Debugger must be updated to handle this, and should be updated to
// handle i64 in any case.
vp.set(MagicValue(JS_OPTIMIZED_OUT));
break ;
default :
MOZ_CRASH("Global constant type" );
}
return true ;
}
void * dataPtr = instance.data() + global.offset();
if (global.isIndirect()) {
dataPtr = *static_cast <void **>(dataPtr);
}
switch (global.type().kind()) {
case ValType::I32: {
vp.set(Int32Value(*static_cast <int32_t*>(dataPtr)));
break ;
}
case ValType::I64: {
// Just display as a Number; it's ok if we lose some precision
vp.set(NumberValue((double )*static_cast <int64_t*>(dataPtr)));
break ;
}
case ValType::F32: {
vp.set(NumberValue(*static_cast <float *>(dataPtr)));
break ;
}
case ValType::F64: {
vp.set(NumberValue(*static_cast <double *>(dataPtr)));
break ;
}
case ValType::Ref: {
// Just hide it. See above.
vp.set(MagicValue(JS_OPTIMIZED_OUT));
break ;
}
case ValType::V128: {
// Just hide it. See above.
vp.set(MagicValue(JS_OPTIMIZED_OUT));
break ;
}
default : {
MOZ_CRASH("Global variable type" );
break ;
}
}
return true ;
}
bool DebugState::getSourceMappingURL(JSContext* cx,
MutableHandleString result) const {
result.set(nullptr);
for (const CustomSection& customSection :
module_->moduleMeta().customSections) {
const Bytes& sectionName = customSection.name;
if (strlen(SourceMappingURLSectionName) != sectionName.length() ||
memcmp(SourceMappingURLSectionName, sectionName.begin(),
sectionName.length()) != 0 ) {
continue ;
}
// Parse found "SourceMappingURL" custom section.
Decoder d(customSection.payload->begin(), customSection.payload->end(), 0 ,
/* error = */ nullptr);
uint32_t nchars;
if (!d.readVarU32(&nchars)) {
return true ; // ignoring invalid section data
}
const uint8_t* chars;
if (!d.readBytes(nchars, &chars) || d.currentPosition() != d.end()) {
return true ; // ignoring invalid section data
}
JS::UTF8Chars utf8Chars(reinterpret_cast <const char *>(chars), nchars);
JSString* str = JS_NewStringCopyUTF8N(cx, utf8Chars);
if (!str) {
return false ;
}
result.set(str);
return true ;
}
// Check presence of "SourceMap:" HTTP response header.
char * sourceMapURL = codeMeta().sourceMapURL().get();
if (sourceMapURL && strlen(sourceMapURL)) {
JS::UTF8Chars utf8Chars(sourceMapURL, strlen(sourceMapURL));
JSString* str = JS_NewStringCopyUTF8N(cx, utf8Chars);
if (!str) {
return false ;
}
result.set(str);
}
return true ;
}
void DebugState::addSizeOfMisc(
mozilla::MallocSizeOf mallocSizeOf, CodeMetadata::SeenSet* seenCodeMeta,
CodeMetadataForAsmJS::SeenSet* seenCodeMetaForAsmJS,
Code::SeenSet* seenCode, size_t* code, size_t* data) const {
code_->addSizeOfMiscIfNotSeen(mallocSizeOf, seenCodeMeta,
seenCodeMetaForAsmJS, seenCode, code, data);
module_->addSizeOfMisc(mallocSizeOf, seenCodeMeta, seenCodeMetaForAsmJS,
seenCode, code, data);
}
Messung V0.5 in Prozent C=96 H=91 G=93
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland
2026-10-10
Neuigkeiten
Aktuelles
Motto des Tages
Open Source Software
Jenseits des Üblichen ....
Besucherstatistik
Statistik
#Sources=1126438
#Domains=1897691