Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

debugger maintaince (2020) #455

Merged
merged 6 commits into from
Mar 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import org.robovm.compiler.config.Config;
import org.robovm.compiler.config.OS;
import org.robovm.compiler.llvm.Alloca;
import org.robovm.compiler.llvm.ArrayType;
import org.robovm.compiler.llvm.ArrayConstantBuilder;
import org.robovm.compiler.llvm.BasicBlock;
import org.robovm.compiler.llvm.Call;
import org.robovm.compiler.llvm.Constant;
Expand Down Expand Up @@ -337,11 +337,29 @@ public void afterMethod(Config config, Clazz clazz, SootMethod method, ModuleBui
}
}

if (hookInstructionLines.isEmpty()) {
// there was no hook instructions identified, no need to bpTable, debug info and locals information
return;
}

// instrument hooks call, there is known line range, create global for method breakpoints
int arraySize = ((methodEndLineNumber - methodLineNumber + 1) + 7) / 8;
// global value to this array (without values as zeroinit)
Global bpTable = new Global(Symbols.bptableSymbol(method), Linkage.internal,
new ZeroInitializer(new ArrayType(arraySize, Type.I8)));
// use same data to tell debugger what lines are available for break points:
// idea is to set all not available (not instrumented) line with bit set to 1
// these bits will not be used for hooks as there is no corresponding injection
// but debugger will be able to read it and respond to JDWP
byte[] bpTableValue = new byte[arraySize];
// set all lines as breakpoint armed
Arrays.fill(bpTableValue, (byte)0xff);
for (Integer line : hookInstructionLines.keySet()) {
// reset armed for valid lines
int lineOffset = line - methodLineNumber;
int idx = lineOffset >> 3;
int mask = ~(1 << (lineOffset & 7));
bpTableValue[idx] &= mask;
}
// global value to this array
Global bpTable = new Global(Symbols.bptableSymbol(method), Linkage.internal, new ArrayConstantBuilder(Type.I8).add(bpTableValue).build());
mb.addGlobal(bpTable);
// cast to byte pointer
ConstantBitcast bpTableRef = new ConstantBitcast(bpTable.ref(), Type.I8_PTR);
Expand Down
9 changes: 6 additions & 3 deletions compiler/vm/core/src/hooks.c
Original file line number Diff line number Diff line change
Expand Up @@ -1177,9 +1177,6 @@ static inline char getSuspendedEvent(DebugEnv* debugEnv, jint lineNumberOffset,
if (debugEnv->suspended) {
return EVT_THREAD_SUSPENDED;
}
if (debugEnv->stepping && ((pc >= debugEnv->pclow && pc < debugEnv->pchigh) || (pc >= debugEnv->pclow2 && pc < debugEnv->pchigh2))) {
return EVT_THREAD_STEPPED;
}

// we only check for breakpoints if we aren't invoking
// lineNumberOffset may be < 0 if the instrumented unit
Expand All @@ -1189,6 +1186,12 @@ static inline char getSuspendedEvent(DebugEnv* debugEnv, jint lineNumberOffset,
return EVT_BREAKPOINT;
}
}

// check stepping only after breakpoint check. Otherwise breakpoint in one line loops will be ignored
if (debugEnv->stepping && ((pc >= debugEnv->pclow && pc < debugEnv->pchigh) || (pc >= debugEnv->pclow2 && pc < debugEnv->pchigh2))) {
return EVT_THREAD_STEPPED;
}

return 0;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,8 @@ private VmThread createThreadInstance(ClassInfo ci, long objectPtr, Object threa
String name = readStringValue(nameInstance);
VmThreadGroup threadGroup = getFieldValue(objectPtr, ci, ClassDataConsts.fields.JAVA_LANG_THREAD_GROUP);

return new VmThread(objectPtr, (Long)threadPtr, ci, name, threadGroup);
// allow null threadPtr, when thread object is created but thread is not started (attached)
return new VmThread(objectPtr, threadPtr != null ? (Long)threadPtr : 0L, ci, name, threadGroup);
}

private VmThreadGroup createThreadGroupInstance(ClassInfo ci, long objectPtr, @SuppressWarnings("unused") Object unused) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.robovm.debugger.hooks.payloads.HooksThreadEventPayload;
import org.robovm.debugger.hooks.payloads.HooksThreadStoppedEventPayload;
import org.robovm.debugger.jdwp.JdwpConsts;
import org.robovm.debugger.jdwp.handlers.eventrequest.events.EventData;
import org.robovm.debugger.jdwp.handlers.eventrequest.events.IJdwpEventDelegate;
import org.robovm.debugger.jdwp.handlers.eventrequest.events.JdwpClassLoadedEventData;
import org.robovm.debugger.jdwp.handlers.eventrequest.events.JdwpEventData;
Expand Down Expand Up @@ -53,6 +54,7 @@
import java.util.List;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.function.Predicate;

