Segfault al declarar una variable de tipo vector>


Código

Aquí está el programa que da el segfault.

#include <iostream>
#include <vector>
#include <memory>

int main() 
{
    std::cout << "Hello World" << std::endl;

    std::vector<std::shared_ptr<int>> y {};  

    std::cout << "Hello World" << std::endl;
}

Por supuesto, no hay absolutamente nada malo en el programa mismo. La causa raíz de la falla de segmento depende del entorno en el que se construyó y ejecutó.


Antecedentes

Nosotros, en Amazon, usamos un sistema de compilación que compila e implementa los binarios (lib y bin) de una manera casi independiente de la máquina. Para nuestro caso, eso básicamente significa que despliega la ejecutable (construido a partir del programa anterior) en $project_dir/build/bin/ y casi todas sus dependencias (es decir, las bibliotecas compartidas) en $project_dir/build/lib/. Por qué usé la frase "casi" es porque para las bibliotecas compartidas tales libc.so, libm.so, ld-linux-x86-64.so.2 y posiblemente pocos otros, el ejecutable toma del sistema (es decir, de /lib64). Tenga en cuenta que se supone para elegir libstdc++ de $project_dir/build/lib sin embargo.

Ahora lo corro de la siguiente manera:{[54]]}

$ LD_LIBRARY_PATH=$project_dir/build/lib ./build/bin/run

segmentation fault

Sin embargo, si lo corro, sin establecer el LD_LIBRARY_PATH. Funciona bien.


Diagnóstico

1. ldd

Aquí están ldd informaciones para ambos casos (tenga en cuenta que he editado la salida para mencionar la versión completa de las bibliotecas donde haya diferencia )

$ LD_LIBRARY_PATH=$project_dir/build/lib ldd ./build/bin/run

linux-vdso.so.1 =>  (0x00007ffce19ca000)
libstdc++.so.6 => $project_dir/build/lib/libstdc++.so.6.0.20 
libgcc_s.so.1 =>  $project_dir/build/lib/libgcc_s.so.1 
libc.so.6 => /lib64/libc.so.6 
libm.so.6 => /lib64/libm.so.6 
/lib64/ld-linux-x86-64.so.2 (0x0000562ec51bc000)

Y sin LD_LIBRARY_PATH:

$ ldd ./build/bin/run

linux-vdso.so.1 =>  (0x00007fffcedde000)
libstdc++.so.6 => /usr/lib64/libstdc++.so.6.0.16 
libgcc_s.so.1 => /lib64/libgcc_s-4.4.6-20110824.so.1
libc.so.6 => /lib64/libc.so.6 
libm.so.6 => /lib64/libm.so.6 
/lib64/ld-linux-x86-64.so.2 (0x0000560caff38000)

2. gdb cuando se segfaults

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7dea45c in _dl_fixup () from /lib64/ld-linux-x86-64.so.2
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.209.62.al12.x86_64
(gdb) bt
#0  0x00007ffff7dea45c in _dl_fixup () from /lib64/ld-linux-x86-64.so.2
#1  0x00007ffff7df0c55 in _dl_runtime_resolve () from /lib64/ld-linux-x86-64.so.2
#2  0x00007ffff7b1dc41 in std::locale::_S_initialize() () from $project_dir/build/lib/libstdc++.so.6
#3  0x00007ffff7b1dc85 in std::locale::locale() () from $project_dir/build/lib/libstdc++.so.6
#4  0x00007ffff7b1a574 in std::ios_base::Init::Init() () from $project_dir/build/lib/libstdc++.so.6
#5  0x0000000000400fde in _GLOBAL__sub_I_main () at $project_dir/build/gcc-4.9.4/include/c++/4.9.4/iostream:74
#6  0x00000000004012ed in __libc_csu_init ()
#7  0x00007ffff7518cb0 in __libc_start_main () from /lib64/libc.so.6
#8  0x0000000000401021 in _start ()
(gdb)

3. LD_DEBUG=all

También intenté ver la información del enlazador habilitando LD_DEBUG=all para el error de segmento caso. He encontrado algo sospechoso, ya que busca pthread_once símbolo, y cuando no puede encontrar esto, da error de segmento (esa es mi interpretación del siguiente fragmento de salida por cierto):

