Đầu tiên, thực sự có một cách ít hack hơn nhiều. Tất cả những gì chúng ta muốn làm là thay đổi những gì print
in, phải không?
_print = print
def print(*args, **kw):
args = (arg.replace('cat', 'dog') if isinstance(arg, str) else arg
for arg in args)
_print(*args, **kw)
Hoặc, tương tự, bạn có thể khỉpatch sys.stdout
thay vì print
.
Ngoài ra, không có gì sai với exec … getsource …
ý tưởng. Chà, dĩ nhiên là có nhiều sai với nó, nhưng ít hơn những gì diễn ra ở đây
Nhưng nếu bạn muốn sửa đổi các hằng số mã của đối tượng hàm, chúng ta có thể làm điều đó.
Nếu bạn thực sự muốn chơi xung quanh với các đối tượng mã thực sự, bạn nên sử dụng một thư viện như bytecode
(khi nó kết thúc) hoặc byteplay
(cho đến lúc đó, hoặc cho các phiên bản Python cũ hơn) thay vì thực hiện thủ công. Ngay cả đối với một cái gì đó tầm thường này, trình CodeType
khởi tạo là một nỗi đau; nếu bạn thực sự cần phải làm những việc như sửa chữa lnotab
, chỉ có một người mất trí sẽ làm điều đó bằng tay.
Ngoài ra, không cần phải nói rằng không phải tất cả các triển khai Python đều sử dụng các đối tượng mã kiểu CPython. Mã này sẽ hoạt động trong CPython 3.7 và có thể tất cả các phiên bản trở lại ít nhất là 2.2 với một vài thay đổi nhỏ (và không phải là công cụ hack mã, nhưng những thứ như biểu thức của trình tạo), nhưng nó sẽ không hoạt động với bất kỳ phiên bản IronPython nào.
import types
def print_function():
print ("This cat was scared.")
def main():
# A function object is a wrapper around a code object, with
# a bit of extra stuff like default values and closure cells.
# See inspect module docs for more details.
co = print_function.__code__
# A code object is a wrapper around a string of bytecode, with a
# whole bunch of extra stuff, including a list of constants used
# by that bytecode. Again see inspect module docs. Anyway, inside
# the bytecode for string (which you can read by typing
# dis.dis(string) in your REPL), there's going to be an
# instruction like LOAD_CONST 1 to load the string literal onto
# the stack to pass to the print function, and that works by just
# reading co.co_consts[1]. So, that's what we want to change.
consts = tuple(c.replace("cat", "dog") if isinstance(c, str) else c
for c in co.co_consts)
# Unfortunately, code objects are immutable, so we have to create
# a new one, copying over everything except for co_consts, which
# we'll replace. And the initializer has a zillion parameters.
# Try help(types.CodeType) at the REPL to see the whole list.
co = types.CodeType(
co.co_argcount, co.co_kwonlyargcount, co.co_nlocals,
co.co_stacksize, co.co_flags, co.co_code,
consts, co.co_names, co.co_varnames, co.co_filename,
co.co_name, co.co_firstlineno, co.co_lnotab,
co.co_freevars, co.co_cellvars)
print_function.__code__ = co
print_function()
main()
Điều gì có thể đi sai với hack các đối tượng mã? Chủ yếu chỉ là segfaults, RuntimeError
s ăn hết toàn bộ stack, RuntimeError
s bình thường hơn có thể được xử lý hoặc các giá trị rác có thể sẽ chỉ tăng TypeError
hoặc AttributeError
khi bạn cố gắng sử dụng chúng. Ví dụ: thử tạo một đối tượng mã chỉ RETURN_VALUE
không có gì trên ngăn xếp (mã byte b'S\0'
cho 3.6+, b'S'
trước đó) hoặc với một tuple trống co_consts
khi có LOAD_CONST 0
mã byte hoặc varnames
giảm 1 để mức cao nhất LOAD_FAST
thực sự tải một freevar / tế bào tế bào. Đối với một số niềm vui thực sự, nếu bạn nhận đượclnotab
đủ sai, mã của bạn sẽ chỉ segfault khi chạy trong trình gỡ lỗi.
Sử dụng bytecode
hoặc byteplay
sẽ không bảo vệ bạn khỏi tất cả những vấn đề đó, nhưng chúng có một số kiểm tra vệ sinh cơ bản và những người trợ giúp tốt cho phép bạn làm những việc như chèn một đoạn mã và để nó lo lắng về việc cập nhật tất cả các lỗi và nhãn để bạn có thể ' T hiểu sai, và như vậy. (Thêm vào đó, chúng giúp bạn không phải gõ vào hàm tạo 6 dòng lố bịch đó và phải gỡ lỗi các lỗi chính tả ngớ ngẩn xuất phát từ việc này.)
Bây giờ là # 2.
Tôi đã đề cập rằng các đối tượng mã là bất biến. Và dĩ nhiên, các hằng số là một tuple, vì vậy chúng ta không thể thay đổi điều đó trực tiếp. Và thứ trong const tuple là một chuỗi, chúng ta cũng không thể thay đổi trực tiếp. Đó là lý do tại sao tôi phải xây dựng một chuỗi mới để xây dựng một bộ dữ liệu mới để xây dựng một đối tượng mã mới.
Nhưng nếu bạn có thể thay đổi một chuỗi trực tiếp thì sao?
Chà, đủ sâu dưới vỏ bọc, mọi thứ chỉ là một con trỏ đến một số dữ liệu C, phải không? Nếu bạn đang sử dụng CPython, có API C để truy cập các đối tượng và bạn có thể sử dụng ctypes
để truy cập API đó từ bên trong Python, đó là một ý tưởng khủng khiếp mà họ đặt pythonapi
quyền ở đó trong ctypes
mô-đun của stdlib . :) Thủ thuật quan trọng nhất bạn cần biết là id(x)
con trỏ thực tế x
trong bộ nhớ (dưới dạng int
).
Thật không may, API C cho các chuỗi sẽ không cho phép chúng tôi nhận được một cách an toàn tại bộ lưu trữ nội bộ của một chuỗi đã bị đóng băng. Vì vậy, hãy an toàn, hãy đọc các tệp tiêu đề và tự tìm bộ lưu trữ đó.
Nếu bạn đang sử dụng CPython 3.4 - 3.7 (nó khác với các phiên bản cũ hơn và là người biết về tương lai), một chuỗi ký tự từ một mô-đun được tạo từ ASCII thuần túy sẽ được lưu trữ bằng định dạng ASCII nhỏ gọn, có nghĩa là cấu trúc kết thúc sớm và bộ đệm của byte ASCII theo ngay trong bộ nhớ. Điều này sẽ bị hỏng (như trong segfault) nếu bạn đặt một ký tự không phải ASCII trong chuỗi hoặc một số loại chuỗi không theo nghĩa đen, nhưng bạn có thể đọc 4 cách khác để truy cập bộ đệm cho các loại chuỗi khác nhau.
Để làm cho mọi thứ dễ dàng hơn một chút, tôi đang sử dụng superhackyinternals
dự án ngoài GitHub của mình. (Đó là cố ý không cài đặt được bởi vì bạn thực sự không nên sử dụng điều này ngoại trừ để thử nghiệm bản dựng trình thông dịch cục bộ của bạn và tương tự.)
import ctypes
import internals # https://github.com/abarnert/superhackyinternals/blob/master/internals.py
def print_function():
print ("This cat was scared.")
def main():
for c in print_function.__code__.co_consts:
if isinstance(c, str):
idx = c.find('cat')
if idx != -1:
# Too much to explain here; just guess and learn to
# love the segfaults...
p = internals.PyUnicodeObject.from_address(id(c))
assert p.compact and p.ascii
addr = id(c) + internals.PyUnicodeObject.utf8_length.offset
buf = (ctypes.c_int8 * 3).from_address(addr + idx)
buf[:3] = b'dog'
print_function()
main()
Nếu bạn muốn chơi với những thứ này, int
thì đơn giản hơn rất nhiều str
. Và nó dễ dàng hơn rất nhiều để đoán những gì bạn có thể phá vỡ bằng cách thay đổi giá trị của 2
để 1
, phải không? Trên thực tế, hãy quên tưởng tượng, hãy làm điều đó (sử dụng các loại từ superhackyinternals
một lần nữa):
>>> n = 2
>>> pn = PyLongObject.from_address(id(n))
>>> pn.ob_digit[0]
2
>>> pn.ob_digit[0] = 1
>>> 2
1
>>> n * 3
3
>>> i = 10
>>> while i < 40:
... i *= 2
... print(i)
10
10
10
Pa giả vờ rằng hộp mã có một thanh cuộn có chiều dài vô hạn.
Tôi đã thử điều tương tự trong IPython và lần đầu tiên tôi thử đánh giá 2
tại dấu nhắc, nó đã đi vào một loại vòng lặp vô hạn không thể gián đoạn. Có lẽ nó đang sử dụng số 2
cho một cái gì đó trong vòng REPL của nó, trong khi trình thông dịch chứng khoán thì không?
42
để23
hơn lý do tại sao đó là một ý tưởng tồi để thay đổi giá trị của"My name is Y"
để"My name is X"
.