繁体   English   中英

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

[英]Linux: is it possible to share code between processes?

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

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

=== 编辑 ===

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

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

如果您正在共享编译代码,然后什么共享被创造。 您可以以普通方式链接它们,共享将自动发生,或者您可以使用dlopen (例如,对于插件)手动加载它们。


更新:

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

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

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

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

当您编译共享库时,您希望:

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

另请参阅程序库方法

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

附注。 你可以dlopen大很多共享对象(数十万) lib*.so文件; 您可能想要dlclose它们(但实际上您不必这样做)。

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

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

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

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

不好了! 无论如何...

这是对这种能力的疯狂、不合理、不好、纯粹的学术展示。 这对我来说很有趣,我希望对你来说也很有趣。

概述

程序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 PROT_READ | PROT_WRITE | PROT_EXEC PROT_READ | PROT_WRITE | PROT_EXEC在对mmap的调用中。 这个程序是用

gcc A.c -lrt -o A

常量fn_size是通过查看objdump -dj .text A的输出确定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个字节,我不知道。 我想我可以放任何比这更大的东西,它会做同样的事情。 任何更短的东西,我可能会从处理器那里得到一个例外。 另外,请注意来自foox值( 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

并在Aenter并查看与上述类似的结果。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM