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:
- Use
ld.gold
lugar deld.bfd
- Usar -
--as-needed
- Forzar una definición débil de
__pthread_key_create
, en este caso mediante instanciación destd::shared_ptr
. - No enlazar con
pthread
, o vincularpthread
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:
- Compatibilidad binaria entre una antigua libstdc ++ estática y una nueva dinámica
- más seguimientos en el próximo mes
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.