mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2025-01-03 05:44:10 -08:00
Wasm: working on control flow analyzer for debugger
This commit is contained in:
parent
44204b952d
commit
87d63168d2
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* 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.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import org.teavm.common.CollectionUtil;
|
||||||
|
|
||||||
|
public class ControlFlowInfo {
|
||||||
|
private List<? extends FunctionControlFlow> functions;
|
||||||
|
|
||||||
|
public ControlFlowInfo(FunctionControlFlow[] functions) {
|
||||||
|
this.functions = Collections.unmodifiableList(Arrays.asList(functions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<? extends FunctionControlFlow> functions() {
|
||||||
|
return functions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FunctionControlFlow find(int address) {
|
||||||
|
var index = CollectionUtil.binarySearch(functions, address, FunctionControlFlow::end);
|
||||||
|
if (index < 0) {
|
||||||
|
index = -index - 1;
|
||||||
|
}
|
||||||
|
if (index > functions.size()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var fn = functions.get(index);
|
||||||
|
if (fn.start() > address) {
|
||||||
|
return fn;
|
||||||
|
}
|
||||||
|
return fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dump(PrintStream out) {
|
||||||
|
for (int i = 0; i < functions.size(); ++i) {
|
||||||
|
var range = functions.get(i);
|
||||||
|
out.println("Range #" + i + ": [" + range.start() + ".." + range.end() + ")");
|
||||||
|
for (var iter = range.iterator(); iter.hasNext(); iter.next()) {
|
||||||
|
out.print(" " + Integer.toHexString(iter.address()));
|
||||||
|
if (iter.isCall()) {
|
||||||
|
out.print(" (call)");
|
||||||
|
}
|
||||||
|
out.print(" -> ");
|
||||||
|
var followers = iter.targets();
|
||||||
|
for (var j = 0; j < followers.length; ++j) {
|
||||||
|
if (j > 0) {
|
||||||
|
out.print(", ");
|
||||||
|
}
|
||||||
|
out.print(Integer.toHexString(followers[j]));
|
||||||
|
}
|
||||||
|
out.println();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
public class DebugInfo {
|
||||||
|
private LineInfo lines;
|
||||||
|
private ControlFlowInfo controlFlow;
|
||||||
|
private int offset;
|
||||||
|
|
||||||
|
public DebugInfo(LineInfo lines, ControlFlowInfo controlFlow, int offset) {
|
||||||
|
this.lines = lines;
|
||||||
|
this.controlFlow = controlFlow;
|
||||||
|
this.offset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineInfo lines() {
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ControlFlowInfo controlFlow() {
|
||||||
|
return controlFlow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int offset() {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dump(PrintStream out) {
|
||||||
|
if (offset != 0) {
|
||||||
|
out.println("Code section offset: " + Integer.toHexString(offset));
|
||||||
|
}
|
||||||
|
if (lines != null) {
|
||||||
|
out.println("LINES");
|
||||||
|
lines.dump(out);
|
||||||
|
}
|
||||||
|
if (controlFlow != null) {
|
||||||
|
out.println("CONTROL FLOW");
|
||||||
|
controlFlow.dump(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* 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 class FunctionControlFlow {
|
||||||
|
private int start;
|
||||||
|
private int end;
|
||||||
|
int[] offsets;
|
||||||
|
int[] data;
|
||||||
|
|
||||||
|
FunctionControlFlow(int[] offsets, int[] data) {
|
||||||
|
this.offsets = offsets;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int start() {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int end() {
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FunctionControlFlowIterator iterator() {
|
||||||
|
return new FunctionControlFlowIterator(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 com.carrotsearch.hppc.IntArrayList;
|
||||||
|
|
||||||
|
public class FunctionControlFlowBuilder {
|
||||||
|
private IntArrayList offsets = new IntArrayList();
|
||||||
|
private IntArrayList data = new IntArrayList();
|
||||||
|
|
||||||
|
public void addBranch(int position, int[] targets) {
|
||||||
|
offsets.add(data.size() << 1);
|
||||||
|
data.add(position);
|
||||||
|
data.add(targets);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addCall(int position, int[] targets) {
|
||||||
|
offsets.add((data.size() << 1) | 1);
|
||||||
|
data.add(position);
|
||||||
|
data.add(targets);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return offsets.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public FunctionControlFlow build() {
|
||||||
|
return new FunctionControlFlow(offsets.toArray(), data.toArray());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* 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.Arrays;
|
||||||
|
|
||||||
|
public class FunctionControlFlowIterator {
|
||||||
|
private FunctionControlFlow controlFlow;
|
||||||
|
private int index;
|
||||||
|
private boolean valid;
|
||||||
|
private int offset;
|
||||||
|
private boolean isCall;
|
||||||
|
|
||||||
|
FunctionControlFlowIterator(FunctionControlFlow controlFlow) {
|
||||||
|
this.controlFlow = controlFlow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasNext() {
|
||||||
|
return index < controlFlow.offsets.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void next() {
|
||||||
|
++index;
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fill() {
|
||||||
|
if (!valid) {
|
||||||
|
valid = true;
|
||||||
|
var n = controlFlow.offsets[index];
|
||||||
|
offset = n >>> 1;
|
||||||
|
isCall = (n & 1) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int address() {
|
||||||
|
fill();
|
||||||
|
return controlFlow.data[offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] targets() {
|
||||||
|
fill();
|
||||||
|
var nextOffset = index < controlFlow.offsets.length - 1
|
||||||
|
? controlFlow.offsets[index + 1] >>> 1
|
||||||
|
: controlFlow.data.length;
|
||||||
|
return Arrays.copyOfRange(controlFlow.data, offset + 1, nextOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCall() {
|
||||||
|
fill();
|
||||||
|
return isCall;
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,20 +22,14 @@ import java.util.List;
|
||||||
import org.teavm.common.CollectionUtil;
|
import org.teavm.common.CollectionUtil;
|
||||||
|
|
||||||
public class LineInfo {
|
public class LineInfo {
|
||||||
private int offset;
|
|
||||||
private LineInfoSequence[] sequences;
|
private LineInfoSequence[] sequences;
|
||||||
private List<? extends LineInfoSequence> sequenceList;
|
private List<? extends LineInfoSequence> sequenceList;
|
||||||
|
|
||||||
public LineInfo(int offset, LineInfoSequence[] sequences) {
|
public LineInfo(LineInfoSequence[] sequences) {
|
||||||
this.offset = offset;
|
|
||||||
this.sequences = sequences.clone();
|
this.sequences = sequences.clone();
|
||||||
sequenceList = Collections.unmodifiableList(Arrays.asList(this.sequences));
|
sequenceList = Collections.unmodifiableList(Arrays.asList(this.sequences));
|
||||||
}
|
}
|
||||||
|
|
||||||
public int offset() {
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<? extends LineInfoSequence> sequences() {
|
public List<? extends LineInfoSequence> sequences() {
|
||||||
return sequenceList;
|
return sequenceList;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,206 @@
|
||||||
|
/*
|
||||||
|
* 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 com.carrotsearch.hppc.IntArrayList;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.teavm.backend.wasm.debug.info.ControlFlowInfo;
|
||||||
|
import org.teavm.backend.wasm.debug.info.FunctionControlFlow;
|
||||||
|
import org.teavm.backend.wasm.debug.info.FunctionControlFlowBuilder;
|
||||||
|
import org.teavm.backend.wasm.model.WasmType;
|
||||||
|
import org.teavm.backend.wasm.parser.AddressListener;
|
||||||
|
import org.teavm.backend.wasm.parser.BranchOpcode;
|
||||||
|
import org.teavm.backend.wasm.parser.CodeListener;
|
||||||
|
import org.teavm.backend.wasm.parser.CodeSectionListener;
|
||||||
|
import org.teavm.backend.wasm.parser.Opcode;
|
||||||
|
|
||||||
|
public class ControlFlowParser implements CodeSectionListener, CodeListener, AddressListener {
|
||||||
|
private int previousAddress;
|
||||||
|
private int address;
|
||||||
|
private FunctionControlFlowBuilder cfb;
|
||||||
|
private List<Branch> branches = new ArrayList<>();
|
||||||
|
private List<FunctionControlFlow> ranges = new ArrayList<>();
|
||||||
|
private List<Branch> pendingBranches = new ArrayList<>();
|
||||||
|
private List<Block> blocks = new ArrayList<>();
|
||||||
|
|
||||||
|
public ControlFlowInfo build() {
|
||||||
|
return new ControlFlowInfo(ranges.toArray(new FunctionControlFlow[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void address(int address) {
|
||||||
|
previousAddress = this.address;
|
||||||
|
this.address = address;
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean functionStart(int index, int size) {
|
||||||
|
cfb = new FunctionControlFlowBuilder();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CodeListener code() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int startBlock(boolean loop, WasmType type) {
|
||||||
|
return startBlock(loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int startConditionalBlock(WasmType type) {
|
||||||
|
return startBlock(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int startBlock(boolean loop) {
|
||||||
|
var token = blocks.size();
|
||||||
|
var branch = !loop ? newBranch(false) : null;
|
||||||
|
var block = new Block(branch, address);
|
||||||
|
blocks.add(block);
|
||||||
|
if (branch != null) {
|
||||||
|
block.pendingBranches.add(branch);
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startElseSection(int token) {
|
||||||
|
var block = blocks.get(blocks.size() - 1);
|
||||||
|
var lastBranch = branches.get(branches.size() - 1);
|
||||||
|
if (lastBranch.address != previousAddress) {
|
||||||
|
lastBranch = new Branch(previousAddress, false);
|
||||||
|
branches.add(lastBranch);
|
||||||
|
}
|
||||||
|
block.pendingBranches.add(lastBranch);
|
||||||
|
block.branch.targets.add(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void endBlock(int token, boolean loop) {
|
||||||
|
var block = blocks.remove(blocks.size() - 1);
|
||||||
|
pendingBranches.addAll(block.pendingBranches);
|
||||||
|
if (loop) {
|
||||||
|
var branch = newBranch(false);
|
||||||
|
branch.targets.add(block.address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void call(int functionIndex) {
|
||||||
|
call();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void indirectCall(int typeIndex, int tableIndex) {
|
||||||
|
call();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void call() {
|
||||||
|
newPendingBranch(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void opcode(Opcode opcode) {
|
||||||
|
switch (opcode) {
|
||||||
|
case RETURN:
|
||||||
|
case UNREACHABLE: {
|
||||||
|
newBranch(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void branch(BranchOpcode opcode, int depth, int target) {
|
||||||
|
var branch = newBranch(false);
|
||||||
|
if (opcode == BranchOpcode.BR_IF) {
|
||||||
|
pendingBranches.add(branch);
|
||||||
|
}
|
||||||
|
var block = blocks.get(target);
|
||||||
|
block.pendingBranches.add(branch);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tableBranch(int[] depths, int[] targets, int defaultDepth, int defaultTarget) {
|
||||||
|
var branch = newPendingBranch(false);
|
||||||
|
for (var target : targets) {
|
||||||
|
blocks.get(target).pendingBranches.add(branch);
|
||||||
|
}
|
||||||
|
blocks.get(defaultTarget).pendingBranches.add(branch);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Branch newPendingBranch(boolean isCall) {
|
||||||
|
var branch = newBranch(isCall);
|
||||||
|
pendingBranches.add(branch);
|
||||||
|
return branch;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Branch newBranch(boolean isCall) {
|
||||||
|
var branch = new Branch(address, isCall);
|
||||||
|
branches.add(branch);
|
||||||
|
return branch;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flush() {
|
||||||
|
for (var branch : pendingBranches) {
|
||||||
|
branch.targets.add(address);
|
||||||
|
}
|
||||||
|
pendingBranches.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void functionEnd() {
|
||||||
|
for (var branch : branches) {
|
||||||
|
if (branch.isCall) {
|
||||||
|
cfb.addCall(branch.address, branch.targets.toArray());
|
||||||
|
} else {
|
||||||
|
cfb.addBranch(branch.address, branch.targets.toArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ranges.add(cfb.build());
|
||||||
|
branches.clear();
|
||||||
|
pendingBranches.clear();
|
||||||
|
blocks.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Block {
|
||||||
|
Branch branch;
|
||||||
|
final int address;
|
||||||
|
List<Branch> pendingBranches = new ArrayList<>();
|
||||||
|
|
||||||
|
Block(Branch branch, int address) {
|
||||||
|
this.branch = branch;
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Branch {
|
||||||
|
final int address;
|
||||||
|
final IntArrayList targets = new IntArrayList();
|
||||||
|
final boolean isCall;
|
||||||
|
|
||||||
|
Branch(int address, boolean isCall) {
|
||||||
|
this.address = address;
|
||||||
|
this.isCall = isCall;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,7 +21,9 @@ import java.nio.file.Files;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import org.teavm.backend.wasm.debug.info.LineInfo;
|
import org.teavm.backend.wasm.debug.info.ControlFlowInfo;
|
||||||
|
import org.teavm.backend.wasm.debug.info.DebugInfo;
|
||||||
|
import org.teavm.backend.wasm.parser.CodeSectionParser;
|
||||||
import org.teavm.backend.wasm.parser.ModuleParser;
|
import org.teavm.backend.wasm.parser.ModuleParser;
|
||||||
import org.teavm.common.AsyncInputStream;
|
import org.teavm.common.AsyncInputStream;
|
||||||
import org.teavm.common.ByteArrayAsyncInputStream;
|
import org.teavm.common.ByteArrayAsyncInputStream;
|
||||||
|
@ -29,6 +31,8 @@ import org.teavm.common.ByteArrayAsyncInputStream;
|
||||||
public class DebugInfoParser extends ModuleParser {
|
public class DebugInfoParser extends ModuleParser {
|
||||||
private Map<String, DebugSectionParser> sectionParsers = new HashMap<>();
|
private Map<String, DebugSectionParser> sectionParsers = new HashMap<>();
|
||||||
private DebugLinesParser lines;
|
private DebugLinesParser lines;
|
||||||
|
private ControlFlowInfo controlFlow;
|
||||||
|
private int offset;
|
||||||
|
|
||||||
public DebugInfoParser(AsyncInputStream reader) {
|
public DebugInfoParser(AsyncInputStream reader) {
|
||||||
super(reader);
|
super(reader);
|
||||||
|
@ -45,8 +49,8 @@ public class DebugInfoParser extends ModuleParser {
|
||||||
return section;
|
return section;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LineInfo getLineInfo() {
|
public DebugInfo getDebugInfo() {
|
||||||
return lines.getLineInfo();
|
return new DebugInfo(lines.getLineInfo(), controlFlow, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -55,11 +59,19 @@ public class DebugInfoParser extends ModuleParser {
|
||||||
var parser = sectionParsers.get(name);
|
var parser = sectionParsers.get(name);
|
||||||
return parser != null ? parser::parse : null;
|
return parser != null ? parser::parse : null;
|
||||||
} else if (code == 10) {
|
} else if (code == 10) {
|
||||||
lines.setOffset(pos);
|
this.offset = pos;
|
||||||
|
return this::parseCode;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void parseCode(byte[] data) {
|
||||||
|
var builder = new ControlFlowParser();
|
||||||
|
var codeParser = new CodeSectionParser(builder, builder);
|
||||||
|
codeParser.parse(data);
|
||||||
|
controlFlow = builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
if (args.length != 1) {
|
if (args.length != 1) {
|
||||||
System.err.println("Pass single argument - path to wasm file");
|
System.err.println("Pass single argument - path to wasm file");
|
||||||
|
@ -69,9 +81,9 @@ public class DebugInfoParser extends ModuleParser {
|
||||||
var input = new ByteArrayAsyncInputStream(Files.readAllBytes(file.toPath()));
|
var input = new ByteArrayAsyncInputStream(Files.readAllBytes(file.toPath()));
|
||||||
var parser = new DebugInfoParser(input);
|
var parser = new DebugInfoParser(input);
|
||||||
input.readFully(parser::parse);
|
input.readFully(parser::parse);
|
||||||
var lineInfo = parser.getLineInfo();
|
var debugInfo = parser.getDebugInfo();
|
||||||
if (lineInfo != null) {
|
if (debugInfo != null) {
|
||||||
lineInfo.dump(System.out);
|
debugInfo.dump(System.out);
|
||||||
} else {
|
} else {
|
||||||
System.out.println("No debug information found");
|
System.out.println("No debug information found");
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,6 @@ public class DebugLinesParser extends DebugSectionParser {
|
||||||
private int address;
|
private int address;
|
||||||
private MethodInfo currentMethod;
|
private MethodInfo currentMethod;
|
||||||
private int sequenceStartAddress;
|
private int sequenceStartAddress;
|
||||||
private int offset;
|
|
||||||
|
|
||||||
public DebugLinesParser(
|
public DebugLinesParser(
|
||||||
DebugFileParser files,
|
DebugFileParser files,
|
||||||
|
@ -57,13 +56,8 @@ public class DebugLinesParser extends DebugSectionParser {
|
||||||
return lineInfo;
|
return lineInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOffset(int offset) {
|
|
||||||
this.offset = offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doParse() {
|
protected void doParse() {
|
||||||
address = offset;
|
|
||||||
while (ptr < data.length) {
|
while (ptr < data.length) {
|
||||||
var cmd = data[ptr++] & 0xFF;
|
var cmd = data[ptr++] & 0xFF;
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
|
@ -91,7 +85,7 @@ public class DebugLinesParser extends DebugSectionParser {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lineInfo = new LineInfo(offset, sequences.toArray(new LineInfoSequence[0]));
|
lineInfo = new LineInfo(sequences.toArray(new LineInfoSequence[0]));
|
||||||
sequences = null;
|
sequences = null;
|
||||||
commands = null;
|
commands = null;
|
||||||
stateStack = null;
|
stateStack = null;
|
||||||
|
|
|
@ -152,7 +152,7 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void endBlock(int token) {
|
public void endBlock(int token, boolean loop) {
|
||||||
writer.address(address).outdent().write("end (; $label_" + token + " ;)").eol();
|
writer.address(address).outdent().write("end (; $label_" + token + " ;)").eol();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ public interface CodeListener {
|
||||||
default void startElseSection(int token) {
|
default void startElseSection(int token) {
|
||||||
}
|
}
|
||||||
|
|
||||||
default void endBlock(int token) {
|
default void endBlock(int token, boolean loop) {
|
||||||
}
|
}
|
||||||
|
|
||||||
default void branch(BranchOpcode opcode, int depth, int target) {
|
default void branch(BranchOpcode opcode, int depth, int target) {
|
||||||
|
|
|
@ -18,17 +18,26 @@ package org.teavm.backend.wasm.parser;
|
||||||
import org.teavm.backend.wasm.model.WasmType;
|
import org.teavm.backend.wasm.model.WasmType;
|
||||||
|
|
||||||
public interface CodeSectionListener {
|
public interface CodeSectionListener {
|
||||||
void sectionStart(int functionCount);
|
default void sectionStart(int functionCount) {
|
||||||
|
}
|
||||||
boolean functionStart(int index, int size);
|
|
||||||
|
default boolean functionStart(int index, int size) {
|
||||||
void localsStart(int count);
|
return false;
|
||||||
|
}
|
||||||
void local(int start, int count, WasmType type);
|
|
||||||
|
default void localsStart(int count) {
|
||||||
CodeListener code();
|
}
|
||||||
|
|
||||||
void functionEnd();
|
default void local(int start, int count, WasmType type) {
|
||||||
|
}
|
||||||
void sectionEnd();
|
|
||||||
|
default CodeListener code() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
default void functionEnd() {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void sectionEnd() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,11 +68,11 @@ public class CodeSectionParser {
|
||||||
var end = ptr + functionSize;
|
var end = ptr + functionSize;
|
||||||
if (listener.functionStart(index, functionSize)) {
|
if (listener.functionStart(index, functionSize)) {
|
||||||
parseLocals();
|
parseLocals();
|
||||||
}
|
|
||||||
codeListener = listener.code();
|
codeListener = listener.code();
|
||||||
if (codeListener != null) {
|
if (codeListener != null) {
|
||||||
parseCode();
|
parseCode();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
ptr = end;
|
ptr = end;
|
||||||
reportAddress();
|
reportAddress();
|
||||||
listener.functionEnd();
|
listener.functionEnd();
|
||||||
|
@ -625,7 +625,7 @@ public class CodeSectionParser {
|
||||||
}
|
}
|
||||||
blockStack.remove(blockStack.size() - 1);
|
blockStack.remove(blockStack.size() - 1);
|
||||||
reportAddress();
|
reportAddress();
|
||||||
codeListener.endBlock(token);
|
codeListener.endBlock(token, isLoop);
|
||||||
++ptr;
|
++ptr;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -656,7 +656,7 @@ public class CodeSectionParser {
|
||||||
}
|
}
|
||||||
blockStack.remove(blockStack.size() - 1);
|
blockStack.remove(blockStack.size() - 1);
|
||||||
reportAddress();
|
reportAddress();
|
||||||
codeListener.endBlock(token);
|
codeListener.endBlock(token, false);
|
||||||
++ptr;
|
++ptr;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.teavm.backend.wasm.debug.info.LineInfo;
|
import org.teavm.backend.wasm.debug.info.DebugInfo;
|
||||||
import org.teavm.backend.wasm.debug.info.LineInfoFileCommand;
|
import org.teavm.backend.wasm.debug.info.LineInfoFileCommand;
|
||||||
import org.teavm.backend.wasm.debug.info.MethodInfo;
|
import org.teavm.backend.wasm.debug.info.MethodInfo;
|
||||||
import org.teavm.backend.wasm.debug.parser.DebugInfoParser;
|
import org.teavm.backend.wasm.debug.parser.DebugInfoParser;
|
||||||
|
@ -55,9 +55,9 @@ public class Debugger {
|
||||||
private List<JavaScriptBreakpoint> temporaryBreakpoints = new ArrayList<>();
|
private List<JavaScriptBreakpoint> temporaryBreakpoints = new ArrayList<>();
|
||||||
private Map<JavaScriptScript, DebugInformation> debugInformationMap = new HashMap<>();
|
private Map<JavaScriptScript, DebugInformation> debugInformationMap = new HashMap<>();
|
||||||
private Map<String, Set<DebugInformation>> debugInformationFileMap = new HashMap<>();
|
private Map<String, Set<DebugInformation>> debugInformationFileMap = new HashMap<>();
|
||||||
private Map<JavaScriptScript, LineInfo> wasmLineInfoMap = new HashMap<>();
|
private Map<JavaScriptScript, DebugInfo> wasmDebugInfoMap = new HashMap<>();
|
||||||
private Map<LineInfo, JavaScriptScript> wasmScriptMap = new HashMap<>();
|
private Map<DebugInfo, JavaScriptScript> wasmScriptMap = new HashMap<>();
|
||||||
private Map<String, Set<LineInfo>> wasmInfoFileMap = new HashMap<>();
|
private Map<String, Set<DebugInfo>> wasmInfoFileMap = new HashMap<>();
|
||||||
private Map<DebugInformation, JavaScriptScript> scriptMap = new HashMap<>();
|
private Map<DebugInformation, JavaScriptScript> scriptMap = new HashMap<>();
|
||||||
private Map<JavaScriptBreakpoint, Breakpoint> breakpointMap = new HashMap<>();
|
private Map<JavaScriptBreakpoint, Breakpoint> breakpointMap = new HashMap<>();
|
||||||
private Set<Breakpoint> breakpoints = new LinkedHashSet<>();
|
private Set<Breakpoint> breakpoints = new LinkedHashSet<>();
|
||||||
|
@ -130,12 +130,14 @@ public class Debugger {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case WASM: {
|
case WASM: {
|
||||||
var info = wasmLineInfoMap.get(script);
|
var info = wasmDebugInfoMap.get(script);
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
return enterMethod ? javaScriptDebugger.stepInto() : javaScriptDebugger.stepOver();
|
return enterMethod ? javaScriptDebugger.stepInto() : javaScriptDebugger.stepOver();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case UNKNOWN:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
enterMethod = false;
|
enterMethod = false;
|
||||||
first = false;
|
first = false;
|
||||||
|
@ -240,7 +242,7 @@ public class Debugger {
|
||||||
return list != null ? new ArrayList<>(list) : Collections.emptyList();
|
return list != null ? new ArrayList<>(list) : Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<LineInfo> wasmLineInfoBySource(String sourceFile) {
|
private List<DebugInfo> wasmLineInfoBySource(String sourceFile) {
|
||||||
var list = wasmInfoFileMap.get(sourceFile);
|
var list = wasmInfoFileMap.get(sourceFile);
|
||||||
return list != null ? new ArrayList<>(list) : Collections.emptyList();
|
return list != null ? new ArrayList<>(list) : Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
@ -315,16 +317,19 @@ public class Debugger {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (var wasmLineInfo : wasmLineInfoBySource(location.getFileName())) {
|
for (var wasmDebugInfo : wasmLineInfoBySource(location.getFileName())) {
|
||||||
for (var sequence : wasmLineInfo.sequences()) {
|
if (wasmDebugInfo.lines() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (var sequence : wasmDebugInfo.lines().sequences()) {
|
||||||
for (var loc : sequence.unpack().locations()) {
|
for (var loc : sequence.unpack().locations()) {
|
||||||
if (loc.location() == null) {
|
if (loc.location() == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (loc.location().line() == location.getLine()
|
if (loc.location().line() == location.getLine()
|
||||||
&& loc.location().file().fullName().equals(location.getFileName())) {
|
&& loc.location().file().fullName().equals(location.getFileName())) {
|
||||||
var jsLocation = new JavaScriptLocation(wasmScriptMap.get(wasmLineInfo),
|
var jsLocation = new JavaScriptLocation(wasmScriptMap.get(wasmDebugInfo),
|
||||||
0, loc.address());
|
0, loc.address() + wasmDebugInfo.offset());
|
||||||
promises.add(javaScriptDebugger.createBreakpoint(jsLocation).thenVoid(jsBreakpoint -> {
|
promises.add(javaScriptDebugger.createBreakpoint(jsLocation).thenVoid(jsBreakpoint -> {
|
||||||
jsBreakpoints.add(jsBreakpoint);
|
jsBreakpoints.add(jsBreakpoint);
|
||||||
breakpointMap.put(jsBreakpoint, breakpoint);
|
breakpointMap.put(jsBreakpoint, breakpoint);
|
||||||
|
@ -427,15 +432,20 @@ public class Debugger {
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<SourceLocationWithMethod> mapWasmFrames(JavaScriptCallFrame frame) {
|
private List<SourceLocationWithMethod> mapWasmFrames(JavaScriptCallFrame frame) {
|
||||||
var lineInfo = wasmLineInfoMap.get(frame.getLocation().getScript());
|
var debugInfo = wasmDebugInfoMap.get(frame.getLocation().getScript());
|
||||||
|
if (debugInfo == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
var lineInfo = debugInfo.lines();
|
||||||
if (lineInfo == null) {
|
if (lineInfo == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
var sequence = lineInfo.find(frame.getLocation().getColumn());
|
var address = frame.getLocation().getColumn() - debugInfo.offset();
|
||||||
|
var sequence = lineInfo.find(address);
|
||||||
if (sequence == null) {
|
if (sequence == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
var instructionLocation = sequence.unpack().find(frame.getLocation().getColumn());
|
var instructionLocation = sequence.unpack().find(address);
|
||||||
if (instructionLocation == null) {
|
if (instructionLocation == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
@ -525,15 +535,18 @@ public class Debugger {
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
if (parser.getLineInfo() != null) {
|
var debugInfo = parser.getDebugInfo();
|
||||||
wasmLineInfoMap.put(script, parser.getLineInfo());
|
if (debugInfo != null) {
|
||||||
wasmScriptMap.put(parser.getLineInfo(), script);
|
wasmDebugInfoMap.put(script, debugInfo);
|
||||||
for (var sequence : parser.getLineInfo().sequences()) {
|
wasmScriptMap.put(debugInfo, script);
|
||||||
|
if (debugInfo.lines() != null) {
|
||||||
|
for (var sequence : debugInfo.lines().sequences()) {
|
||||||
for (var command : sequence.commands()) {
|
for (var command : sequence.commands()) {
|
||||||
if (command instanceof LineInfoFileCommand) {
|
if (command instanceof LineInfoFileCommand) {
|
||||||
var file = ((LineInfoFileCommand) command).file();
|
var file = ((LineInfoFileCommand) command).file();
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
addWasmInfoFile(file.fullName(), parser.getLineInfo());
|
addWasmInfoFile(file.fullName(), debugInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -542,13 +555,13 @@ public class Debugger {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addWasmInfoFile(String sourceFile, LineInfo wasmLineInfo) {
|
private void addWasmInfoFile(String sourceFile, DebugInfo debugInfo) {
|
||||||
var list = wasmInfoFileMap.get(sourceFile);
|
var list = wasmInfoFileMap.get(sourceFile);
|
||||||
if (list == null) {
|
if (list == null) {
|
||||||
list = new HashSet<>();
|
list = new HashSet<>();
|
||||||
wasmInfoFileMap.put(sourceFile, list);
|
wasmInfoFileMap.put(sourceFile, list);
|
||||||
}
|
}
|
||||||
list.add(wasmLineInfo);
|
list.add(debugInfo);
|
||||||
allSourceFiles.add(sourceFile);
|
allSourceFiles.add(sourceFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,11 @@
|
||||||
<groupId>javax.websocket</groupId>
|
<groupId>javax.websocket</groupId>
|
||||||
<artifactId>javax.websocket-api</artifactId>
|
<artifactId>javax.websocket-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.carrotsearch</groupId>
|
||||||
|
<artifactId>hppc</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user