wasm gc: add stack trace deobfuscator

This commit is contained in:
Alexey Andreev 2024-10-11 20:35:20 +02:00
parent 0b292bb510
commit 40d2ab97ec
28 changed files with 643 additions and 56 deletions

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
};

View File

@ -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);
}

View File

@ -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) : [];
}
}

View File

@ -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();
}

View File

@ -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 "";

View File

@ -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")

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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]));
}

View File

@ -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();
}

View File

@ -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));
}

View File

@ -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;

View 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)

View File

@ -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()));
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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 {

View File

@ -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")

View File

@ -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);
}

View File

@ -25,6 +25,7 @@
<script type="text/javascript">
let instance;
TeaVM.wasmGC.load("${SCRIPT}", {
attachStackDeobfuscator: true,
installImports(o) {
o.teavmTest = {
success() {