Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Segmentation fault appears when I use shared memory only from statically build program

When I built a program with --static option and it calls shm_open() function I get Segmentation fault. Without -static option all works like a charm.

Does anybody know why?

Below I have cited a debug info and a part of truncated source code from a big project.

You can comment/uncomment

#STATIC = -static

string from Makefile to reproduce the bug.

$ gdb --args ./debug/example sample017

GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Reading symbols from ./debug/example...done.
(gdb) run
Starting program: ./example sample017

Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
(gdb) backtrace
#0  0x0000000000000000 in ?? ()
#1  0x000000000049a2e3 in __shm_directory (len=0x7fffffffdca8) at ../sysdeps/unix/sysv/linux/shm-directory.c:124
#2  0x0000000000499ff3 in shm_open ()
#3  0x0000000000499d55 in read_shm (memory=0x6d1be0, share_name=0x6d1d20 "sample017") at main.c:51
#4  0x0000000000499efe in read_memory (memory=0x6d1be0, argc=0x7fffffffde0c, argv=0x7fffffffdf68) at main.c:98
#5  0x0000000000499f70 in main (argc=2, argv=0x7fffffffdf68) at main.c:112

Strace for statically linked

$strace ./debug/example sample017

execve("./debug/example", ["./debug/example", "sample017"], [/* 64 vars */]) = 0
uname({sysname="Linux", nodename="Lubuntu", ...}) = 0
brk(NULL)                               = 0xc5e000
brk(0xc5f1c0)                           = 0xc5f1c0
arch_prctl(ARCH_SET_FS, 0xc5e880)       = 0
readlink("/proc/self/exe", "/home/52034/111/debug/example", 4096) = 29
brk(0xc801c0)                           = 0xc801c0
brk(0xc81000)                           = 0xc81000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0} ---
+++ killed by SIGSEGV +++
Segmentation fault

Strace for dynamically linked:

$ strace ./debug/example sample017

