Как общий объект может содержать локальное хранилище статического потока?

Фон

Я столкнулся с проблемой, которая нарушает мою концептуальную модель позиционно-независимого кода и локального хранилища потоков. О проблеме, вызвавшей это, можно узнать в этом сообщении StackOverflow. ; У меня есть двоичный файл, который, в свою очередь, dlopen является общим объектом. Открытие общего объекта вызывает ошибку с указанием dlopen: cannot load any more object with static TLS.

Насколько я понимаю, модель initial-exec — это то, что называется статическим TLS, и что это часто используется по умолчанию, когда не создается позиционно-независимый код. Когда кто-то создает позиционно-независимый код, по умолчанию обычно используется что-то другое, например модель global-dynamic, которую использует GCC. Я полагал, что причина этого в том, что initial-exec не может работать в общем объекте. Ответ на другой пост StackOverflow поддержал это мнение, заявив:

Связывание кода, отличного от fPIC, с общей библиотекой невозможно на x86_64, но разрешено на ix86 (и приводит ко многим тонким проблемам, таким как эта).

Учитывая, что я на машине x86_64, это привело к некоторой путанице. Затем я наткнулся на еще один вопрос StackOverflow, в котором содержится ответ на создание общего объекта с использованием статической модели TLS.

Увидев это, я решил вернуться к своему проблемному двоичному файлу и рекурсивно просмотреть зависимости для использования статической модели TLS, просмотрев вывод readelf -d в соответствии с ответом на этот вопрос. К моему удивлению, я нахожу несколько библиотек. К моему ужасу, это не библиотеки, созданные приложением.

Вот вывод readelf -d для одного из них:

/lib64/libpthread.so.0

Dynamic section at offset 0x17d90 contains 29 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [ld-linux-x86-64.so.2]
 0x000000000000000e (SONAME)             Library soname: [libpthread.so.0]
 0x000000000000000c (INIT)               0x38c46052d0
 0x000000000000000d (FINI)               0x38c4611120
 0x0000000000000004 (HASH)               0x38c4615e90
 0x000000006ffffef5 (GNU_HASH)           0x38c4600280
 0x0000000000000005 (STRTAB)             0x38c4602dd8
 0x0000000000000006 (SYMTAB)             0x38c4600f00
 0x000000000000000a (STRSZ)              4918 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000003 (PLTGOT)             0x38c4817fe8
 0x0000000000000002 (PLTRELSZ)           1680 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0x38c4604c38
 0x0000000000000007 (RELA)               0x38c4604578
 0x0000000000000008 (RELASZ)             1728 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x000000006ffffffc (VERDEF)             0x38c46043a0
 0x000000006ffffffd (VERDEFNUM)          10
 0x000000000000001e (FLAGS)              STATIC_TLS
 0x000000006ffffffb (FLAGS_1)            Flags: NODELETE INITFIRST
 0x000000006ffffffe (VERNEED)            0x38c46044f8
 0x000000006fffffff (VERNEEDNUM)         2
 0x000000006ffffff0 (VERSYM)             0x38c460410e
 0x000000006ffffff9 (RELACOUNT)          60
 0x000000006ffffdf8 (CHECKSUM)           0x86f709c8
 0x000000006ffffdf5 (GNU_PRELINKED)      2018-05-23T11:25:00
 0x0000000000000000 (NULL)               0x0

Здесь мы видим STATIC_TLS, что наводит меня на мысль, что использовалась модель initial-exec.

Вывод readelf -l:

