-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathcpython.py
More file actions
executable file
·286 lines (251 loc) · 14.5 KB
/
cpython.py
File metadata and controls
executable file
·286 lines (251 loc) · 14.5 KB
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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
#!/usr/bin/env python3
# PyCPython - interpret CPython in Python
# by Albert Zeyer, 2011
# code under BSD 2-Clause License
from __future__ import print_function
import argparse
import os
import sys
MyDir = os.path.dirname(os.path.abspath(__file__))
CPythonDir = MyDir + "/CPython"
import cparser
import cparser.interpreter
class CPythonState(cparser.State):
def __init__(self):
super(CPythonState, self).__init__()
self.autoSetupSystemMacros()
self.autoSetupGlobalIncludeWrappers()
self.included_files = set() # type: set[str]
def findIncludeFullFilename(self, filename, local):
fullfn = CPythonDir + "/Include/" + filename
if os.path.exists(fullfn): return fullfn
return super(CPythonState, self).findIncludeFullFilename(filename, local)
def readLocalInclude(self, filename):
#print " ", filename, "..."
if filename == "pyconfig.h":
if filename in self.included_files: return "", None
self.included_files.add(filename)
def reader():
# see CPython/pyconfig.h.in for reference
import ctypes
sizeofMacro = lambda t: cparser.Macro(rightside=str(ctypes.sizeof(t)))
self.macros["SIZEOF_SHORT"] = sizeofMacro(ctypes.c_short)
self.macros["SIZEOF_INT"] = sizeofMacro(ctypes.c_int)
self.macros["SIZEOF_LONG"] = sizeofMacro(ctypes.c_long)
self.macros["SIZEOF_LONG_LONG"] = sizeofMacro(ctypes.c_longlong)
self.macros["SIZEOF_DOUBLE"] = sizeofMacro(ctypes.c_double)
self.macros["SIZEOF_FLOAT"] = sizeofMacro(ctypes.c_float)
self.macros["SIZEOF_VOID_P"] = sizeofMacro(ctypes.c_void_p)
self.macros["SIZEOF_SIZE_T"] = sizeofMacro(ctypes.c_size_t)
self.macros["SIZE_MAX"] = cparser.Macro(rightside=str(ctypes.c_size_t(-1).value))
self.macros["SIZEOF_UINTPTR_T"] = sizeofMacro(ctypes.POINTER(ctypes.c_uint))
self.macros["SIZEOF_PTHREAD_T"] = self.macros["SIZEOF_LONG"]
self.macros["SIZEOF_WCHAR_T"] = sizeofMacro(ctypes.c_wchar)
self.macros["SIZEOF_PID_T"] = self.macros["SIZEOF_INT"]
self.macros["SIZEOF_TIME_T"] = self.macros["SIZEOF_LONG"]
self.macros["SIZEOF__BOOL"] = cparser.Macro(rightside="1")
self.macros["HAVE_ERRNO_H"] = cparser.Macro(rightside="1")
self.macros["HAVE_FCNTL_H"] = cparser.Macro(rightside="1")
self.macros["HAVE_SYS_STAT_H"] = cparser.Macro(rightside="1")
self.macros["HAVE_SYS_TYPES_H"] = cparser.Macro(rightside="1")
self.macros["HAVE_UNISTD_H"] = cparser.Macro(rightside="1")
# Force strict ANSI mode so pymacro.h uses the simple Py_ARRAY_LENGTH
# (without GCC-extension typeof/__builtin_types_compatible_p).
self.macros["__STRICT_ANSI__"] = cparser.Macro(rightside="1")
# _PYTHONFRAMEWORK: name of the macOS framework bundle (empty when not bundled)
self.macros["_PYTHONFRAMEWORK"] = cparser.Macro(rightside='""')
self.macros["HAVE_SIGNAL_H"] = cparser.Macro(rightside="1")
self.macros["HAVE_STDARG_PROTOTYPES"] = cparser.Macro(rightside="1")
self.macros["HAVE_STD_ATOMIC"] = cparser.Macro(rightside="1")
self.macros["HAVE_WCHAR_H"] = cparser.Macro(rightside="1")
self.macros["_POSIX_THREADS"] = cparser.Macro(rightside="1")
# _GNU_SOURCE, _POSIX_C_SOURCE or so?
return
yield None # make it a generator
return reader(), None
fullfn = self.findIncludeFullFilename(filename, True)
# Stringlib headers are template headers: they are intentionally included
# multiple times with different macro definitions (STRINGLIB=ucs1lib_ etc.)
# and must NOT be deduplicated by filename.
is_template_header = fullfn and os.path.join(CPythonDir, "Objects", "stringlib") in fullfn
if not is_template_header:
if fullfn and fullfn in self.included_files: return "", fullfn
if fullfn: self.included_files.add(fullfn)
return super(CPythonState, self).readLocalInclude(filename)
def readGlobalInclude(self, filename):
"""Fall back to CPython/Include/<filename> for any unhandled global include."""
fullfn = os.path.join(CPythonDir, "Include", filename)
if os.path.exists(fullfn):
return self.readLocalInclude(fullfn)
return super(CPythonState, self).readGlobalInclude(filename)
def parse_cpython(self):
# We keep all in the same state, i.e. the same static space.
# This also means that we don't reset macro definitions. This speeds up header includes.
# Usually this is not a problem.
self.macros["Py_BUILD_CORE"] = cparser.Macro(rightside="1") # Makefile
self.macros["Py_BUILD_CORE_BUILTIN"] = cparser.Macro(rightside="1") # Makefile
cparser.parse(CPythonDir + "/Modules/main.c", self) # Py_Main
self.macros["FAST_LOOPS"] = cparser.Macro(rightside="0") # not sure where this would come from
cparser.parse(CPythonDir + "/Python/ceval.c", self) # PyEval_EvalFrameEx etc
del self.macros["EMPTY"] # will be redefined later
cparser.parse(CPythonDir + "/Python/getopt.c", self) # _PyOS_GetOpt
cparser.parse(CPythonDir + "/Python/pythonrun.c", self) # Py_Initialize
cparser.parse(CPythonDir + "/Python/pystate.c", self) # PyInterpreterState_New
cparser.parse(CPythonDir + "/Python/errors.c", self) # PyErr_Clear
cparser.parse(CPythonDir + "/Python/import.c", self) # _PyImport_Fini2
cparser.parse(CPythonDir + "/Python/thread.c", self) # PyThread_allocate_lock
cparser.parse(CPythonDir + "/Python/bootstrap_hash.c", self) # _Py_ReadHashSeed
cparser.parse(CPythonDir + "/Python/pylifecycle.c", self) # _PyRuntime_Initialize, _Py_SetLocaleFromEnv
self.macros.pop("NAME", None) # token.h defines NAME=1; sysmodule.c redefines it as "cpython"
cparser.parse(CPythonDir + "/Python/sysmodule.c", self) # PySys_ResetWarnOptions
if os.path.exists(CPythonDir + "/Python/random.c"):
cparser.parse(CPythonDir + "/Python/random.c", self) # _PyRandom_Init
cparser.parse(CPythonDir + "/Python/pyhash.c", self) # _Py_HashPointer, _Py_HashDouble, etc.
cparser.parse(CPythonDir + "/Objects/object.c", self) # _Py_ReadyTypes etc
cparser.parse(CPythonDir + "/Objects/unicodeobject.c", self) # PyUnicode_InternFromString etc.
cparser.parse(CPythonDir + "/Objects/typeobject.c", self) # PyType_Ready
cparser.parse(CPythonDir + "/Objects/tupleobject.c", self) # PyTuple_New
del self.macros["Return"] # will be used differently
# We need these macro hacks because dictobject.c will use the same vars.
self.macros["length_hint_doc"] = cparser.Macro(rightside="length_hint_doc__dict")
self.macros["numfree"] = cparser.Macro(rightside="numfree__dict")
self.macros["free_list"] = cparser.Macro(rightside="free_list__dict")
cparser.parse(CPythonDir + "/Objects/dictobject.c", self) # PyDict_New
# We need this macro hack because stringobject.c will use the same var.
self.macros["sizeof__doc__"] = cparser.Macro(rightside="sizeof__doc__str")
if os.path.exists(CPythonDir + "/Objects/stringobject.c"):
cparser.parse(CPythonDir + "/Objects/stringobject.c", self) # PyString_FromString
cparser.parse(CPythonDir + "/Objects/obmalloc.c", self) # PyObject_Free
cparser.parse(CPythonDir + "/Modules/gcmodule.c", self) # _PyObject_GC_NewVar
cparser.parse(CPythonDir + "/Objects/descrobject.c", self) # PyDescr_NewWrapper
# We need these macro hacks because methodobject.c will use the same vars.
self.macros["numfree"] = cparser.Macro(rightside="numfree__methodobj")
self.macros["free_list"] = cparser.Macro(rightside="free_list__methodobj")
cparser.parse(CPythonDir + "/Objects/methodobject.c", self) # PyCFunction_NewEx
# We need these macro hacks because methodobject.c used the same vars.
self.macros["numfree"] = cparser.Macro(rightside="numfree__list")
self.macros["free_list"] = cparser.Macro(rightside="free_list__list")
self.macros["sizeof_doc"] = cparser.Macro(rightside="sizeof_doc__list")
self.macros["length_hint_doc"] = cparser.Macro(rightside="length_hint_doc__list")
self.macros["index_doc"] = cparser.Macro(rightside="index_doc__list")
self.macros["count_doc"] = cparser.Macro(rightside="count__list")
self.macros.pop("OFF", None) # reused later
cparser.parse(CPythonDir + "/Objects/listobject.c", self) # PyList_New
cparser.parse(CPythonDir + "/Objects/abstract.c", self) # PySequence_List
cparser.parse(CPythonDir + "/Python/modsupport.c", self) # Py_BuildValue
# fileutils.c must come before traceback.c (provides _Py_write_noraise)
cparser.parse(CPythonDir + "/Python/fileutils.c", self) # _Py_ResetForceASCII, _Py_open_noraise
cparser.parse(CPythonDir + "/Python/pathconfig.c", self) # _PyPathConfig_Init
cparser.parse(CPythonDir + "/Python/traceback.c", self) # _Py_DumpTracebackThreads
self.macros.pop("PUTS", None) # traceback.c and faulthandler.c both define PUTS identically
self.macros.pop("OFF", None) # traceback.c and faulthandler.c both define OFF differently
cparser.parse(CPythonDir + "/Modules/faulthandler.c", self) # _PyFaulthandler_Fini
def init_faulthandler(sigusr1_chain=False):
"""
:param bool sigusr1_chain: whether the default SIGUSR1 handler should also be called.
"""
try:
import faulthandler
except ImportError as e:
print("faulthandler import error. %s" % e)
return
# Only enable if not yet enabled -- otherwise, leave it in its current state.
if not faulthandler.is_enabled():
faulthandler.enable()
if os.name != 'nt':
import signal
# This will print a backtrace on SIGUSR1.
# Note that this also works when Python is hanging,
# i.e. in cases where register_sigusr1_print_backtrace() will not work.
faulthandler.register(signal.SIGUSR1, all_threads=True, chain=sigusr1_chain)
def register_sigusr1_print_backtrace():
if os.name == "nt":
return
def sigusr1_handler(sig, frame):
print("--- SIGUSR1 handler")
better_exchook.print_tb(tb=frame)
import signal
signal.signal(signal.SIGUSR1, sigusr1_handler)
def main(argv):
argparser = argparse.ArgumentParser(
usage="%s [PyCPython options, see below] [CPython options, see via --help]" % argv[0],
description="Emulate CPython by interpreting its C code via PyCParser.",
epilog="All other options are passed on to CPython. Use --help to see them.",
add_help=False # we will add our own
)
argparser.add_argument(
'--pycpython-help', action='help', help='show this help message and exit')
argparser.add_argument(
'--dump-python', action='store', nargs=1,
help="Dumps the converted Python code of the specified function, e.g. Py_Main.")
argparser.add_argument(
'--verbose-jit', action='store_true',
help="Prints what functions and global vars we are going to translate.")
args_ns, argv_rest = argparser.parse_known_args(argv[1:])
argv = argv[:1] + argv_rest
print("PyCPython -", argparser.description,)
print("(use --pycpython-help for help)")
state = CPythonState()
print("Parsing CPython...", end="")
state.parse_cpython()
if state._errors:
print("finished, parse errors:")
for m in state._errors:
print(m)
else:
print("finished, no parse errors.")
interpreter = cparser.interpreter.Interpreter()
interpreter.register(state)
# Py_GetPrefix/ExecPrefix/Path/PythonHome/ProgramFullPath are defined in
# pathconfig.c, which is intentionally not parsed. Their C implementation
# calls _PyPathConfig_Init() (from Modules/getpath.c, also not parsed),
# which walks the real filesystem to discover where CPython is installed.
# That filesystem-discovery logic is platform-specific, requires many
# unwrapped syscalls, and would return the *wrong* paths anyway — we want
# the host Python's prefix/path so the interpreted CPython can find its
# stdlib. We therefore supply these values directly from the host runtime.
import ctypes as _ctypes
def _make_wchar_const(s):
"""Create a wchar_t* constant string suitable for returning from Py_Get* functions."""
buf = interpreter._make_wchar_string(s)
return _ctypes.cast(buf, _ctypes.c_void_p).value or 0
for _fn, _s in [
("Py_GetProgramFullPath", argv[0]),
("Py_GetPrefix", sys.prefix),
("Py_GetExecPrefix", sys.exec_prefix),
("Py_GetPath", ":".join(sys.path)),
("Py_GetPythonHome", ""),
]:
def _make_path_func(s=_s):
return _make_wchar_const(s)
_make_path_func.C_argTypes = None
_make_path_func.C_resType = _ctypes.c_void_p
interpreter._func_cache[_fn] = _make_path_func
# _PyPathConfig_Calculate is defined in Modules/getpath.c (not parsed).
# It fills a _PyPathConfig struct from the filesystem. We provide a stub
# that returns success (_PyInitError with msg=NULL) and leaves the config
# fields at zero — the public Py_Get* functions above supply the real values.
_PyInitError_ctype = interpreter.getCType(state.typedefs['_PyInitError'])
def _path_config_calculate_stub(*args):
return _PyInitError_ctype() # all-zero = success (msg=NULL)
_path_config_calculate_stub.C_argTypes = None
_path_config_calculate_stub.C_resType = state.typedefs['_PyInitError']
interpreter._func_cache['_PyPathConfig_Calculate'] = _path_config_calculate_stub
if args_ns.dump_python:
for fn in args_ns.dump_python:
print()
print("PyAST of %s:" % fn)
interpreter.dumpFunc(fn)
sys.exit()
if args_ns.verbose_jit:
interpreter.debug_print_getFunc = True
interpreter.debug_print_getVar = True
args = ("Py_Main", len(argv), argv + [None])
print("Run", args, ":")
interpreter.runFunc(*args)
if __name__ == '__main__':
import better_exchook
better_exchook.install()
register_sigusr1_print_backtrace()
init_faulthandler(sigusr1_chain=True)
main(sys.argv)