众所周知,小bytes
- 对象由CPython自动“实现”(类似于intern - 字符串函数)。 更正: @abarnert的explained更像是整数池而不是实习字符串。
是否可以通过假设“实验性”第三方库来恢复被破坏的字节对象,或者是重启内核的唯一方法?
可以使用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`
或@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
显然,通过滥用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'
可以恢复/清除字节对象池,以便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'
答案 0 :(得分:3)
Python 3没有以bytes
的方式对象str
对象。相反,它使用int
保持静态数组。
这在封面上是非常不同的。在不利方面,这意味着没有表格(带有API)可以被操纵。从好的方面来说,这意味着如果你能找到静态数组,就可以像修改一样修复它,因为数组索引和字符串的字符值应该是相同的。
如果查看bytesobject.c
,数组将在顶部声明:
static PyBytesObject *characters[UCHAR_MAX + 1];
...然后,例如,在PyBytes_FromStringAndSize
:
if (size == 1 && str != NULL &&
(op = characters[*str & UCHAR_MAX]) != NULL)
{
#ifdef COUNT_ALLOCS
one_strings++;
#endif
Py_INCREF(op);
return (PyObject *)op;
}
请注意,该数组是static
,因此无法从此文件外部访问它,并且它仍在重新计算对象,因此调用者(甚至是解释器中的内部资源,很多少了你的C API扩展名)不能告诉你有什么特别的事情发生。
所以,没有&#34;正确&#34;清理它的方法。
但如果你想得到hacky ......
如果您对任何单字节字节有引用,并且您知道它应该是哪个字符,那么您可以到达数组的开头然后清理整个字符。
除非你搞得比你想象的还要多,否则你可以构造一个字符bytes
并减去所谓的字符。 PyBytes_FromStringAndSize("a", 1)
会将假设的的对象返回'a'
,即使实际暂挂'b'
也是如此}。我们怎么知道的?因为这正是您尝试解决的问题。
实际上,你可能有办法让事情变得更糟......这一切似乎都不太可能,但为了安全起见,让我们使用一个你不太可能破坏的角色而不是{{1} },如a
:
\x80
唯一的另一个警告是,如果你试图用Python PyBytesObject *byte80 = (PyBytesObject *)PyBytes_FromStringAndSize("\x80", 1);
PyBytesObject *characters = byte80 - 0x80;
而不是C代码来做这件事,那么需要额外注意, 1 但是因为你&#39;不要使用ctypes
,请不要担心。
所以,现在我们有一个指向ctypes
的指针,我们可以走了它。我们不能将对象删除到&#34; unintern&#34;他们,因为这会影响任何提及其中任何一个的人,并可能导致段错误。但我们不必这样做。表格中的任何对象,我们知道它应该是什么 - characters
应该是一个字符characters[i]
,其中一个字符是bytes
。所以只需将它设置回来,使用类似这样的循环:
i
这就是它的全部内容。
好吧,除了编译。 2
幸运的是,在交互式解释器中,每个完整的顶级语句都是它自己的编译单元,所以......运行修复后你输入的任何新行都应该没问题。
但是你输入的一个模块,必须要编译,而你有破碎的字符串?你可能搞砸了它的常数。除了强行重新编译和重新导入每个模块之外,我无法想出一个清理它的好方法。
<子> 1。在进入C调用之前,编译器可能会将for (size_t char i=0; i!=UCHAR_MAX; i++) {
if (characters[i]) {
// do the same hacky stuff you did to break the string in the first place
}
}
参数转换为错误的参数。你会认为自己在b'\x80'
周围经过的所有地方都会感到惊讶,而且它实际上会被c_char_p
神奇地转换为。{1}}。可能更好地使用bytes
。
<子> 2。如果您在其中编译了一些代码POINTER(c_uint8)
,则consts数组应该引用b'a'
,这将得到修复。但是,由于b'a'
已知为编译器不可变,如果它知道bytes
,它实际上可能存储指向b'a' == b'b'
单例的指针,原因与{{1}相同。是的,在这种情况下,修复b'b'
实际上可能无法解决问题。
答案 1 :(得分:2)
我遵循了@abarnert的精彩解释,这是我在Cython中实现他的想法。
需要考虑的事项:
PyBytes_FromStringAndSize
和PyBytes_FromString
构建的字节对象才使用内部池,因此请务必使用它们。这导致以下实施:
%%cython
from libc.limits cimport UCHAR_MAX
from cpython.bytes cimport PyBytes_FromStringAndSize
cdef replace_first_byte(bytes obj, unsigned char new_value):
cdef const unsigned char[:] safe=obj
cdef unsigned char *unsafe=<unsigned char *> &safe[0]
unsafe[0]=new_value
def restore_bytes_pool():
cdef char[1] ch
#create all possible bytes-objects b`\x00` to b`x255`:
for i in range(UCHAR_MAX+1):
ch[0]=<unsigned char>(i)
obj=PyBytes_FromStringAndSize(ch, 1) #use it so the pool is used
replace_first_byte(obj,i)
略有差异(在我看来,与原始提案有利):
现在:
>>> do_bad_things()
>>> print(b'a')
b'b'
>>> restore_bytes_pool()
>>> print(b'a')
b'a'
出于测试目的,有功能损坏(几乎)池中的所有对象:
def corrupt_bytes_pool():
cdef char[1] ch
for i in range(UCHAR_MAX+1):
ch[0]=<unsigned char>(i)
obj=PyBytes_FromStringAndSize(ch, 1)
replace_first_byte(obj,98) #sets all to b'b'