C: add option to strip off information about call site locations.

This decreases executable size significantly. However, this produces
obfuscated stack traces which can be deobfuscated using JSON symbol table.
This commit is contained in:
Alexey Andreev 2019-10-23 17:34:23 +03:00
parent c78874f426
commit 3b4cc43e79
11 changed files with 207 additions and 83 deletions

View File

@ -111,10 +111,7 @@ public class TThrowable extends RuntimeException {
@Unmanaged @Unmanaged
private static TStackTraceElement[] fillInStackTraceLowLevel() { private static TStackTraceElement[] fillInStackTraceLowLevel() {
int stackSize = ExceptionHandling.callStackSize(); return (TStackTraceElement[]) (Object) ExceptionHandling.fillStackTrace();
TStackTraceElement[] stackTrace = new TStackTraceElement[stackSize];
ExceptionHandling.fillStackTrace((StackTraceElement[]) (Object) stackTrace);
return stackTrace;
} }
@Rename("getMessage") @Rename("getMessage")

View File

@ -18,6 +18,10 @@ package org.teavm.backend.c;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -78,6 +82,7 @@ import org.teavm.backend.lowlevel.transform.CoroutineTransformation;
import org.teavm.backend.lowlevel.transform.WeakReferenceTransformation; import org.teavm.backend.lowlevel.transform.WeakReferenceTransformation;
import org.teavm.cache.EmptyMethodNodeCache; import org.teavm.cache.EmptyMethodNodeCache;
import org.teavm.cache.MethodNodeCache; import org.teavm.cache.MethodNodeCache;
import org.teavm.common.JsonUtil;
import org.teavm.dependency.ClassDependency; import org.teavm.dependency.ClassDependency;
import org.teavm.dependency.DependencyAnalyzer; import org.teavm.dependency.DependencyAnalyzer;
import org.teavm.dependency.DependencyListener; import org.teavm.dependency.DependencyListener;
@ -169,6 +174,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost {
private SimpleStringPool stringPool; private SimpleStringPool stringPool;
private boolean longjmpUsed = true; private boolean longjmpUsed = true;
private boolean heapDump; private boolean heapDump;
private boolean obfuscated;
private List<CallSiteDescriptor> callSites = new ArrayList<>(); private List<CallSiteDescriptor> callSites = new ArrayList<>();
public CTarget(NameProvider nameProvider) { public CTarget(NameProvider nameProvider) {
@ -203,6 +209,10 @@ public class CTarget implements TeaVMTarget, TeaVMCHost {
this.astCache = astCache; this.astCache = astCache;
} }
public void setObfuscated(boolean obfuscated) {
this.obfuscated = obfuscated;
}
@Override @Override
public List<ClassHolderTransformer> getTransformers() { public List<ClassHolderTransformer> getTransformers() {
List<ClassHolderTransformer> transformers = new ArrayList<>(); List<ClassHolderTransformer> transformers = new ArrayList<>();
@ -392,7 +402,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost {
controller.getDependencyInfo(), stringPool, nameProvider, controller.getDiagnostics(), classes, controller.getDependencyInfo(), stringPool, nameProvider, controller.getDiagnostics(), classes,
intrinsics, generators, asyncMethods::contains, buildTarget, intrinsics, generators, asyncMethods::contains, buildTarget,
controller.getClassInitializerInfo(), incremental, longjmpUsed, controller.getClassInitializerInfo(), incremental, longjmpUsed,
vmAssertions, vmAssertions || heapDump); vmAssertions, vmAssertions || heapDump, obfuscated);
BufferedCodeWriter specialWriter = new BufferedCodeWriter(false); BufferedCodeWriter specialWriter = new BufferedCodeWriter(false);
BufferedCodeWriter configHeaderWriter = new BufferedCodeWriter(false); BufferedCodeWriter configHeaderWriter = new BufferedCodeWriter(false);
@ -410,6 +420,9 @@ public class CTarget implements TeaVMTarget, TeaVMCHost {
if (heapDump) { if (heapDump) {
configHeaderWriter.println("#define TEAVM_HEAP_DUMP 1"); configHeaderWriter.println("#define TEAVM_HEAP_DUMP 1");
} }
if (obfuscated) {
configHeaderWriter.println("#define TEAVM_OBFUSCATED 1");
}
ClassGenerator classGenerator = new ClassGenerator(context, tagRegistry, decompiler, ClassGenerator classGenerator = new ClassGenerator(context, tagRegistry, decompiler,
controller.getCacheStatus()); controller.getCacheStatus());
@ -522,6 +535,61 @@ public class CTarget implements TeaVMTarget, TeaVMCHost {
? this.callSites ? this.callSites
: CallSiteDescriptor.extract(context.getClassSource(), classNames); : CallSiteDescriptor.extract(context.getClassSource(), classNames);
new CallSiteGenerator(context, writer, includes, "teavm_callSites").generate(callSites); new CallSiteGenerator(context, writer, includes, "teavm_callSites").generate(callSites);
if (obfuscated) {
generateCallSitesJson(context.getBuildTarget(), callSites);
}
}
private void generateCallSitesJson(BuildTarget buildTarget, List<? extends CallSiteDescriptor> callSites) {
try (OutputStream output = buildTarget.createResource("callsites.json");
Writer writer = new OutputStreamWriter(output, StandardCharsets.UTF_8)) {
writer.append("[\n");
boolean first = true;
for (CallSiteDescriptor descriptor : callSites) {
if (!first) {
writer.append(",\n");
}
first = false;
writer.append("{\"id\":").append(Integer.toString(descriptor.getId()));
writer.append(",\"locations\":[");
org.teavm.model.lowlevel.CallSiteLocation[] locations = descriptor.getLocations();
if (locations != null) {
boolean firstLocation = true;
for (org.teavm.model.lowlevel.CallSiteLocation location : locations) {
if (!firstLocation) {
writer.append(",");
}
firstLocation = false;
writer.append("{\"class\":");
appendJsonString(writer, location.getClassName());
writer.append(",\"method\":");
appendJsonString(writer, location.getMethodName());
writer.append(",\"file\":");
appendJsonString(writer, location.getFileName());
writer.append(",\"line\":").append(Integer.toString(location.getLineNumber()));
writer.append("}");
}
}
writer.append("]}");
}
if (!first) {
writer.append("\n");
}
writer.append("]");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static void appendJsonString(Writer writer, String string) throws IOException {
if (string == null) {
writer.append("null");
return;
}
writer.append("\"");
JsonUtil.writeEscapedString(writer, string);
writer.append("\"");
} }
private void generateStrings(BuildTarget buildTarget, GenerationContext context) throws IOException { private void generateStrings(BuildTarget buildTarget, GenerationContext context) throws IOException {

View File

@ -26,7 +26,6 @@ import org.teavm.model.lowlevel.CallSiteLocation;
import org.teavm.model.lowlevel.ExceptionHandlerDescriptor; import org.teavm.model.lowlevel.ExceptionHandlerDescriptor;
public class CallSiteGenerator { public class CallSiteGenerator {
private GenerationContext context; private GenerationContext context;
private CodeWriter writer; private CodeWriter writer;
private IncludeManager includes; private IncludeManager includes;
@ -79,20 +78,22 @@ public class CallSiteGenerator {
first = false; first = false;
int locationIndex = -1; int locationIndex = -1;
CallSiteLocation[] locations = callSite.getLocations(); if (!context.isObfuscated()) {
if (locations != null && locations.length > 0) { CallSiteLocation[] locations = callSite.getLocations();
LocationList prevList = null; if (locations != null && locations.length > 0) {
for (int i = locations.length - 1; i >= 0; --i) { LocationList prevList = null;
LocationList list = new LocationList(locations[i], prevList); for (int i = locations.length - 1; i >= 0; --i) {
locationIndex = locationMap.getOrDefault(list, -1); LocationList list = new LocationList(locations[i], prevList);
if (locationIndex < 0) { locationIndex = locationMap.getOrDefault(list, -1);
locationIndex = this.locations.size(); if (locationIndex < 0) {
locationMap.put(list, locationIndex); locationIndex = this.locations.size();
this.locations.add(list); locationMap.put(list, locationIndex);
} else { this.locations.add(list);
list = this.locations.get(locationIndex); } else {
list = this.locations.get(locationIndex);
}
prevList = list;
} }
prevList = list;
} }
} }

View File

@ -50,13 +50,14 @@ public class GenerationContext {
private boolean longjmp; private boolean longjmp;
private boolean vmAssertions; private boolean vmAssertions;
private boolean heapDump; private boolean heapDump;
private boolean obfuscated;
public GenerationContext(VirtualTableProvider virtualTableProvider, Characteristics characteristics, public GenerationContext(VirtualTableProvider virtualTableProvider, Characteristics characteristics,
DependencyInfo dependencies, StringPool stringPool, NameProvider names, Diagnostics diagnostics, DependencyInfo dependencies, StringPool stringPool, NameProvider names, Diagnostics diagnostics,
ClassReaderSource classSource, List<Intrinsic> intrinsics, List<Generator> generators, ClassReaderSource classSource, List<Intrinsic> intrinsics, List<Generator> generators,
Predicate<MethodReference> asyncMethods, BuildTarget buildTarget, Predicate<MethodReference> asyncMethods, BuildTarget buildTarget,
ClassInitializerInfo classInitializerInfo, boolean incremental, boolean longjmp, boolean vmAssertions, ClassInitializerInfo classInitializerInfo, boolean incremental, boolean longjmp, boolean vmAssertions,
boolean heapDump) { boolean heapDump, boolean obfuscated) {
this.virtualTableProvider = virtualTableProvider; this.virtualTableProvider = virtualTableProvider;
this.characteristics = characteristics; this.characteristics = characteristics;
this.dependencies = dependencies; this.dependencies = dependencies;
@ -73,6 +74,7 @@ public class GenerationContext {
this.longjmp = longjmp; this.longjmp = longjmp;
this.vmAssertions = vmAssertions; this.vmAssertions = vmAssertions;
this.heapDump = heapDump; this.heapDump = heapDump;
this.obfuscated = obfuscated;
} }
public void addIntrinsic(Intrinsic intrinsic) { public void addIntrinsic(Intrinsic intrinsic) {
@ -152,4 +154,8 @@ public class GenerationContext {
public boolean isVmAssertions() { public boolean isVmAssertions() {
return vmAssertions; return vmAssertions;
} }
public boolean isObfuscated() {
return obfuscated;
}
} }

View File

@ -31,6 +31,7 @@ public class ExceptionHandlingIntrinsic implements Intrinsic {
case "isJumpSupported": case "isJumpSupported":
case "jumpToFrame": case "jumpToFrame":
case "abort": case "abort":
case "isObfuscated":
return true; return true;
default: default:
return false; return false;
@ -64,6 +65,10 @@ public class ExceptionHandlingIntrinsic implements Intrinsic {
context.includes().addInclude("<stdlib.h>"); context.includes().addInclude("<stdlib.h>");
context.writer().print("abort();"); context.writer().print("abort();");
break; break;
case "isObfuscated":
context.writer().print("TEAVM_OBFUSCATED");
break;
} }
} }
} }

View File

@ -54,6 +54,7 @@ public class ExceptionHandlingIntrinsic implements WasmIntrinsic {
case "isJumpSupported": case "isJumpSupported":
case "jumpToFrame": case "jumpToFrame":
case "abort": case "abort":
case "isObfuscated":
return true; return true;
} }
return false; return false;
@ -83,6 +84,7 @@ public class ExceptionHandlingIntrinsic implements WasmIntrinsic {
} }
case "isJumpSupported": case "isJumpSupported":
case "isObfuscated":
return new WasmInt32Constant(0); return new WasmInt32Constant(0);
case "jumpToFrame": case "jumpToFrame":

View File

@ -0,0 +1,53 @@
/*
* Copyright 2019 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.common;
import java.io.IOException;
import java.io.Writer;
public final class JsonUtil {
private JsonUtil() {
}
public static void writeEscapedString(Writer output, String str) throws IOException {
for (int i = 0; i < str.length(); ++i) {
char c = str.charAt(i);
switch (c) {
case '\n':
output.write("\\n");
break;
case '\r':
output.write("\\r");
break;
case '\t':
output.write("\\t");
break;
case '\b':
output.write("\\b");
break;
case '\\':
output.write("\\\\");
break;
case '"':
output.write("\\\"");
break;
default:
output.write(c);
break;
}
}
}
}

View File

@ -17,6 +17,7 @@ package org.teavm.debugging.information;
import java.io.IOException; import java.io.IOException;
import java.io.Writer; import java.io.Writer;
import org.teavm.common.JsonUtil;
class SourceMapsWriter { class SourceMapsWriter {
private static final String BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; private static final String BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@ -34,10 +35,10 @@ class SourceMapsWriter {
public void write(String generatedFile, String sourceRoot, DebugInformation debugInfo) throws IOException { public void write(String generatedFile, String sourceRoot, DebugInformation debugInfo) throws IOException {
output.write("{\"version\":3"); output.write("{\"version\":3");
output.write(",\"file\":\""); output.write(",\"file\":\"");
writeEscapedString(generatedFile); JsonUtil.writeEscapedString(output, generatedFile);
output.write("\""); output.write("\"");
output.write(",\"sourceRoot\":\""); output.write(",\"sourceRoot\":\"");
writeEscapedString(sourceRoot); JsonUtil.writeEscapedString(output, sourceRoot);
output.write("\""); output.write("\"");
output.write(",\"sources\":["); output.write(",\"sources\":[");
for (int i = 0; i < debugInfo.fileNames.length; ++i) { for (int i = 0; i < debugInfo.fileNames.length; ++i) {
@ -45,7 +46,7 @@ class SourceMapsWriter {
output.write(','); output.write(',');
} }
output.write("\""); output.write("\"");
writeEscapedString(debugInfo.fileNames[i]); JsonUtil.writeEscapedString(output, debugInfo.fileNames[i]);
output.write("\""); output.write("\"");
} }
output.write("]"); output.write("]");
@ -85,35 +86,6 @@ class SourceMapsWriter {
first = false; first = false;
} }
private void writeEscapedString(String str) throws IOException {
for (int i = 0; i < str.length(); ++i) {
char c = str.charAt(i);
switch (c) {
case '\n':
output.write("\\n");
break;
case '\r':
output.write("\\r");
break;
case '\t':
output.write("\\t");
break;
case '\b':
output.write("\\b");
break;
case '\\':
output.write("\\\\");
break;
case '"':
output.write("\\\"");
break;
default:
output.write(c);
break;
}
}
}
private void writeVLQ(int number) throws IOException { private void writeVLQ(int number) throws IOException {
if (number < 0) { if (number < 0) {
number = ((-number) << 1) | 1; number = ((-number) << 1) | 1;

View File

@ -37,33 +37,42 @@ public final class ExceptionHandling {
@Unmanaged @Unmanaged
public static native void abort(); public static native void abort();
@Unmanaged
private static native boolean isObfuscated();
@Unmanaged @Unmanaged
public static void printStack() { public static void printStack() {
Address stackFrame = ShadowStack.getStackTop(); Address stackFrame = ShadowStack.getStackTop();
while (stackFrame != null) { while (stackFrame != null) {
int callSiteId = ShadowStack.getCallSiteId(stackFrame); int callSiteId = ShadowStack.getCallSiteId(stackFrame);
CallSite callSite = findCallSiteById(callSiteId, stackFrame); if (isObfuscated()) {
CallSiteLocation location = callSite.location; Console.printString(" at Obfuscated.obfuscated(Obfuscated.java:");
while (location != null) { Console.printInt(callSiteId);
MethodLocation methodLocation = location != null ? location.method : null;
Console.printString(" at ");
if (methodLocation.className == null || methodLocation.methodName == null) {
Console.printString("(Unknown method)");
} else {
Console.printString(methodLocation.className.value);
Console.printString(".");
Console.printString(methodLocation.methodName.value);
}
Console.printString("(");
if (methodLocation.fileName != null && location.lineNumber >= 0) {
Console.printString(methodLocation.fileName.value);
Console.printString(":");
Console.printInt(location.lineNumber);
}
Console.printString(")\n"); Console.printString(")\n");
} else {
CallSite callSite = findCallSiteById(callSiteId, stackFrame);
CallSiteLocation location = callSite.location;
while (location != null) {
MethodLocation methodLocation = location != null ? location.method : null;
location = location.next; Console.printString(" at ");
if (methodLocation.className == null || methodLocation.methodName == null) {
Console.printString("(Unknown method)");
} else {
Console.printString(methodLocation.className.value);
Console.printString(".");
Console.printString(methodLocation.methodName.value);
}
Console.printString("(");
if (methodLocation.fileName != null && location.lineNumber >= 0) {
Console.printString(methodLocation.fileName.value);
Console.printString(":");
Console.printInt(location.lineNumber);
}
Console.printString(")\n");
location = location.next;
}
} }
stackFrame = ShadowStack.getNextStackFrame(stackFrame); stackFrame = ShadowStack.getNextStackFrame(stackFrame);
} }
@ -137,14 +146,14 @@ public final class ExceptionHandling {
} }
@Unmanaged @Unmanaged
public static int callStackSize() { private static int callStackSize() {
Address stackFrame = ShadowStack.getStackTop(); Address stackFrame = ShadowStack.getStackTop();
int size = 0; int size = 0;
while (stackFrame != null) { while (stackFrame != null) {
int callSiteId = ShadowStack.getCallSiteId(stackFrame); int callSiteId = ShadowStack.getCallSiteId(stackFrame);
CallSite callSite = findCallSiteById(callSiteId, stackFrame); CallSite callSite = findCallSiteById(callSiteId, stackFrame);
CallSiteLocation location = callSite.location; CallSiteLocation location = callSite.location;
if (location == null) { if (isObfuscated() || location == null) {
size++; size++;
} else { } else {
while (location != null) { while (location != null) {
@ -159,27 +168,35 @@ public final class ExceptionHandling {
} }
@Unmanaged @Unmanaged
public static void fillStackTrace(StackTraceElement[] target) { public static StackTraceElement[] fillStackTrace() {
Address stackFrame = ShadowStack.getStackTop(); Address stackFrame = ShadowStack.getStackTop();
int size = callStackSize();
ShadowStack.allocStack(1);
StackTraceElement[] target = new StackTraceElement[size];
ShadowStack.registerGCRoot(0, target);
int index = 0; int index = 0;
while (stackFrame != null) { while (stackFrame != null) {
int callSiteId = ShadowStack.getCallSiteId(stackFrame); int callSiteId = ShadowStack.getCallSiteId(stackFrame);
CallSite callSite = findCallSiteById(callSiteId, stackFrame); CallSite callSite = findCallSiteById(callSiteId, stackFrame);
CallSiteLocation location = callSite.location; CallSiteLocation location = callSite.location;
if (location == null) { if (isObfuscated()) {
target[index++] = createElement("", "", null, location.lineNumber); target[index++] = new StackTraceElement("Obfuscated", "obfuscated", "Obfuscated.java", callSiteId);
} else if (location == null) {
target[index++] = new StackTraceElement("", "", null, location.lineNumber);
} else { } else {
while (location != null) { while (location != null) {
MethodLocation methodLocation = location.method; MethodLocation methodLocation = location.method;
StackTraceElement element; StackTraceElement element;
if (methodLocation != null) { if (methodLocation != null) {
element = createElement( element = new StackTraceElement(
methodLocation.className != null ? methodLocation.className.value : "", methodLocation.className != null ? methodLocation.className.value : "",
methodLocation.methodName != null ? methodLocation.methodName.value : "", methodLocation.methodName != null ? methodLocation.methodName.value : "",
methodLocation.fileName != null ? methodLocation.fileName.value : null, methodLocation.fileName != null ? methodLocation.fileName.value : null,
location.lineNumber); location.lineNumber);
} else { } else {
element = createElement("", "", null, location.lineNumber); element = new StackTraceElement("", "", null, location.lineNumber);
} }
target[index++] = element; target[index++] = element;
location = location.next; location = location.next;
@ -187,10 +204,8 @@ public final class ExceptionHandling {
} }
stackFrame = ShadowStack.getNextStackFrame(stackFrame); stackFrame = ShadowStack.getNextStackFrame(stackFrame);
} }
} ShadowStack.releaseStack(1);
private static StackTraceElement createElement(String className, String methodName, String fileName, return target;
int lineNumber) {
return new StackTraceElement(className, methodName, fileName, lineNumber);
} }
} }

View File

@ -57,4 +57,8 @@
#ifndef TEAVM_GC_LOG #ifndef TEAVM_GC_LOG
#define TEAVM_GC_LOG 0 #define TEAVM_GC_LOG 0
#endif
#ifndef TEAVM_OBFUSCATED
#define TEAVM_OBFUSCATED 0
#endif #endif

View File

@ -342,6 +342,7 @@ public class TeaVMTool {
cTarget.setLineNumbersGenerated(debugInformationGenerated); cTarget.setLineNumbersGenerated(debugInformationGenerated);
cTarget.setLongjmpUsed(longjmpSupported); cTarget.setLongjmpUsed(longjmpSupported);
cTarget.setHeapDump(heapDump); cTarget.setHeapDump(heapDump);
cTarget.setObfuscated(minifying);
return cTarget; return cTarget;
} }