Skip to content
Merged
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