From 6808d9e517968bc7e8b44f6b3bb66ede14ef5d46 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Tue, 13 Dec 2022 21:18:41 +0100 Subject: [PATCH] Wasm: support local variables in debugger --- .../org/teavm/backend/wasm/WasmTarget.java | 7 +- .../backend/wasm/debug/DebugConstants.java | 1 + .../backend/wasm/debug/DebugInfoBuilder.java | 11 +- .../wasm/debug/DebugMethodsBuilder.java | 2 +- .../backend/wasm/debug/DebugVariables.java | 28 ++++ .../wasm/debug/DebugVariablesBuilder.java | 101 ++++++++++++ .../backend/wasm/debug/info/DebugInfo.java | 12 +- .../backend/wasm/debug/info/VariableInfo.java | 26 +++ .../wasm/debug/info/VariableRangeInfo.java | 26 +++ .../backend/wasm/debug/info/VariableType.java | 26 +++ .../wasm/debug/info/VariablesInfo.java | 43 +++++ .../wasm/debug/parser/DebugInfoParser.java | 4 +- .../debug/parser/DebugVariablesParser.java | 154 ++++++++++++++++++ .../wasm/generate/DwarfClassGenerator.java | 2 +- .../wasm/generate/DwarfFunctionGenerator.java | 2 +- .../backend/wasm/generate/WasmGenerator.java | 18 +- .../teavm/backend/wasm/model/WasmLocal.java | 2 +- .../wasm/render/WasmBinaryRenderer.java | 20 ++- .../java/org/teavm/debugging/CallFrame.java | 7 +- .../java/org/teavm/debugging/Debugger.java | 36 +++- .../main/java/org/teavm/debugging/Value.java | 8 + .../teavm/chromerdp/ChromeRDPDebugger.java | 1 - 22 files changed, 522 insertions(+), 15 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/wasm/debug/DebugVariables.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/debug/DebugVariablesBuilder.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/debug/info/VariableInfo.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/debug/info/VariableRangeInfo.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/debug/info/VariableType.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/debug/info/VariablesInfo.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugVariablesParser.java diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java index 6eab14442..daf92e6dd 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java @@ -552,8 +552,11 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { var writer = new WasmBinaryWriter(); var debugBuilder = debugging ? new DebugInfoBuilder() : null; - var renderer = new WasmBinaryRenderer(writer, version, obfuscated, dwarfGenerator, dwarfClassGen, - debugBuilder != null ? debugBuilder.lines() : null); + var renderer = new WasmBinaryRenderer( + writer, version, obfuscated, dwarfGenerator, dwarfClassGen, + debugBuilder != null ? debugBuilder.lines() : null, + debugBuilder != null ? debugBuilder.variables() : null + ); renderer.render(module, buildDebug(dwarfGenerator, dwarfClassGen, debugBuilder)); try (OutputStream output = buildTarget.createResource(outputName)) { diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/DebugConstants.java b/core/src/main/java/org/teavm/backend/wasm/debug/DebugConstants.java index c23d42d22..fcc3cc85a 100644 --- a/core/src/main/java/org/teavm/backend/wasm/debug/DebugConstants.java +++ b/core/src/main/java/org/teavm/backend/wasm/debug/DebugConstants.java @@ -32,4 +32,5 @@ public final class DebugConstants { public static final String SECTION_CLASSES = "teavm_cls"; public static final String SECTION_METHODS = "teavm_mtd"; public static final String SECTION_LINES = "teavm_line"; + public static final String SECTION_VARIABLES = "teavm_var"; } diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/DebugInfoBuilder.java b/core/src/main/java/org/teavm/backend/wasm/debug/DebugInfoBuilder.java index 5057b2a1a..17cbc5f35 100644 --- a/core/src/main/java/org/teavm/backend/wasm/debug/DebugInfoBuilder.java +++ b/core/src/main/java/org/teavm/backend/wasm/debug/DebugInfoBuilder.java @@ -25,6 +25,7 @@ public class DebugInfoBuilder { private DebugPackagesBuilder packages; private DebugClassesBuilder classes; private DebugMethodsBuilder methods; + private DebugVariablesBuilder variables; private DebugLinesBuilder lines; public DebugInfoBuilder() { @@ -33,6 +34,7 @@ public class DebugInfoBuilder { packages = new DebugPackagesBuilder(strings); classes = new DebugClassesBuilder(packages, strings); methods = new DebugMethodsBuilder(classes, strings); + variables = new DebugVariablesBuilder(strings); lines = new DebugLinesBuilder(files, methods); } @@ -52,11 +54,15 @@ public class DebugInfoBuilder { return classes; } - public DebugMethodsBuilder methods() { + public DebugMethods methods() { return methods; } - public DebugLinesBuilder lines() { + public DebugVariables variables() { + return variables; + } + + public DebugLines lines() { return lines; } @@ -67,6 +73,7 @@ public class DebugInfoBuilder { addSection(result, packages); addSection(result, classes); addSection(result, methods); + addSection(result, variables); addSection(result, lines); return result; } diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/DebugMethodsBuilder.java b/core/src/main/java/org/teavm/backend/wasm/debug/DebugMethodsBuilder.java index c1c0b814f..dd415a8d8 100644 --- a/core/src/main/java/org/teavm/backend/wasm/debug/DebugMethodsBuilder.java +++ b/core/src/main/java/org/teavm/backend/wasm/debug/DebugMethodsBuilder.java @@ -19,7 +19,7 @@ import com.carrotsearch.hppc.ObjectIntHashMap; import com.carrotsearch.hppc.ObjectIntMap; import org.teavm.model.MethodReference; -public class DebugMethodsBuilder extends DebugSectionBuilder implements DebugMethods { +public class DebugMethodsBuilder extends DebugSectionBuilder implements DebugMethods { private DebugClasses classes; private DebugStrings strings; private ObjectIntMap methods = new ObjectIntHashMap<>(); diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/DebugVariables.java b/core/src/main/java/org/teavm/backend/wasm/debug/DebugVariables.java new file mode 100644 index 000000000..b9ce21b9d --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/DebugVariables.java @@ -0,0 +1,28 @@ +/* + * Copyright 2022 Alexey Andreev. + * + * 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. + */ +package org.teavm.backend.wasm.debug; + +import org.teavm.backend.wasm.debug.info.VariableType; + +public interface DebugVariables { + void startSequence(int pointer); + + void type(String name, VariableType type); + + void range(String name, int start, int end, int pointer); + + void endSequence(); +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/DebugVariablesBuilder.java b/core/src/main/java/org/teavm/backend/wasm/debug/DebugVariablesBuilder.java new file mode 100644 index 000000000..47e9cfca6 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/DebugVariablesBuilder.java @@ -0,0 +1,101 @@ +/* + * Copyright 2022 Alexey Andreev. + * + * 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. + */ +package org.teavm.backend.wasm.debug; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.teavm.backend.wasm.debug.info.VariableType; + +public class DebugVariablesBuilder extends DebugSectionBuilder implements DebugVariables { + private DebugStrings strings; + private int sequenceStart; + private int lastSequenceStart; + private Map variables = new LinkedHashMap<>(); + + public DebugVariablesBuilder(DebugStrings strings) { + super(DebugConstants.SECTION_VARIABLES); + this.strings = strings; + } + + @Override + public void startSequence(int pointer) { + sequenceStart = pointer; + } + + @Override + public void type(String name, VariableType type) { + getInfo(name).type = type; + } + + @Override + public void range(String name, int start, int end, int pointer) { + getInfo(name).ranges.add(new Range(start, end, pointer)); + } + + private VarInfo getInfo(String name) { + var info = variables.get(name); + if (info == null) { + info = new VarInfo(); + variables.put(name, info); + } + return info; + } + + @Override + public void endSequence() { + if (variables.isEmpty()) { + return; + } + blob.writeLEB(sequenceStart - lastSequenceStart); + lastSequenceStart = sequenceStart; + blob.writeLEB(variables.size()); + for (var variable : variables.entrySet()) { + blob.writeLEB(strings.stringPtr(variable.getKey())); + var info = variable.getValue(); + blob.writeLEB(info.type.ordinal()); + blob.writeLEB(info.ranges.size()); + var lastPtr = sequenceStart; + var lastPointer = 0; + for (var range : info.ranges) { + blob.writeSLEB(range.start - lastPtr); + blob.writeLEB(range.end - range.start); + blob.writeSLEB(range.pointer - lastPointer); + lastPointer = range.pointer; + lastPointer = range.end; + } + } + variables.clear(); + } + + private static class VarInfo { + VariableType type = VariableType.UNDEFINED; + List ranges = new ArrayList<>(); + } + + private static class Range { + int start; + int end; + int pointer; + + Range(int start, int end, int pointer) { + this.start = start; + this.end = end; + this.pointer = pointer; + } + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/DebugInfo.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/DebugInfo.java index c41449567..34a487666 100644 --- a/core/src/main/java/org/teavm/backend/wasm/debug/info/DebugInfo.java +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/DebugInfo.java @@ -18,16 +18,22 @@ package org.teavm.backend.wasm.debug.info; import java.io.PrintStream; public class DebugInfo { + private VariablesInfo variables; private LineInfo lines; private ControlFlowInfo controlFlow; private int offset; - public DebugInfo(LineInfo lines, ControlFlowInfo controlFlow, int offset) { + public DebugInfo(VariablesInfo variables, LineInfo lines, ControlFlowInfo controlFlow, int offset) { + this.variables = variables; this.lines = lines; this.controlFlow = controlFlow; this.offset = offset; } + public VariablesInfo variables() { + return variables; + } + public LineInfo lines() { return lines; } @@ -52,5 +58,9 @@ public class DebugInfo { out.println("CONTROL FLOW"); controlFlow.dump(out); } + if (variables != null) { + out.println("VARIABLES"); + variables.dump(out); + } } } diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/VariableInfo.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/VariableInfo.java new file mode 100644 index 000000000..590656748 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/VariableInfo.java @@ -0,0 +1,26 @@ +/* + * Copyright 2022 Alexey Andreev. + * + * 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. + */ +package org.teavm.backend.wasm.debug.info; + +import java.util.Collection; + +public abstract class VariableInfo { + public abstract String name(); + + public abstract VariableType type(); + + public abstract Collection ranges(); +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/VariableRangeInfo.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/VariableRangeInfo.java new file mode 100644 index 000000000..2759031a0 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/VariableRangeInfo.java @@ -0,0 +1,26 @@ +/* + * Copyright 2022 Alexey Andreev. + * + * 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. + */ +package org.teavm.backend.wasm.debug.info; + +public abstract class VariableRangeInfo { + public abstract VariableInfo variable(); + + public abstract int start(); + + public abstract int end(); + + public abstract int index(); +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/VariableType.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/VariableType.java new file mode 100644 index 000000000..953d0f1e1 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/VariableType.java @@ -0,0 +1,26 @@ +/* + * Copyright 2022 Alexey Andreev. + * + * 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. + */ +package org.teavm.backend.wasm.debug.info; + +public enum VariableType { + INT, + LONG, + FLOAT, + DOUBLE, + OBJECT, + ADDRESS, + UNDEFINED +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/VariablesInfo.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/VariablesInfo.java new file mode 100644 index 000000000..da18cedd4 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/VariablesInfo.java @@ -0,0 +1,43 @@ +/* + * Copyright 2022 Alexey Andreev. + * + * 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. + */ +package org.teavm.backend.wasm.debug.info; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public abstract class VariablesInfo { + public abstract List ranges(); + + public Collection find(int address) { + var result = new ArrayList(); + for (var range : ranges()) { + if (address >= range.start() && address < range.end()) { + result.add(range); + } + } + return result; + } + + public void dump(PrintStream out) { + for (var range : ranges()) { + out.println(range.variable().name() + ": " + range.variable().type() + " - " + + Integer.toHexString(range.start()) + ".." + Integer.toHexString(range.end()) + " at " + + range.index()); + } + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugInfoParser.java b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugInfoParser.java index 7ca083967..812d6cce2 100644 --- a/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugInfoParser.java +++ b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugInfoParser.java @@ -31,6 +31,7 @@ import org.teavm.common.ByteArrayAsyncInputStream; public class DebugInfoParser extends ModuleParser { private Map sectionParsers = new HashMap<>(); private DebugLinesParser lines; + private DebugVariablesParser variables; private ControlFlowInfo controlFlow; private int offset; @@ -41,6 +42,7 @@ public class DebugInfoParser extends ModuleParser { var packages = addSection(new DebugPackageParser(strings)); var classes = addSection(new DebugClassParser(strings, packages)); var methods = addSection(new DebugMethodParser(strings, classes)); + variables = addSection(new DebugVariablesParser(strings)); lines = addSection(new DebugLinesParser(files, methods)); } @@ -50,7 +52,7 @@ public class DebugInfoParser extends ModuleParser { } public DebugInfo getDebugInfo() { - return new DebugInfo(lines.getLineInfo(), controlFlow, offset); + return new DebugInfo(variables.getVariablesInfo(), lines.getLineInfo(), controlFlow, offset); } @Override diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugVariablesParser.java b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugVariablesParser.java new file mode 100644 index 000000000..a99199565 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugVariablesParser.java @@ -0,0 +1,154 @@ +/* + * Copyright 2022 Alexey Andreev. + * + * 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. + */ +package org.teavm.backend.wasm.debug.parser; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import org.teavm.backend.wasm.debug.DebugConstants; +import org.teavm.backend.wasm.debug.info.VariableInfo; +import org.teavm.backend.wasm.debug.info.VariableRangeInfo; +import org.teavm.backend.wasm.debug.info.VariableType; +import org.teavm.backend.wasm.debug.info.VariablesInfo; + +public class DebugVariablesParser extends DebugSectionParser { + private static final VariableType[] typeByOrdinal = VariableType.values(); + private DebugStringParser strings; + private VariablesInfoImpl variablesInfo; + + public DebugVariablesParser(DebugStringParser strings) { + super(DebugConstants.SECTION_VARIABLES, strings); + this.strings = strings; + } + + public VariablesInfoImpl getVariablesInfo() { + return variablesInfo; + } + + @Override + protected void doParse() { + var lastAddress = 0; + var ranges = new ArrayList(); + var localRanges = new ArrayList(); + while (ptr < data.length) { + var baseAddress = lastAddress + readLEB(); + lastAddress = baseAddress; + var variableCount = readLEB(); + for (var i = 0; i < variableCount; ++i) { + var name = strings.getString(readLEB()); + var type = typeByOrdinal[readLEB()]; + var rangeCount = readLEB(); + var varInfo = new VariableInfoImpl(name, type); + var address = baseAddress; + var lastLocation = 0; + for (var j = 0; j < rangeCount; ++j) { + var start = address + readSignedLEB(); + var size = readLEB(); + var end = start + size; + address = end; + var location = lastLocation + readSignedLEB(); + lastLocation = location; + var rangeInfo = new VariableRangeInfoImpl(varInfo, start, end, location); + ranges.add(rangeInfo); + localRanges.add(rangeInfo); + } + localRanges.clear(); + varInfo.ranges = Collections.unmodifiableList(Arrays.asList( + localRanges.toArray(new VariableRangeInfoImpl[0]))); + } + } + ranges.sort(Comparator.comparing(VariableRangeInfo::start)); + + this.variablesInfo = new VariablesInfoImpl(Collections.unmodifiableList(Arrays.asList( + ranges.toArray(new VariableRangeInfoImpl[0])))); + } + + private static class VariablesInfoImpl extends VariablesInfo { + private List ranges; + + VariablesInfoImpl(List ranges) { + this.ranges = ranges; + } + + @Override + public List ranges() { + return ranges; + } + } + + private static class VariableInfoImpl extends VariableInfo { + private String name; + private VariableType type; + private List ranges; + + VariableInfoImpl(String name, VariableType type) { + this.name = name; + this.type = type; + } + + @Override + public String name() { + return name; + } + + @Override + public VariableType type() { + return type; + } + + @Override + public Collection ranges() { + return ranges; + } + } + + private static class VariableRangeInfoImpl extends VariableRangeInfo { + private VariableInfoImpl variableInfo; + private int start; + private int end; + private int index; + + VariableRangeInfoImpl(VariableInfoImpl variableInfo, int start, int end, int index) { + this.variableInfo = variableInfo; + this.start = start; + this.end = end; + this.index = index; + } + + @Override + public VariableInfo variable() { + return variableInfo; + } + + @Override + public int start() { + return start; + } + + @Override + public int end() { + return end; + } + + @Override + public int index() { + return index; + } + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/DwarfClassGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/DwarfClassGenerator.java index c059f18ad..57f20b96b 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/DwarfClassGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/DwarfClassGenerator.java @@ -47,13 +47,13 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.teavm.backend.wasm.blob.Blob; +import org.teavm.backend.wasm.debug.info.VariableType; import org.teavm.backend.wasm.dwarf.DwarfAbbreviation; import org.teavm.backend.wasm.dwarf.DwarfInfoWriter; import org.teavm.backend.wasm.dwarf.DwarfPlaceholder; import org.teavm.model.MethodDescriptor; import org.teavm.model.PrimitiveType; import org.teavm.model.ValueType; -import org.teavm.model.util.VariableType; public class DwarfClassGenerator { private static final ValueType objectType = ValueType.object("java.lang.Object"); diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/DwarfFunctionGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/DwarfFunctionGenerator.java index 9e445e60b..e7fb4de8d 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/DwarfFunctionGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/DwarfFunctionGenerator.java @@ -32,9 +32,9 @@ import static org.teavm.backend.wasm.dwarf.DwarfConstants.DW_TAG_SUBPROGRAM; import static org.teavm.backend.wasm.dwarf.DwarfConstants.DW_TAG_VARIABLE; import org.teavm.backend.wasm.blob.Blob; import org.teavm.backend.wasm.blob.Marker; +import org.teavm.backend.wasm.debug.info.VariableType; import org.teavm.backend.wasm.dwarf.DwarfAbbreviation; import org.teavm.backend.wasm.model.WasmFunction; -import org.teavm.model.util.VariableType; public class DwarfFunctionGenerator { private DwarfClassGenerator classGen; diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerator.java index 84e8e3fa9..9ac08c182 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerator.java @@ -21,6 +21,7 @@ import org.teavm.ast.VariableNode; import org.teavm.ast.decompilation.Decompiler; import org.teavm.backend.lowlevel.generate.NameProvider; import org.teavm.backend.wasm.binary.BinaryWriter; +import org.teavm.backend.wasm.debug.info.VariableType; import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmLocal; import org.teavm.backend.wasm.model.WasmType; @@ -87,7 +88,7 @@ public class WasmGenerator { ? WasmGeneratorUtil.mapType(variable.getType()) : WasmType.INT32; var local = new WasmLocal(type, variable.getName()); - local.setJavaType(variable.getType()); + local.setJavaType(mapType(variable.getType())); function.add(local); } @@ -107,6 +108,21 @@ public class WasmGenerator { return function; } + private VariableType mapType(org.teavm.model.util.VariableType type) { + switch (type) { + case INT: + return VariableType.INT; + case LONG: + return VariableType.LONG; + case FLOAT: + return VariableType.FLOAT; + case DOUBLE: + return VariableType.DOUBLE; + default: + return VariableType.OBJECT; + } + } + public WasmFunction generateNative(MethodReference methodReference) { WasmFunction function = context.getFunction(names.forMethod(methodReference)); diff --git a/core/src/main/java/org/teavm/backend/wasm/model/WasmLocal.java b/core/src/main/java/org/teavm/backend/wasm/model/WasmLocal.java index 3ca56b93e..0676d394a 100644 --- a/core/src/main/java/org/teavm/backend/wasm/model/WasmLocal.java +++ b/core/src/main/java/org/teavm/backend/wasm/model/WasmLocal.java @@ -16,7 +16,7 @@ package org.teavm.backend.wasm.model; import java.util.Objects; -import org.teavm.model.util.VariableType; +import org.teavm.backend.wasm.debug.info.VariableType; public class WasmLocal { WasmFunction function; diff --git a/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java index f959c335c..d0eaa55b7 100644 --- a/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.function.Supplier; import java.util.stream.Collectors; import org.teavm.backend.wasm.debug.DebugLines; +import org.teavm.backend.wasm.debug.DebugVariables; import org.teavm.backend.wasm.generate.DwarfClassGenerator; import org.teavm.backend.wasm.generate.DwarfFunctionGenerator; import org.teavm.backend.wasm.generate.DwarfGenerator; @@ -59,15 +60,18 @@ public class WasmBinaryRenderer { private DwarfGenerator dwarfGenerator; private DwarfFunctionGenerator dwarfFunctionGen; private DebugLines debugLines; + private DebugVariables debugVariables; public WasmBinaryRenderer(WasmBinaryWriter output, WasmBinaryVersion version, boolean obfuscated, - DwarfGenerator dwarfGenerator, DwarfClassGenerator dwarfClassGen, DebugLines debugLines) { + DwarfGenerator dwarfGenerator, DwarfClassGenerator dwarfClassGen, DebugLines debugLines, + DebugVariables debugVariables) { this.output = output; this.version = version; this.obfuscated = obfuscated; this.dwarfGenerator = dwarfGenerator; dwarfFunctionGen = dwarfClassGen != null ? new DwarfFunctionGenerator(dwarfClassGen, dwarfGenerator) : null; this.debugLines = debugLines; + this.debugVariables = debugVariables; } public void render(WasmModule module) { @@ -335,10 +339,24 @@ public class WasmBinaryRenderer { if (dwarfFunctionGen != null) { dwarfFunctionGen.end(code.getPosition()); } + if (debugVariables != null) { + writeDebugVariables(function, offset, code.getPosition()); + } return code.getData(); } + private void writeDebugVariables(WasmFunction function, int offset, int size) { + debugVariables.startSequence(offset); + for (var local : function.getLocalVariables()) { + if (local.getName() != null && local.getJavaType() != null) { + debugVariables.type(local.getName(), local.getJavaType()); + debugVariables.range(local.getName(), offset, offset + size, local.getIndex()); + } + } + debugVariables.endSequence(); + } + private void renderInitializer(WasmBinaryWriter output, int value) { output.writeByte(0x41); output.writeLEB(value); diff --git a/core/src/main/java/org/teavm/debugging/CallFrame.java b/core/src/main/java/org/teavm/debugging/CallFrame.java index 2bfc261d5..7a2f72555 100644 --- a/core/src/main/java/org/teavm/debugging/CallFrame.java +++ b/core/src/main/java/org/teavm/debugging/CallFrame.java @@ -17,6 +17,7 @@ package org.teavm.debugging; import java.util.Collections; import java.util.Map; +import org.teavm.backend.wasm.debug.info.DebugInfo; import org.teavm.common.Promise; import org.teavm.debugging.information.DebugInformation; import org.teavm.debugging.information.SourceLocation; @@ -31,14 +32,16 @@ public class CallFrame { private MethodReference method; private Promise> variables; private DebugInformation debugInformation; + private DebugInfo wasmDebugInfo; CallFrame(Debugger debugger, JavaScriptCallFrame originalFrame, SourceLocation location, MethodReference method, - DebugInformation debugInformation) { + DebugInformation debugInformation, DebugInfo wasmDebugInfo) { this.debugger = debugger; this.originalCallFrame = originalFrame; this.location = location; this.method = method; this.debugInformation = debugInformation; + this.wasmDebugInfo = wasmDebugInfo; } public Debugger getDebugger() { @@ -65,6 +68,8 @@ public class CallFrame { if (variables == null) { if (debugInformation != null) { variables = debugger.createVariables(originalCallFrame, debugInformation); + } else if (wasmDebugInfo != null) { + variables = debugger.createVariables(originalCallFrame, wasmDebugInfo); } else { variables = Promise.of(Collections.emptyMap()); } diff --git a/core/src/main/java/org/teavm/debugging/Debugger.java b/core/src/main/java/org/teavm/debugging/Debugger.java index 78410400f..ac72912c7 100644 --- a/core/src/main/java/org/teavm/debugging/Debugger.java +++ b/core/src/main/java/org/teavm/debugging/Debugger.java @@ -403,6 +403,7 @@ public class Debugger { for (var jsFrame : javaScriptDebugger.getCallStack()) { List locations; DebugInformation debugInformation = null; + DebugInfo wasmDebugInfo = null; switch (jsFrame.getLocation().getScript().getLanguage()) { case JS: debugInformation = debugInformationMap.get(jsFrame.getLocation().getScript()); @@ -410,6 +411,9 @@ public class Debugger { break; case WASM: locations = mapWasmFrames(jsFrame); + if (!locations.isEmpty()) { + wasmDebugInfo = wasmDebugInfoMap.get(jsFrame.getLocation().getScript()); + } break; default: locations = Collections.emptyList(); @@ -419,7 +423,7 @@ public class Debugger { var loc = locWithMethod.loc; var method = locWithMethod.method; if (!locWithMethod.empty || !wasEmpty) { - frames.add(new CallFrame(this, jsFrame, loc, method, debugInformation)); + frames.add(new CallFrame(this, jsFrame, loc, method, debugInformation, wasmDebugInfo)); } wasEmpty = locWithMethod.empty; } @@ -513,6 +517,36 @@ public class Debugger { }); } + Promise> createVariables(JavaScriptCallFrame jsFrame, DebugInfo debugInfo) { + return jsFrame.getVariables().thenAsync(jsVariables -> { + var vars = new HashMap(); + var variables = debugInfo.variables(); + var promises = new ArrayList>(); + if (variables != null) { + var address = jsFrame.getLocation().getColumn(); + address -= debugInfo.offset(); + for (var range : variables.find(address)) { + var propertiesPromise = jsVariables.get("$var" + range.index()).getValue().getProperties(); + promises.add(propertiesPromise + .then(prop -> { + var variable = prop.get("value"); + return variable != null ? variable.getValue() : null; + }) + .thenAsync(value -> { + if (value != null) { + var varValue = new Value(this, debugInfo, value); + var variable = new Variable(range.variable().name(), varValue); + vars.put(variable.getName(), variable); + } + return Promise.VOID; + }) + ); + } + } + return Promise.allVoid(promises).then(x -> vars); + }); + } + private void addScript(JavaScriptScript script) { Promise promise; switch (script.getLanguage()) { diff --git a/core/src/main/java/org/teavm/debugging/Value.java b/core/src/main/java/org/teavm/debugging/Value.java index 516fcdd7b..70563c134 100644 --- a/core/src/main/java/org/teavm/debugging/Value.java +++ b/core/src/main/java/org/teavm/debugging/Value.java @@ -17,6 +17,7 @@ package org.teavm.debugging; import java.util.HashMap; import java.util.Map; +import org.teavm.backend.wasm.debug.info.DebugInfo; import org.teavm.common.Promise; import org.teavm.debugging.information.DebugInformation; import org.teavm.debugging.javascript.JavaScriptValue; @@ -25,6 +26,7 @@ import org.teavm.debugging.javascript.JavaScriptVariable; public class Value { private Debugger debugger; private DebugInformation debugInformation; + private DebugInfo wasmDebugInfo; private JavaScriptValue jsValue; private Promise> properties; private Promise type; @@ -35,6 +37,12 @@ public class Value { this.jsValue = jsValue; } + Value(Debugger debugger, DebugInfo wasmDebugInfo, JavaScriptValue jsValue) { + this.debugger = debugger; + this.wasmDebugInfo = wasmDebugInfo; + this.jsValue = jsValue; + } + private static boolean isNumeric(String str) { for (int i = 0; i < str.length(); ++i) { char c = str.charAt(i); diff --git a/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPDebugger.java b/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPDebugger.java index 3dbad3991..a1a915ec0 100644 --- a/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPDebugger.java +++ b/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPDebugger.java @@ -86,7 +86,6 @@ public class ChromeRDPDebugger extends BaseChromeRDPDebugger implements JavaScri protected void onDetach() { suspended = false; callStack = null; - } private Promise injectFunctions(int contextId) {