c++ gcc segmentation-fault redhat ld

c++ - Segfault al declarar una variable de tipo vector<shared_ptr<int>>



gcc segmentation-fault (2)

Dado el punto de caída, y el hecho de que la precarga de libpthread parece solucionarlo, creo que la ejecución de los dos casos diverge en locale_init.cc:315 . Aquí hay 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 verdadero si su programa está vinculado contra pthread, específicamente comprueba si pthread_key_create está disponible. En mi sistema, este símbolo se define en "/usr/include/c++/7.2.0/x86_64-pc-linux-gnu/bits/gthr-default.h" como static inline , por lo tanto, es una fuente potencial de violación de ODR .

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

__gthread_once es otro símbolo __gthread_once que siempre debe reenviarse a pthread_once .

Es difícil adivinar qué está pasando sin depuración, pero sospecho que está llegando a la rama verdadera 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 forma en que veo un bloqueo en std::locale::_S_initialize es si __gthread_active_p devuelve verdadero, pero pthread_once no está vinculado.

libstdc ++ no se vincula directamente con pthread , pero importa la mitad de pthread_xx como objetos débiles, lo que significa que no se pueden definir y no causa un error de enlazador.

Obviamente, vincular pthread hará que desaparezca el bloqueo, pero si estoy en lo cierto, el problema principal es que libstdc++ cree que está dentro de un ejecutable multiproceso incluso si no vinculáramos pthread in.

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á allí debido a shared_ptr (quítelo y compruebe nm nuevo para estar seguro). Entonces, de alguna manera, __pthread_key_create se enlaza a una dirección válida, tal vez debido a ese último -lpthread en los indicadores de tu linker. Puedes verificar esta teoría colocando un punto de interrupción en locale_init.cc:315 y verificando qué rama tomas.

EDIT2 :

Resumen de los comentarios, el problema solo es reproducible si tenemos todo lo siguiente:

  1. Use ld.gold lugar de ld.bfd
  2. Usar - --as-needed
  3. Forzar una definición débil de __pthread_key_create , en este caso mediante instanciación de std::shared_ptr .
  4. No enlazar con pthread , o vincular pthread después de - --as-needed .

Para responder las preguntas en los comentarios:

¿Por qué usa oro por defecto?

Por defecto, usa /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 . Tal defecto puede ser manipulado usando update-alternatives . No estoy seguro de por qué en su caso es ld.gold , por lo que entiendo, RHEL5 se envía con ld.bfd como valor predeterminado.

¿Y por qué el oro no agrega 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):

--según sea necesario

--no-como-necesitado

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

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

¿Por qué debo mencionar -pthread and -lpthread both? (-pthread se pasa de forma predeterminada por nuestro sistema de compilación, y he pasado -lpthread para que funcione con oro se utiliza).

-pthread y -lpthread hacen cosas diferentes (ver pthread vs lpthread ). Entiendo que el primero debería implicar lo último.

De todos modos, es probable que pase -lpthread solo una vez, pero debe hacerlo antes --as-needed , o use --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 utilizando el vinculador de oro. Así que sospecho que se ha corregido en una versión más reciente de libstdc ++, que también podría explicar por qué no segfault si usas la biblioteca estándar del sistema.

Código

Aquí está el programa que da la 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 de malo en el programa en sí. La causa raíz de segfault depende del entorno en el que se creó y ejecutó.

Fondo

Nosotros, en Amazon, usamos un sistema de compilación que construye y despliega los binarios ( lib y bin ) de una manera casi independiente de la máquina. Para nuestro caso, eso básicamente significa que despliega el ejecutable (creado 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 como libc.so , libm.so , ld-linux-x86-64.so.2 y posiblemente algunas otras, el ejecutable elige del sistema (es decir, desde /lib64 ) . Tenga en cuenta que se supone que debe elegir libstdc++ desde $project_dir/build/lib .

Ahora lo ejecuto de la siguiente manera:

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

Sin embargo, si lo ejecuto, sin configurar LD_LIBRARY_PATH . Funciona bien

Diagnostico

1. ldd

Aquí hay información ldd para ambos casos (tenga en cuenta que he editado el resultado 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 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 caso segfault. Encontré algo sospechoso, ya que busca el símbolo pthread_once , y cuando no puede encontrarlo, da segfault (esa es mi interpretación del siguiente fragmento de salida BTW):

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 de esta manera 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 la posible causa raíz de este segfault? ¿Cómo depurar más y encontrar eso? Una vez que encuentre el problema, la solución 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 {};

¡Compila y funciona bien!

E # 2

Acabo de incluir el siguiente encabezado para mi programa:

#include <boost/filesystem.hpp>

y vinculado en consecuencia. Ahora funciona sin ningún segfault. Entonces parece tener una dependencia en libboost_system.so.1.53.0. , se cumplen algunos requisitos o se elude el problema.

E # 3

Desde que lo vi funcionar cuando hice que el ejecutable se vincule con libboost_system.so.1.53.0 , entonces hice lo siguiente paso a paso.

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

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

¡Y funcionó con éxito!

Luego hice ldd en libboost_system.so que dio una lista de libs, dos de las cuales fueron:

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

Entonces, en lugar de precargar libboost_system , libboost_system librt y libpthread 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 cargando librt o libpthread (o ambos ), se cumplen algunos requisitos o se elude el problema. Todavía no sé la causa raíz del problema, sin embargo.

Opciones de compilación y enlace

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

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

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

$ / gcc-4.9.4 / cc1plus -quiet -v -I / a / include -I / b / include -iprefix $ / gcc-4.9.4 / -MMD main.cpp.d -MF main.cpp.od - 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 / main.cpp -quiet -dumpbase main.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 línea de comandos para cc1plus son exactamente los mismos. No hay diferencia en absoluto. Eso no parece ser muy útil.

La diferencia, sin embargo, está en el tiempo de enlace. Esto es lo que veo, 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-frame-hdr -m elf_x86_64 -export-dynamic -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o run /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 --as-needed main .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 -lbo ost_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 puede ver, -lpthread se menciona dos veces . El primer -lpthread (que es seguido por --as-needed ) 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 se ignoran 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


Es probable que este sea un problema causado por sutiles desajustes 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 qué está utilizando allí (DTS 3?).

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

Su mejor apuesta es averiguar qué bits de libstdc++ enlazados y, de algún modo, lograr coherencia (ocultando símbolos o recomponiendo cosas para que sean compatibles).

También puede ser útil investigar el modelo de vinculación híbrido utilizado para libstdc++ en el Developer Toolset 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 ++ usa el sistema DSO existente), pero el sistema libstdc++ en Red hat Enterprise Linux 5 puede ser demasiado viejo para eso si necesita soporte para las funciones de idioma actuales.