execve("./debug/example", ["./debug/example", "sample017"], [/* 64 vars */]) = 0
brk(NULL)                               = 0x740000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f78fa50e000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=135773, ...}) = 0
mmap(NULL, 135773, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f78fa4ec000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\t\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0
mmap(NULL, 3971488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f78f9f21000
mprotect(0x7f78fa0e1000, 2097152, PROT_NONE) = 0
mmap(0x7f78fa2e1000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c0000) = 0x7f78fa2e1000
mmap(0x7f78fa2e7000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f78fa2e7000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/librt.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0!\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=31712, ...}) = 0
mmap(NULL, 2128832, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f78f9d19000
mprotect(0x7f78f9d20000, 2093056, PROT_NONE) = 0
mmap(0x7f78f9f1f000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x6000) = 0x7f78f9f1f000
close(3)                                = 0
... end etc...

Compiler:

$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 5.4.0-6ubuntu1~16.04.5' --with-bugurl=file:///usr/share/doc/gcc-5/README.Bugs --enable-languages=c,ada,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-5 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-5-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-5-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-5-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.5) 

Linux:

$ cat /etc/issue
Ubuntu 16.04.3 LTS \n \l

$cat main.c

#define _GNU_SOURCE

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>

// String library
#include <string.h>

// Shared memory
#include <sys/mman.h>
#include <sys/stat.h> /* For mode constants */
#include <fcntl.h>    /* For O_* constants */

typedef struct {
    size_t length;

    int fd;

    float *array;

    #define MAX_NAME_LENGTH 255
    char name[MAX_NAME_LENGTH];

} Memory;

static Memory *new_memory(void)
{
    Memory *structure = (Memory*)malloc(sizeof(Memory));
    if(structure == NULL){
        exit(EXIT_FAILURE);
    }
    memset(structure, 0, sizeof(Memory));

    return structure;
}

static void detach_memory(Memory *memory)
{
    munmap(memory->array,(memory->length)*sizeof(float));
    free(memory);
}

static bool read_shm(
    Memory *memory,
    char *share_name
)
{
    if ((memory->fd = shm_open(share_name, O_RDONLY|O_EXCL, 0400)) == -1){
        return(false);
    }

    memcpy(memory->name,share_name,strlen(share_name)+1);

    struct stat info;

    if (fstat(memory->fd, &info) == -1 || errno == EBADF){
        return(false);
    }

    memory->length = (size_t)info.st_size / sizeof(float);

    float *array = mmap(0, (size_t)info.st_size, PROT_READ, MAP_SHARED,memory->fd, 0);
    if (array == (float*)-1){
        exit(EXIT_FAILURE);
    }

    if (memory->length < 2){
        exit(EXIT_FAILURE);
    }

    memory->array = array;

    return(true);
}

void read_memory(
    Memory *memory,
    int *argc,
    char **argv
)
{
    if (*argc == 1){
        exit(EXIT_FAILURE);
    }

    char *path = strdup(argv[1]);
    const char * const file = basename(path);

    char *share_name = strdup(file);
    char *ext = strchr(share_name, '.');
    if(ext){
        *ext = '\0';
    }

    if(!(read_shm(memory,share_name))){
        free(path);
        free(share_name);
        exit(EXIT_FAILURE);
    }

    free(path);
    free(share_name);
}

int main(int argc, char **argv)
{
    Memory *memory = new_memory();

    read_memory(memory,&argc,argv);

    for(size_t i = 0; i < memory->length;i++){
        printf("memory: %.5f\n",memory->array[i]);
    }

    detach_memory(memory);

    return(EXIT_SUCCESS);
}

$cat Makefile

CC ?= cc

CFLAGS += -pipe -std=c11
CFLAGS += -fbuiltin
LDFLAGS += -lm -lrt -lpthread
EXE = example
ARGUMENTS ?=
#STATIC = -static
STRIP = -s
WFLAGS += -Wall -Wextra -Wpedantic -Wshadow
WFLAGS += -Wconversion -Wsign-conversion -Winit-self -Wunreachable-code -Wformat-y2k
WFLAGS += -Wformat-nonliteral -Wformat-security -Wmissing-include-dirs
WFLAGS += -Wswitch-default -Wtrigraphs -Wstrict-overflow=5
WFLAGS += -Wfloat-equal -Wundef -Wshadow
WFLAGS += -Wbad-function-cast -Wcast-qual -Wcast-align
WFLAGS += -Wwrite-strings
WFLAGS += -Winline
ifneq ($(CC), clang)
WFLAGS += -Wlogical-op
CFLAGS += -finline-functions
CFLAGS += -flto
endif

MAKEFLAGS += --no-print-directory
CONFIG += ordered

#
# Project files
#
SRCS = $(wildcard *.c)
# Exclude a file
OBJS = $(SRCS:.c=.o)

#
# Debug build settings
#
DBGDIR = debug
DBGEXE = $(DBGDIR)/$(EXE)
DBGOBJS = $(addprefix $(DBGDIR)/, $(OBJS))
DBGLIBPATH = ../../logger/lib/release
DBGCFLAGS += -g -ggdb -ggdb1 -ggdb2 -ggdb3 -O0 -DDEBUG
DBGLDFLAGS += -lm -lc

.PHONY: all clean debug prep release remake clang openmp one liblogger

# Default build
all: debug

#
# Debug rules
#
debug: liblogger $(DBGEXE)

$(DBGEXE): $(DBGOBJS)
    $(CC) $(CFLAGS) $(DBGCFLAGS) -L$(DBGLIBPATH) $(WFLAGS) $(STATIC) $(DBGLDFLAGS) -o $(DBGEXE) $^ $(LDFLAGS)
    @echo "$(DBGEXE) linked."

-include $(DBGDEP)

$(DBGDIR)/%.o: %.c
    @mkdir -p $(DBGDIR)
    @$(CC) -MM $(CFLAGS) $(DBGCFLAGS) $(WFLAGS) $< | sed '1s/^/$$\(DBGDIR\)\//' > $(@D)/$(*F).d
    $(CC) -c $(CFLAGS) $(DBGCFLAGS) $(WFLAGS) -o $@ $<
    @echo $<" compiled."

clean:
    @rm -rf *.out.* doc $(DBGDEP) $(DBGEXE) $(DBGOBJS) $(DBGDIR)/*.d
    @test -d $(DBGDIR) && rm -d $(DBGDIR) || true

By the way, is is works well on the older platform:

Linux:

$cat /etc/issue
Ubuntu 13.10 \n \l

Compiler:

$gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.8/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.8.1-10ubuntu9' --with-bugurl=file:///usr/share/doc/gcc-4.8/README.Bugs --enable-languages=c,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.8 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.8 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-4.8-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.8.1 (Ubuntu/Linaro 4.8.1-10ubuntu9) 
like image 888
Dennis V Avatar asked Jan 29 '23 00:01

Dennis V


2 Answers

You've uncovered a bug in how the static librt.a and libpthread.a interact. (librt.a:shm_open() calls it, and it is defined in libpthread.a. Their dynamic versions link it just fine.)

Specifically, libpthread.a does implement const char *__shm_directory(size_t *len);, but for whatever reason, it gets mislinked somehow, causing a segfault. I suspect some kind of weak symbol shadowing, but haven't investigated it much further.

The fix is to implement the function yourself. It is a simple function, that returns the path to the directory, including a final slash, where the shared memory files are to be created in. On Linux systems, this should always be a tmpfs mount at /dev/shm/.

I would suggest creating a new C source file, maybe shm_open_fix.c:

#include <stdlib.h>
#include <string.h>

/* This avoids a segfault when code using shm_open()
   is compiled statically. (For some reason, compiling
   the code statically causes the __shm_directory()
   function calls in librt.a to not reach the implementation
   in libpthread.a. Implementing the function ourselves
   fixes this issue.)
*/

#ifndef  SHM_MOUNT
#define  SHM_MOUNT "/dev/shm/"
#endif
static const char  shm_mount[] = SHM_MOUNT;

const char *__shm_directory(size_t *len)
{
    if (len)
        *len = strlen(shm_mount);
    return shm_mount;
}

and compile and link it into your final binary. This should take care of the segfault. Also, the resulting binary no longer depends on libpthread.a at all, unless you use the pthread functions elsewhere in your code.

like image 136
Nominal Animal Avatar answered Jan 31 '23 14:01

Nominal Animal


I have the same problem with Ubuntu 16.04 and GCC 5.4

I statically link an executable and segfaults at the same exact point, inside __shm_directory.

I've looked into it, and it seems that the function __pthread_once, defined as a weak symbol, is missing from the linked executable. Thus, a call to address 0x0 is made.

A colleage has compiled it with Ubuntu 14.04, with GCC 4.8.4 and it works well.

This looks certainly like a bug in either GCC or Glibc

EDIT:

Adding -Wl,--whole-archive -lpthread -Wl,--no-whole-archive to the compiler comand line does the trick. It forces the linker to include all the symbols from libpthread. Otherwise, the linker leaves the weak reference, which is the expected behaviour for dynamic linking (where symbols are resolved at runtime), but its invalid for static libraries. In any case, despite the workaround, I still consider this behaviour a bug.

like image 25
I. Martin Avatar answered Jan 31 '23 13:01

I. Martin