initialize program: $project_dir/build/bin/run

symbol=_ZNSt8ios_base4InitC1Ev;  lookup in file=$project_dir/build/bin/run [0]
symbol=_ZNSt8ios_base4InitC1Ev;  lookup in file=$project_dir/build/lib/libstdc++.so.6 [0]
binding file $project_dir/build/bin/run [0] to $project_dir/build/lib/libstdc++.so.6 [0]: normal symbol `_ZNSt8ios_base4InitC1Ev' [GLIBCXX_3.4]
symbol=_ZNSt6localeC1Ev;  lookup in file=$project_dir/build/bin/run [0]
symbol=_ZNSt6localeC1Ev;  lookup in file=$project_dir/build/lib/libstdc++.so.6 [0]
binding file $project_dir/build/lib/libstdc++.so.6 [0] to $project_dir/build/lib/libstdc++.so.6 [0]: normal symbol `_ZNSt6localeC1Ev' [GLIBCXX_3.4]
symbol=pthread_once;  lookup in file=$project_dir/build/bin/run [0]
symbol=pthread_once;  lookup in file=$project_dir/build/lib/libstdc++.so.6 [0]
symbol=pthread_once;  lookup in file=$project_dir/build/lib/libgcc_s.so.1 [0]
symbol=pthread_once;  lookup in file=/lib64/libc.so.6 [0]
symbol=pthread_once;  lookup in file=/lib64/libm.so.6 [0]
symbol=pthread_once;  lookup in file=/lib64/ld-linux-x86-64.so.2 [0]

Pero no veo ningún pthread_once para el caso cuando se ejecuta con éxito!


Preguntas

Sé que es muy difícil depurar así y probablemente no he dado mucha información sobre los entornos y todo. Pero aún así, mi pregunta es: ¿cuál podría ser el ¿posible causa raíz de este fallo de segmento? ¿Cómo depurar más y encontrar eso? Una vez que encuentre el problema, arreglarlo sería fácil.


Compilador y Plataforma

Estoy usando GCC 4.9 en RHEL5.


Experimentos

E#1

Si comento la siguiente línea:

std::vector<std::shared_ptr<int>> y {}; 

Se compila y funciona bien!

E # 2

Acabo de incluir el siguiente encabezado en mi programa:

#include <boost/filesystem.hpp>

Y vinculado en consecuencia. Ahora funciona sin ningún segfault. Así que parece que al tener una dependencia de libboost_system.so.1.53.0., se cumplen algunos requisitos, o el problema se evita!

E # 3

Ya que lo vi funcionando cuando hice que el ejecutable se vinculara con libboost_system.so.1.53.0, así que hice las siguientes cosas paso a paso.

En lugar de usar #include <boost/filesystem.hpp> en el código mismo, utilizo el código original y lo ejecuté precargando libboost_system.so usando LD_PRELOAD de la siguiente manera:

$ LD_PRELOAD=$project_dir/build/lib/libboost_system.so $project_dir/build/bin/run

Y se ejecutó con éxito!

A continuación hice ldd en el libboost_system.so que dio una lista de libs, dos de los cuales eran:{[54]]}

  /lib64/librt.so.1
  /lib64/libpthread.so.0

Así que en lugar de precarga libboost_system, precarga librt y libpthread por separado:

$ LD_PRELOAD=/lib64/librt.so.1 $project_dir/build/bin/run

$ LD_PRELOAD=/lib64/libpthread.so.0 $project_dir/build/bin/run

En ambos casos, se ejecutó con éxito.

Ahora mi conclusión es que al cargar ya sea librt o libpthread (o ambos), se cumplen algunos requisitos o se evita el problema! Sin embargo, todavía no sé la causa raíz del problema.


Opciones de compilación y enlace

Desde el el sistema de compilación es complejo y hay muchas opciones que están ahí por defecto. Así que traté de agregar explícitamente -lpthread usando el comando set de CMake, entonces funcionó, como ya hemos visto al precargar libpthread ¡funciona!

Para ver la diferencia build entre estos dos casos ( when-it-works y when-it-gives-segfault ), la construí en el modo verbose pasando -v a GCC, para ver las etapas de compilación y la opciones que en realidad pasa a cc1plus (compilador) y collect2 (enlazador).