Elf file type is DYN (Shared object file)
Entry point 0x38c4605de0
There are 9 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x00000038c4600040 0x00000038c4600040
                 0x00000000000001f8 0x00000000000001f8  R E    8
  INTERP         0x0000000000011830 0x00000038c4611830 0x00000038c4611830
                 0x000000000000001c 0x000000000000001c  R      10
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x00000038c4600000 0x00000038c4600000
                 0x0000000000016df0 0x0000000000016df0  R E    200000
  LOAD           0x0000000000017b90 0x00000038c4817b90 0x00000038c4817b90
                 0x00000000000006e0 0x0000000000004860  RW     200000
  DYNAMIC        0x0000000000017d90 0x00000038c4817d90 0x00000038c4817d90
                 0x00000000000001f0 0x00000000000001f0  RW     8
  NOTE           0x0000000000000238 0x00000038c4600238 0x00000038c4600238
                 0x0000000000000044 0x0000000000000044  R      4
  GNU_EH_FRAME   0x000000000001184c 0x00000038c461184c 0x00000038c461184c
                 0x0000000000000a5c 0x0000000000000a5c  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     8
  GNU_RELRO      0x0000000000017b90 0x00000038c4817b90 0x00000038c4817b90
                 0x0000000000000470 0x0000000000000470  R      1

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_d .gnu.version_r .rela.dyn .rela.plt .init .plt .text __libc_freeres_fn .fini .rodata .interp .eh_frame_hdr .eh_frame .gcc_except_table .hash 
   03     .ctors .dtors .jcr .data.rel.ro .dynamic .got .got.plt .data .bss 
   04     .dynamic 
   05     .note.gnu.build-id .note.ABI-tag 
   06     .eh_frame_hdr 
   07     
   08     .ctors .dtors .jcr .data.rel.ro .dynamic .got 

Я был удивлен отсутствием раздела TLS здесь, но даже в этом случае у нас есть четкие указания на то, что общий объект использует статическую модель initial-exec TLS.

Наконец, я видел людей с похожими проблемами, которые переупорядочивали зависимости, чтобы избавиться от более ранней ошибки dlopen. Я не почему это имеет значение.

Вопрос(ы)

  • Как initial-exec работает внутри перемещаемого кода, особенно общих объектов на x86_64?
  • Почему изменение порядка зависимостей иногда решает проблему dlopen; наверняка количество используемых слотов останется прежним?

Любые другие предложения по оригинальному выпуску dlopen также приветствуются.

Обновление 1

Еще немного покопавшись в проблеме, я наткнулся на другой источник, в котором говорится, что статические модели TLS нельзя использовать в общих библиотеках:

DF_STATIC_TLS
    If set in a shared object or executable, this flag instructs the dynamic linker to reject attempts to load this file dynamically. It indicates that the shared object or executable contains code using a static thread-local storage scheme. Implementations need not support any form of thread-local storage.

person OMGtechy    schedule 25.06.2020    source источник
comment
initial-exec подходит для общих объектов. Он сообщает компилятору и компоновщику, что ни один доступный символ не будет найден в другом общем объекте, который еще не присутствует и не загружен dlopen. Это означает, что и компилятор, и компоновщик могут предполагать, что модули, которые они компилируют/связывают, — это все, что есть, и расположение блоков TLS фиксировано, а символ GOT может использоваться для доступа к локальному символу потока. Я не знаю, как изменение порядка что-то исправит.   -  person Margaret Bloom    schedule 25.06.2020
comment
@MargaretBloom спасибо, это хотя бы часть ответа на мой вопрос! :) Надеюсь, кто-то еще дополнит остальное.   -  person OMGtechy    schedule 26.06.2020
comment
Прочтите документ Дреппера Как писать разделяемые библиотеки   -  person Basile Starynkevitch    schedule 26.06.2020
comment
@MargaretBloom Я все еще читаю предложение Базиля, но тем временем я наткнулся на другой источник, который, кажется, противоречит тому, что вы сказали, и тому, что я вижу. Можете ли вы пролить на это свет (см. обновление 1)? Большое спасибо!   -  person OMGtechy    schedule 01.07.2020
comment
@OMGtechy DF_STATIC_TLS указывает среде выполнения немедленно выделить блок TLS для модуля (а не лениво, см. __tls_get_addr). Это разрешено, если модуль находится в начальном наборе модулей (т. е. это общий объект, DSO, указанный как зависимость в заголовке ELF). Глава 3.1 Обработка ELF для TLS фактически подразумевает, что DF_STATIC_TLS также разрешено динамически загруженные DSO, где он игнорируется. Обратите внимание, что статическая и динамическая модели почти ортогональны моделям доступа (например, initial-exec). Связанный PDF-файл обязателен к прочтению ;)   -  person Margaret Bloom    schedule 01.07.2020
comment
Я сказал, что статическая и динамическая модели почти ортогональны моделям доступа, но это немного преувеличение. Например, для модели initial-exec требуется DF_STATIC_TLS, поскольку __tls_get_addr не вызывается, поэтому возможно ленивое выделение блока TLS. На самом деле, initial-exec модулей требуют DF_STATIC_TLS.   -  person Margaret Bloom    schedule 01.07.2020