Skip to content

[GR-62872] WebAssembly debugger fixes and extensions. #11073

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Apr 24, 2025
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -55,6 +55,8 @@
import java.util.regex.PatternSyntaxException;

import org.graalvm.collections.Pair;
import org.graalvm.shadowed.org.json.JSONArray;
import org.graalvm.shadowed.org.json.JSONObject;

import com.oracle.truffle.api.debug.Breakpoint;
import com.oracle.truffle.api.debug.DebugException;
Expand Down Expand Up @@ -94,8 +96,6 @@
import com.oracle.truffle.tools.chromeinspector.types.Script;
import com.oracle.truffle.tools.chromeinspector.types.StackTrace;
import com.oracle.truffle.tools.chromeinspector.util.LineSearch;
import org.graalvm.shadowed.org.json.JSONArray;
import org.graalvm.shadowed.org.json.JSONObject;

public final class InspectorDebugger extends DebuggerDomain {

Expand Down Expand Up @@ -338,9 +338,15 @@ public Params getScriptSource(String scriptId) throws CommandProcessException {
if (scriptId == null) {
throw new CommandProcessException("A scriptId required.");
}
CharSequence characters = getScript(scriptId).getCharacters();
Script script = getScript(scriptId);
JSONObject json = new JSONObject();
json.put("scriptSource", characters.toString());
if (script.hasWasmSource()) {
CharSequence bytecode = script.getWasmBytecode();
json.put("bytecode", bytecode.toString());
} else {
CharSequence characters = script.getCharacters();
json.put("scriptSource", characters.toString());
}
return new Params(json);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand All @@ -25,6 +25,7 @@
package com.oracle.truffle.tools.chromeinspector.types;

import java.text.MessageFormat;
import java.util.Base64;

import com.oracle.truffle.api.source.Source;

Expand Down Expand Up @@ -62,6 +63,10 @@ public Source getSourceLoaded() {
return sourceLoaded;
}

public boolean hasWasmSource() {
return source.hasBytes() && "application/wasm".equals(source.getMimeType());
}

public CharSequence getCharacters() {
if (source.hasCharacters()) {
return source.getCharacters();
Expand All @@ -72,6 +77,19 @@ public CharSequence getCharacters() {
}
}

/**
* @return A base64 encoded representation of the wasm bytecode inside this script.
*/
public CharSequence getWasmBytecode() {
if (source.hasBytes()) {
return Base64.getEncoder().encodeToString(source.getBytes().toByteArray());
} else {
return MessageFormat.format("Can not load source from {0}\n" +
"Please use the --inspect.SourcePath option to point to the source locations.\n" +
"Example: --inspect.SourcePath=/home/joe/project/src\n", source.getURI().toString());
}
}

public String getHash() {
CharSequence code = getCharacters();
// See
Expand Down
4 changes: 4 additions & 0 deletions wasm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

This changelog summarizes major changes to the WebAssembly engine implemented in GraalVM (GraalWasm).

## Version 25.0.0

* Implemented support for editing primitive values during debugging. Fixed several debugger-related issues.

## Version 24.2.0

* Updated developer metadata of Maven artifacts.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -41,9 +41,10 @@

package org.graalvm.wasm.test.suites.debugging;

import com.oracle.truffle.api.source.Source;
import java.nio.file.Path;
import java.util.ArrayList;

import org.graalvm.collections.EconomicMap;
import org.graalvm.wasm.WasmLanguage;
import org.graalvm.wasm.collection.LongArrayList;
import org.graalvm.wasm.debugging.DebugLineMap;
import org.graalvm.wasm.debugging.data.DebugFunction;
Expand All @@ -60,9 +61,6 @@
import org.junit.Assert;
import org.junit.Test;

import java.nio.file.Path;
import java.util.ArrayList;

/**
* Test suite for debug entries based on the specification in the
* <a href="https://dwarfstd.org/doc/DWARF4.pdf">DWARF Debug Information format</a>.
Expand Down Expand Up @@ -118,7 +116,7 @@ private static DebugParserContext parseCompilationUnit(TestObjectFactory factory
return parseCompilationUnit(factory, null, null, children);
}

private static DebugParserContext parseCompilationUnit(TestObjectFactory factory, DebugLineMap lineMap, Source source, DebugData... children) {
private static DebugParserContext parseCompilationUnit(TestObjectFactory factory, DebugLineMap lineMap, Path path, DebugData... children) {
final byte[] data = {};
final DebugData compUnit = getCompilationUnit(children);
final DebugLineMap[] lineMaps;
Expand All @@ -127,13 +125,13 @@ private static DebugParserContext parseCompilationUnit(TestObjectFactory factory
} else {
lineMaps = null;
}
final Source[] sources;
if (source != null) {
sources = new Source[]{source};
final Path[] paths;
if (path != null) {
paths = new Path[]{path};
} else {
sources = null;
paths = null;
}
final DebugParserContext context = new DebugParserContext(data, 0, getEntryData(compUnit), lineMaps, sources);
final DebugParserContext context = new DebugParserContext(data, 0, getEntryData(compUnit), lineMaps, paths, "wasm", factory);
final DebugParserScope scope = DebugParserScope.createGlobalScope();
for (DebugData d : compUnit.children()) {
factory.parse(context, scope, d);
Expand Down Expand Up @@ -1073,29 +1071,29 @@ public void testEnumTypeMissingBaseType() {

@Test
public void testFunction() {
final DebugLineMap lineMap = new DebugLineMap(Path.of(""));
final Path p = Path.of("");
final DebugLineMap lineMap = new DebugLineMap(p);
lineMap.add(0, 1);
lineMap.add(10, 2);
final Source s = Source.newBuilder(WasmLanguage.ID, "", "test").internal(true).build();
final AttributeBuilder funcAttr = AttributeBuilder.create().add(Attributes.DECL_FILE, 0x0F, 0).add(Attributes.NAME, 0x08, "func").add(Attributes.LOW_PC, 0x0F, 0).add(Attributes.HIGH_PC, 0x0F,
10).add(Attributes.FRAME_BASE, 0x09, new byte[]{});
final DebugData func = new DebugData(Tags.SUBPROGRAM, 1, funcAttr.attributeInfo(), funcAttr.attributeValues(), new DebugData[0]);

final TestObjectFactory factory = new TestObjectFactory();
final DebugParserContext context = parseCompilationUnit(factory, lineMap, s, func);
final DebugParserContext context = parseCompilationUnit(factory, lineMap, p, func);

Assert.assertEquals(1, context.functions().size());
}

@Test
public void testFunctionMissingLineMap() {
final Source s = Source.newBuilder(WasmLanguage.ID, "", "test").internal(true).build();
final Path p = Path.of("");
final AttributeBuilder funcAttr = AttributeBuilder.create().add(Attributes.DECL_FILE, 0x0F, 0).add(Attributes.NAME, 0x08, "func").add(Attributes.LOW_PC, 0x0F, 0).add(Attributes.HIGH_PC, 0x0F,
10).add(Attributes.FRAME_BASE, 0x09, new byte[]{});
final DebugData func = new DebugData(Tags.SUBPROGRAM, 1, funcAttr.attributeInfo(), funcAttr.attributeValues(), new DebugData[0]);

final TestObjectFactory factory = new TestObjectFactory();
final DebugParserContext context = parseCompilationUnit(factory, null, s, func);
final DebugParserContext context = parseCompilationUnit(factory, null, p, func);

Assert.assertEquals(0, context.functions().size());
}
Expand All @@ -1117,42 +1115,42 @@ public void testFunctionMissingSource() {

@Test
public void testInlinedFunction() {
final DebugLineMap lineMap = new DebugLineMap(Path.of(""));
final Path p = Path.of("");
final DebugLineMap lineMap = new DebugLineMap(p);
lineMap.add(0, 1);
lineMap.add(10, 2);
final Source s = Source.newBuilder(WasmLanguage.ID, "", "test").internal(true).build();
final AttributeBuilder funcAttr = AttributeBuilder.create().add(Attributes.DECL_FILE, 0x0F, 0).add(Attributes.NAME, 0x08, "func").add(Attributes.LOW_PC, 0x0F, 0).add(Attributes.HIGH_PC, 0x0F,
10).add(Attributes.FRAME_BASE, 0x09, new byte[]{}).add(Attributes.INLINE, 0x0F, 1);
final DebugData func = new DebugData(Tags.SUBPROGRAM, 1, funcAttr.attributeInfo(), funcAttr.attributeValues(), new DebugData[0]);

final TestObjectFactory factory = new TestObjectFactory();
final DebugParserContext context = parseCompilationUnit(factory, lineMap, s, func);
final DebugParserContext context = parseCompilationUnit(factory, lineMap, p, func);

Assert.assertEquals(0, context.functions().size());
}

@Test
public void testFunctionMissingFrameBase() {
final DebugLineMap lineMap = new DebugLineMap(Path.of(""));
final Path p = Path.of("");
final DebugLineMap lineMap = new DebugLineMap(p);
lineMap.add(0, 1);
lineMap.add(10, 2);
final Source s = Source.newBuilder(WasmLanguage.ID, "", "test").internal(true).build();
final AttributeBuilder funcAttr = AttributeBuilder.create().add(Attributes.DECL_FILE, 0x0F, 0).add(Attributes.NAME, 0x08, "func").add(Attributes.LOW_PC, 0x0F, 0).add(Attributes.HIGH_PC, 0x0F,
10);
final DebugData func = new DebugData(Tags.SUBPROGRAM, 1, funcAttr.attributeInfo(), funcAttr.attributeValues(), new DebugData[0]);

final TestObjectFactory factory = new TestObjectFactory();
final DebugParserContext context = parseCompilationUnit(factory, lineMap, s, func);
final DebugParserContext context = parseCompilationUnit(factory, lineMap, p, func);

Assert.assertEquals(0, context.functions().size());
}

@Test
public void testFunctionWithVariables() {
final DebugLineMap lineMap = new DebugLineMap(Path.of(""));
final Path p = Path.of("");
final DebugLineMap lineMap = new DebugLineMap(p);
lineMap.add(0, 1);
lineMap.add(10, 2);
final Source s = Source.newBuilder(WasmLanguage.ID, "", "test").internal(true).build();

final AttributeBuilder baseAttr = AttributeBuilder.create().add(Attributes.NAME, 0x08, "int").add(Attributes.ENCODING, 0x0F, AttributeEncodings.SIGNED).add(Attributes.BYTE_SIZE, 0x0F, 4);
final DebugData baseType = new DebugData(Tags.BASE_TYPE, 1, baseAttr.attributeInfo(), baseAttr.attributeValues(), new DebugData[0]);
Expand All @@ -1172,7 +1170,7 @@ public void testFunctionWithVariables() {
final DebugData func = new DebugData(Tags.SUBPROGRAM, 3, funcAttr.attributeInfo(), funcAttr.attributeValues(), new DebugData[]{var1, var2, var3});

final TestObjectFactory factory = new TestObjectFactory();
final DebugParserContext context = parseCompilationUnit(factory, lineMap, s, baseType, func);
final DebugParserContext context = parseCompilationUnit(factory, lineMap, p, baseType, func);

Assert.assertEquals(1, context.functions().size());
final DebugFunction function = context.functions().get(0);
Expand All @@ -1181,10 +1179,10 @@ public void testFunctionWithVariables() {

@Test
public void testFunctionWithVariableMissingTypeAttribute() {
final DebugLineMap lineMap = new DebugLineMap(Path.of(""));
final Path p = Path.of("");
final DebugLineMap lineMap = new DebugLineMap(p);
lineMap.add(0, 1);
lineMap.add(10, 2);
final Source s = Source.newBuilder(WasmLanguage.ID, "", "test").internal(true).build();

final AttributeBuilder baseAttr = AttributeBuilder.create().add(Attributes.NAME, 0x08, "int").add(Attributes.ENCODING, 0x0F, AttributeEncodings.SIGNED).add(Attributes.BYTE_SIZE, 0x0F, 4);
final DebugData baseType = new DebugData(Tags.BASE_TYPE, 1, baseAttr.attributeInfo(), baseAttr.attributeValues(), new DebugData[0]);
Expand All @@ -1198,7 +1196,7 @@ public void testFunctionWithVariableMissingTypeAttribute() {
final DebugData func = new DebugData(Tags.SUBPROGRAM, 3, funcAttr.attributeInfo(), funcAttr.attributeValues(), new DebugData[]{var});

final TestObjectFactory factory = new TestObjectFactory();
final DebugParserContext context = parseCompilationUnit(factory, lineMap, s, baseType, func);
final DebugParserContext context = parseCompilationUnit(factory, lineMap, p, baseType, func);

Assert.assertEquals(1, context.functions().size());
final DebugFunction function = context.functions().get(0);
Expand All @@ -1207,10 +1205,10 @@ public void testFunctionWithVariableMissingTypeAttribute() {

@Test
public void testFunctionWithVariableMissingType() {
final DebugLineMap lineMap = new DebugLineMap(Path.of(""));
final Path p = Path.of("");
final DebugLineMap lineMap = new DebugLineMap(p);
lineMap.add(0, 1);
lineMap.add(10, 2);
final Source s = Source.newBuilder(WasmLanguage.ID, "", "test").internal(true).build();

final AttributeBuilder varAttr = AttributeBuilder.create().add(Attributes.NAME, 0x08, "c").add(Attributes.TYPE, 0x0F, 1).add(Attributes.LOCATION, 0x09, new byte[0]).add(Attributes.DECL_FILE,
0x0F, 0).add(Attributes.DECL_LINE, 0x0F, 1);
Expand All @@ -1221,7 +1219,7 @@ public void testFunctionWithVariableMissingType() {
final DebugData func = new DebugData(Tags.SUBPROGRAM, 3, funcAttr.attributeInfo(), funcAttr.attributeValues(), new DebugData[]{var});

final TestObjectFactory factory = new TestObjectFactory();
final DebugParserContext context = parseCompilationUnit(factory, lineMap, s, func);
final DebugParserContext context = parseCompilationUnit(factory, lineMap, p, func);

Assert.assertEquals(1, context.functions().size());
final DebugFunction function = context.functions().get(0);
Expand Down
56 changes: 33 additions & 23 deletions wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -576,18 +576,19 @@ private static byte[] encapsulateResultType(int type) {
}

private CodeEntry readFunction(int functionIndex, byte[] locals, byte[] resultTypes, int sourceCodeEndOffset, boolean hasNextFunction, RuntimeBytecodeGen bytecode,
int codeEntryIndex, EconomicMap<Integer, Integer> sourceLocationToLineMap) {
int codeEntryIndex, EconomicMap<Integer, Integer> offsetToLineIndexMap) {
final ParserState state = new ParserState(bytecode);
final ArrayList<CallNode> callNodes = new ArrayList<>();
final int bytecodeStartOffset = bytecode.location();
state.enterFunction(resultTypes);

int opcode;
end: while (offset < sourceCodeEndOffset) {
// Insert a debug instruction if a line mapping exists.
if (sourceLocationToLineMap != null) {
if (sourceLocationToLineMap.containsKey(offset)) {
bytecode.addNotify(sourceLocationToLineMap.get(offset), offset);
// Insert a debug instruction if a line mapping (line index) exists.
if (offsetToLineIndexMap != null) {
final Integer lineIndex = offsetToLineIndexMap.get(offset);
if (lineIndex != null) {
bytecode.addNotify(lineIndex, offset);
}
}

Expand Down Expand Up @@ -713,6 +714,10 @@ private CodeEntry readFunction(int functionIndex, byte[] locals, byte[] resultTy
break;
}
case Instructions.RETURN: {
if (offsetToLineIndexMap != null) {
// Make sure we exit the current statement before leaving the function
bytecode.addNotify(-1, -1);
}
state.addReturn(multiValue);

// This instruction is stack-polymorphic
Expand Down Expand Up @@ -1019,22 +1024,27 @@ private CodeEntry readFunction(int functionIndex, byte[] locals, byte[] resultTy
}
}
final int bytecodeEndOffset = bytecode.location();
bytecode.addCodeEntry(functionIndex, state.maxStackSize(), bytecodeEndOffset - bytecodeStartOffset, locals.length, resultTypes.length);
for (byte local : locals) {
bytecode.addByte(local);
}
if (locals.length != 0) {
bytecode.addByte((byte) 0);
}
for (byte result : resultTypes) {
bytecode.addByte(result);
}
if (resultTypes.length != 0) {
bytecode.addByte((byte) 0);
}
if (sourceLocationToLineMap == null) {

if (offsetToLineIndexMap == null) {
bytecode.addCodeEntry(functionIndex, state.maxStackSize(), bytecodeEndOffset - bytecodeStartOffset, locals.length, resultTypes.length);
for (byte local : locals) {
bytecode.addByte(local);
}
if (locals.length != 0) {
bytecode.addByte((byte) 0);
}
for (byte result : resultTypes) {
bytecode.addByte(result);
}
if (resultTypes.length != 0) {
bytecode.addByte((byte) 0);
}

// Do not override the code entry offset when rereading the function.
module.setCodeEntryOffset(codeEntryIndex, bytecodeEndOffset);
} else {
// Make sure we notify a statement exit before leaving the function
bytecode.addNotify(-1, -1);
}
return new CodeEntry(functionIndex, state.maxStackSize(), locals, resultTypes, callNodes, bytecodeStartOffset, bytecodeEndOffset, state.usesMemoryZero());
}
Expand Down Expand Up @@ -3248,17 +3258,17 @@ private Vector128 readUnsignedInt128() {
* Creates a runtime bytecode of a function with added debug opcodes.
*
* @param functionIndex the function index
* @param sourceLocationToLineMap a mapping from source code locations to source code lines
* numbers (extracted from debugging information)
* @param offsetToLineIndexMap a mapping from source code locations to line indices in the line
* index map
*/
@TruffleBoundary
public byte[] createFunctionDebugBytecode(int functionIndex, EconomicMap<Integer, Integer> sourceLocationToLineMap) {
public byte[] createFunctionDebugBytecode(int functionIndex, EconomicMap<Integer, Integer> offsetToLineIndexMap) {
final RuntimeBytecodeGen bytecode = new RuntimeBytecodeGen();
final int codeEntryIndex = functionIndex - module.numImportedFunctions();
final CodeEntry codeEntry = BytecodeParser.readCodeEntry(module, module.bytecode(), codeEntryIndex);
offset = module.functionSourceCodeInstructionOffset(functionIndex);
final int endOffset = module.functionSourceCodeEndOffset(functionIndex);
readFunction(functionIndex, codeEntry.localTypes(), codeEntry.resultTypes(), endOffset, true, bytecode, codeEntryIndex, sourceLocationToLineMap);
readFunction(functionIndex, codeEntry.localTypes(), codeEntry.resultTypes(), endOffset, true, bytecode, codeEntryIndex, offsetToLineIndexMap);
return bytecode.toArray();
}
}
Loading