ead*_*ead 6 python cpython python-3.x python-internals
众所周知,小bytes-objects是自动的CPython(类似于"实习" 实习生 -function为字符串).更正:正如@abarnert 所解释的,它更像是整数池而不是实习字符串.
是否可以通过让我们说"实验性"第三方库或者是重启内核的唯一方法来恢复被破坏的字节对象?
可以使用Cython功能(Cython> = 0.28)完成概念验证:
%%cython
def do_bad_things():
cdef bytes b=b'a'
cdef const unsigned char[:] safe=b
cdef char *unsafe=<char *> &safe[0] #who needs const and type-safety anyway?
unsafe[0]=98 #replace through `b`
Run Code Online (Sandbox Code Playgroud)
或者@jfs建议ctypes:
import ctypes
import sys
def do_bad_things():
b = b'a';
(ctypes.c_ubyte * sys.getsizeof(b)).from_address(id(b))[-2] = 98
Run Code Online (Sandbox Code Playgroud)
显然,通过滥用C-功能,do_bad_things改变不可变的(或者CPython认为的)对象b'a',b'b'并且因为这个对象是实例bytes,我们可以看到事后发生的坏事:
>>> do_bad_things() #b'a' means now b'b'
>>> b'a'==b'b' #wait for a surprise
True
>>> print(b'a') #another one
b'b'
Run Code Online (Sandbox Code Playgroud)
有可能恢复/清除字节对象池,这b'a'意味着b'a'再一次?
一点注意事项:似乎不是每个bytes创建过程都使用此池.例如:
>>> do_bad_things()
>>> print(b'a')
b'b'
>>> print((97).to_bytes(1, byteorder='little')) #ord('a')=97
b'a'
Run Code Online (Sandbox Code Playgroud)
Python 3 并不像bytes它那样实习对象str。相反,它以与int.
这在幕后是非常不同的。不利的一面是,这意味着没有可操作的表(带有 API)。从好的方面来说,这意味着如果你能找到静态数组,你就可以修复它,就像处理整数一样,因为数组索引和字符串的字符值应该是相同的。
\n\n如果查看bytesobject.c,会发现该数组在顶部声明:
static PyBytesObject *characters[UCHAR_MAX + 1];\nRun Code Online (Sandbox Code Playgroud)\n\n\xe2\x80\xa6 然后,例如,在PyBytes_FromStringAndSize:
if (size == 1 && str != NULL &&\n (op = characters[*str & UCHAR_MAX]) != NULL)\n{\n#ifdef COUNT_ALLOCS\n one_strings++;\n#endif\n Py_INCREF(op);\n return (PyObject *)op;\n}\nRun Code Online (Sandbox Code Playgroud)\n\n请注意,该数组是static,因此无法从该文件外部访问它,并且它仍在对对象进行重新计数,因此调用者(即使是解释器中的内部内容,更不用说您的 C API 扩展)无法访问告诉他们有什么特别的事情发生。
因此,没有“正确”的方法来清理它。
\n\n但如果你想得到 hacky\xe2\x80\xa6
\n\n如果您有对任何单字符字节的引用,并且知道它应该是哪个字符,则可以到达数组的开头,然后清理整个内容。
\n\n除非你搞砸的比你想象的还要多,否则你可以构造一个单字符并减去它应该bytes是的字符。将返回应该是的对象,即使它碰巧实际上持有。我们怎么知道这一点?因为这正是您要解决的问题。PyBytes_FromStringAndSize("a", 1)\'a\'\'b\'
实际上,可能有一些方法可以破坏更糟糕的情况\xe2\x80\xa6,这些似乎都不太可能,但为了安全起见,让我们使用一个比 更不可能破坏的字符a,例如\\x80:
PyBytesObject *byte80 = (PyBytesObject *)PyBytes_FromStringAndSize("\\x80", 1);\nPyBytesObject *characters = byte80 - 0x80;\nRun Code Online (Sandbox Code Playgroud)\n\n唯一需要注意的是,如果您尝试从 Pythonctypes而不是 C 代码中执行此操作,则需要额外小心,1但由于您没有使用ctypes,所以我们不必担心这一点。
所以,现在我们有了一个指向 的指针characters,我们可以遍历它了。我们不能只是删除对象来“取消”它们,因为这会影响任何引用它们的人,并可能导致段错误。但我们不必这样做。表中的任何对象,我们都知道它应该是什么,\xe2\x80\x94characters[i]应该是一个单字符bytes,其一个字符是i。因此,只需将其设置回原样,并使用如下循环:
for (size_t char i=0; i!=UCHAR_MAX; i++) {\n if (characters[i]) {\n // do the same hacky stuff you did to break the string in the first place\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n这里的所有都是它的。
\n\n好吧,除了编译。2
\n\n幸运的是,在交互式解释器中,每个完整的顶级语句都是其自己的编译单元,因此 \xe2\x80\xa6 应该可以接受运行修复程序后键入的任何新行。
\n\n但是您导入的模块必须在字符串损坏的情况下进行编译?你可能搞砸了它的常数。除了强制重新编译和重新导入每个模块之外,我想不出一个好方法来清理这个问题。
\n\n1. 编译器可能b\'\\x80\'在到达 C 调用之前就将你的参数变成错误的东西。你会惊讶于所有你认为你正在传递 a 的地方,c_char_p而它实际上神奇地在 和 之间进行转换bytes。可能更好地使用POINTER(c_uint8).
2. 如果你用b\'a\'in 编译了一些代码,consts 数组应该有一个对 的引用b\'a\',这将得到修复。但是,由于bytes已知对编译器来说是不可变的,如果它知道b\'a\' == b\'b\',它实际上可能会存储指向单例的指针b\'b\',出于同样的原因123456 is 123456,在这种情况下修复b\'a\'可能实际上无法解决问题。
| 归档时间: |
|
| 查看次数: |
123 次 |
| 最近记录: |