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
private static TStackTraceElement[] fillInStackTraceLowLevel() {
int stackSize = ExceptionHandling.callStackSize();
TStackTraceElement[] stackTrace = new TStackTraceElement[stackSize];
ExceptionHandling.fillStackTrace((StackTraceElement[]) (Object) stackTrace);
return stackTrace;
return (TStackTraceElement[]) (Object) ExceptionHandling.fillStackTrace();
}
@Rename("getMessage")

View File

@ -18,6 +18,10 @@ package org.teavm.backend.c;
import java.io.BufferedReader;
import java.io.IOException;
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.Arrays;
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.cache.EmptyMethodNodeCache;
import org.teavm.cache.MethodNodeCache;
import org.teavm.common.JsonUtil;
import org.teavm.dependency.ClassDependency;
import org.teavm.dependency.DependencyAnalyzer;
import org.teavm.dependency.DependencyListener;
@ -169,6 +174,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost {
private SimpleStringPool stringPool;
private boolean longjmpUsed = true;
private boolean heapDump;
private boolean obfuscated;
private List<CallSiteDescriptor> callSites = new ArrayList<>();
public CTarget(NameProvider nameProvider) {
@ -203,6 +209,10 @@ public class CTarget implements TeaVMTarget, TeaVMCHost {
this.astCache = astCache;
}
public void setObfuscated(boolean obfuscated) {
this.obfuscated = obfuscated;
}
@Override
public List<ClassHolderTransformer> getTransformers() {
List<ClassHolderTransformer> transformers = new ArrayList<>();
@ -392,7 +402,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost {
controller.getDependencyInfo(), stringPool, nameProvider, controller.getDiagnostics(), classes,
intrinsics, generators, asyncMethods::contains, buildTarget,
controller.getClassInitializerInfo(), incremental, longjmpUsed,
vmAssertions, vmAssertions || heapDump);
vmAssertions, vmAssertions || heapDump, obfuscated);
BufferedCodeWriter specialWriter = new BufferedCodeWriter(false);
BufferedCodeWriter configHeaderWriter = new BufferedCodeWriter(false);
@ -410,6 +420,9 @@ public class CTarget implements TeaVMTarget, TeaVMCHost {
if (heapDump) {
configHeaderWriter.println("#define TEAVM_HEAP_DUMP 1");
}
if (obfuscated) {
configHeaderWriter.println("#define TEAVM_OBFUSCATED 1");
}
ClassGenerator classGenerator = new ClassGenerator(context, tagRegistry, decompiler,
controller.getCacheStatus());
@ -522,6 +535,61 @@ public class CTarget implements TeaVMTarget, TeaVMCHost {
? this.callSites
: CallSiteDescriptor.extract(context.getClassSource(), classNames);
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 {

View File

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

View File

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

View File

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

View File

@ -54,6 +54,7 @@ public class ExceptionHandlingIntrinsic implements WasmIntrinsic {
case "isJumpSupported":
case "jumpToFrame":
case "abort":
case "isObfuscated":
return true;
}
return false;
@ -83,6 +84,7 @@ public class ExceptionHandlingIntrinsic implements WasmIntrinsic {
}
case "isJumpSupported":
case "isObfuscated":
return new WasmInt32Constant(0);
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.Writer;
import org.teavm.common.JsonUtil;
class SourceMapsWriter {
private static final String BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@ -34,10 +35,10 @@ class SourceMapsWriter {
public void write(String generatedFile, String sourceRoot, DebugInformation debugInfo) throws IOException {
output.write("{\"version\":3");
output.write(",\"file\":\"");
writeEscapedString(generatedFile);
JsonUtil.writeEscapedString(output, generatedFile);
output.write("\"");
output.write(",\"sourceRoot\":\"");
writeEscapedString(sourceRoot);
JsonUtil.writeEscapedString(output, sourceRoot);
output.write("\"");
output.write(",\"sources\":[");
for (int i = 0; i < debugInfo.fileNames.length; ++i) {
@ -45,7 +46,7 @@ class SourceMapsWriter {
output.write(',');
}
output.write("\"");
writeEscapedString(debugInfo.fileNames[i]);
JsonUtil.writeEscapedString(output, debugInfo.fileNames[i]);
output.write("\"");
}
output.write("]");
@ -85,35 +86,6 @@ class SourceMapsWriter {
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 {
if (number < 0) {
number = ((-number) << 1) | 1;

View File

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

View File

@ -58,3 +58,7 @@
#ifndef TEAVM_GC_LOG
#define TEAVM_GC_LOG 0
#endif
#ifndef TEAVM_OBFUSCATED
#define TEAVM_OBFUSCATED 0
#endif

View File

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