Experimental feature in GraalVM

将 Insight 嵌入应用程序

在 Java 中嵌入 Insight #

Graal 语言(使用 Truffle 框架实现的语言,如 JavaScript、Python、Ruby 和 R)可以通过 Polyglot Context API 嵌入到自定义 Java 应用程序中。GraalVM Insight 也可以通过相同的 API 进行控制。例如

final Engine engine = context.getEngine();
Instrument instrument = engine.getInstruments().get("insight");
Function<Source, AutoCloseable> access = instrument.lookup(Function.class);
AutoCloseable handle = access.apply(agentSrc);

获取 ContextEngine 并请求 insight 工具。然后使用 GraalVM Insight 脚本创建 Source,并在获取其检测句柄时应用它。在不再需要时,使用 handle.close() 禁用脚本的所有检测。例如

Source instrument = Source.create("js", """
    insight.on('return', function(ctx, frame) {
        console.log(`Instrumented where = ${frame.where}`);
    }, {
        roots: true,
        rootNameFilter: 'end',
    });
    """);
Source script = Source.create("js", """
    function end() {
        var where = 'end';
        console.log(where + ' invoked')
    }
    end();
    """);
try (Context context = Context.newBuilder().build()) {
    @SuppressWarnings("unchecked")
    Function<Source, AutoCloseable> insight = context.getEngine().getInstruments().get("insight").lookup(Function.class);

    // run without instrumentation
    context.eval(script);

    // run with instrumentation
    try (AutoCloseable handle = insight.apply(instrument)) {
        context.eval(script);
    }

    // run without instrumentation
    context.eval(script);
}

参见 嵌入依赖设置。添加对 insight 的依赖。

<dependency>
    <groupId>org.graalvm.polyglot</groupId>
    <!-- Select tools: profiler, inspect, coverage, dap, tools -->
    <artifactId>insight</artifactId>
    <version>23.1.1</version>
    <type>pom</type>
</dependency>

忽略内部脚本 #

通常,人们希望将用动态语言编写的某些代码视为特权代码。想象一下各种绑定到操作系统概念或应用程序其他特性的代码。此类脚本最好保持黑盒状态,并对 GraalVM Insight 的检测能力隐藏。

为了隐藏特权脚本,将它们标记为内部。默认情况下,GraalVM Insight 会忽略内部脚本并且不进行处理。

扩展 Insight 脚本的功能 #

将 GraalVM Insight 嵌入到 Java 应用程序中时,您可以让其他对象可供正在评估的 Insight 脚本使用。例如

@TruffleInstrument.Registration(
    id = "meaningOfWorld", name = "Meaning Of World", version = "demo",
    services = { Insight.SymbolProvider.class }
)
public final class MeaningOfWorldInstrument extends TruffleInstrument {
    @Override
    protected void onCreate(Env env) {
        Map<String, Integer> symbols = Collections.singletonMap("meaning", 42);
        Insight.SymbolProvider provider = () -> symbols;
        env.registerService(provider);
    }
}

前面的 Java 代码创建了一个工具,它将新符号 meaning 注册到每个评估的 Insight 脚本。每个脚本都可以引用并使用它,例如,限制方法调用的数量

insight.on('enter', (ctx, frames) => { if (--meaning <= 0) throw 'Stop!' }, { roots : true });

可以公开简单值以及复杂对象。有关详细信息,请参阅 javadoc。请注意,检测可以改变程序执行的许多方面,并且不受任何安全沙箱的限制。

将 Insight 嵌入到 Node.js 中 #

Insight 手册 中展示了许多将 GraalVM Insight 与 node 结合使用的示例。但是,其中大多数依赖于命令行选项 --insight,并未充分利用该工具的动态特性。下一个示例展示了如何创建管理服务器。

将此代码保存到 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 });

该程序在端口 9999 上打开一个 HTTP 服务器,并监听随时传入的脚本。调用应用程序

node --insight=adminserver.js yourapp.js
Admin ready at 9999

在它运行时,连接到管理端口。向它发送任何 GraalVM Insight 脚本。例如,以下脚本将观察谁调用了 process.exit

curl --data \
'insight.on("enter", (ctx, frame) => { console.log(new Error("call to exit").stack); }, \
{ roots: true, rootNameFilter: "exit" });' \
-X POST https://:9999/

在编写自己的 adminserver.js 时,请注意安全性。只有授权人员才能对您的应用程序应用任意钩子。不要向所有人开放管理服务器端口。

要了解有关 Insight 的更多信息并查找一些用例,请访问 Insight 手册。它以一个必不可少的 HelloWorld 示例开始,然后演示更具挑战性的任务。

联系我们