/**
* @author Demyan Kimitsa
Expand Down Expand Up @@ -326,9 +328,24 @@ private void applyRequestToTarget(JdwpEventRequest request) {
if (thread.suspendCount() == 0)
throw new DebuggerException(JdwpConsts.Error.THREAD_NOT_SUSPENDED);

// simple event data and validator for step out/over operations to properly find back-stack to stop
// and make sure it is not in ignored class
class StackTraceValidator extends EventData implements Predicate<VmStackTrace> {
String className;
public String getClassName() { return className; }
public boolean test(VmStackTrace vmStackTrace) {
if (vmStackTrace.classInfo() instanceof ClassInfoImpl) {
className = ((ClassInfoImpl) vmStackTrace.classInfo()).className().replace('/', '.');
return request.test(this, JdwpConsts.EventModifier.CLASS_MATCH) &&
request.test(this, JdwpConsts.EventModifier.CLASS_EXCLUDE) ;
}
return false; // can't get class name
}
}

// apply it to target
// size modifier is ignored as stepping in hooks implemented as line only
RuntimeUtils.RuntimeStepReference ref = delegates.runtime().step(thread, stepMod.depth());
RuntimeUtils.RuntimeStepReference ref = delegates.runtime().step(thread, stepMod.depth(), new StackTraceValidator());

// remember the request to be able to resume it in case some of criteria doesn't pass (e.g. class is filtered out)
// or execution is interrupted and stepping is canceled by exception event
Expand Down Expand Up @@ -632,13 +649,16 @@ private JdwpEventData processThreadEvent(HooksThreadEventPayload event) {
// get corresponding thread object
VmThread thread = delegates.state().referenceRefIdHolder().instanceByAddr(event.threadObj());
if (event.eventId() == HookConsts.events.THREAD_ATTACHED || event.eventId() == HookConsts.events.THREAD_STARTED) {
if (thread != null)
throw new DebuggerException("Thread " + Long.toHexString(event.threadObj()) + " already attached/started!");
if (thread != null) {
if (thread.threadPtr() != 0)
throw new DebuggerException("Thread " + Long.toHexString(event.threadObj()) + " already attached/started!");
// thread object was resolved before it was started, just attach thread
thread.attach(event.thread());
} else {
thread = delegates.instances().instanceByPointer(event.threadObj(), event.thread(), true);
}

// attach thread
thread = delegates.instances().instanceByPointer(event.threadObj(), event.thread(), true);
delegates.state().threads().add(thread);

log.debug("THREAD_STARTED: " + thread);

return new JdwpEventData(JdwpConsts.EventKind.THREAD_START, thread);
Expand Down Expand Up @@ -729,6 +749,10 @@ private VmStackTrace convertStackTrace(int eventId, HooksCallStackEntry payload)
return null;
}

// skip synthetic methods from being visible to debugger
if (methodInfo.isBridge() || methodInfo.isBroCallback() || methodInfo.isBroBridge())
return null;

return new VmStackTrace(classInfo, methodInfo, payload.lineNumber(), payload.fp(), payload.pc() - payload.impl());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;

/**
* @author Demyan Kimitsa
Expand Down Expand Up @@ -119,7 +120,7 @@ public void clearBreakPoint(MethodInfo methodInfo, int line) {
* @param thread to step
* @param depth of step, {@link org.robovm.debugger.jdwp.JdwpConsts.StepDepth}
*/
public RuntimeStepReference step(VmThread thread, int depth) {
public RuntimeStepReference step(VmThread thread, int depth, Predicate<VmStackTrace> stackTraceValidator) {
long pclow;
long pchigh;
long pclow2;
Expand All @@ -137,7 +138,7 @@ public RuntimeStepReference step(VmThread thread, int depth) {
// find out previous not native entry
VmStackTrace prevStackEntry = null;
for (int idx = 1; idx < stack.length; idx++) {
if (stack[idx].methodInfo().isNative())
if (stack[idx].methodInfo().isNative() || !stackTraceValidator.test(stack[idx]))
continue;
prevStackEntry = stack[idx];
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public void suspendThread(VmThread thread) throws DebuggerException {
int suspendCount = thread.markSuspended();
if (suspendCount == 1) {
delegates.hooksApi().threadSuspend(thread.threadPtr());
thread.setStatus(VmThread.Status.SUPENDED);
thread.setStatus(VmThread.Status.SUSPENDED);
}
}

Expand Down Expand Up @@ -103,7 +103,7 @@ public void onThreadSuspended(VmThread thread, VmStackTrace[] stack, boolean kee
if (keepSuspended) {
setThreadStack(thread, stack);
thread.markSuspended();
thread.setStatus(VmThread.Status.SUPENDED);
thread.setStatus(VmThread.Status.SUSPENDED);
} else {
if (thread.suspendCount() == 0) {
// thread is not suspended and this event is filtered out, so resume thread
Expand Down Expand Up @@ -166,7 +166,7 @@ public void resumeAllOtherThreads(VmThread stoppedThread) {
*/
public VmThread anySuspendedThread() {
for (VmThread thread : delegates.state().threads()) {
if (thread.status() == VmThread.Status.SUPENDED)
if (thread.status() == VmThread.Status.SUSPENDED)
return thread;
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ public boolean test(EventData data) {
return true;
}

public boolean test(EventData data, int modifierKind) {
// test through specific predicates
for (EventPredicate predicate : predicates)
if (predicate.modifierKind() == modifierKind && !predicate.test(data))
return false;
return true;
}

public int requestId() {
return requestId;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,37 @@ public short handle(ByteBufferPacket payload, ByteBufferPacket output) {
output.writeLong(startLine); // start code index
output.writeLong(finalLine); // end code index

output.writeInt32(finalLine - startLine + 1); // count map entrie
// read bptable from mach-o executable, it contains bits set where lines are not available
int arraySize = ((finalLine - startLine + 1) + 7) / 8;
state.appFileDataMemoryReader().setAddress(methodInfo.bpTableAddr());
byte[] bpTable = state.appFileDataMemoryReader().readBytes(arraySize);

int savedCntPos = output.position();
output.writeInt32(0); // count map entries -- will be set once calculated
int cnt = 0;
int idx = 0;
byte mask = 0;
for (int lineNo = startLine; lineNo <= finalLine; lineNo++) {
output.writeLong(lineNo); // code index
output.writeInt32(lineNo); // line number
// pick mask from bp table each 8 bits
if ((idx & 7) == 0)
mask = bpTable[idx >> 3];

if ((mask & 1) == 0) {
// line is available
output.writeLong(lineNo); // code index
output.writeInt32(lineNo); // line number
cnt += 1;
}

idx += 1;
mask >>= 1;
}

// update cnt
output.setPosition(savedCntPos);
output.writeInt32(cnt);
output.setPosition(output.size());

return JdwpConsts.Error.NONE;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ private void readMethods(ByteBufferMemoryReader reader, int methodCount, ClassIn
MethodInfo methodInfo = new MethodInfo();
methods[idx] = methodInfo;
methodInfo.readMethodInfo(reader);
if (methodInfo.isBridge() || methodInfo.isBroCallback() || methodInfo.isBroCallback() || methodInfo.isBroBridge()) {
if (methodInfo.isBridge() || methodInfo.isBroCallback() || methodInfo.isBroBridge()) {
// mark bridge and callbacks as native just to keep debugger away from attempts to get information about
// these methods(line tables etc)
methodInfo.markAsNative();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,11 @@ public int spFpAlign() {
return spFpAlign;
}

boolean isBroCallback() {
public boolean isBroCallback() {
return (flags & ClassDataConsts.methodinfo.BRO_CALLBACK) != 0;
}

boolean isBroBridge() {
public boolean isBroBridge() {
return (flags & ClassDataConsts.methodinfo.BRO_BRIDGE) != 0;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@
public class VmThread extends VmInstance {

public enum Status {
SUPENDED,
SUSPENDED,
RUNNING
}

/** native (not java) thread object pointer */
private final long threadPtr;
private long threadPtr;

/** thread name */
private final String name;
Expand Down Expand Up @@ -81,6 +81,10 @@ public long threadPtr() {
return threadPtr;
}

public void attach(long threadPtr) {
this.threadPtr = threadPtr;
}

public VmStackTrace[] stack() {
return stack;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,13 @@ public byte[] readBytes(int numBytes) {
throw new DebuggerException("Cant read zero number of bytes!");

expects(numBytes);
return Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.position() + numBytes);
if (byteBuffer.hasArray())
return Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.position() + numBytes);
else {
byte[] res = new byte[numBytes];
byteBuffer.get(res);
return res;
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ public ByteBufferMemoryReader memoryReader() {
return null;

return new ByteBufferMemoryReader(rootReader, 0, rootReader.size(), isPatform64Bit(),
regions.toArray(new ByteBufferMemoryReader.MemoryRegion[regions.size()]));
regions.toArray(new ByteBufferMemoryReader.MemoryRegion[0]));
}

public static void main(String[] argv) {
Expand Down
Loading