mirror of
https://github.com/konsoletyper/teavm.git
synced 2024-11-27 01:30:35 +08:00
wasm gc: add stack trace deobfuscator
This commit is contained in:
parent
0b292bb510
commit
40d2ab97ec
@ -19,9 +19,13 @@ import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import org.teavm.classlib.PlatformDetector;
|
||||
import org.teavm.classlib.java.util.TArrays;
|
||||
import org.teavm.interop.Import;
|
||||
import org.teavm.interop.Remove;
|
||||
import org.teavm.interop.Rename;
|
||||
import org.teavm.interop.Superclass;
|
||||
import org.teavm.jso.JSIndexer;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.JSProperty;
|
||||
import org.teavm.runtime.ExceptionHandling;
|
||||
|
||||
@Superclass("java.lang.Object")
|
||||
@ -33,6 +37,7 @@ public class TThrowable extends RuntimeException {
|
||||
private boolean writableStackTrace;
|
||||
private TThrowable[] suppressed = new TThrowable[0];
|
||||
private TStackTraceElement[] stackTrace;
|
||||
private LazyStackSupplier lazyStackTrace;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Rename("fakeInit")
|
||||
@ -104,10 +109,35 @@ public class TThrowable extends RuntimeException {
|
||||
public Throwable fillInStackTrace() {
|
||||
if (PlatformDetector.isLowLevel()) {
|
||||
stackTrace = (TStackTraceElement[]) (Object) ExceptionHandling.fillStackTrace();
|
||||
} else if (PlatformDetector.isWebAssemblyGC()) {
|
||||
lazyStackTrace = takeWasmGCStack();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private void ensureStackTrace() {
|
||||
if (PlatformDetector.isWebAssemblyGC()) {
|
||||
if (lazyStackTrace != null) {
|
||||
var supplier = lazyStackTrace;
|
||||
lazyStackTrace = null;
|
||||
var nativeStack = supplier.getStack();
|
||||
if (nativeStack == null) {
|
||||
return;
|
||||
}
|
||||
var stack = new TStackTraceElement[nativeStack.getLength()];
|
||||
for (var i = 0; i < nativeStack.getLength(); ++i) {
|
||||
var frame = nativeStack.get(i);
|
||||
stack[i] = new TStackTraceElement(frame.getClassName(), frame.getMethod(), frame.getFile(),
|
||||
frame.getLine());
|
||||
}
|
||||
stackTrace = stack;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Import(name = "takeStackTrace")
|
||||
private native LazyStackSupplier takeWasmGCStack();
|
||||
|
||||
@Rename("getMessage")
|
||||
public String getMessage0() {
|
||||
return message;
|
||||
@ -158,6 +188,7 @@ public class TThrowable extends RuntimeException {
|
||||
stream.print(": " + message);
|
||||
}
|
||||
stream.println();
|
||||
ensureStackTrace();
|
||||
if (stackTrace != null) {
|
||||
for (TStackTraceElement element : stackTrace) {
|
||||
stream.print("\tat ");
|
||||
@ -177,6 +208,7 @@ public class TThrowable extends RuntimeException {
|
||||
stream.print(": " + message);
|
||||
}
|
||||
stream.println();
|
||||
ensureStackTrace();
|
||||
if (stackTrace != null) {
|
||||
for (TStackTraceElement element : stackTrace) {
|
||||
stream.print("\tat ");
|
||||
@ -191,10 +223,14 @@ public class TThrowable extends RuntimeException {
|
||||
|
||||
@Rename("getStackTrace")
|
||||
public TStackTraceElement[] getStackTrace0() {
|
||||
ensureStackTrace();
|
||||
return stackTrace != null ? stackTrace.clone() : new TStackTraceElement[0];
|
||||
}
|
||||
|
||||
public void setStackTrace(@SuppressWarnings("unused") TStackTraceElement[] stackTrace) {
|
||||
if (PlatformDetector.isWebAssemblyGC()) {
|
||||
lazyStackTrace = null;
|
||||
}
|
||||
this.stackTrace = stackTrace.clone();
|
||||
}
|
||||
|
||||
@ -210,4 +246,30 @@ public class TThrowable extends RuntimeException {
|
||||
suppressed = TArrays.copyOf(suppressed, suppressed.length + 1);
|
||||
suppressed[suppressed.length - 1] = exception;
|
||||
}
|
||||
|
||||
interface LazyStackSupplier extends JSObject {
|
||||
StackFrames getStack();
|
||||
}
|
||||
|
||||
interface StackFrames extends JSObject {
|
||||
@JSProperty
|
||||
int getLength();
|
||||
|
||||
@JSIndexer
|
||||
StackFrame get(int index);
|
||||
}
|
||||
|
||||
interface StackFrame extends JSObject {
|
||||
@JSProperty
|
||||
String getClassName();
|
||||
|
||||
@JSProperty
|
||||
String getMethod();
|
||||
|
||||
@JSProperty
|
||||
String getFile();
|
||||
|
||||
@JSProperty
|
||||
int getLine();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2024 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 DeobfuscatedLocation {
|
||||
public final FileInfo file;
|
||||
public final MethodInfo method;
|
||||
public final int line;
|
||||
|
||||
public DeobfuscatedLocation(FileInfo file, MethodInfo method, int line) {
|
||||
this.file = file;
|
||||
this.method = method;
|
||||
this.line = line;
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@
|
||||
package org.teavm.backend.wasm.debug.info;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -34,6 +35,44 @@ public class LineInfo {
|
||||
return sequenceList;
|
||||
}
|
||||
|
||||
public DeobfuscatedLocation[] deobfuscate(int[] addresses) {
|
||||
var result = new ArrayList<DeobfuscatedLocation>();
|
||||
for (var address : addresses) {
|
||||
var part = deobfuscateSingle(address);
|
||||
if (part != null) {
|
||||
result.addAll(List.of(part));
|
||||
}
|
||||
}
|
||||
return result.toArray(new DeobfuscatedLocation[0]);
|
||||
}
|
||||
|
||||
public DeobfuscatedLocation[] deobfuscateSingle(int address) {
|
||||
var sequence = find(address);
|
||||
if (sequence == null) {
|
||||
return null;
|
||||
}
|
||||
var instructionLoc = sequence.unpack().find(address);
|
||||
if (instructionLoc == null) {
|
||||
return null;
|
||||
}
|
||||
var location = instructionLoc.location();
|
||||
if (location == null) {
|
||||
return null;
|
||||
}
|
||||
var result = new DeobfuscatedLocation[location.depth()];
|
||||
var method = sequence.method();
|
||||
var i = 0;
|
||||
while (true) {
|
||||
result[i++] = new DeobfuscatedLocation(location.file(), method, location.line());
|
||||
if (i >= result.length) {
|
||||
break;
|
||||
}
|
||||
method = location.inlining().method();
|
||||
location = location.inlining().location();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public LineInfoSequence find(int address) {
|
||||
var index = CollectionUtil.binarySearch(sequenceList, address, LineInfoSequence::endAddress);
|
||||
if (index < 0) {
|
||||
|
@ -54,7 +54,7 @@ public class LineInfoUnpackedSequence {
|
||||
}
|
||||
|
||||
public int findIndex(int address) {
|
||||
if (address < startAddress || address >= endAddress) {
|
||||
if (address < startAddress || address >= endAddress || locations.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
var index = CollectionUtil.binarySearch(locations, address, InstructionLocation::address);
|
||||
|
@ -37,4 +37,14 @@ public class Location {
|
||||
public InliningLocation inlining() {
|
||||
return inlining;
|
||||
}
|
||||
|
||||
public int depth() {
|
||||
var result = 0;
|
||||
var loc = this;
|
||||
while (loc != null) {
|
||||
++result;
|
||||
loc = loc.inlining != null ? loc.inlining.location() : null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2024 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.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import org.teavm.backend.wasm.debug.info.LineInfo;
|
||||
|
||||
public class LinesDeobfuscationParser {
|
||||
private Map<String, DebugSectionParser> sectionParsers = new LinkedHashMap<>();
|
||||
private DebugLinesParser lines;
|
||||
|
||||
public LinesDeobfuscationParser() {
|
||||
var strings = addSection(new DebugStringParser());
|
||||
var files = addSection(new DebugFileParser(strings));
|
||||
var packages = addSection(new DebugPackageParser(strings));
|
||||
var classes = addSection(new DebugClassParser(strings, packages));
|
||||
var methods = addSection(new DebugMethodParser(strings, classes));
|
||||
lines = addSection(new DebugLinesParser(files, methods));
|
||||
}
|
||||
|
||||
private <T extends DebugSectionParser> T addSection(T section) {
|
||||
sectionParsers.put(section.name(), section);
|
||||
return section;
|
||||
}
|
||||
|
||||
public boolean canHandleSection(String name) {
|
||||
return sectionParsers.keySet().contains(name);
|
||||
}
|
||||
|
||||
public void applySection(String name, byte[] data) {
|
||||
var parser = sectionParsers.get(name);
|
||||
if (parser != null) {
|
||||
parser.parse(data);
|
||||
}
|
||||
}
|
||||
|
||||
public void pullSections(Function<String, byte[]> provider) {
|
||||
for (var parser : sectionParsers.values()) {
|
||||
var section = provider.apply(parser.name());
|
||||
if (section != null) {
|
||||
parser.parse(section);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public LineInfo getLineInfo() {
|
||||
return lines.getLineInfo();
|
||||
}
|
||||
}
|
@ -28,7 +28,6 @@ public abstract class DisassemblyWriter {
|
||||
private PrintWriter out;
|
||||
private boolean withAddress;
|
||||
private int indentLevel;
|
||||
private int addressWithinSection;
|
||||
private int address;
|
||||
private boolean hasAddress;
|
||||
private boolean lineStarted;
|
||||
@ -55,7 +54,6 @@ public abstract class DisassemblyWriter {
|
||||
}
|
||||
|
||||
public void startSection() {
|
||||
addressWithinSection = -1;
|
||||
currentSequenceIndex = 0;
|
||||
}
|
||||
|
||||
@ -105,13 +103,13 @@ public abstract class DisassemblyWriter {
|
||||
return;
|
||||
}
|
||||
if (currentCommandIndex < 0) {
|
||||
if (addressWithinSection < debugLines.sequences().get(currentSequenceIndex).startAddress()) {
|
||||
if (address < debugLines.sequences().get(currentSequenceIndex).startAddress()) {
|
||||
return;
|
||||
}
|
||||
currentCommandIndex = 0;
|
||||
printSingleDebugAnnotation("start debug line sequence");
|
||||
} else {
|
||||
if (addressWithinSection >= debugLines.sequences().get(currentSequenceIndex).endAddress()) {
|
||||
if (address >= debugLines.sequences().get(currentSequenceIndex).endAddress()) {
|
||||
printSingleDebugAnnotation("end debug line sequence");
|
||||
++currentSequenceIndex;
|
||||
currentCommandIndex = -1;
|
||||
@ -123,7 +121,7 @@ public abstract class DisassemblyWriter {
|
||||
var sequence = debugLines.sequences().get(currentSequenceIndex);
|
||||
while (currentCommandIndex < sequence.commands().size()) {
|
||||
var command = sequence.commands().get(currentCommandIndex);
|
||||
if (addressWithinSection < command.address()) {
|
||||
if (address < command.address()) {
|
||||
break;
|
||||
}
|
||||
|
||||
@ -219,7 +217,6 @@ public abstract class DisassemblyWriter {
|
||||
public final AddressListener addressListener = new AddressListener() {
|
||||
@Override
|
||||
public void address(int address) {
|
||||
addressWithinSection = address;
|
||||
DisassemblyWriter.this.address = address + addressOffset;
|
||||
}
|
||||
};
|
||||
|
@ -336,8 +336,9 @@ public class WasmBinaryRenderer {
|
||||
.collect(Collectors.toList());
|
||||
|
||||
section.writeLEB(functions.size());
|
||||
var sectionOffset = output.getPosition() + 4;
|
||||
for (var function : functions) {
|
||||
var body = renderFunction(module, function, section.getPosition() + 4);
|
||||
var body = renderFunction(module, function, section.getPosition() + 4, sectionOffset);
|
||||
var startPos = section.getPosition();
|
||||
section.writeLEB4(body.length);
|
||||
section.writeBytes(body);
|
||||
@ -351,10 +352,10 @@ public class WasmBinaryRenderer {
|
||||
dwarfGenerator.setCodeSize(section.getPosition());
|
||||
}
|
||||
|
||||
writeSection(SECTION_CODE, "code", section.getData());
|
||||
writeSection(SECTION_CODE, "code", section.getData(), true);
|
||||
}
|
||||
|
||||
private byte[] renderFunction(WasmModule module, WasmFunction function, int offset) {
|
||||
private byte[] renderFunction(WasmModule module, WasmFunction function, int offset, int sectionOffset) {
|
||||
var code = new WasmBinaryWriter();
|
||||
|
||||
var dwarfSubprogram = dwarfClassGen != null ? dwarfClassGen.getSubprogram(function.getName()) : null;
|
||||
@ -393,7 +394,7 @@ public class WasmBinaryRenderer {
|
||||
}
|
||||
|
||||
var visitor = new WasmBinaryRenderingVisitor(code, module, dwarfGenerator,
|
||||
function.getJavaMethod() != null ? debugLines : null, offset);
|
||||
function.getJavaMethod() != null ? debugLines : null, offset + sectionOffset);
|
||||
for (var part : function.getBody()) {
|
||||
visitor.preprocess(part);
|
||||
}
|
||||
@ -408,7 +409,7 @@ public class WasmBinaryRenderer {
|
||||
dwarfSubprogram.endOffset = code.getPosition() + offset;
|
||||
}
|
||||
if (debugVariables != null) {
|
||||
writeDebugVariables(function, offset, code.getPosition());
|
||||
writeDebugVariables(function, offset + sectionOffset, code.getPosition());
|
||||
}
|
||||
|
||||
return code.getData();
|
||||
@ -600,13 +601,21 @@ public class WasmBinaryRenderer {
|
||||
}
|
||||
|
||||
private void writeSection(int id, String name, byte[] data) {
|
||||
writeSection(id, name, data, false);
|
||||
}
|
||||
|
||||
private void writeSection(int id, String name, byte[] data, boolean constantSizeLength) {
|
||||
var start = output.getPosition();
|
||||
output.writeByte(id);
|
||||
int length = data.length;
|
||||
if (id == 0) {
|
||||
length += name.length() + 1;
|
||||
}
|
||||
output.writeLEB(length);
|
||||
if (constantSizeLength) {
|
||||
output.writeLEB4(length);
|
||||
} else {
|
||||
output.writeLEB(length);
|
||||
}
|
||||
if (id == 0) {
|
||||
output.writeAsciiString(name);
|
||||
}
|
||||
|
@ -32,7 +32,8 @@ let setGlobalName = function(name, value) {
|
||||
|
||||
function defaults(imports) {
|
||||
let context = {
|
||||
exports: null
|
||||
exports: null,
|
||||
stackDeobfuscator: null
|
||||
};
|
||||
dateImports(imports);
|
||||
consoleImports(imports, context);
|
||||
@ -41,7 +42,10 @@ function defaults(imports) {
|
||||
imports.teavmMath = Math;
|
||||
return {
|
||||
supplyExports(exports) {
|
||||
context.exports = exports
|
||||
context.exports = exports;
|
||||
},
|
||||
supplyStackDeobfuscator(deobfuscator) {
|
||||
context.stackDeobfuscator = deobfuscator;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -142,26 +146,39 @@ function coreImports(imports, context) {
|
||||
return weakRef;
|
||||
},
|
||||
stringDeref: weakRef => weakRef.deref(),
|
||||
currentStackTrace() {
|
||||
if (stackDeobfuscator) {
|
||||
return;
|
||||
}
|
||||
let reportCallFrame = context.exports["teavm.reportCallFrame"];
|
||||
if (typeof reportCallFrame !== "function") {
|
||||
return;
|
||||
}
|
||||
takeStackTrace() {
|
||||
let stack = new Error().stack;
|
||||
for (let line in stack.split("\n")) {
|
||||
let addresses = [];
|
||||
for (let line of stack.split("\n")) {
|
||||
let match = chromeExceptionRegex.exec(line);
|
||||
if (match !== null) {
|
||||
let address = parseInt(match.groups[1], 16);
|
||||
let frames = stackDeobfuscator(address);
|
||||
for (let frame of frames) {
|
||||
let line = frame.line;
|
||||
reportCallFrame(file, method, cls, line);
|
||||
}
|
||||
if (match !== null && match.length >= 2) {
|
||||
let address = parseInt(match[1], 16);
|
||||
addresses.push(address);
|
||||
}
|
||||
}
|
||||
return {
|
||||
getStack() {
|
||||
let result;
|
||||
if (context.stackDeobfuscator) {
|
||||
try {
|
||||
result = context.stackDeobfuscator(addresses);
|
||||
} catch (e) {
|
||||
console.warn("Could not deobfuscate stack", e);
|
||||
}
|
||||
}
|
||||
if (!result) {
|
||||
result = addresses.map(address => {
|
||||
return {
|
||||
className: "java.lang.Throwable$FakeClass",
|
||||
method: "fakeMethod",
|
||||
file: "Throwable.java",
|
||||
line: address
|
||||
};
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -175,7 +192,7 @@ function jsoImports(imports, context) {
|
||||
let jsWrappers = new WeakMap();
|
||||
let javaWrappers = new WeakMap();
|
||||
let primitiveWrappers = new Map();
|
||||
let primitiveFinalization = new FinalizationRegistry(token => primitiveFinalization.delete(token));
|
||||
let primitiveFinalization = new FinalizationRegistry(token => primitiveWrappers.delete(token));
|
||||
let hashCodes = new WeakMap();
|
||||
let javaExceptionWrappers = new WeakMap();
|
||||
let lastHashCode = 2463534242;
|
||||
@ -576,34 +593,74 @@ function jsoImports(imports, context) {
|
||||
}
|
||||
}
|
||||
|
||||
function load(path, options) {
|
||||
async function load(path, options) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
const importObj = {};
|
||||
const defaultsResults = defaults(importObj);
|
||||
const defaultsResult = defaults(importObj);
|
||||
if (typeof options.installImports !== "undefined") {
|
||||
options.installImports(importObj);
|
||||
}
|
||||
|
||||
return WebAssembly.instantiateStreaming(fetch(path), importObj)
|
||||
.then(r => {
|
||||
defaultsResults.supplyExports(r.instance.exports);
|
||||
let userExports = {};
|
||||
let teavm = {
|
||||
exports: userExports,
|
||||
instance: r.instance,
|
||||
module: r.module
|
||||
};
|
||||
for (let key in r.instance.exports) {
|
||||
let exportObj = r.instance.exports[key];
|
||||
if (exportObj instanceof WebAssembly.Global) {
|
||||
Object.defineProperty(userExports, key, {
|
||||
get: () => exportObj.value
|
||||
});
|
||||
}
|
||||
let [deobfuscatorFactory, { module, instance }] = await Promise.all([
|
||||
options.attachStackDeobfuscator ? getDeobfuscator(path, options) : Promise.resolve(null),
|
||||
WebAssembly.instantiateStreaming(fetch(path), importObj)
|
||||
]);
|
||||
|
||||
defaultsResult.supplyExports(instance.exports);
|
||||
if (deobfuscatorFactory) {
|
||||
let deobfuscator = createDeobfuscator(module, deobfuscatorFactory);
|
||||
if (deobfuscator !== null) {
|
||||
defaultsResult.supplyStackDeobfuscator(deobfuscator);
|
||||
}
|
||||
}
|
||||
let userExports = {};
|
||||
let teavm = {
|
||||
exports: userExports,
|
||||
instance: instance,
|
||||
module: module
|
||||
};
|
||||
for (let key in instance.exports) {
|
||||
let exportObj = instance.exports[key];
|
||||
if (exportObj instanceof WebAssembly.Global) {
|
||||
Object.defineProperty(userExports, key, {
|
||||
get: () => exportObj.value
|
||||
});
|
||||
}
|
||||
}
|
||||
return teavm;
|
||||
}
|
||||
|
||||
async function getDeobfuscator(path, options) {
|
||||
try {
|
||||
const importObj = {};
|
||||
const defaultsResult = defaults(importObj, {});
|
||||
const { instance } = await WebAssembly.instantiateStreaming(fetch(path + "-deobfuscator.wasm"), importObj);
|
||||
defaultsResult.supplyExports(instance.exports)
|
||||
return instance;
|
||||
} catch (e) {
|
||||
console.warn("Could not load deobfuscator", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function createDeobfuscator(module, deobfuscatorFactory) {
|
||||
let deobfuscator = null;
|
||||
let deobfuscatorInitialized = false;
|
||||
function ensureDeobfuscator() {
|
||||
if (!deobfuscatorInitialized) {
|
||||
deobfuscatorInitialized = true;
|
||||
try {
|
||||
deobfuscator = deobfuscatorFactory.exports.createForModule.value(module);
|
||||
} catch (e) {
|
||||
console.warn("Could not load create deobfuscator", e);
|
||||
}
|
||||
return teavm;
|
||||
});
|
||||
}
|
||||
}
|
||||
return addresses => {
|
||||
ensureDeobfuscator();
|
||||
return deobfuscator !== null ? deobfuscator.deobfuscate(addresses) : [];
|
||||
}
|
||||
}
|
@ -245,7 +245,8 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
|
||||
invocation.setReceiver(result);
|
||||
resultType = method.getResultType();
|
||||
}
|
||||
exit.setValueToReturn(marshaller.wrapArgument(callLocation, result, resultType, JSType.JAVA, false));
|
||||
exit.setValueToReturn(marshaller.wrapArgument(callLocation, result, resultType,
|
||||
typeHelper.mapType(method.getResultType()), false));
|
||||
basicBlock.addAll(marshallInstructions);
|
||||
marshallInstructions.clear();
|
||||
}
|
||||
@ -322,7 +323,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
|
||||
if (method.getResultType() != ValueType.VOID) {
|
||||
invocation.setReceiver(program.createVariable());
|
||||
exit.setValueToReturn(marshaller.wrapArgument(callLocation, invocation.getReceiver(),
|
||||
method.getResultType(), JSType.JAVA, false));
|
||||
method.getResultType(), typeHelper.mapType(method.getResultType()), false));
|
||||
basicBlock.addAll(marshallInstructions);
|
||||
marshallInstructions.clear();
|
||||
}
|
||||
|
@ -23,6 +23,9 @@ final class WasmGCJSRuntime {
|
||||
}
|
||||
|
||||
static JSObject stringToJs(String str) {
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
if (str.isEmpty()) {
|
||||
return emptyString();
|
||||
}
|
||||
@ -34,6 +37,9 @@ final class WasmGCJSRuntime {
|
||||
}
|
||||
|
||||
static String jsToString(JSObject obj) {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
var length = stringLength(obj);
|
||||
if (length == 0) {
|
||||
return "";
|
||||
|
@ -38,6 +38,7 @@ include("classlib")
|
||||
include("tools:core")
|
||||
include("tools:browser-runner")
|
||||
include("tools:deobfuscator-js")
|
||||
include("tools:deobfuscator-wasm-gc")
|
||||
include("tools:junit")
|
||||
include("tools:devserver")
|
||||
include("tools:c-incremental")
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.teavm.jso.export;
|
||||
|
||||
import java.util.Arrays;
|
||||
import org.teavm.jso.JSExport;
|
||||
import org.teavm.jso.JSProperty;
|
||||
|
||||
@ -60,5 +61,10 @@ public final class ModuleWithExportedClassMembers {
|
||||
public static String staticProp() {
|
||||
return "I'm static";
|
||||
}
|
||||
|
||||
@JSExport
|
||||
public String consumeIntArray(int[] array) {
|
||||
return "accepted int array: " + Arrays.toString(array);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.teavm.jso.export;
|
||||
|
||||
import java.util.Arrays;
|
||||
import org.teavm.jso.JSExport;
|
||||
|
||||
public final class ModuleWithPrimitiveTypes {
|
||||
@ -125,4 +126,9 @@ public final class ModuleWithPrimitiveTypes {
|
||||
public static String stringParam(String param) {
|
||||
return "string:" + param;
|
||||
}
|
||||
|
||||
@JSExport
|
||||
public static String intArrayParam(int[] param) {
|
||||
return "intArray:" + Arrays.toString(param);
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,8 @@
|
||||
package org.teavm.jso.export;
|
||||
|
||||
import org.teavm.jso.JSExport;
|
||||
import org.teavm.jso.core.JSArray;
|
||||
import org.teavm.jso.core.JSString;
|
||||
|
||||
public final class SimpleModule {
|
||||
private SimpleModule() {
|
||||
@ -25,4 +27,13 @@ public final class SimpleModule {
|
||||
public static int foo() {
|
||||
return 23;
|
||||
}
|
||||
|
||||
@JSExport
|
||||
public static JSArray<JSString> bar(int count) {
|
||||
var array = new JSArray<JSString>();
|
||||
for (var i = 0; i < count; ++i) {
|
||||
array.push(JSString.valueOf("item" + i));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
@ -22,4 +22,5 @@ export async function test() {
|
||||
assertEquals("consumeObject:qwe:42", consumeObject(o));
|
||||
assertEquals(99, C.baz());
|
||||
assertEquals("I'm static", C.staticProp);
|
||||
assertEquals("accepted int array: [2, 3, 5]", o.consumeIntArray([2, 3, 5]));
|
||||
}
|
@ -45,8 +45,13 @@ function testConsumePrimitives() {
|
||||
assertEquals("string:q", java.stringParam("q"));
|
||||
}
|
||||
|
||||
function testConsumePrimitiveArrays() {
|
||||
assertEquals("intArray:[2, 3, 5]", java.intArrayParam([2, 3, 5]));
|
||||
}
|
||||
|
||||
export async function test() {
|
||||
testReturnPrimitives();
|
||||
testReturnArrays();
|
||||
testConsumePrimitives();
|
||||
testConsumePrimitiveArrays();
|
||||
}
|
@ -13,8 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
const { foo } = await (await import('/tests/simple/provider.js')).default;
|
||||
const { foo, bar } = await (await import('/tests/simple/provider.js')).default;
|
||||
|
||||
export async function test() {
|
||||
assertEquals(23, foo());
|
||||
assertEquals(["item0", "item1"], bar(2));
|
||||
}
|
@ -211,6 +211,7 @@ function launchWasmGCTest(file, argument, callback) {
|
||||
}
|
||||
|
||||
TeaVM.wasmGC.load(file.path, {
|
||||
attachStackDeobfuscator: true,
|
||||
installImports: function(o) {
|
||||
o.teavmConsole.putcharStdout = putchar;
|
||||
o.teavmConsole.putcharStderr = putcharStderr;
|
||||
|
62
tools/deobfuscator-wasm-gc/build.gradle.kts
Normal file
62
tools/deobfuscator-wasm-gc/build.gradle.kts
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
}
|
||||
|
||||
description = "JavaScript deobfuscator"
|
||||
|
||||
configurations {
|
||||
val teavmCompile = create("teavmCompile")
|
||||
compileClasspath.configure {
|
||||
extendsFrom(teavmCompile)
|
||||
}
|
||||
create("wasm")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(":jso:apis"))
|
||||
"teavmCompile"(project(":classlib"))
|
||||
"teavmCompile"(project(":tools:core"))
|
||||
}
|
||||
|
||||
val generateWasm by tasks.register<JavaExec>("generateWasm") {
|
||||
outputs.dir(layout.buildDirectory.dir("teavm"))
|
||||
dependsOn(tasks.classes)
|
||||
classpath += configurations["teavmCompile"]
|
||||
classpath += java.sourceSets.main.get().output.classesDirs
|
||||
mainClass = "org.teavm.tooling.deobfuscate.wasmgc.Compiler"
|
||||
args(
|
||||
"org.teavm.tooling.deobfuscate.wasmgc.DeobfuscatorFactory",
|
||||
layout.buildDirectory.dir("teavm").get().asFile.absolutePath,
|
||||
"deobfuscator.wasm"
|
||||
)
|
||||
}
|
||||
|
||||
val zipWithWasm by tasks.register<Jar>("zipWithWasm") {
|
||||
dependsOn(generateWasm)
|
||||
archiveClassifier = "wasm"
|
||||
from(layout.buildDirectory.dir("teavm"), layout.buildDirectory.dir("teavm-lib"))
|
||||
include("*.wasm")
|
||||
entryCompression = ZipEntryCompression.DEFLATED
|
||||
}
|
||||
|
||||
tasks.assemble.configure {
|
||||
dependsOn(zipWithWasm)
|
||||
}
|
||||
|
||||
artifacts.add("wasm", zipWithWasm)
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2023 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.tooling.deobfuscate.wasmgc;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.file.Files;
|
||||
import org.teavm.backend.wasm.disasm.Disassembler;
|
||||
import org.teavm.backend.wasm.disasm.DisassemblyHTMLWriter;
|
||||
import org.teavm.tooling.ConsoleTeaVMToolLog;
|
||||
import org.teavm.tooling.TeaVMProblemRenderer;
|
||||
import org.teavm.tooling.TeaVMTargetType;
|
||||
import org.teavm.tooling.TeaVMTool;
|
||||
import org.teavm.tooling.TeaVMToolException;
|
||||
import org.teavm.vm.TeaVMOptimizationLevel;
|
||||
|
||||
public final class Compiler {
|
||||
private Compiler() {
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws TeaVMToolException, IOException {
|
||||
var tool = new TeaVMTool();
|
||||
var log = new ConsoleTeaVMToolLog(false);
|
||||
tool.setTargetType(TeaVMTargetType.WEBASSEMBLY_GC);
|
||||
tool.setMainClass(args[0]);
|
||||
tool.setTargetDirectory(new File(args[1]));
|
||||
tool.setTargetFileName(args[2]);
|
||||
tool.setObfuscated(true);
|
||||
tool.setOptimizationLevel(TeaVMOptimizationLevel.ADVANCED);
|
||||
|
||||
tool.generate();
|
||||
TeaVMProblemRenderer.describeProblems(tool.getDependencyInfo().getCallGraph(), tool.getProblemProvider(), log);
|
||||
if (!tool.getProblemProvider().getSevereProblems().isEmpty()) {
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
var fileName = args[2];
|
||||
var disassemblyOutputFile = new File(args[1] + "/" + fileName.substring(0, fileName.length() - 5)
|
||||
+ ".wast.html");
|
||||
try (var disassemblyWriter = new PrintWriter(new OutputStreamWriter(
|
||||
new FileOutputStream(disassemblyOutputFile)))) {
|
||||
var htmlWriter = new DisassemblyHTMLWriter(disassemblyWriter);
|
||||
htmlWriter.setWithAddress(true);
|
||||
var disassembler = new Disassembler(htmlWriter);
|
||||
var inputFile = new File(new File(args[1]), args[2]);
|
||||
disassembler.disassemble(Files.readAllBytes(inputFile.toPath()));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2021 konsoletyper.
|
||||
*
|
||||
* 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.tooling.deobfuscate.wasmgc;
|
||||
|
||||
import org.teavm.backend.wasm.debug.info.LineInfo;
|
||||
import org.teavm.jso.JSExport;
|
||||
import org.teavm.jso.core.JSArray;
|
||||
import org.teavm.jso.core.JSArrayReader;
|
||||
|
||||
public final class Deobfuscator {
|
||||
private LineInfo debugInformation;
|
||||
|
||||
Deobfuscator(LineInfo debugInformation) {
|
||||
this.debugInformation = debugInformation;
|
||||
}
|
||||
|
||||
@JSExport
|
||||
public JSArrayReader<Frame> deobfuscate(int[] addresses) {
|
||||
var locations = debugInformation.deobfuscate(addresses);
|
||||
var frames = new JSArray<Frame>();
|
||||
for (var location : locations) {
|
||||
var frame = new Frame(location.method.cls().fullName(), location.method.name(),
|
||||
location.file != null ? location.file.fullName() : null, location.line);
|
||||
frames.push(frame);
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2024 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.tooling.deobfuscate.wasmgc;
|
||||
|
||||
import org.teavm.backend.wasm.debug.parser.LinesDeobfuscationParser;
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.JSExport;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.core.JSArrayReader;
|
||||
import org.teavm.jso.typedarrays.ArrayBuffer;
|
||||
import org.teavm.jso.typedarrays.Int8Array;
|
||||
|
||||
public final class DeobfuscatorFactory {
|
||||
private DeobfuscatorFactory() {
|
||||
}
|
||||
|
||||
@JSExport
|
||||
public static Deobfuscator createForModule(JSObject module) {
|
||||
var parser = new LinesDeobfuscationParser();
|
||||
parser.pullSections(name -> {
|
||||
var result = getSection(module, name);
|
||||
if (result == null || result.getLength() != 1) {
|
||||
return null;
|
||||
}
|
||||
var data = new Int8Array(result.get(0));
|
||||
var bytes = new byte[data.getLength()];
|
||||
for (var i = 0; i < data.getLength(); ++i) {
|
||||
bytes[i] = data.get(i);
|
||||
}
|
||||
return bytes;
|
||||
});
|
||||
return new Deobfuscator(parser.getLineInfo());
|
||||
}
|
||||
|
||||
@JSBody(params = { "module", "name"}, script = "return WebAssembly.Module.customSections(module, name);")
|
||||
private static native JSArrayReader<ArrayBuffer> getSection(JSObject module, String name);
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2021 konsoletyper.
|
||||
*
|
||||
* 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.tooling.deobfuscate.wasmgc;
|
||||
|
||||
import org.teavm.jso.JSExport;
|
||||
import org.teavm.jso.JSProperty;
|
||||
|
||||
public class Frame {
|
||||
private String className;
|
||||
private String fileName;
|
||||
private String methodName;
|
||||
private int lineNumber;
|
||||
|
||||
public Frame(String className, String methodName, String fileName, int lineNumber) {
|
||||
this.className = className;
|
||||
this.methodName = methodName;
|
||||
this.fileName = fileName;
|
||||
this.lineNumber = lineNumber;
|
||||
}
|
||||
|
||||
@JSExport
|
||||
@JSProperty
|
||||
public String getClassName() {
|
||||
return className;
|
||||
}
|
||||
|
||||
@JSExport
|
||||
@JSProperty
|
||||
public String getFile() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
@JSExport
|
||||
@JSProperty
|
||||
public String getMethod() {
|
||||
return methodName;
|
||||
}
|
||||
|
||||
@JSExport
|
||||
@JSProperty
|
||||
public int getLine() {
|
||||
return lineNumber;
|
||||
}
|
||||
}
|
@ -31,6 +31,7 @@ dependencies {
|
||||
implementation(project(":core"))
|
||||
implementation(project(":tools:core"))
|
||||
implementation(project(":tools:browser-runner"))
|
||||
runtimeOnly(project(":tools:deobfuscator-wasm-gc", "wasm"))
|
||||
}
|
||||
|
||||
teavmPublish {
|
||||
|
@ -32,7 +32,6 @@ final class TestWasmGCEntryPoint {
|
||||
e.printStackTrace(out);
|
||||
reportFailure(JSString.valueOf(out.toString()));
|
||||
}
|
||||
TestEntryPoint.run(args.length > 0 ? args[0] : null);
|
||||
}
|
||||
|
||||
@Import(module = "teavmTest", name = "success")
|
||||
|
@ -126,8 +126,11 @@ class WebAssemblyGCPlatformSupport extends TestPlatformSupport<WasmGCTarget> {
|
||||
htmlOutput(outputPath, outputPathForMethod, configuration, reference, "teavm-run-test-wasm-gc.html");
|
||||
var testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(),
|
||||
getExtension() + "-runtime.js");
|
||||
var testDeobfuscatorPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(),
|
||||
getExtension() + "-deobfuscator.wasm");
|
||||
try {
|
||||
TestUtil.resourceToFile("org/teavm/backend/wasm/wasm-gc-runtime.js", testPath, Map.of());
|
||||
TestUtil.resourceToFile("deobfuscator.wasm", testDeobfuscatorPath, Map.of());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@ -142,8 +145,11 @@ class WebAssemblyGCPlatformSupport extends TestPlatformSupport<WasmGCTarget> {
|
||||
htmlSingleTestOutput(outputPathForMethod, configuration, "teavm-run-test-wasm-gc.html");
|
||||
var testPath = getOutputFile(outputPathForMethod, "test", configuration.getSuffix(),
|
||||
getExtension() + "-runtime.js");
|
||||
var testDeobfuscatorPath = getOutputFile(outputPathForMethod, "test", configuration.getSuffix(),
|
||||
getExtension() + "-deobfuscator.wasm");
|
||||
try {
|
||||
TestUtil.resourceToFile("org/teavm/backend/wasm/wasm-gc-runtime.js", testPath, Map.of());
|
||||
TestUtil.resourceToFile("deobfuscator.wasm", testDeobfuscatorPath, Map.of());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
@ -25,6 +25,7 @@
|
||||
<script type="text/javascript">
|
||||
let instance;
|
||||
TeaVM.wasmGC.load("${SCRIPT}", {
|
||||
attachStackDeobfuscator: true,
|
||||
installImports(o) {
|
||||
o.teavmTest = {
|
||||
success() {
|
||||
|
Loading…
Reference in New Issue
Block a user