GraalVM Insight: Embedding
GraalVM Insight is a multipurpose, flexible tool providing enourmous possiblilities when it comes to dynamic understanding of user application behavior. See its manual for more details. Read on to learn how to embed Insight into your own application.
GraalVM languages can be embedded into custom Java applications via polyglot Context API. Insight isn't an exception and it can also be controlled via the same API as well. See formal Insight documentation for more details:
final Engine engine = context.getEngine();
Instrument instrument = engine.getInstruments().get("insight");
Function<Source, AutoCloseable> access = instrument.lookup(Function.class);
AutoCloseable handle = access.apply(agentSrc);
Obtain Engine
for your Context
and ask for insight
instrument. Then create
Source
with your Insight script and apply it while obtaining
its instrumentation handle. Use handle.close()
to disable all the script's
instrumentations when when no longer needed.
Often one wants to treat certain code written in a dynamic language as a priviledged one - imagine various bindings to OS concepts or other features of one's application. Such scripts are better to remain blackboxed and hidden from Insight instrumentation capabilities.
To hide priviledged scripts from Insight sight mark such scripts as internal. By default Insight ignores and doesn't process internal scripts.
The Insight hacker's manual shows many examples of using
Insight with node
- however most of them rely on the command
line option --insight
and don't benefit from the dynamic nature of
Insight much. Let's fix that by showing how to create an
admin server. Define adminserver.js
:
function initialize(insight, require) {
const http = require("http");
const srv = http.createServer((req, res) => {
let method = req.method;
if (method === 'POST') {
var data = '';
req.on('data', (chunk) => {
data += chunk.toString();
});
req.on('end', () => {
const fn = new Function('insight', data);
try {
fn(insight);
res.write('GraalVM Insight hook activated\n');
} finally {
res.end();
}
});
}
});
srv.listen(9999, () => console.log("Admin ready at 9999"));
}
let waitForRequire = function (event) {
if (typeof process === 'object' && process.mainModule && process.mainModule.require) {
insight.off('source', waitForRequire);
initialize(insight, process.mainModule.require.bind(process.mainModule));
}
};
insight.on('source', waitForRequire, { roots: true });
which opens an HTTP server at port 9999
and listens for incoming scripts to
be applied any time later. Invoke your application as
$ node --insight=adminserver.js --experimental-options yourapp.js
Admin ready at 9999
and while it is running connect to the admin port. Send in any Insight script you want.
For example following script is going to observe who calls process.exit
:
$ curl --data \
'insight.on("enter", (ctx, frame) => { console.log(new Error("call to exit").stack); }, \
{ roots: true, rootNameFilter: "exit" });' \
-X POST http://localhost:9999/
When writing your own adminserver.js
pay attention to security. Insight
scripts are very powerful and you want only authorized persons to apply arbitrary
hooks to your application. Don't open the admin server port to everybody.
Read about more Insight use-cases in its hacker's manual.