Linux:可以在进程之间共享代码吗?

时间:2013-02-27 14:12:04

标签: c linux memory ipc mmap

我想知道linux进程是否可以调用位于另一个进程内存中的代码?

假设我们在进程A中有一个函数f(),我们希望进程B调用它。我想到的是使用带有MAP_SHARED和PROT_EXEC标志的mmap映射包含函数代码的内存并将指针传递给B,假设f()不会调用A二进制中的任何其他函数。它会工作吗?如果是,那么如何确定内存中f()的大小?

===编辑===

我知道,共享库会做到这一点,但我想知道是否可以在进程之间动态共享代码。

4 个答案:

答案 0 :(得分:5)

是的,您可以这样做,但第一个进程必须首先通过mmap和内存映射文件或使用shm_open创建的共享区域创建共享内存。

如果您正在共享已编译的代码,那么共享库是为创建的。您可以通过普通方式链接它们,并自动进行共享,也可以使用dlopen手动加载它们(例如插件)。


更新

由于代码已由编译器生成,因此您将需要担心重定位。编译器不会生成只在任何地方工作的代码。它希望.data部分位于某个位置,并且.bss部分已归零。需要填充GOT。必须调用任何静态构造函数。

简而言之,您想要的可能是dlopen。此系统允许您打开共享库,就像它是一个文件,然后按名称提取函数指针。 dlopen库的每个程序将共享代码段,从而节省内存,但每个程序都有自己的数据部分副本,因此它们不会相互干扰。

请注意,您需要使用-fPIC编译库代码,否则您将无法获得任何代码共享(实际上,许多架构的链接器和动态加载器可能不支持非库的库PIC无论如何)。

答案 1 :(得分:4)

标准方法是将f()的代码放在共享库libfoo.so中。然后你可以链接到那个库(例如通过用gcc -Wall a.c -lfoo -o a.bin构建程序 A ),或者使用{{3在动态 B 中动态加载它)。然后使用f检索dlsym的地址。

编译共享库时,您需要:

  • foo1.c的每个源文件gcc -Wall -fPIC -c foo1.c -o foo1.pic.o编译为dlopen(3),同样将foo2.c编译为foo2.pic.o
  • 使用libfoo.so将所有这些链接到gcc -Wall -shared foo*.pic.o -o libfoo.so;请注意,您可以将其他共享库链接到lbfoo.so(例如,通过将-lm附加到链接命令)

另请参阅position independent code

你可以通过mmap - 其他一些/proc/1234/mem来玩疯狂的伎俩,但这根本不合理。使用共享库。

PS。你可以dlopen一大堆(数十万)共享对象lib*.so文件;你可能想要dlclose他们(但实际上你不需要)。

答案 2 :(得分:2)

有可能这样做,但这正是共享库的用途。

另外,请注意,您需要检查两个进程的共享内存地址是否相同,否则任何“绝对”引用(即指向共享代码中某些内容的指针)。和共享库一样,代码的位数必须相同,并且与所有共享内存一样,如果修改任何共享内容,则需要确保不会为其他进程“搞乱”记忆。

确定函数的大小范围从“硬”到“几乎不可能”,具体取决于生成的实际代码以及您可用的信息级别。调试符号将具有函数的大小,但要注意我已经看到编译器生成代码,其中两个函数共享相同的“返回”代码段(也就是说,编译器生成跳转到另一个具有相同代码位的函数返回结果,因为它保存了几个字节的代码,并且无论如何都已经有了跳转[例如,编译器必须跳转的if / else]。)

答案 3 :(得分:0)

  • 不直接
  • 这就是共享库的作用
  • 搬迁

哦,不!无论如何...

这是这种能力的疯狂,不合理,不好,纯粹的学术证明。对我来说很有趣,我希望对您来说很有趣。

概述

程序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");
}

请注意在PROT_READ | PROT_WRITE | PROT_EXEC的调用中使用mmap。该程序用

编译
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个字节。我想我可以放任何更大的东西,它会做同样的事情。短一点的东西,我可能会从处理器中得到一个例外。另外,请注意,x中的foo的值(14,在LE中(显然)是0e 00 00 00)位于foo + 10。这将是程序x_offset中的常量B

// 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,然后运行BB的输出是:

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,将看到与上述类似的结果。