共享对象(.so 文件)的代码(又名 .text 部分)通常在进程之间共享。您可以在这里阅读。
我写了一个小例子,其中事情似乎表现不同。
简而言之:程序证明对共享对象的代码部分所做的更改不会影响程序的其他实例。因此,共享对象的代码部分不会在进程之间共享。
长解释:该代码由一个加载 .so 文件(mylib.c)的程序(main.c)组成。
该库由一个返回整数的函数组成。该整数与机器指令一起存储在代码段中。如果您不相信整数存储在代码部分中,您可以运行
objdump -d libmy.so
。
程序加载库,然后修改共享对象代码部分中的整数。在此之前,程序运行
mprotect()
以避免分段错误。该值正在更改为当前时间戳。
现在我运行该程序两次,延迟 2 秒,因此每个实例都将自己的值写入 .so 代码部分。令人惊讶的是,第一个实例的值没有被第二个实例的值覆盖。
这怎么可能?
我仅在 x64 上测试了该程序。只需保存文件并运行 bash 脚本即可。
libmy.c:
int getval()
{
return 123;
}
main.c:
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/mman.h>
int getval();
void unprotect(uint64_t addr)
{
uint64_t pagesize = sysconf(_SC_PAGE_SIZE);
addr -= addr % pagesize;
if (mprotect((void*)addr, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC) != 0)
{
printf("mprotect failed\n");
exit(1);
}
}
void main()
{
for (int i = 0; i < 30; i++)
{
int* ip = (int*)((char*)&getval + i);
if (*ip == 123)
{
printf("found value at offset %i\n", i);
unprotect((uint64_t)ip);
unprotect((uint64_t)ip + 3);
*ip = (int)time(NULL);
printf("value changed successfully\n");
break;
}
}
while (1)
{
printf("getval() returns %i\n", getval());
sleep(1);
}
}
运行.bash:
#!/bin/bash
set -e
gcc -shared -fPIC -o libmy.so libmy.c
gcc main.c -L. -lmy -Wl,-rpath . -o bin
./bin &
sleep 2
./bin
共享对象使用
MAP_PRIVATE
标志进行映射。这意味着内存最初在所有用户之间共享,但为每个页面设置了“写入时复制”标志。因此,如果一个进程修改了它们自己的副本的内存,则不会影响与同一目标文件链接的其他进程。
很明显,这对于数据部分是必要的,因为每个进程都需要对此进行本地更改。但它也适用于文本部分,尽管默认情况下它是只读的。正如您所发现的,您可以覆盖只读标志,但由于它仍然是 COW,所以当您写入它时,您会得到一个私有副本。