Run IPyKernel in subinterpreter and thus expose Cutter bindings to it

This commit is contained in:
Florian Märkl 2018-02-21 17:37:46 +01:00 committed by xarkes
parent abd9cca091
commit b9b903052c
6 changed files with 198 additions and 8 deletions

View File

@ -0,0 +1,39 @@
import logging
import threading
from ipykernel.kernelapp import IPKernelApp
from ipykernel.ipkernel import IPythonKernel
# TODO: Make this behave like a Popen instance and pipe it to IPyKernelInterfaceJupyter!
class IPyKernelInterfaceKernel:
def poll(self):
return None
class CutterIPythonKernel(IPythonKernel):
def pre_handler_hook(self):
pass
def post_handler_hook(self):
pass
class CutterIPKernelApp(IPKernelApp):
def init_signal(self):
pass
def launch_ipykernel(argv):
def run_kernel():
app = CutterIPKernelApp.instance()
app.kernel_class = CutterIPythonKernel
app.log_level = logging.DEBUG
app.initialize(argv[3:])
app.start()
thread = threading.Thread(target=run_kernel)
thread.start()
return IPyKernelInterfaceKernel()

View File

@ -1,8 +1,64 @@
import sys
import threading from jupyter_client.ioloop import IOLoopKernelManager
import time
from notebook.notebookapp import * from notebook.notebookapp import *
import cutter
# TODO: this must behave like a Popen instance and pipe to IPyKernelInterfaceKernel!
class IPyKernelInterfaceJupyter:
def poll(self):
return None
class CutterInternalIPyKernelManager(IOLoopKernelManager):
def start_kernel(self, **kw):
"""Starts a kernel on this host in a separate process.
If random ports (port=0) are being used, this method must be called
before the channels are created.
Parameters
----------
`**kw` : optional
keyword arguments that are passed down to build the kernel_cmd
and launching the kernel (e.g. Popen kwargs).
"""
# write connection file / get default ports
self.write_connection_file()
# save kwargs for use in restart
self._launch_args = kw.copy()
# build the Popen cmd
extra_arguments = kw.pop('extra_arguments', [])
kernel_cmd = self.format_kernel_cmd(extra_arguments=extra_arguments)
env = kw.pop('env', os.environ).copy()
# Don't allow PYTHONEXECUTABLE to be passed to kernel process.
# If set, it can bork all the things.
env.pop('PYTHONEXECUTABLE', None)
if not self.kernel_cmd:
# If kernel_cmd has been set manually, don't refer to a kernel spec
# Environment variables from kernel spec are added to os.environ
env.update(self.kernel_spec.env or {})
# launch the kernel subprocess
self.log.debug("Starting kernel: %s", kernel_cmd)
# TODO: kernel_cmd including python executable and so on is currently used for argv. Make a clean version!
import cutter_internal
cutter_internal.launch_ipykernel(kernel_cmd, env=env, **kw)
self.kernel = IPyKernelInterfaceJupyter()
# self._launch_kernel(kernel_cmd, env=env,
# **kw)
self.start_restarter()
self._connect_control_socket()
def kernel_manager_factory(kernel_name, **kwargs):
if kernel_name in {"python", "python2", "python3"}:
return CutterInternalIPyKernelManager(kernel_name=kernel_name, **kwargs)
else:
return IOLoopKernelManager(kernel_name=kernel_name, **kwargs)
class CutterNotebookApp(NotebookApp): class CutterNotebookApp(NotebookApp):
@ -13,6 +69,8 @@ class CutterNotebookApp(NotebookApp):
def start(self): def start(self):
""" see NotebookApp.start() """ """ see NotebookApp.start() """
self.kernel_manager.kernel_manager_factory = kernel_manager_factory
super(NotebookApp, self).start() super(NotebookApp, self).start()
self.write_server_info_file() self.write_server_info_file()
@ -48,7 +106,9 @@ def start_jupyter():
app = CutterNotebookApp() app = CutterNotebookApp()
app.initialize() app.initialize()
app.start() app.start()
print('TODO: Export cutter bindings to any kernel')
print(cutter.version())
print(cutter.cmd('?e That is executed from radare2'))
return app return app
if __name__ == "__main__":
app = start_jupyter()
print("Started " + app.url_with_token)

View File

@ -62,5 +62,6 @@
<file>img/cutter.svg</file> <file>img/cutter.svg</file>
<file>python/cutter_jupyter.py</file> <file>python/cutter_jupyter.py</file>
<file>python/cutter_ipykernel.py</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -32,6 +32,7 @@ JupyterConnection::~JupyterConnection()
void JupyterConnection::start() void JupyterConnection::start()
{ {
PyImport_AppendInittab("cutter", &PyInit_api); PyImport_AppendInittab("cutter", &PyInit_api);
PyImport_AppendInittab("cutter_internal", &PyInit_api_internal);
Py_Initialize(); Py_Initialize();
PyEval_InitThreads(); PyEval_InitThreads();

View File

@ -1,6 +1,9 @@
#include "PythonAPI.h" #include "PythonAPI.h"
#include "cutter.h" #include "cutter.h"
#include <QFile>
/* Return the number of arguments of the application command line */ /* Return the number of arguments of the application command line */
PyObject *api_version(PyObject *self, PyObject *null) PyObject *api_version(PyObject *self, PyObject *null)
{ {
@ -34,8 +37,92 @@ PyModuleDef CutterModule = {
NULL, NULL, NULL, NULL NULL, NULL, NULL, NULL
}; };
PyObject *PyInit_api() PyObject *PyInit_api()
{ {
return PyModule_Create(&CutterModule); return PyModule_Create(&CutterModule);
} }
// -----------------------------
PyObject *api_internal_launch_ipykernel(PyObject *self, PyObject *args, PyObject *kw)
{
QStringList argv;
{
PyObject *argvListObject;
if (!PyArg_ParseTuple(args, "O", &argvListObject)
|| !PyList_Check(argvListObject))
{
qWarning() << "Invalid args passed to api_internal_launch_ipykernel().";
return nullptr;
}
for (int i=0; i<PyList_Size(argvListObject); i++)
{
PyObject *o = PyList_GetItem(argvListObject, i);
QString s = QString::fromUtf8(PyUnicode_AsUTF8(o));
argv.append(s);
}
}
PyThreadState *parentThreadState = PyThreadState_Get();
PyThreadState *threadState = Py_NewInterpreter();
QFile moduleFile(":/python/cutter_ipykernel.py");
moduleFile.open(QIODevice::ReadOnly);
QByteArray moduleCode = moduleFile.readAll();
moduleFile.close();
auto moduleCodeObject = Py_CompileString(moduleCode.constData(), "cutter_ipykernel.py", Py_file_input);
if (!moduleCodeObject)
{
qWarning() << "Could not compile cutter_ipykernel.";
return nullptr;
}
auto cutterIPykernelModule = PyImport_ExecCodeModule("cutter_ipykernel", moduleCodeObject);
Py_DECREF(moduleCodeObject);
if (!cutterIPykernelModule)
{
qWarning() << "Could not import cutter_ipykernel.";
return nullptr;
}
auto launchFunc = PyObject_GetAttrString(cutterIPykernelModule, "launch_ipykernel");
PyObject *argvListObject = PyList_New(argv.size());
for (int i=0; i<argv.size(); i++)
{
QString s = argv[i];
PyList_SetItem(argvListObject, i, PyUnicode_DecodeUTF8(s.toUtf8().constData(), s.length(), nullptr));
}
auto ipyKernel = PyObject_CallFunction(launchFunc, "O", argvListObject);
PyThreadState_Swap(parentThreadState);
return PyLong_FromLong(42);
}
PyMethodDef CutterInternalMethods[] = {
{"launch_ipykernel", (PyCFunction)api_internal_launch_ipykernel, METH_VARARGS | METH_KEYWORDS,
"Launch an IPython Kernel in a subinterpreter"},
{NULL, NULL, 0, NULL}
};
PyModuleDef CutterInternalModule = {
PyModuleDef_HEAD_INIT, "cutter_internal", NULL, -1, CutterInternalMethods,
NULL, NULL, NULL, NULL
};
PyObject *PyInit_api_internal()
{
return PyModule_Create(&CutterInternalModule);
}

View File

@ -2,6 +2,8 @@
#define PYTHONAPI_H #define PYTHONAPI_H
#include <Python.h> #include <Python.h>
PyObject *PyInit_api(); PyObject *PyInit_api();
PyObject *PyInit_api_internal();
#endif // PYTHONAPI_H #endif // PYTHONAPI_H