-
Notifications
You must be signed in to change notification settings - Fork 46
/
Copy pathipythonEmbed.py
169 lines (133 loc) · 5.58 KB
/
ipythonEmbed.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import traceback
try:
import os
import sys
import platform
import subprocess
import idaapi
import atexit
import contextlib
# This is a hack to get zmq to work with the Anaconda distribution and IDA.
try:
platform.python_implementation()
except ValueError:
sys.version = '2.7.5 |Anaconda 2.1.0 (32-bit)| (default, May 31 2013, 10:43:53) [MSC v.1500 32 bit (Intel)]'
import __main__
from ipykernel.kernelapp import IPKernelApp
from IPython.utils.frame import extract_module_locals
kernel_app = None
menu_items = []
qtconsole_processes = []
def embed_kernel(module=None, local_ns=None, **kwargs):
"""Embed and start an IPython kernel in a given scope.
Parameters
----------
module : ModuleType, optional
The module to load into IPython globals (default: caller)
local_ns : dict, optional
The namespace to load into IPython user namespace (default: caller)
kwargs : various, optional
Further keyword args are relayed to the IPKernelApp constructor,
allowing configuration of the Kernel. Will only have an effect
on the first embed_kernel call for a given process.
"""
# get the app if it exists, or set it up if it doesn't
if IPKernelApp.initialized():
app = IPKernelApp.instance()
else:
app = IPKernelApp.instance(**kwargs)
app.initialize(sys.argv)
# Undo unnecessary sys module mangling from init_sys_modules.
# This would not be necessary if we could prevent it
# in the first place by using a different InteractiveShell
# subclass, as in the regular embed case.
main = app.kernel.shell._orig_sys_modules_main_mod
if main is not None:
sys.modules[app.kernel.shell._orig_sys_modules_main_name] = main
# load the calling scope if not given
(caller_module, caller_locals) = extract_module_locals(1)
if module is None:
module = caller_module
if local_ns is None:
local_ns = caller_locals
app.kernel.user_module = None
app.kernel.user_ns = None
app.shell.set_completer_frame()
if app.poller is not None:
app.poller.start()
app.kernel.start()
return app
@contextlib.contextmanager
def capture_output_streams():
_capture_output_streams()
try:
yield
finally:
_release_output_streams()
def _capture_output_streams():
sys.__stdout__, sys.__stderr__, sys.stdout, sys.stderr = sys.stdout, sys.stderr, sys.__stdout__, sys.__stderr__
def _release_output_streams():
sys.stdout, sys.stderr, sys.__stdout__, sys.__stderr__ = sys.__stdout__, sys.__stderr__, sys.stdout, sys.stderr
def find_python_dir():
# We need to get the python directory like this, because
# sys.executable will return idaq.exe. This just goes two
# directories up from os.py location
return os.path.dirname(os.path.dirname(os.__file__))
def start_qtconsole():
try:
if kernel_app:
python_directory = find_python_dir()
cmd_line = [
"{}/pythonw".format(python_directory),
"-m", "qtconsole",
"--existing", kernel_app.connection_file
]
process = subprocess.Popen(cmd_line,
stdin=None,
stdout=None,
stderr=None,
close_fds=True)
qtconsole_processes.append(process)
else:
print "Error: No kernel defined!"
except Exception, e:
traceback.print_exc()
@atexit.register
def term():
kill_qtconsoles()
remove_menus()
def kill_qtconsoles():
for process in qtconsole_processes:
process.kill()
def remove_menus():
for menu_item in menu_items:
idaapi.del_menu_item(menu_item)
def add_idaipython_menu():
menu_item = idaapi.add_menu_item('View/', 'IDAIPython QtConsole', '', 0, start_qtconsole, tuple())
menu_items.append(menu_item)
def start(argv=None):
try:
with capture_output_streams():
global kernel_app
if argv:
sys.argv = argv
kernel_app = embed_kernel(module=__main__, local_ns={})
"""
Starting with ipython 4.2.0 whenever certain exceptions are thrown, there is a call to get_terminal_size().
in that function , in case environment variables for "COLUMNS" and "LINES" are not defined there is a call
to sys.__stdout__.fileno() in order to get a handle to the current terminal. IDAPythonStdOut doesn't have an attribute fileno
so the call fails , and the kernel dies. the right way to solve it, is add AttributeError to the try/except in get_terminal_size.
a work around is to add this 2 environment variables
"""
os.environ["COLUMNS"] = "80"
os.environ["LINES"] = "24"
def kernel_iteration():
with capture_output_streams():
kernel_app.kernel.do_one_iteration()
add_idaipython_menu()
return kernel_iteration
except Exception, e:
traceback.print_exc()
raise
except Exception, e:
traceback.print_exc()