Skip to content

Commit 9f646d2

Browse files
committed
Now works on windows
1 parent cf9760a commit 9f646d2

File tree

2 files changed

+85
-35
lines changed

2 files changed

+85
-35
lines changed

README.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ Usage
2222
with two_plus_two_equals(5):
2323
print(eval("2 + 2")) # prints "5"
2424
25-
Or, if you’re feeling adventurous (currently only works on x86 architectures
26-
running some flavor of Linux/Unix/BSD, including OS X)::
25+
Or, if you’re feeling adventurous (currently only works on x86 architectures)::
2726

2827
export DOUBLEPLUSNOPYTHONOPT=1
2928

@@ -47,7 +46,9 @@ your scripts. This applies equally to .pyc files: if the pyc files were
4746
generated with the normal python opcode optimizations, this library will have
4847
no effect on inline ``2 + 2`` expressions, since they will have already been
4948
turned into ``4``. At present, disabling opcode optimizations only works in x86
50-
architectures, and it does not currently work on windows.
49+
architectures.
50+
51+
Also, to state the obvious: don’t use this library in production.
5152

5253
License
5354
-------

doublescript/internals/asm_hooks.py

Lines changed: 81 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import ctypes
22
import ctypes.util
3-
from ctypes import c_void_p, c_size_t, c_int, py_object, pythonapi, CFUNCTYPE, c_ubyte
3+
from ctypes import (
4+
c_void_p, c_size_t, c_int, py_object, pythonapi, CFUNCTYPE, c_ubyte,
5+
c_long, c_ulong, c_ulonglong, POINTER)
46
import errno
57
import os
68
import platform
7-
import resource
89
import struct
910
import warnings
1011

@@ -13,20 +14,81 @@
1314
from .utils import cdata_ptr
1415
from .refs import Py_INCREF
1516

17+
try:
18+
import resource
19+
except ImportError:
20+
PAGE_SIZE = None # windows
21+
else:
22+
PAGE_SIZE = resource.getpagesize()
1623

17-
PAGE_SIZE = resource.getpagesize()
1824

25+
IS_X86 = (platform.machine() in ('i386', 'i486', 'i586', 'i686', 'x86', 'x86_64'))
26+
IS_WINDOWS = (platform.system() == 'Windows')
27+
IS_64BIT = ctypes.sizeof(c_void_p) == ctypes.sizeof(c_ulonglong)
1928

29+
# <sys/mman.h> constants
2030
PROT_NONE = 0 # The memory cannot be accessed at all
2131
PROT_READ = 1 # The memory can be read
2232
PROT_WRITE = 2 # The momory can be modified
2333
PROT_EXEC = 4 # The memory can be executed
2434

35+
# Microsoft, what's the point of using bitmasks if you can't OR the values?
36+
PAGE_NOACCESS = 0x01
37+
PAGE_READONLY = 0x02
38+
PAGE_READWRITE = 0x04
39+
PAGE_WRITECOPY = 0x08
40+
PAGE_EXECUTE = 0x10
41+
PAGE_EXECUTE_READ = 0x20
42+
PAGE_EXECUTE_READWRITE = 0x40
43+
44+
MS_PAGE_CONSTANTS = {
45+
PROT_NONE: PAGE_NOACCESS,
46+
PROT_READ: PAGE_READONLY,
47+
PROT_WRITE: PAGE_READWRITE,
48+
(PROT_WRITE | PROT_READ): PAGE_READWRITE,
49+
PROT_EXEC: PAGE_EXECUTE,
50+
(PROT_EXEC | PROT_READ): PAGE_EXECUTE_READ,
51+
(PROT_EXEC | PROT_WRITE): PAGE_EXECUTE_READWRITE,
52+
(PROT_EXEC | PROT_WRITE | PROT_READ): PAGE_EXECUTE_READWRITE,
53+
}
54+
55+
# windows datatypes
56+
BOOL = c_long
57+
DWORD = c_ulong
58+
SIZE_T = c_ulonglong if IS_64BIT else DWORD
59+
LPVOID = c_void_p
60+
PDWORD = POINTER(DWORD)
61+
62+
63+
def mprotect_winapi(addr, size, flags):
64+
kernel32 = ctypes.windll.kernel32
65+
VirtualProtect = kernel32.VirtualProtect
66+
VirtualProtect.argtypes = [LPVOID, SIZE_T, DWORD, PDWORD]
67+
VirtualProtect.restype = BOOL
68+
old_prot = DWORD()
69+
prot = MS_PAGE_CONSTANTS[flags]
70+
ret = VirtualProtect(addr, size, prot, ctypes.byref(old_prot))
71+
if not ret:
72+
raise ctypes.WinError()
73+
74+
75+
def mprotect_libc(addr, size, flags):
76+
libc = ctypes.CDLL(ctypes.util.find_library('libc'), use_errno=True)
77+
libc.mprotect.argtypes = [c_void_p, c_size_t, c_int]
78+
libc.mprotect.restype = c_int
79+
addr_align = addr & ~(PAGE_SIZE - 1)
80+
memlen = PAGE_SIZE
81+
# In the unlikely event that the first five bytes of the function occur
82+
# across a page boundary, we need to call mprotect on two pages
83+
if ((addr + size) - addr_align) > PAGE_SIZE:
84+
memlen *= 2
85+
ret = libc.mprotect(addr_align, memlen, flags)
86+
if ret == -1:
87+
e = ctypes.get_errno()
88+
raise OSError(e, errno.errorcodes[e], os.strerror(e))
89+
2590

