clinuxshared-librariesshared-objects

Shared object code does not seem to be shared among processes on Linux


The code (aka .text section) of a shared object (.so file) is normally being shared among processes. You can read it here.

I have written a little example where things seem to behave different.

In short: The program proofs that changes made to the code section of a shared object do not affect other instances of the program. Thus the code section of the shared object is not being shared among processes.

Long explanation: The code consists of a program (main.c) which loads a .so file (mylib.c).

The library consists of one single function which returns an integer. This integer is stored in the code section among with the machine instructions. If you don't belive the integer is stored in the code section you can run objdump -d libmy.so.

The program loads the library and then modifies the integer in the code section of the shared object. Before that the program runs mprotect() to avoid a segmentation fault. The value is being changed to the current timestamp.

Now I run the program twice with a delay of 2 seconds so every instance writes its own value to the .so code section. By surprise the value of the first instance is not being overwritten by the value of the second instance.

How is that possible?

I've tested this program on x64 only. Simply save the files and run the bash script.

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);
    }
}

run.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

Solution

  • A shared object is mapped with the MAP_PRIVATE flag. This means that the memory is initially shared among all the users, but with the "copy on write" flag set for each page. As a result, if a process modifies the memory they get their own copy, it doesn't affect the other processes that have linked with the same object file.

    It's obvious that this is necessary for the data section, since each process is expected to make local changes to this. But it's also done for the text section, even though it's read-only by default. As you discovered, you can override the read-only flag, but since it's still COW you get a private copy when you write to it.