enso/docs/debugger/runtime-debugging.md

150 lines
7.1 KiB
Markdown

# High & Low Runtime Debugging
This document describes how to debug **everything** - e.g. how to debug the
_Java code_ that powers the Enso _engine & interpreter_ as well as Enso standard
_libraries Java code_ (used to interact with the operating system via Java
APIs). At the same thetime the document describes how to _map that information_
onto the execution of the actual _Enso source code_ being interpreted.
## Debugging Single Source File
To analyze what is happening in the interpreter, we often want to debug
excecution of a _single Enso file_.
Get started by building the _Engine distribution_ built with
`sbt buildEngineDistribution` command. Then configure your IDE to understand the
_engine Java & Scala sources_ which are mostly located in the
[engine subdirectory](https://github.com/enso-org/enso/tree/develop/engine). The
sources are known to be understandable by:
- VSCode - with
[Enso Extension described herein](../../tools/enso4igv/README.md)
- IntelliJ
- IGV - with [extension described herein](../../tools/enso4igv/IGV.md)
- [NetBeans](http://netbeans.apache.org) - with the same
[extension as IGV](../../tools/enso4igv/IGV.md) is using
There are two ways to start JVM in a debug mode. The first one is fully
integrated into the `sbt` build system. It builds the engine (in case there were
any changes) and then starts the JVM in debug mode trying to attach to port
`5005`:
```sh
sbt:enso> runEngineDistribution --debug --run ./test/Base_Tests/src/Data/Numbers_Spec.enso
```
The second options gives one a complete control as it launches everything from a
command line. By specifying `JAVA_OPTS` environment variable one influences the
special JVM arguments when launching the `bin/enso` from the engine
distribution:
```bash
enso$ JAVA_OPTS=-agentlib:jdwp=transport=dt_socket,server=n,address=5005 ./built-distribution/enso-engine-*/enso-*/bin/enso --run ./test/Base_Tests/src/Data/Numbers_Spec.enso
```
Both of the approaches launch the JVM in a _debug mode_. Once the JVM is
started, simply attach the debugger to the JVM process at the specified port
(usually `5005` is used).
### Attaching from VSCode
First of all make sure your VSCode contains the
[Enso supporting extension](https://marketplace.visualstudio.com/items?itemName=Enso.enso4vscode).
Follow the [instructions provided herein](../../tools/enso4igv/README.md) to
install everything that's needed.
Once the
[Enso extension](https://marketplace.visualstudio.com/items?itemName=Enso.enso4vscode)
is installed, open the root of Enso repository as workspace and select _"Listen
to 5005"_ debug configuration:
![Listen to 5005](https://github.com/enso-org/enso/assets/26887752/1874bcb1-cf8b-4df4-92d8-e7fb57e1b17a)
Once the connection with the Enso JVM is made, debug the Java code as usual.
### Mapping Java and Enso Code
The _Enso interpreter_ maintains a mapping between its Java code and appropriate
_Enso Code_ internally. It shall be no problem to use that information in the
debugger. All the mentioned IDEs (alas except IntelliJ) support such _mixed
debugging_ of Java and Enso code.
Put a breakpoint into `.enso` file and after a while the breakpoint is hit and
one can inspect variables, step over the _Enso statements_ and more...
![Breakpoint in Enso](https://github.com/enso-org/enso/assets/26887752/54ae4126-f77a-4463-9647-4dd3a5f83526)
...as one can seamlessly switch to debugging on the Enso interpreter itself! One
can place breakpoint into Java class like `PanicException.java` and continue
debugging with `F5`:
![Breakpoint in Java](https://github.com/enso-org/enso/assets/26887752/db3fbe4e-3bb3-4d4a-bb2a-b5039f716c85)
Should one ever want to jump back from Java to Enso one can use the _"Pause in
GraalVM Script"_ action. Select it and continue with `F5` - as soon as the code
reaches a statement in Enso, it stops:
![Pause in GraalVM](https://github.com/enso-org/enso/assets/26887752/98eb0bb7-48c2-4208-9d9a-5b8bacc99de2)
Similar behavior to _VSCode_ is available in _IGV_ and _NetBeans_. Understanding
both the _engine Java code_ as well as _Enso Code_ shall be pretty simple with
these tools. For example _NetBeans_ offers _Toggle Pause in GraalVM Script_
button in the toolbar:
![NetBeans Debugger](https://user-images.githubusercontent.com/26887752/209614191-b0513635-819b-4c64-a6f9-9823b90a1513.png)
and your execution shall stop on the next `.enso` line of code.
These advanced developer tools allow one to debug both - the _Enso code_ as well
as _Java code_. The stack traces shows a _mixture of Java and Enso stack frames_
by default. Right-clicking on the thread allows one to switch to plain Java view
(with a way more stack frames) and back. Analyzing low level details as well as
Enso developer point of view shall be simple with such tool.
### Tips and Tricks (for poor IntelliJ users)
Finding the mapping of the Enso source code to the engine's Java code in
_IntelliJ_ isn't easy. Trying to find out how to debug a specific expression is
way more tricky than in case of _VSCode_, _IGV_ or _NetBeans_. However, if you
really want to stick with _IntelliJ_ as your only tool, following steps may help
you to skip all the irrelevant code and get to the code you are interested in:
- To get the mapping to Enso source code, evaluate the following expression in
the Java debugger: `this.getRootNode().getSourceSection()`. Note that this,
obviously, works only when the debugger is stopped in some Truffle node code.
- To debug a method called `foo`, put a breakpoint in
`org.enso.interpreter.node.ClosureRootNode#execute` with a condition on
`this.name.contains("foo")`
- To debug a specific expression, put some _unique_ expression, like
`Debug.eval "1+1"`, in front of it and put a breakpoint in a Truffle node
corresponding to that unique expression, in this case that is
`org.enso.interpreter.node.expression.builtin.debug.DebugEvalNode`.
## Debugging annotation processors
The Engine uses annotation processors to generate some of the Java code, e.g.,
the builtin methods with `org.enso.interpreter.dsl.MethodProcessor`, or JMH
benchmark sources with `org.enso.benchmarks.processor.BenchProcessor`.
Annotation processors are invoked by the Java compiler (`javac`), therefore, we
need special instructions to attach the debugger to them.
Let's debug `org.enso.interpreter.dsl.MethodProcessor` as an example. The
following are the commands invoked in the `sbt` shell:
- `project runtime`
- `clean`
- Delete all the compiled class files along with all the generated sources by
the annotation processor. This ensures that the annotation processor will be
invoked.
- `set javacOptions += FrgaalJavaCompiler.debugArg`
- This sets a special flag that is passed to the frgaal Java compiler, which
in turn waits for the debugger to attach. Note that this setting is not
persisted and will be reset once the project is reloaded.
- `compile`
- Launches the Java compiler, which will wait for the debugger to attach. Put
a breakpoint in some class of `org.enso.interpreter.dsl` package. Wait for
the message in the console instructing to attach the debugger.
- To reset the `javacOptions` setting, either run
`set javacOptions -= FrgaalJavaCompiler.debugArg`, or reload the project with
`reload`.