26-
# TODO: Use VirtualProtect on windows
2791
def mprotect(addr, size, flags):
28-
if addr % PAGE_SIZE != 0:
29-
raise Exception("mprotect address must be aligned to a page boundary")
3092
if not isinstance(addr, seven.integer_types):
3193
raise ValueError("addr must be an integer type")
3294
if not isinstance(size, seven.integer_types):
@@ -38,13 +100,10 @@ def mprotect(addr, size, flags):
38100
"flags must be a bitmask of PROT_NONE(%d), PROT_READ (%d), "
39101
"PROT_WRITE (%d), and/or PROT_EXEC (%d)" % (
40102
PROT_NONE, PROT_READ, PROT_WRITE, PROT_EXEC))
41-
libc = ctypes.CDLL(ctypes.util.find_library('libc'), use_errno=True)
42-
libc.mprotect.argtypes = [c_void_p, c_size_t, c_int]
43-
libc.mprotect.restype = c_int
44-
ret = libc.mprotect(addr, size, flags)
45-
if ret == -1:
46-
e = ctypes.get_errno()
47-
raise OSError(e, errno.errorcodes[e], os.strerror(e))
103+
if IS_WINDOWS:
104+
mprotect_winapi(addr, size, flags)
105+
else:
106+
mprotect_libc(addr, size, flags)
48107

49108

50109
def pycode_optimize(code, consts, name, lineno_obj):
@@ -59,14 +118,6 @@ def pycode_optimize(code, consts, name, lineno_obj):
59118
Override_PyCode_Optimize = quaternaryfunc(pycode_optimize)
60119

61120

62-
def is_x86():
63-
return platform.machine() in ('i386', 'i486', 'i586', 'i686', 'x86', 'x86_64')
64-
65-
66-
def is_windows():
67-
return platform.system() == 'Windows'
68-
69-
70121
class UnsupportedPlatformException(Exception):
71122
pass
72123

@@ -80,21 +131,19 @@ def force_ord(char):
80131

81132

82133
def override_cfunc(cfunc, new_cfunc):
83-
if not is_x86():
134+
"""
135+
Overrides a CFUNCTION by inserting a JMP instruction at the function's
136+
address in memory.
137+
138+
If the new cfunction doesn't have the same prototype as the old one,
139+
this will almost certainly cause a segfault.
140+
"""
141+
if not IS_X86:
84142
raise UnsupportedPlatformException(
85143
"override_cfunc() only works on x86 architectures")
86-
if is_windows():
87-
raise UnsupportedPlatformException(
88-
"override_cfunc() does not work on windows")
89144
old_cfunc_ptr = cdata_ptr(cfunc)
90145
new_cfunc_ptr = cdata_ptr(new_cfunc)
91-
page_boundary = int((old_cfunc_ptr // PAGE_SIZE) * PAGE_SIZE)
92-
memlen = PAGE_SIZE
93-
# In the unlikely event that the first five bytes of the function occur
94-
# across a page boundary, we need to call mprotect on two pages
95-
if page_boundary + PAGE_SIZE - old_cfunc_ptr < 5:
96-
memlen *= 2
97-
mprotect(page_boundary, memlen, PROT_READ | PROT_WRITE | PROT_EXEC)
146+
mprotect(old_cfunc_ptr, 5, PROT_READ | PROT_WRITE | PROT_EXEC)
98147
offset = new_cfunc_ptr - old_cfunc_ptr
99148
JMP = b'\xe9'
100149
opcodes = list(map(force_ord, JMP + struct.pack('<l', offset - 5)))

0 commit comments

Comments
 (0)