(Tenga en cuenta que las rutas se han editado por brevedad, utilizando rutas de signo de dólar y ficticias.)

$ / gcc-4.9.4 / cc1plus-quiet-v-I / a / include-I / b / include-iprefix $/ gcc-4.9.4 / - MMD principal.cpp.d-MF main.cpp.o. d-MT main.cpp.o -D_GNU_SOURCE-D_REENTRANT-D __USE_XOPEN2K8-D _LARGEFILE_SOURCE-D _FILE_OFFSET_BITS = 64-D __STDC_FORMAT _ MACROS-D _ _ STDC _ LIMIT _ MACROS-D NDEBUG lab / lab / main.cpp-quiet-dumpbase principal.cpp-msse-mfpmath=sse-march=core2-auxbase-strip main.cpp.o-g-O3-Wall-Wextra-std=gnu++1y-version-fdiagnostics-color=auto-ftemplate-depth=128-fno-operator-names-o /tmp/ccxfkRyd.s

Independientemente de si funciona o no, los argumentos de la línea de comandos para cc1plus son exactamente los mismos. No hay ninguna diferencia. Eso no parece ser muy útil.

La diferencia, sin embargo, está en el momento del enlace. Esto es lo que ver, para el caso cuando funciona :

{/gcc-4.9.4/collect2-plugin {/gcc-4.9.4 / liblto_plugin.so
-plugin-opt=$/gcc-4.9.4/lto-wrapper -plugin-opt=-fresolution=/tmp/cchl8RtI.res -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lpthread -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc --eh-marco-hdr -m elf_x86_64 -exportación-dinámico -dinámico-vinculador /lib64 /ld-linux-x86-64.so.2-o ejecutar/usr/lib/..lib64 / crt1.o / usr / lib/../ lib64 / crti.o $ / gcc-4.9.4 / crtbegin.o-L / a / lib-L / b / lib - L / c / lib -lpthread main principal según sea necesario.cpp.o-lboost_timer-lboost_wave-lboost_chrono-lboost_filesystem-lboost_graph-lboost_locale-lboost_thread-lboost_wserialization-lboost_atomic-lboost_context-lboost_date_time-lboost_iostreams-lboost_math_c99-lboost_math_c99f-lboost_math_c99l-lboost_math_tr1-lboost_math_tr1f-lboost_math_tr1l -lboost_mpi-lboost_prg_exec_monitor-lboost_program_options-lboost_random-lboost_regex-lboost_serialization-lboost_signals-lboost_system-lboost_unit_test_framework-lboost_exception-lboost_test_exec_monitor-lbz2-licui18n-licuuc-licudata-lz-rpath /a/lib:/b/lib:/c/lib: -lstdc++ -lm-lgcc_s-lgcc -lpthread -lc-lgcc_s-lgcc. /gcc-4.9.4/crtend.o / usr / lib/../ lib64 / crtn.o

Como puedes ver, -lpthreadse menciona dos veces! La primera -lpthread (que es seguido por --as-needed) es falta para el caso cuando da segfault . Esa es la única diferencia entre estos dos casos.


Salida de nm -C en ambos casos

Curiosamente, la salida de nm -C en ambos casos es idéntica ( si ignora los valores enteros en las primeras columnas).

0000000000402580 d _DYNAMIC
0000000000402798 d _GLOBAL_OFFSET_TABLE_
0000000000401000 t _GLOBAL__sub_I_main
0000000000401358 R _IO_stdin_used
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 w _Jv_RegisterClasses
                 U _Unwind_Resume
0000000000401150 W std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_destroy()
0000000000401170 W std::vector<std::shared_ptr<int>, std::allocator<std::shared_ptr<int> > >::~vector()
0000000000401170 W std::vector<std::shared_ptr<int>, std::allocator<std::shared_ptr<int> > >::~vector()
0000000000401250 W std::vector<std::unique_ptr<int, std::default_delete<int> >, std::allocator<std::unique_ptr<int, std::default_delete<int> > > >::~vector()
0000000000401250 W std::vector<std::unique_ptr<int, std::default_delete<int> >, std::allocator<std::unique_ptr<int, std::default_delete<int> > > >::~vector()
                 U std::ios_base::Init::Init()
                 U std::ios_base::Init::~Init()
0000000000402880 B std::cout
                 U std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
0000000000402841 b std::__ioinit
                 U std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
                 U operator delete(void*)
                 U operator new(unsigned long)
0000000000401510 r __FRAME_END__
0000000000402818 d __JCR_END__
0000000000402818 d __JCR_LIST__
0000000000402820 d __TMC_END__
0000000000402820 d __TMC_LIST__
0000000000402838 A __bss_start
                 U __cxa_atexit
0000000000402808 D __data_start
0000000000401100 t __do_global_dtors_aux
0000000000402820 t __do_global_dtors_aux_fini_array_entry
0000000000402810 d __dso_handle
0000000000402828 t __frame_dummy_init_array_entry
                 w __gmon_start__
                 U __gxx_personality_v0
0000000000402838 t __init_array_end
0000000000402828 t __init_array_start
00000000004012b0 T __libc_csu_fini
00000000004012c0 T __libc_csu_init
                 U __libc_start_main
                 w __pthread_key_create
0000000000402838 A _edata
0000000000402990 A _end
000000000040134c T _fini
0000000000400e68 T _init
0000000000401028 T _start
0000000000401054 t call_gmon_start
0000000000402840 b completed.6661
0000000000402808 W data_start
0000000000401080 t deregister_tm_clones
0000000000401120 t frame_dummy
0000000000400f40 T main
00000000004010c0 t register_tm_clones
Author: Nawaz, 2017-11-09

2 answers

Dado el punto de choque, y el hecho de que la precarga libpthread parece solucionarlo, creo que la ejecución de los dos casos diverge en locale_init.cc:315. He aquí un extracto del código:

  void
  locale::_S_initialize()
  {
#ifdef __GTHREADS
    if (__gthread_active_p())
      __gthread_once(&_S_once, _S_initialize_once);
#endif
    if (!_S_classic)
      _S_initialize_once();
  }

__gthread_active_p() devuelve true si su programa está vinculado con pthread, específicamente comprueba si pthread_key_create está disponible. En mi sistema, este símbolo está definido en "/usr/include/c++/7.2.0/x86_64-pc-linux-gnu/bits/gthr predeterminado.h " como static inline, por lo tanto es una fuente potencial de ODR violación.

Observe que LD_PRELOAD=libpthread,so siempre hará que __gthread_active_p() devuelva true.

__gthread_once es otro símbolo en línea que siempre debe reenviar a pthread_once.

Es difícil adivinar lo que está pasando sin depurar, pero sospecho que estás golpeando la verdadera rama de __gthread_active_p() incluso cuando no debería, y el programa se bloquea porque no hay pthread_once para llamar.

EDITAR : Así que hice algunos experimentos, la única manera que veo para conseguir un accidente en std::locale::_S_initialize es si __gthread_active_p devuelve true, pero pthread_once no está enlazado.

Libstdc++ no enlaza directamente con pthread, pero importa la mitad de pthread_xx como objetos débiles, lo que significa que pueden ser indefinidos y no causar un error de enlazador.

Obviamente, vincular pthread hará que el bloqueo desaparezca, pero si tengo razón, el problema principal es que su libstdc++ piensa que está dentro de un ejecutable multi-thread, incluso si no vinculamos pthread.

Ahora, __gthread_active_p usa __pthread_key_create para decidir si tenemos hilos o no. Esto se define en su ejecutable como un objeto débil (puede ser nullptr y aún así estar bien). Estoy 99% seguro de que el símbolo está ahí debido a shared_ptr (elimínelo y verifique nm nuevamente para estar seguro). Por lo tanto, de alguna manera __pthread_key_create se vincula a una dirección válida, tal vez debido a esa última -lpthread en sus banderas enlazadoras. Puede verificar esta teoría poniendo un punto de interrupción en locale_init.cc:315 y comprobando qué rama toma.

EDIT2 :

Resumen de los comentarios, la cuestión solo es reproducible si tenemos todo lo siguiente:

  1. Use ld.gold en lugar de ld.bfd
  2. Use --as-needed
  3. Forzando una definición débil de __pthread_key_create, en este caso a través de la instanciación de std::shared_ptr.
  4. No vinculación a pthread, o enlaces pthread después de --as-needed.

Para responder a las preguntas en los comentarios:

¿Por qué usa oro por defecto?

De forma predeterminada utiliza /usr/bin/ld, que en la mayoría de las distribuciones es un enlace simbólico a /usr/bin/ld.bfd o /usr/bin/ld.gold. Este valor predeterminado se puede manipular usando update-alternatives. No estoy seguro de por qué en su caso es ld.gold, por lo que entiendo que RHEL5 viene con ld.bfd como predeterminado.

Y por qué el oro no añade pthread.so ¿dependencia al binario si es necesario?

Porque la definición de lo que se necesita es de alguna manera sombría. man ld dice (énfasis mío):

As según sea necesario

No no según sea necesario

Esta opción afecta a las etiquetas ELF DT_NEEDED para bibliotecas dinámicas mencionadas en la línea de comandos después de la opción --as-needed. Normalmente el enlazador agregará un DT_NEEDED etiqueta para cada biblioteca dinámica mencionada en la línea de comandos, independientemente de si la biblioteca es realmente necesaria o no. --as-needed hace que una etiqueta DT_NEEDED solo se emitirá para una biblioteca que en ese punto del enlace satisfaga una referencia no débil undefined symbol de un símbolo regular archivo objeto o, si la biblioteca no se encuentra en las listas DT_NEEDED de otras bibliotecas necesarias, una referencia de símbolo indefinido no débil de otra dinámica necesaria biblioteca. Archivos objeto o bibliotecas aparecer en la línea de comandos después de la biblioteca en cuestión no afecta si la biblioteca se ve como necesaria. Esto es similar a las reglas para la extracción de archivos objeto de archivos. --no-as-needed restaura el comportamiento predeterminado.

Ahora, de acuerdo con este error informe, gold está honrando la parte "símbolo indefinido no débil", mientras que ld.bfd ve símbolos débiles según sea necesario. TBH No tengo una comprensión completa sobre esto, y hay alguna discusión sobre ese enlace en cuanto a si esto debe ser considerado un error ld.gold, o un error libstdc++.

¿Por qué necesito mencionar-pthread y-lpthread ambos? (-pthread es pasado por defecto por nuestro sistema de compilación, y he pasado-lpthread para hacer se usa trabajo con oro).

-pthread y -lpthread hacer cosas diferentes (ver pthread vs lpthread). Entiendo que lo primero debe implicar lo segundo.

A pesar de todo, es probable que pueda pasar -lpthread solo una vez, pero debe hacerlo antes --as-needed, o utilice --no-as-needed después de la última biblioteca y antes de -lpthread.

También vale la pena mencionar que no pude reproducir este problema en mi sistema (GCC 7.2), incluso usando el enlazador de oro. Así que sospecho que se ha fijado en un más reciente versión libstdc++, que también podría explicar por qué no se segfault si se utiliza la biblioteca estándar del sistema.

 10
Author: sbabbi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-17 12:32:08

Esto es probablemente un problema causado por desajustes sutiles entre libstdc++ ABIs. GCC 4.9 no es el compilador del sistema en Red Hat Enterprise Linux 5, por lo que no está muy claro lo que está utilizando allí (DTS 3?).

Se sabe que la implementación local es bastante sensible a los desajustes ABI. Vea este hilo en la lista de ayuda de gcc:

Su mejor apuesta es averiguar qué bits de libstdc++ están vinculados y dónde, y de alguna manera lograr consistencia (ya sea ocultando símbolos o recompilando cosas para que sean compatibles).

También puede ser útil investigar el modelo de enlace híbrido utilizado para libstdc++ en el Conjunto de herramientas para desarrolladores de Red Hat (donde los bits más nuevos están vinculados estáticamente, pero la mayor parte de la biblioteca estándar de C++ utiliza el DSO del sistema existente), pero el sistema libstdc++ en Red hat Enterprise Linux 5 podría ser demasiado viejo para eso si necesita soporte para las características del idioma actual.

 9
Author: Florian Weimer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-11 16:22:42