无论如何要将包含编译代码而不是文件路径的字符串传递给 ctypes.CDLL?

Anyway to pass string containing compiled code instead of file path to ctypes.CDLL?

背景

我试图在 python 中调用 C 函数并发现了 ctypes 库(我对 C 和 python 的 ctypes 都很陌生),动机(无论多么愚蠢)是为了使 python 代码的速度与 c++ 相当或在竞争网站上足够接近。我已经编写了 C 代码并使用以下命令创建了一个共享库 cc -fPIC -shared -o lib.so test.c 并使用以下代码将其导入到带有 ctypes 的 python 中:

import ctypes


def main():
    clib = ctypes.CDLL('./lib.so')
    # ... irrelevant code below ...

main()

问题

问题是这段代码需要在我无法控制的环境中运行,即:

  1. 尝试创建文件时权限被拒绝
  2. 无法访问互联网

我已经尝试过的

  1. 我尝试将我的 lib.so 放在 github 上并下载它,但由于上述原因,此解决方案失败。
  2. 我试图在我的机器上 pickle clib 变量,希望我可以将序列化代码存储在程序本身的字符串中,然后在受限环境中解开。但这不起作用,因为 pickle 无法序列化 clib 对象。

我想到的最后一个解决方案是在程序中将lib.so的内容存储在一个字符串中,但是问题出现了:

#...
def main():
    lib_contents = b"contents of the lib.so file"
    clib = ctypes.CDLL(lib_contents) # passing the contents of the file instead of the file path
# ...

如何实现上述解决方案或任何替代解决方案?

编辑:建议的答案无效(我不知道我在这里做什么,所以很可能是我出了问题)。这是我 运行ning 从问题和接受的答案中复制的代码:

import ctypes
from ctypes import *

# Initialise ctypes prototype for mprotect().
# According to the manpage:
#     int mprotect(const void *addr, size_t len, int prot);
libc = CDLL("libc.so.6")
mprotect = libc.mprotect
mprotect.restype = c_int
mprotect.argtypes = [c_void_p, c_size_t, c_int]

# PROT_xxxx constants
# Output of gcc -E -dM -x c /usr/include/sys/mman.h | grep PROT_
#     #define PROT_NONE 0x0
#     #define PROT_READ 0x1
#     #define PROT_WRITE 0x2
#     #define PROT_EXEC 0x4
#     #define PROT_GROWSDOWN 0x01000000
#     #define PROT_GROWSUP 0x02000000
PROT_NONE = 0x0
PROT_READ = 0x1
PROT_WRITE = 0x2
PROT_EXEC = 0x4

# Machine code of an empty C function, generated with gcc
# Disassembly:
#     55        push   %ebp
#     89 e5     mov    %esp,%ebp
#     5d        pop    %ebp
#     c3        ret
with open("./libsum.so", "rb") as file:
    raw = file.read()
code = ctypes.create_string_buffer(raw)

# Get the address of the code
address = addressof(c_char_p(code))

# Get the start of the page containing the code and set the permissions
pagesize = 0x1000
pagestart = address & ~(pagesize - 1)
if mprotect(pagestart, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC):
    raise RuntimeError("Failed to set permissions using mprotect()")

# Generate ctypes function object from code
functype = CFUNCTYPE(None)
f = functype(address)

# Call the function
print("Calling f()")
f()

我收到以下错误:

Traceback (most recent call last):
  File "/home/user/main.py", line 36, in <module>
    address = addressof(c_char_p(code))
TypeError: bytes or integer address expected instead of c_char_Array_15697 instance
from ctypes import *

# int add(int x, int y)
# {
#   return (x+y);
# }
code = b'\x55\x48\x89\xe5\x89\x7d\xfc\x89\x75\xf8\x8b\x55\xfc\x8b\x45' \
       b'\xf8\x01\xd0\x5d\xc3'

copy = create_string_buffer(code)
address = addressof(copy)
aligned = address & ~0xfff
size = 0x2000
prototype = CFUNCTYPE(c_int, c_int, c_int)
add = prototype(address)
pythonapi.mprotect(c_void_p(aligned), size, 7)
print(add(20, 30))

说明:代码是用cc -shared -o libadd.so add.c编译为共享库,二进制代码是用objdump -S提取的。它被放置在一个二进制字符串(字节)对象中。使用 create_string_buffer() 创建的副本(适合检索其地址)被创建,其地址被检索并调用 mprotect() 与缓冲区分配区域对应的 2 个虚拟页面和保护值 7 ( == 读取 + 写入 + 执行)。此时该函数已准备就绪并被调用(add(20, 30))。打印出结果50。