Статическая связь с glibc без вызова основной

Я создал простой мир привет, используя NASM, который вызывает printf и _exit из libc, но не использует main.

extern printf
extern _exit

section .data
    hello:     db 'Hello world!',10

section .text
    global _start   
_start:
    xor eax, eax
    mov edi, hello
    call printf
    mov rax, 0    
    jmp _exit

Я создаю объектный файл, подобный этому

nasm -felf64 hello.asm

Затем я могу связать его, используя динамическую связь с glibc, как это

ld hello.o -dynamic-linker /lib64/ld-linux-x86-64.so.2 -lc -melf_x86_64

Это работает правильно, без ошибок. Но теперь я хочу сделать это статически. Я делаю

ln -s `gcc -print-file-name=libc.a`
ln -s `gcc -print-file-name=libgcc_eh.a`
ld hello.o -static libc.a libgcc_eh.a libc.a -melf_x86_64

Это ссылки, но когда я запускаю код, я получаю ошибку сегментации. Используя gdb, я вижу, что он дает

Program received signal SIGSEGV, Segmentation fault.
0x0000000000401004 in vfprintf ()

Если я напишу простой мир привет в C и скомпилирую со статическими в сценариях, то, видимо, можно связать статически с glibc в моей системе. Как использовать статическую связь с glibc с моим кодом сборки?

Если я ссылаюсь на альтернативу glibc, например musl-libc, он отлично работает

ld hello.o -static /usr/local/musl/lib/libc.a -melf_x86_64

Я использую Ubuntu 14.04, eglibc 2.19 и GCC 4.9.1

Ответ 1

Glibc имеет огромную последовательность инициализации, потому что это сделано с сильным намерением работать в многопоточных системах. Также GLIBC правильно обрабатывает некоторые расширения GNU, такие как атрибуты конструктора. При запуске он много кэширует внутри TLS, включая информацию о локали, инициализирует объекты синхронизации и так далее.

Точная проблема с вашим vprintf - это неинициализированный доступ к локали.

Когда вы динамически связываетесь с ней, вся эта работа выполняется при загрузке, и все работает.

Статически связанный glibc требует, чтобы __libc_init_first вызывался для инициализации всего, что ему нужно. Перед этим вызовом вам понадобится __dl_tls_setup, чтобы правильно настроить TLS, и после этого вызова вам понадобится __libc_csu_init, чтобы правильно вызвать все глобальные конструкторы.

Все эти вещи сильно зависят от версии и практически не документируются. Строго говоря, нет надежного способа связать статически с glibc, пропустить или изменить его нормальную последовательность _start.

С другой стороны, встроенные ориентированные библиотеки, такие как musl или newlib, не настолько ограничивают инициализацию и многопоточность и локали.