Linux:是不是可以在进程之间共享代码?
Posted
技术标签:
【中文标题】Linux:是不是可以在进程之间共享代码?【英文标题】:Linux: is it possible to share code between processes?Linux:是否可以在进程之间共享代码? 【发布时间】:2013-02-27 14:12:04 【问题描述】:我想知道一个linux进程是否有可能调用位于另一个进程内存中的代码?
假设我们在进程 A 中有一个函数 f(),我们希望进程 B 调用它。我想到的是使用带有 MAP_SHARED 和 PROT_EXEC 标志的 mmap 来映射包含函数代码的内存并将指针传递给 B,假设 f() 不会从 A 二进制文件调用任何其他函数。它会起作用吗?如果是,那么如何确定 f() 在内存中的大小?
=== 编辑 ===
我知道,共享库可以做到这一点,但我想知道是否可以在进程之间动态共享代码。
【问题讨论】:
不是直接的。使用动态库或RPC。 这正是图书馆的用途。根据您要完成的任务,IPC 也可能完成。 你要分享的代码是编译器生成的,还是你的程序生成的? 您如何确定f
没有调用任何其他函数? (即使您的源代码没有任何明显的调用,编译器也会插入一些调用)。
【参考方案1】:
是的,您可以这样做,但第一个进程必须首先通过mmap
和内存映射文件或使用shm_open
创建的共享区域创建共享内存。
如果您要共享编译后的代码,那么这就是 shared 库的创建为。您可以以普通方式链接它们,共享将自动发生,或者您可以使用 dlopen
手动加载它们(例如插件)。
更新:
由于代码是由编译器生成的,因此您需要担心重定位。编译器不会生成可以在任何地方工作的代码。它预计.data
部分位于某个位置,并且.bss
部分已被归零。需要填充 GOT。必须调用任何静态构造函数。
简而言之,你想要的可能是dlopen
。该系统允许您像打开文件一样打开共享库,然后按名称提取函数指针。 dlopen
s 库的每个程序都将共享代码段,从而节省内存,但每个程序都有自己的数据段副本,因此它们不会相互干扰。
请注意,您需要使用 -fPIC
编译您的库代码,否则您也不会获得任何代码共享(实际上,许多架构的链接器和动态加载器可能不支持非 PIC 库)。
【讨论】:
我实际上从未尝试过从 mmaped 内存运行代码。它真的有效吗?权限呢? @KBart:只要设置了 PROT_EXEC 权限,它就应该可以工作。当然,并非所有架构都支持该标志,但在大多数情况下,您无法停止它的工作。 很好。你能举个小例子吗?无论如何,我相信这正是这个问题的意义所在。【参考方案2】:标准做法是将f()
的代码放在共享库libfoo.so
中。然后您可以链接到该库(例如,通过使用gcc -Wall a.c -lfoo -o a.bin
构建程序A),或者使用dlopen(3) 动态加载它(例如在程序B 中),然后检索f
的地址使用dlsym
。
当你编译一个你想要的共享库时:
将每个源文件foo1.c
和gcc -Wall -fPIC -c foo1.c -o foo1.pic.o
编译成position independent code,同样将foo2.c
编译成foo2.pic.o
用gcc -Wall -shared foo*.pic.o -o libfoo.so
将它们全部链接到libfoo.so
;请注意,您可以将其他共享库链接到 lbfoo.so
(例如,通过将 -lm
附加到链接命令)
另请参阅Program Library Howto。
你可以通过mmap
-ing 其他一些/proc/1234/mem
来玩疯狂的把戏,但这根本不合理。使用共享库。
PS。您可以dlopen
大量(数十万)共享对象lib*.so
files;你可能想dlclose
them(但实际上你不必这样做)。
【讨论】:
【参考方案3】:这样做是可能的,但这正是共享库的用途。
另外,请注意,您需要检查两个进程的共享内存地址是否相同,否则任何“绝对”引用(即指向共享代码中某些内容的指针)。与共享库一样,代码的位数必须相同,并且与所有共享内存一样,如果您修改任何共享内存,您需要确保不会“搞砸”其他进程记忆。
确定函数大小的范围从“困难”到“几乎不可能”,具体取决于生成的实际代码以及您可用的信息级别。调试符号将具有函数的大小,但请注意,我已经看到编译器生成代码,其中两个函数共享相同的“返回”代码段(即,编译器生成跳转到具有相同代码位的另一个函数返回结果,因为它节省了几个字节的代码,并且无论如何已经存在跳转[例如,编译器必须跳转的 if/else])。
【讨论】:
【参考方案4】: 不是直接 这就是共享库的用途 搬迁哦不!总之……
这是这种能力的疯狂、不合理、不好、纯粹的学术演示。这对我来说很有趣,我希望它对你来说很有趣。
概述
程序A
将使用shm_open
创建一个共享内存对象,并使用mmap
将其映射到它的内存空间。然后它将一些代码从A
中定义的函数复制到共享内存。然后程序B
会打开共享内存,执行函数,顺便对代码做一个非常简单的修改。然后A
将执行代码以证明更改已生效。
同样,这不是关于如何解决问题的建议,而是学术演示。
// A.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
int foo(int y)
int x = 14;
return x + y;
int main(int argc, char *argv[])
const size_t mem_size = 0x1000;
// create shared memory objects
int shared_fd = shm_open("foobar2", O_RDWR | O_CREAT, 0777);
ftruncate(shared_fd, mem_size);
void *shared_mem =
mmap(NULL, mem_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED, shared_fd, 0);
// copy function to shared memory
const size_t fn_size = 24;
memcpy(shared_mem, &foo, fn_size);
// wait
getc(stdin);
// execute the shared function
int(*shared_foo)(int) = shared_mem;
printf("shared_foo(3) = %d\n", shared_foo(3));
// clean up
shm_unlink("foobar2");
注意在对mmap
的调用中使用PROT_READ | PROT_WRITE | PROT_EXEC
。这个程序是用
gcc A.c -lrt -o A
常量fn_size
是通过查看objdump -dj .text A
的输出确定的
...
000000000000088a <foo>:
88a: 55 push %rbp
88b: 48 89 e5 mov %rsp,%rbp
88e: 89 7d ec mov %edi,-0x14(%rbp)
891: c7 45 fc 0e 00 00 00 movl $0xe,-0x4(%rbp)
898: 8b 55 fc mov -0x4(%rbp),%edx
89b: 8b 45 ec mov -0x14(%rbp),%eax
89e: 01 d0 add %edx,%eax
8a0: 5d pop %rbp
8a1: c3 retq
...
我认为那是 24
字节,我不知道。我想我可以放任何比这更大的东西,它也会做同样的事情。任何更短的东西,我可能会从处理器那里得到一个例外。另外,请注意foo
中的x
的值(14
,即(显然)LE 中的0e 00 00 00
)位于foo + 10
。这将是程序 B
中的常量 x_offset
。
// B.c
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
const int x_offset = 10;
int main(int argc, char *argv[])
// create shared memory objects
int shared_fd = shm_open("foobar2", O_RDWR | O_CREAT, 0777);
void *shared_mem = mmap(NULL, 0x1000, PROT_EXEC | PROT_WRITE, MAP_SHARED, shared_fd, 0);
int (*shared_foo)(int) = shared_mem;
int z = shared_foo(13);
printf("result: %d\n", z);
int *x_p = (int*)((char*)shared_mem + x_offset);
*x_p = 100;
shm_unlink("foobar");
无论如何我首先运行A
,然后运行B
。 B
的输出为:
result: 27
然后我回到A
并推送enter
,然后我得到:
shared_foo(3) = 103
对我来说已经足够了。
/dev/shm/foobar2
为了完全消除这一切的神秘感,在运行A
之后,您可以执行类似的操作
xxd /dev/shm/foobar2 | vim -
然后,像以前一样编辑该常量0e 00 00 00
,然后使用“ol”保存文件
:w !xxd -r > /dev/shm/foobar2
并将enter
推入A
并看到与上述类似的结果。
【讨论】:
以上是关于Linux:是不是可以在进程之间共享代码?的主要内容,如果未能解决你的问题,请参考以下文章
Windows 进程和 WSL Linux 进程之间的共享内存