Commit a066e608 authored by Stephanie Gawroriski's avatar Stephanie Gawroriski
Browse files

Merge in JDWP work, much yay!

parents d03c3bc7 ed42be13
Pipeline #25794 passed with stages
in 6 minutes and 31 seconds
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel>
<bytecodeTargetLevel target="1.8">
<module name="squirreljme.emulators.emulator-runner.main" target="8" />
<module name="squirreljme.emulators.emulator-runner.test" target="8" />
<module name="squirreljme.main" target="1.7" />
......
......@@ -68,6 +68,7 @@
<w>squirreljme</w>
<w>stdint</w>
<w>summercoat</w>
<w>sysprop</w>
<w>tabbedpanes</w>
<w>targetting</w>
<w>textboxes</w>
......@@ -78,6 +79,8 @@
<w>vmtypes</w>
<w>vtable</w>
<w>xemulator</w>
<w>xjdwp</w>
<w>xlibraries</w>
<w>xsnapshot</w>
</words>
</dictionary>
......
......@@ -23,6 +23,7 @@
<option value="$PROJECT_DIR$/modules/cldc-compact" />
<option value="$PROJECT_DIR$/modules/collections" />
<option value="$PROJECT_DIR$/modules/common-vm" />
<option value="$PROJECT_DIR$/modules/debug-jdwp" />
<option value="$PROJECT_DIR$/modules/demo-hello" />
<option value="$PROJECT_DIR$/modules/dio" />
<option value="$PROJECT_DIR$/modules/dio-adc" />
......@@ -77,8 +78,6 @@
<option value="$PROJECT_DIR$/tools/txt-to-pbm" />
</set>
</option>
<option name="useAutoImport" value="true" />
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings>
</option>
</component>
......
......@@ -15,6 +15,7 @@
<inspection_tool class="AnonymousHasLambdaAlternative" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="AnonymousInnerClass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ArrayLengthInLoopCondition" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ArraysAsListWithZeroOrOneArgument" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="AssertEqualsBetweenInconvertibleTypes" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="AssignmentUsedAsCondition" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true" />
......@@ -283,7 +284,7 @@
<inspection_tool class="OverlyLargePrimitiveArrayInitializer" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_limit" value="64" />
</inspection_tool>
<inspection_tool class="PackageInfoWithoutPackage" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PackageDotHtmlMayBePackageInfo" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PackageNamingConvention" enabled="false" level="WEAK WARNING" enabled_by_default="false">
<option name="m_regex" value="[a-z]*" />
<option name="m_minLength" value="2" />
......
......@@ -7,8 +7,11 @@
</list>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="1.8" project-jdk-type="JavaSDK" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK" />
</project>
\ No newline at end of file
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="JDWP Connect" type="Remote">
<option name="USE_SOCKET_TRANSPORT" value="true" />
<option name="SERVER_MODE" value="false" />
<option name="SHMEM_ADDRESS" />
<option name="HOST" value="localhost" />
<option name="PORT" value="5005" />
<option name="AUTO_RESTART" value="false" />
<RunnerSettings RunnerId="Debug">
<option name="DEBUG_PORT" value="5005" />
<option name="LOCAL" value="false" />
</RunnerSettings>
<method v="2" />
</configuration>
</component>
\ No newline at end of file
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="JDWP Listen" type="Remote">
<option name="USE_SOCKET_TRANSPORT" value="true" />
<option name="SERVER_MODE" value="true" />
<option name="SHMEM_ADDRESS" />
<option name="HOST" value="localhost" />
<option name="PORT" value="5005" />
<option name="AUTO_RESTART" value="true" />
<RunnerSettings RunnerId="Debug">
<option name="DEBUG_PORT" value="5005" />
<option name="LOCAL" value="false" />
</RunnerSettings>
<method v="2" />
</configuration>
</component>
\ No newline at end of file
......@@ -11,6 +11,7 @@ package cc.squirreljme.plugin.multivm;
import javax.inject.Inject;
import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.SourceSet;
/**
* Task for running the full-suite of SquirrelJME.
......@@ -19,7 +20,11 @@ import org.gradle.api.DefaultTask;
*/
public class VMFullSuite
extends DefaultTask
implements VMExecutableTask
{
/** The source set used. */
public final String sourceSet;
/** The virtual machine type. */
public final VMSpecifier vmType;
......@@ -37,6 +42,7 @@ public class VMFullSuite
if (__vmType == null)
throw new NullPointerException("NARG");
this.sourceSet = SourceSet.MAIN_SOURCE_SET_NAME;
this.vmType = __vmType;
// Runs the entire API/Library suite of SquirrelJME to run a given
......@@ -49,9 +55,20 @@ public class VMFullSuite
this.getOutputs().upToDateWhen(new AlwaysFalse());
// This depends on everything!
this.dependsOn(new VMFullSuiteDepends(this, __vmType));
this.dependsOn(new VMFullSuiteDepends(this, __vmType),
new VMEmulatorDependencies(this, __vmType));
// Actual running of everything
this.doLast(new VMFullSuiteTaskAction(__vmType));
}
/**
* {@inheritDoc}
* @since 2021/03/08
*/
@Override
public String getSourceSet()
{
return this.sourceSet;
}
}
......@@ -38,7 +38,6 @@ public class VMTestTask
* Initializes the task.
*
* @param __executor The executor for the task.
* @param __execFactory This is a work around to use internal executions
* for specifications needed to spawn VMs within workers.
* @param __sourceSet The source set to use.
* @param __vmType The virtual machine type.
......
......@@ -28,6 +28,7 @@ import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.gradle.api.Project;
import org.gradle.api.Task;
......@@ -67,7 +68,8 @@ public enum VMType
* @since 2020/08/15
*/
@Override
public void spawnJvmArguments(Task __task, JavaExecSpecFiller __execSpec,
public void spawnJvmArguments(Task __task,
JavaExecSpecFiller __execSpec,
String __mainClass, Map<String, String> __sysProps,
Path[] __libPath, Path[] __classPath, String... __args)
throws NullPointerException
......@@ -95,6 +97,9 @@ public enum VMType
emuLib.toString());
}
// Bring in any system defined properties we want to truly set?
VMType.__copySysProps(sysProps);
// Start with the base emulator class path
List<Object> classPath = new ArrayList<>();
classPath.add(VMHelpers.projectRuntimeClasspath(
......@@ -376,6 +381,10 @@ public enum VMType
/* End. */
;
/** Prefix for system properties to appear in the VM. */
private static final String _JVM_KEY_PREFIX =
"squirreljme.sysprop.";
/** The proper name of the VM. */
public final String properName;
......@@ -506,6 +515,12 @@ public enum VMType
__args == null)
throw new NullPointerException("NARG");
// Copy system properties
Map<String, String> sysProps = new LinkedHashMap<>(__sysProps);
// Bring in any system defined properties we want to truly set?
VMType.__copySysProps(sysProps);
// Determine the class-path for the emulator
List<Path> vmClassPath = new ArrayList<>();
for (File file : VMHelpers.projectRuntimeClasspath(
......@@ -529,6 +544,11 @@ public enum VMType
// Add library paths, suites that are available for consumption
vmArgs.add("-Xlibraries:" + VMHelpers.classpathAsString(__libPath));
// Enable JDWP debugging?
String jdwpProp = System.getProperty("squirreljme.jdwp");
if (jdwpProp != null)
vmArgs.add("-Xjdwp:" + jdwpProp);
// Determine where profiler snapshots are to go, try to use the
// profiler directory for that
Path profilerDir = ((__task instanceof VMExecutableTask) ?
......@@ -550,7 +570,7 @@ public enum VMType
vmArgs.add(VMHelpers.classpathAsString(__classPath));
// Any system properties
for (Map.Entry<String, String> sysProp : __sysProps.entrySet())
for (Map.Entry<String, String> sysProp : sysProps.entrySet())
vmArgs.add("-D" + sysProp.getKey() + "=" + sysProp.getValue());
// Main class of the target to run
......@@ -595,4 +615,34 @@ public enum VMType
return properName;
}
}
/**
* Copies system properties with the given prefix into the system
* properties for the VM.
*
* @param __sysProps The system properties to copy into.
* @throws NullPointerException On null arguments.
* @since 2021/03/08
*/
private static void __copySysProps(Map<String, String> __sysProps)
throws NullPointerException
{
if (__sysProps == null)
throw new NullPointerException("NARG");
// Copy any that are set
for (Map.Entry<Object, Object> prop :
System.getProperties().entrySet())
{
// Only match certain keys
String baseKey = Objects.toString(prop.getKey());
if (!baseKey.startsWith(VMType._JVM_KEY_PREFIX))
continue;
// Add it in
__sysProps.put(
baseKey.substring(VMType._JVM_KEY_PREFIX.length()),
Objects.toString(prop.getValue()));
}
}
}
......@@ -192,6 +192,11 @@ is not able to reliably test within SquirrelJME.
The following _System_ properties are available:
* `squirreljme.jdwp=[hostname]:port` -- Enable JDWP.
* Enables JDWP for the given virtual machine.
* If only `:port` is specified it will listen for incoming connections from
a debugger.
* If both `hostname:port` is specified it will connect to a remote debugger.
* `squirreljme.midlet=value` -- The MIDlet to run, in the following order:
* If `value` is a number and is `-1`, then no MIDlet will be selected and
the `Main-Class` attribute will be force selected.
......@@ -201,8 +206,14 @@ The following _System_ properties are available:
with a matching case-insensitive title.
* Otherwise if `value` is a string, it will construct a virtual MIDlet
which executes the given value as the name of a class within the JAR.
* `squirreljme.sysprop.<systemProperty>=value` -- Add system property.
* This allows any system properties that are prefixed with this to be
added into the target virtual machine when running.
* As an example `squirreljme.sysprop.favorite.animal=squirrel`:
* Will define system property `favorite.animal=squirrel` within the
virtual machine.
* `test.single=classname` -- Run only the given test:
* Will be the test class to be ran.
* Will be the test class to be run.
* Multi-parameter tests are in the form of `classname@parameter`, if a
parameter is specified then only that one will be matched. Otherwise
this will include all tests of that given parameter.
......
......@@ -154,3 +154,33 @@ repositories {
}
}
```
# Debugging
SquirrelJME's VMs may be debugged by having it connect to a debugger or
waiting for a debug connection. Debugging happens purely over TCP/IP. It
is not possible to use existing debuggers to debug SquirrelJME virtual machines
there are debug interfaces available through standardized JDWP. Note that
it is _not_ recommended for the debugger port to be open to the internet as
it will allow the remote debugger to have access to all the virtual machine
internals.
Arguments to the virtual machines for _SpringCoat_, _SummerCoat_, etc.:
* Connect to remote debugger
* `-Dsquirreljme.jdwp=localhost:5005`
* Listen for a remote connection
* `-Dsquirreljme.jdwp=:5005`
Instructions for specific debuggers:
* [jdb](
https://docs.oracle.com/javase/7/docs/technotes/tools/solaris/jdb.html)
* Connect:
* `jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=5005`
* Listen:
* `jdb -connect com.sun.jdi.SocketListen:port=5005`
* [IntelliJ](
https://www.jetbrains.com/help/idea/attaching-to-local-process.html)
* [Eclipse](
https://www.eclipse.org/community/eclipse_newsletter/2017/june/article1.php)
......@@ -37,6 +37,7 @@ dependencies
compileClasspath project(":modules:io")
compileClasspath project(":modules:tac")
compileClasspath project(":modules:zip")
compileClasspath project(":modules:debug-jdwp")
// And for run-time to be able to be ran
runtimeClasspath project(":modules:cldc-compact")
......@@ -44,6 +45,7 @@ dependencies
runtimeClasspath project(":modules:io")
runtimeClasspath project(":modules:tac")
runtimeClasspath project(":modules:zip")
runtimeClasspath project(":modules:debug-jdwp")
}
// We need the native library in the JAR before we can properly use it
......
......@@ -36,22 +36,25 @@ public final class ProfiledFrame
new LinkedHashMap<>();
/** The number of calls made into the frame. */
int _numcalls;
int _numCalls;
/** Cumulative time spent in this frame and child frames. */
long _traceruntime;
long _totalTime;
/** Cumulative time spent in this frame and child frames without sleep. */
long _tracecputime;
/** Cumulative time spent in this frame and child frames with sleep. */
long _totalCpuTime;
/** Time only spent in this frame. */
long _frameruntime;
long _selfTime;
/** Time only spent in this frame without sleep. */
long _framecputime;
/** Time only spent in this frame with sleep. */
long _selfCpuTime;
/** Time to subtract from the measured self times. */
private long _subtractself;
/** Time spent calling other methods. */
private long _invokingTime;
/** The time spent sleeping. */
long _sleepTime;
/** Current frame start time. */
private long _currentstart =
......@@ -61,6 +64,10 @@ public final class ProfiledFrame
private long _currentsubstart =
Long.MIN_VALUE;
/** Starting sleep time. */
private long _sleepStart =
Long.MIN_VALUE;
/** The current in-call count for this frame. */
private int _inCallCount;
......@@ -107,14 +114,17 @@ public final class ProfiledFrame
// Increase call count
this._inCallCount++;
// Increase call count
this._numCalls++;
}
/**
* Indicates that the frame has been exited.
*
* @param __ns The time of the exit.
* @return The time spent in this frame, in total, self time, and CPU
* time.
* @return Total time with invocations, self time, self CPU time, and
* sleep time.
* @throws IllegalStateException If the frame has not been entered.
* @since 2018/11/11
*/
......@@ -131,31 +141,33 @@ public final class ProfiledFrame
if (this._currentsubstart != Long.MIN_VALUE)
throw new IllegalStateException("AH04");
// Determine the cumulative and self time spent
long total = __ns - cs,
self = total - this._subtractself;
// Determine the cumulative and self time spent (without sub-invokes)
long total = __ns - cs;
long self = total - this._invokingTime;
// Increase call count
this._numcalls++;
// Along with the CPU time
long sleepTime = this._sleepTime;
long selfCPU = self - sleepTime;
// All sub-frames times
this._traceruntime += total;
this._tracecputime += total;
// Before we leave, add our self time to be tracked
this._totalTime += self;
this._totalCpuTime += selfCPU;
// And only this frame time
this._frameruntime += self;
this._framecputime += self;
this._selfTime += self;
this._selfCpuTime += selfCPU;
// Clear these for next time
this._currentstart = Long.MIN_VALUE;
this._subtractself = 0;
this._invokingTime = 0;
this._sleepTime = 0;
// Lower the in-call count, make sure it never goes below zero
if ((--this._inCallCount) < 0)
this._inCallCount = 0;
// Return both times since they may be useful
return new long[]{total, self, total};
return new long[]{total, self, selfCPU, sleepTime};
}
/**
......@@ -188,9 +200,9 @@ public final class ProfiledFrame
// Reset
this._currentsubstart = Long.MIN_VALUE;
// Return the amount of time that was spend in this invocation
long rv = __ns - css;
this._subtractself += rv;
// Return the amount of time that was spent in this invocation
long rv = Math.max(0, __ns - css);
this._invokingTime += rv;
return rv;
}
......@@ -212,5 +224,43 @@ public final class ProfiledFrame
// Mark it
this._currentsubstart = __ns;
}
/**
* Enters sleep mode for the current frame.
*
* @param __enter Are we entering sleep?
* @param __ns The current time.
* @since 2021/04/25
*/
public void sleep(boolean __enter, long __ns)
{
long sleepStart = this._sleepStart;
// Entering sleep?
if (__enter)
{
// Frame already sleeping
if (sleepStart != Long.MIN_VALUE)
throw new IllegalStateException(
"Frame is already sleeping.");
// Mark it
this._sleepStart = __ns;
}
// Ending sleep?
else
{
// Frame is not asleep
if (sleepStart == Long.MIN_VALUE)
throw new IllegalStateException("Frame is not asleep.");
// Reset
this._sleepStart = Long.MIN_VALUE;
// Reduce the amount of spent time on this
this._sleepTime += Math.max(0, __ns - sleepStart);
}
}
}
......@@ -37,14 +37,16 @@ public final class ProfiledThread
private final Deque<ProfiledFrame> _stack =
new LinkedList<>();
/** A reference to the current thread for time tracking. */
/** Grand invocation total. */
long _invtotal;
/** Total time. */
long _totaltime;
long _totalTime;
/** CPU time. */
long _cputime;
long _cpuTime;
/**
* Initializes the thread information.
......@@ -109,7 +111,8 @@ public final class ProfiledThread
this._frames : top._frames);
ProfiledFrame rv = frames.get(loc);
if (rv == null)
frames.put(loc, (rv = new ProfiledFrame(loc, stack.size() + 1)));
frames.put(loc,
(rv = new ProfiledFrame(loc, stack.size() + 1)));
// Tell the top-most frame that we are in an invoke, so this removes
// self time accordingly
......@@ -182,19 +185,33 @@ public final class ProfiledThread
throw new IllegalStateException("AH07");
// Tell that popped frame we left
// returns: [0:total, 1:self, 2:self CPU, 3:sleepTime]
long[] times = rv.exitedFrame(__ns);
// The total thread time is the cumulative of the self times
this._totalTime += Math.max(0, times[1]);
this._cpuTime += Math.max(0, times[2]);
// Every frame that is set gets the cumulative time of the self time
// of the frame that just exited, so this way all parent frames will
// have times greater than or equal to the sub-frames
for (ProfiledFrame frame : stack)
{
frame._totalTime += Math.max(0, times[1]);
frame._totalCpuTime += Math.max(0, times[2]);
}
// If we had a frame underneath, say the invocation has ended
ProfiledFrame newtop = stack.peek();
if (newtop != null)
newtop.invokeEnd(__ns);
ProfiledFrame newTop = stack.peek();
if (newTop != null)
{
// End the invocation
newTop.invokeEnd(__ns);
}
// If all threads are out, count the times
else
{
this._totaltime += times[0];
this._cputime += times[2];
// Invocation total goes up after each method ends
this._invtotal++;
}
......
......@@ -166,8 +166,7 @@ public final class ProfilerSnapshot
cpu.writeInt(1);
// Timestamp and duration
long start = this.startmillis;
cpu.writeLong(start);
cpu.writeLong(this.startmillis);
// Threads are needed now
Map<String, ProfiledThread> threads = this._threads;
......@@ -177,7 +176,7 @@ public final class ProfilerSnapshot
// thread has spent