Or I have two files, each with one global initialization. One depends on the other.
Simplified example:
file1.h:
#include <string>
extern const std::string PREFIX;
file1.cpp:
#include "file1.h"
const std::string PREFIX = "prefix,";
file2.cpp:
#include "file1.h"
std::string MSG = PREFIX + "body";
int main(){}
I compile them as such:
/usr/local/bin/g++-4.6.2 -c -Wall -g -o file1.o file1.cpp
/usr/local/bin/g++-4.6.2 -c -Wall -g -o file2.o file2.cpp
/usr/local/bin/g++-4.6.2 -Wall -g -o example file1.o file2.o
When I run this, it segfaults. gdb trace:
Starting program: example
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7b7ae0b in std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::string const&) ()
from /usr/local/lib/gcc/x86_64-unknown-linux-gnu/4.6.2/libstdc++.so.6
(gdb) bt
#0 0x00007ffff7b7ae0b in std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::string const&) ()
from /usr/local/lib/gcc/x86_64-unknown-linux-gnu/4.6.2/libstdc++.so.6
#1 0x00000000004009b5 in std::operator+<char, std::char_traits<char>, std::allocator<char> > (__lhs=Traceback (most recent call last):
File "/usr/local/lib/gcc/x86_64-unknown-linux-gnu/4.6.2/../../../../share/gcc-4.6.2/python/libstdcxx/v6/printers.py", line 587, in to_string
return ptr.lazy_string (length = len)
RuntimeError: Cannot access memory at address 0xffffffffffffffe8
, __rhs=0x400af0 "body") at /usr/local/lib/gcc/x86_64-unknown-linux-gnu/4.6.2/include/c++/bits/basic_string.h:2346
#2 0x000000000040095f in __static_initialization_and_destruction_0 (__initialize_p=1, __priority=65535) at file2.cpp:3
#3 0x000000000040098b in _GLOBAL__sub_I_MSG () at file2.cpp:6
#4 0x0000000000400ab6 in __do_global_ctors_aux ()
#5 0x00000000004006e3 in _init ()
#6 0x00007ffff7ffa5a0 in ?? ()
#7 0x0000000000400a45 in __libc_csu_init ()
#8 0x00007ffff72dcbe0 in __libc_start_main () from /lib/libc.so.6
#9 0x00000000004007c9 in _start ()
Is there anyway to do what I'm trying to do (keeping in mind that this is an overly simplified example)? Or am I at the mercy of the initialization order chosen by the compiler/linker?
You need to make the prefix string be there before the other strings. One way is to change it to a C string
// header
#include <string>
extern const char * const PREFIX;
// .cpp file
const char * const PREFIX = "prefix,";
Another way is to return the prefix from a function and use PREFIX() where you previously used PREFIX.
// header
inline const string& PREFIX() {
static const string value = "prefix,";
return value;
}
At last, just a hint: Names with all uppercase letters are used for macros only in most coding conventions, so I would avoid such names for variables and functions.
I guess this appears because the code attempts to initialize your globals in the wrong order. There is a SO question about initialisation order, its answers should prove useful.
It's obviously not a portable solution, but for gcc you can use function attributes - with the __attribute__ syntax. i.e., the __init_priority__ (priority) attribute. The initialization priorities cross translation units. link
Related
I am debugging A segfault reported by TSAN in the CI of Boost.Beast.
I strongly believe it to be a false positive, but I don't know what to look for in order to suppress it.
It seems to me from the stack trace that the code is correctly instrumented.
The code passes all other tests, inclding valgrind, ubsan, etc.
I'm hoping some kind expert may put me out of my misery.
Here is the output:
====== BEGIN OUTPUT ======
beast.http.read
ThreadSanitizer:DEADLYSIGNAL
==132842==ERROR: ThreadSanitizer: SEGV on unknown address 0x7ff5d9cff000 (pc 0x7ff5dceba0d0 bp 0x000000000000 sp 0x7ff5d9c3d910 T132844)
==132842==The signal is caused by a READ memory access.
#0 __sanitizer::StackDepotBase<__sanitizer::StackDepotNode, 1, 20>::Put(__sanitizer::StackTrace, bool*) <null> (libtsan.so.2+0xba0d0)
#1 __tsan::CurrentStackId(__tsan::ThreadState*, unsigned long) <null> (libtsan.so.2+0x8c48f)
#2 __sanitizer::DD::MutexInit(__sanitizer::DDCallback*, __sanitizer::DDMutex*) <null> (libtsan.so.2+0xac534)
#3 __tsan::DDMutexInit(__tsan::ThreadState*, unsigned long, __tsan::SyncVar*) <null> (libtsan.so.2+0x9a3f8)
#4 __tsan::MetaMap::GetSync(__tsan::ThreadState*, unsigned long, unsigned long, bool, bool) <null> (libtsan.so.2+0xa85dc)
#5 __tsan_atomic32_fetch_add <null> (libtsan.so.2+0x783e9)
#6 __gnu_cxx::__exchange_and_add(int volatile*, int) /usr/include/c++/12/ext/atomicity.h:66 (read+0x4188f8)
#7 __gnu_cxx::__exchange_and_add_dispatch(int*, int) /usr/include/c++/12/ext/atomicity.h:101 (read+0x4188f8)
#8 std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release_last_use() /usr/include/c++/12/bits/shared_ptr_base.h:187 (read+0x4188f8)
#9 std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() /usr/include/c++/12/bits/shared_ptr_base.h:361 (read+0x40c592)
#10 std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() /usr/include/c++/12/bits/shared_ptr_base.h:1071 (read+0x418fee)
#11 std::__shared_ptr<boost::asio::detail::strand_executor_service::strand_impl, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() /usr/include/c++/12/bits/shared_ptr_base.h:1524 (read+0x420f83)
#12 std::shared_ptr<boost::asio::detail::strand_executor_service::strand_impl>::~shared_ptr() /usr/include/c++/12/bits/shared_ptr.h:175 (read+0x420faf)
#13 boost::asio::detail::strand_executor_service::invoker<boost::asio::io_context::basic_executor_type<std::allocator<void>, 0ul> const, void>::~invoker() <null> (read+0x471f5f)
#14 boost::asio::detail::executor_op<boost::asio::detail::strand_executor_service::invoker<boost::asio::io_context::basic_executor_type<std::allocator<void>, 0ul> const, void>, boost::asio::detail::recycling_allocator<void, boost::asio::detail::thread_info_base::default_tag>, boost::asio::detail::scheduler_operation>::do_complete(void*, boost::asio::detail::scheduler_operation*, boost::system::error_code const&, unsigned long) <null> (read+0x48b0ac)
#15 boost::asio::detail::scheduler_operation::complete(void*, boost::system::error_code const&, unsigned long) boost/asio/detail/scheduler_operation.hpp:40 (read+0x4fa38e)
#16 boost::asio::detail::scheduler::do_run_one(boost::asio::detail::conditionally_enabled_mutex::scoped_lock&, boost::asio::detail::scheduler_thread_info&, boost::system::error_code const&) boost/asio/detail/impl/scheduler.ipp:492 (read+0x4e8835)
#17 boost::asio::detail::scheduler::run(boost::system::error_code&) boost/asio/detail/impl/scheduler.ipp:210 (read+0x4e74fb)
#18 boost::asio::io_context::run() boost/asio/impl/io_context.ipp:63 (read+0x4dc122)
#19 boost::beast::test::enable_yield_to::enable_yield_to(unsigned long)::{lambda()#1}::operator()() const <null> (read+0x412363)
#20 void std::__invoke_impl<void, boost::beast::test::enable_yield_to::enable_yield_to(unsigned long)::{lambda()#1}>(std::__invoke_other, boost::beast::test::enable_yield_to::enable_yield_to(unsigned long)::{lambda()#1}&&) <null> (read+0x4a1f92)
#21 std::__invoke_result<boost::beast::test::enable_yield_to::enable_yield_to(unsigned long)::{lambda()#1}>::type std::__invoke<boost::beast::test::enable_yield_to::enable_yield_to(unsigned long)::{lambda()#1}>(boost::beast::test::enable_yield_to::enable_yield_to(unsigned long)::{lambda()#1}&&) <null> (read+0x49f9dc)
#22 void std::thread::_Invoker<std::tuple<boost::beast::test::enable_yield_to::enable_yield_to(unsigned long)::{lambda()#1}> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) <null> (read+0x49c90a)
#23 std::thread::_Invoker<std::tuple<boost::beast::test::enable_yield_to::enable_yield_to(unsigned long)::{lambda()#1}> >::operator()() <null> (read+0x49941e)
#24 std::thread::_State_impl<std::thread::_Invoker<std::tuple<boost::beast::test::enable_yield_to::enable_yield_to(unsigned long)::{lambda()#1}> > >::_M_run() <null> (read+0x494c2a)
#25 execute_native_thread_routine <null> (libstdc++.so.6+0xdbb72)
#26 __tsan_thread_start_func <null> (libtsan.so.2+0x393ef)
#27 start_thread <null> (libc.so.6+0x8ce2c)
#28 clone3 <null> (libc.so.6+0x1121af)
ThreadSanitizer can not provide additional info.
SUMMARY: ThreadSanitizer: SEGV (/lib64/libtsan.so.2+0xba0d0) in __sanitizer::StackDepotBase<__sanitizer::StackDepotNode, 1, 20>::Put(__sanitizer::StackTrace, bool*)
==132842==ABORTING
The code being tested is latest master branch.
Command line to reproduce:
$ ./b2 toolset=gcc thread-sanitizer=norecover link=static variant=debug libs/beast/test -q -d+2 -j1
My compiler info:
$ gcc --version
gcc (GCC) 12.1.1 20220507 (Red Hat 12.1.1-1)
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
My OS is Fedora 36. But we see this happen on Ubuntu as well.
Using the b2 line I could repro the SEGV on linux using master (00293a6adb5 from the superproject).
I started with a - to me - more convenient setup based on CMake. I modified the CMake to use thread,undefined sanitizers instead of address,undefined for VARIANT=ubasan.
Interestingly, it doesn't segfault. It does however seem to have a legit TSAN violation in basic_stream.cpp, where the effective flags are:
{
"directory": "/backup/cloudbackup/custom_ex/superboost/libs/beast/build/test/beast/core",
"command": "/home/sehe/bin/g++-10 -DBOOST_ALL_STATIC_LINK=1 -DBOOST_ASIO_DISABLE_BOOST_ARRAY=1 -DBOOST_ASIO_DISABLE_BOOST_BIND=1 -DBOOST_ASIO_DISABLE_BOOST_DATE_TIME=1 -DBOOST_ASIO_DISABLE_BOOST_REGEX=1 -DBOOST_ASIO_NO_DEPRECATED=1 -DBOOST_ASIO_SEPARATE_COMPILATION=1 -DBOOST_BEAST_ALLOW_DEPRECATED -DBOOST_BEAST_SEPARATE_COMPILATION=1 -DBOOST_BEAST_TESTS -DBOOST_COROUTINES_NO_DEPRECATION_WARNING=1 -I/backup/cloudbackup/custom_ex/superboost/libs/beast/include -I/backup/cloudbackup/custom_ex/superboost/libs/beast/. -I/backup/cloudbackup/custom_ex/superboost/libs/beast/test/./extern -I/backup/cloudbackup/custom_ex/superboost/libs/beast/test/./extras/include -isystem /backup/cloudbackup/custom_ex/superboost -std=c++11 -Wall -Wextra -Wpedantic -Wno-unused-parameter -DBOOST_BEAST_NO_SLOW_TESTS=1 -msse4.2 -funsigned-char -fno-omit-frame-pointer -fsanitize=thread,undefined -O2 -g -DNDEBUG -pthread -o CMakeFiles/tests-beast-core.dir/basic_stream.cpp.o -c /backup/cloudbackup/custom_ex/superboost/libs/beast/test/beast/core/basic_stream.cpp",
"file": "/backup/cloudbackup/custom_ex/superboost/libs/beast/test/beast/core/basic_stream.cpp"
},
Breaking it down for readability:
g++-10
-DBOOST_ALL_STATIC_LINK=1
-DBOOST_ASIO_DISABLE_BOOST_ARRAY=1
-DBOOST_ASIO_DISABLE_BOOST_BIND=1
-DBOOST_ASIO_DISABLE_BOOST_DATE_TIME=1
-DBOOST_ASIO_DISABLE_BOOST_REGEX=1
-DBOOST_ASIO_NO_DEPRECATED=1
-DBOOST_ASIO_SEPARATE_COMPILATION=1
-DBOOST_BEAST_ALLOW_DEPRECATED
-DBOOST_BEAST_SEPARATE_COMPILATION=1
-DBOOST_BEAST_TESTS
-DBOOST_COROUTINES_NO_DEPRECATION_WARNING=1
-I/superboost/libs/beast/include
-I/superboost/libs/beast/.
-I/superboost/libs/beast/test/./extern
-I/superboost/libs/beast/test/./extras/include
-isystem /superboost
-std=c++11
-Wall
-Wextra
-Wpedantic
-Wno-unused-parameter
-DBOOST_BEAST_NO_SLOW_TESTS=1
-msse4.2
-funsigned-char
-fno-omit-frame-pointer
-fsanitize=thread,undefined
-O2 -g
-DNDEBUG
-pthread
-o CMakeFiles/tests-beast-core.dir/basic_stream.cpp.o
-c /superboost/libs/beast/test/beast/core/basic_stream.cpp
The reported diagnostic: https://paste.ubuntu.com/p/6SKjmZ9wFT/ (lines truncated for SO):
beast.core.basic_stream
==================
WARNING: ThreadSanitizer: data race (pid=24051)
Write of size 8 at 0x7b0400000030 by thread T1:
#0 pipe ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1726 (libtsan.so.0+0x3e574)
#1 __sanitizer::IsAccessibleMemoryRange(unsigned long, unsigned long) ../../../../src/libsanitizer/sanitizer_common/sanitizer_posix_libcdep.cpp:281 (libu...
#2 operator() /superboost/libs/beast/test/beast/core/basic_stream.cpp:228 (tests-beast-core+0x4f0271)
#3 operator() /superboost/boost/asio/detail/bind_handler.hpp:171 (tests-beast-core+0x4f0271)
#4 invoke<boost::asio::detail::binder1<boost::beast::(anonymous namespace)::test_server::test_server(boost::beast::string_view, boost::asio::ip::tcp::end...
#5 complete<boost::asio::detail::binder1<boost::beast::(anonymous namespace)::test_server::test_server(boost::beast::string_view, boost::asio::ip::tcp::e...
#6 do_complete /superboost/boost/asio/detail/reactive_socket_accept_op.hpp:150 (tests-beast-core+0x4f0271)
#7 boost::asio::detail::scheduler_operation::complete(void*, boost::system::error_code const&, unsigned long) /superboost/boost/asio/detail/scheduler_ope...
#8 boost::asio::detail::epoll_reactor::descriptor_state::do_complete(void*, boost::asio::detail::scheduler_operation*, boost::system::error_code const&, ...
#9 boost::asio::detail::scheduler_operation::complete(void*, boost::system::error_code const&, unsigned long) /superboost/boost/asio/detail/scheduler_ope...
#10 boost::asio::detail::scheduler::do_run_one(boost::asio::detail::conditionally_enabled_mutex::scoped_lock&, boost::asio::detail::scheduler_thread_info...
#11 boost::asio::detail::scheduler::run(boost::system::error_code&) /superboost/boost/asio/detail/impl/scheduler.ipp:210 (tests-beast-core+0x90d424)
#12 boost::asio::io_context::run() /superboost/boost/asio/impl/io_context.ipp:63 (tests-beast-core+0x91bf39)
#13 operator() /superboost/libs/beast/test/beast/core/basic_stream.cpp:234 (tests-beast-core+0x4cf1f9)
#14 __invoke_impl<void, boost::beast::(anonymous namespace)::test_server::test_server(boost::beast::string_view, boost::asio::ip::tcp::endpoint, std::ost...
#15 __invoke<boost::beast::(anonymous namespace)::test_server::test_server(boost::beast::string_view, boost::asio::ip::tcp::endpoint, std::ostream&)::<la...
#16 _M_invoke<0> /usr/include/c++/10/thread:264 (tests-beast-core+0x4cf1f9)
#17 operator() /usr/include/c++/10/thread:271 (tests-beast-core+0x4cf1f9)
#18 _M_run /usr/include/c++/10/thread:215 (tests-beast-core+0x4cf1f9)
#19 execute_native_thread_routine ../../../../../src/libstdc++-v3/src/c++11/thread.cc:82 (libstdc++.so.6+0xd44bf)
Previous write of size 8 at 0x7b0400000030 by main thread:
#0 pipe ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1726 (libtsan.so.0+0x3e574)
#1 __sanitizer::IsAccessibleMemoryRange(unsigned long, unsigned long) ../../../../src/libsanitizer/sanitizer_common/sanitizer_posix_libcdep.cpp:281 (libu...
#2 void boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::io_context::basic_executor_type<std::allocator<void>, 0ul> >::initiate_async_...
#3 void boost::asio::detail::completion_handler_async_result<boost::beast::basic_stream<boost::asio::ip::tcp, boost::asio::io_context::basic_executor_typ...
#4 boost::asio::constraint<boost::asio::detail::async_result_has_initiate_memfn<boost::beast::basic_stream<boost::asio::ip::tcp, boost::asio::io_context:...
#5 decltype ((async_initiate<boost::beast::basic_stream<boost::asio::ip::tcp, boost::asio::io_context::basic_executor_type<std::allocator<void>, 0ul>, bo...
#6 boost::beast::basic_stream<boost::asio::ip::tcp, boost::asio::io_context::basic_executor_type<std::allocator<void>, 0ul>, boost::beast::unlimited_rate...
#7 boost::beast::basic_stream<boost::asio::ip::tcp, boost::asio::io_context::basic_executor_type<std::allocator<void>, 0ul>, boost::beast::unlimited_rate...
#8 boost::beast::basic_stream<boost::asio::ip::tcp, boost::asio::io_context::basic_executor_type<std::allocator<void>, 0ul>, boost::beast::unlimited_rate...
#9 void boost::beast::basic_stream<boost::asio::ip::tcp, boost::asio::io_context::basic_executor_type<std::allocator<void>, 0ul>, boost::beast::unlimited...
#10 void boost::asio::detail::completion_handler_async_result<boost::beast::basic_stream_test::handler, void (boost::system::error_code, unsigned long)>:...
#11 boost::asio::constraint<boost::asio::detail::async_result_has_initiate_memfn<boost::beast::basic_stream_test::handler, void (boost::system::error_cod...
#12 boost::asio::async_result<std::decay<boost::beast::basic_stream_test::handler>::type, void (boost::system::error_code, unsigned long)>::return_type b...
#13 boost::beast::basic_stream_test::testRead() /superboost/libs/beast/test/beast/core/basic_stream.cpp:488 (tests-beast-core+0x678b01)
#14 boost::beast::basic_stream_test::run() /superboost/libs/beast/test/beast/core/basic_stream.cpp:1401 (tests-beast-core+0x70b193)
#15 void boost::beast::unit_test::suite::run<void>(boost::beast::unit_test::runner&) /superboost/libs/beast/include/boost/beast/_experimental/unit_test/s...
#16 void boost::beast::unit_test::suite::operator()<void>(boost::beast::unit_test::runner&) /superboost/libs/beast/include/boost/beast/_experimental/unit...
#17 boost::beast::unit_test::make_suite_info<boost::beast::basic_stream_test>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<cha...
#18 void std::__invoke_impl<void, boost::beast::unit_test::make_suite_info<boost::beast::basic_stream_test>(std::__cxx11::basic_string<char, std::char_tr...
#19 std::enable_if<std::__and_<std::is_void<void>, std::__is_invocable<boost::beast::unit_test::make_suite_info<boost::beast::basic_stream_test>(std::__c...
#20 std::_Function_handler<void (boost::beast::unit_test::runner&), boost::beast::unit_test::make_suite_info<boost::beast::basic_stream_test>(std::__cxx1...
#21 std::function<void (boost::beast::unit_test::runner&)>::operator()(boost::beast::unit_test::runner&) const /usr/include/c++/10/bits/std_function.h:62...
#22 boost::beast::unit_test::suite_info::run(boost::beast::unit_test::runner&) const /superboost/libs/beast/include/boost/beast/_experimental/unit_test/s...
#23 bool boost::beast::unit_test::runner::run<void>(boost::beast::unit_test::suite_info const&) /superboost/libs/beast/include/boost/beast/_experimental/...
#24 bool boost::beast::unit_test::runner::run_each<boost::beast::unit_test::suite_list>(boost::beast::unit_test::suite_list const&) /superboost/libs/beas...
#25 main /superboost/libs/beast/include/boost/beast/_experimental/unit_test/main.ipp:82 (tests-beast-core+0x4cc1d3)
Thread T1 (tid=24054, running) created by main thread at:
#0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969 (libtsan.so.0+0x5fe84)
#1 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) /build/gcc-11-YRKbe7/gcc-11-...
#2 boost::beast::basic_stream_test::testRead() /superboost/libs/beast/test/beast/core/basic_stream.cpp:484 (tests-beast-core+0x67891b)
#3 boost::beast::basic_stream_test::run() /superboost/libs/beast/test/beast/core/basic_stream.cpp:1401 (tests-beast-core+0x70b193)
#4 void boost::beast::unit_test::suite::run<void>(boost::beast::unit_test::runner&) /superboost/libs/beast/include/boost/beast/_experimental/unit_test/su...
#5 void boost::beast::unit_test::suite::operator()<void>(boost::beast::unit_test::runner&) /superboost/libs/beast/include/boost/beast/_experimental/unit_...
#6 boost::beast::unit_test::make_suite_info<boost::beast::basic_stream_test>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char...
#7 void std::__invoke_impl<void, boost::beast::unit_test::make_suite_info<boost::beast::basic_stream_test>(std::__cxx11::basic_string<char, std::char_tra...
#8 std::enable_if<std::__and_<std::is_void<void>, std::__is_invocable<boost::beast::unit_test::make_suite_info<boost::beast::basic_stream_test>(std::__cx...
#9 std::_Function_handler<void (boost::beast::unit_test::runner&), boost::beast::unit_test::make_suite_info<boost::beast::basic_stream_test>(std::__cxx11...
#10 std::function<void (boost::beast::unit_test::runner&)>::operator()(boost::beast::unit_test::runner&) const /usr/include/c++/10/bits/std_function.h:62...
#11 boost::beast::unit_test::suite_info::run(boost::beast::unit_test::runner&) const /superboost/libs/beast/include/boost/beast/_experimental/unit_test/s...
#12 bool boost::beast::unit_test::runner::run<void>(boost::beast::unit_test::suite_info const&) /superboost/libs/beast/include/boost/beast/_experimental/...
#13 bool boost::beast::unit_test::runner::run_each<boost::beast::unit_test::suite_list>(boost::beast::unit_test::suite_list const&) /superboost/libs/beas...
#14 main /superboost/libs/beast/include/boost/beast/_experimental/unit_test/main.ipp:82 (tests-beast-core+0x4cc1d3)
SUMMARY: ThreadSanitizer: data race ../../../../src/libsanitizer/sanitizer_common/sanitizer_posix_libcdep.cpp:281 in __sanitizer::IsAccessibleMemoryRange(uns...
==================
559ms, 1 suite, 1 case, 217 tests total, 0 failures
ThreadSanitizer: reported 1 warnings
Given this observation, I thought to see whether excluding the offending TU (basic_stream.cpp) from the b2 build removes the SEGV. No such luck.
On the contrary, the SEGV manfifest with the following TUs:
module
TU
test/beast/core
buffered_read_stream.cpp
test/beast/http
read.cpp
test/beast/http
write.cpp
test/beast/websocket
close.cpp
test/beast/websocket
handshake.cpp
test/beast/websocket
ping.cpp
test/beast/websocket
read2.cpp
test/beast/websocket
write.cpp
test/doc
http_examples.cpp
Dropping these TUs from their respective test/**/Jamfile allows all remaining tests to pass TSAN under b2. Now, I did some soul searching and e.g. unique include diving using a script like:
#!/bin/bash -i
alias PP='find build/ -name *.cpp.i'
PP -delete
make -C build/test/beast/core buffered_read_stream.i
make -C build/test/beast/http read.i
make -C build/test/beast/http write.i
make -C build/test/beast/websocket close.i
make -C build/test/beast/websocket handshake.i
make -C build/test/beast/websocket ping.i
make -C build/test/beast/websocket read2.i
make -C build/test/beast/websocket write.i
make -C build/test/doc http_examples.i
PP | nl
n=$(PP | wc -l)
set -x
export LANG=C
PP -exec sort -b {} \+ | uniq -dc | grep -wP "^\s*$n # 1" | grep -P '1( 3 4)?$' | nl
Which uncovers a common subset of includes of 854 includes. 40 of the beast headers are in that consistent set, but 208 are asio headers.
Questions from here:
why is SEGV not happening in the CMake build?
are there headers in the common subset that do not appear in the TUs that don't trip TSAN up?
is a recent change in Asio relevant?
Choosing to address these 3., 1., 2. (optimizing for return-on-effort)
3. Is a recent Asio change involved? [YES]
Doing the b2 test with only Asio reverted to 1.79.0 (e929e5cf Merge asio from 'develop') passes all the tests cleanly.
Just to check that no compiler flags were harmed in the process e.g. buffered_read_stream.cpp showed the same exact commands:
gcc.compile.c++ bin.v2/libs/beast/test/beast/core/buffered_read_stream.test/gcc-10.0/debug/link-static/thread-sanitizer-norecover/threading-multi/visibility-hidden/buffered_read_stream.o
"g++-10" -fvisibility-inlines-hidden -fsanitize=thread -fno-sanitize-recover=thread -fno-omit-frame-pointer -m64 -pthread -O0 -fno-inline -Wall -g -fvisibility=hidden -DBOOST_ALL_NO_LIB=1 -DBOOST_ASIO_DISABLE_BOOST_ARRAY=1 -DBOOST_ASIO_DISABLE_BOOST_BIND=1 -DBOOST_ASIO_DISABLE_BOOST_DATE_TIME=1 -DBOOST_ASIO_DISABLE_BOOST_REGEX=1 -DBOOST_ASIO_NO_DEPRECATED=1 -DBOOST_ASIO_SEPARATE_COMPILATION -DBOOST_ATOMIC_STATIC_LINK=1 -DBOOST_BEAST_ALLOW_DEPRECATED -DBOOST_BEAST_SEPARATE_COMPILATION -DBOOST_BEAST_TESTS -DBOOST_COROUTINES_NO_DEPRECATION_WARNING=1 -DBOOST_FILESYSTEM_STATIC_LINK=1 -D_GNU_SOURCE=1 -D_XOPEN_SOURCE=600 -I"." -I"libs/beast" -I"libs/beast/test/extern" -I"libs/beast/test/extras/include" -c -o "bin.v2/libs/beast/test/beast/core/buffered_read_stream.test/gcc-10.0/debug/link-static/thread-sanitizer-norecover/threading-multi/visibility-hidden/buffered_read_stream.o" "libs/beast/test/beast/core/buffered_read_stream.cpp"
gcc.link bin.v2/libs/beast/test/beast/core/buffered_read_stream.test/gcc-10.0/debug/link-static/thread-sanitizer-norecover/threading-multi/visibility-hidden/buffered_read_stream
"g++-10" -o "bin.v2/libs/beast/test/beast/core/buffered_read_stream.test/gcc-10.0/debug/link-static/thread-sanitizer-norecover/threading-multi/visibility-hidden/buffered_read_stream" -Wl,--start-group "bin.v2/libs/beast/test/beast/core/buffered_read_stream.test/gcc-10.0/debug/link-static/thread-sanitizer-norecover/threading-multi/visibility-hidden/buffered_read_stream.o" "bin.v2/libs/beast/test/gcc-10.0/debug/link-static/thread-sanitizer-norecover/threading-multi/visibility-hidden/lib-test.a" "bin.v2/libs/beast/gcc-10.0/debug/link-static/thread-sanitizer-norecover/threading-multi/visibility-hidden/lib-asio.a" "bin.v2/libs/beast/gcc-10.0/debug/link-static/thread-sanitizer-norecover/threading-multi/visibility-hidden/lib-beast.a" "bin.v2/libs/coroutine/build/gcc-10.0/debug/link-static/thread-sanitizer-norecover/threading-multi/visibility-hidden/libboost_coroutine.a" "bin.v2/libs/context/build/gcc-10.0/debug/link-static/thread-sanitizer-norecover/threading-multi/visibility-hidden/libboost_context.a" "bin.v2/libs/filesystem/build/gcc-10.0/debug/link-static/thread-sanitizer-norecover/threading-multi/visibility-hidden/libboost_filesystem.a" "bin.v2/libs/atomic/build/gcc-10.0/debug/link-static/thread-sanitizer-norecover/threading-multi/visibility-hidden/libboost_atomic.a" -Wl,-Bstatic -Wl,-Bdynamic -lrt -Wl,--end-group -m64 -pthread -g -fvisibility=hidden -fvisibility-inlines-hidden -fsanitize=thread -fno-sanitize-recover=thread -fno-omit-frame-pointer
So, now we know that something inside the 208 Asio headers must be involved.
1. Why is SEGV not happening in the CMake build?
Surely, here we should be able to spot difference in compilation flags? Ever-so-slightly redacted to remove spelling differences (left = CMake, right = b2):
I used the process of elimination, figured out that the culprit is -fno-inline -O0. Somehow it leads to recursive TSAN errors:
beast.core.buffered_read_stream
ThreadSanitizer:DEADLYSIGNAL
==2827==ERROR: ThreadSanitizer: SEGV on unknown address 0x60000212fff8 (pc 0x7f319a938bfc bp 0x7f3196fbe1c0 sp 0x7f3196fbe1a8 T2829)
==2827==The signal is caused by a WRITE memory access.
ThreadSanitizer:DEADLYSIGNAL
ThreadSanitizer: nested bug in the same thread, aborting.
Observations without -fno-inline:
from -O2 the symptoms go away
at -O1 the symptoms go away if NDEBUG is defined
at -O0 the symptom is there regardless
This suggests that a NDEBUG-sensitive piece of code could be involved. This might be a lead to guide minimization.
Comparing preprocessed sources may highlight specific possible causes. My main suspicions are the revamped spawned_thread_base in asio/spawn.hpp, source-locations and or cancellation slots.
As a courtesy, here's the preprocessed-buffered-stream-reader.zip containing 4 files (/home/sehe/{with,without}-NDEBUG.i.asio1.{79,80}.0).
Thanks everyone who chipped in on this.
I was was able to get some help from the legendary Chris Kohlhoff, author of the Asio library.
Quoting:
It seems that thread-sanitizer does not correctly handle coroutine/fiber stacks that migrate between threads. This would either be considered a bug in thread-sanitizer, or perhaps a feature request for it.
The issue is in the thread sanitiser library itself.
The workaround in this case is to restructure the code so that the initiation of the fiber takes place on the same thread as the one in which it makes progress.
Here's the old (correct but not TSAN-compatible) code:
template<class F0, class... FN>
inline
void
enable_yield_to::
spawn(F0&& f, FN&&... fn)
{
asio::spawn(ioc_,
[&](yield_context yield)
{
f(yield);
std::lock_guard<std::mutex> lock{m_};
if(--running_ == 0)
cv_.notify_all();
}
, boost::coroutines::attributes(2 * 1024 * 1024));
spawn(fn...);
}
And here's the code with the workaround:
template<class F0, class... FN>
inline
void
enable_yield_to::
spawn(F0&& f, FN&&... fn)
{
// dispatch of spawn is a workaround for
// https://github.com/boostorg/beast/issues/2499
asio::dispatch(ioc_,
[&]
{
asio::spawn(ioc_,
[&](yield_context yield)
{
f(yield);
std::lock_guard<std::mutex> lock{m_};
if(--running_ == 0)
cv_.notify_all();
}
, boost::coroutines::attributes(2 * 1024 * 1024));
});
spawn(fn...);
}
My statically linked CryptoPP code (when called via Matlab mex on Linux) jumps into the libmwflcryptocryptopp.so binary (and then it freezes).
How can my code jump into a foreign .so instead of the statically linked library?
from function CryptoPP::SourceTemplate<CryptoPP::FileStore>::PumpAll2 in my vcpkg_installed/.../cryptopp/filters.h:1443 it jumps into CryptoPP::BufferedTransformation::TransferAllTo2 from Matlabs own libmwflcryptocryptopp.so file.
the gdb stack trace
#0 0x00007ffff02e6cab in CryptoPP::BufferedTransformation::Peek(unsigned char&) const () from /usr/local/MATLAB/R2020b/bin/glnxa64/libmwflcryptocryptopp.so
#1 0x00007ffff02e6c19 in CryptoPP::BufferedTransformation::AnyRetrievable() const () from /usr/local/MATLAB/R2020b/bin/glnxa64/libmwflcryptocryptopp.so
#2 0x00007ffff02e6ffe in CryptoPP::BufferedTransformation::TransferMessagesTo2(CryptoPP::BufferedTransformation&, unsigned int&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, bool) () from /usr/local/MATLAB/R2020b/bin/glnxa64/libmwflcryptocryptopp.so
#3 0x00007ffff02e7129 in CryptoPP::BufferedTransformation::TransferAllTo2(CryptoPP::BufferedTransformation&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, bool) () from /usr/local/MATLAB/R2020b/bin/glnxa64/libmwflcryptocryptopp.so
#4 0x00007fff9758aa4d in CryptoPP::SourceTemplate<CryptoPP::FileStore>::PumpAll2 (this=0x7fffdd87fbc0, blocking=<optimized out>)
at /home/keinkoenig/build-o1/vcpkg_installed/x64-linux/include/cryptopp/filters.h:1443
#5 0x00007fff9758a130 in CryptoPP::Source::PumpAll (this=0x7fffdd87fbc0) at /home/keinkoenig/build-o1/vcpkg_installed/x64-linux/include/cryptopp/filters.h:1420
#6 CryptoPP::Source::SourceInitialize (parameters=warning: RTTI symbol not found for class 'CryptoPP::AlgorithmParameters'
..., pumpAll=true, this=0x7fffdd87fbc0) at /home/keinkoenig/build-o1/vcpkg_installed/x64-linux/include/cryptopp/filters.h:1420
#7 CryptoPP::FileSource::FileSource (attachment=0x7fffd6063160, pumpAll=true, in=..., this=0x7fffdd87fbc0) at /home/keinkoenig/build-o1/vcpkg_installed/x64-linux/include/cryptopp/files.h:102
#8 mexFunction (nlhs=<optimized out>, plhs=<optimized out>, nrhs=<optimized out>, prhs=<optimized out>) at /home/keinkoenig/src/CryptoppMinimal/CryptoppMinimal.cpp:18
minmal example code
#include <mex.h>
#include <cryptopp/files.h>
#include <cryptopp/aes.h>
#include <cryptopp/modes.h>
#include <fstream>
unsigned char Key[CryptoPP::AES::DEFAULT_KEYLENGTH] = { 0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xAA,0xBB,0xCC,0xDD,0xEE,0xFF };
unsigned char IV[CryptoPP::AES::BLOCKSIZE] = { 0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xAA,0xBB,0xCC,0xDD,0xEE,0xFF };
extern "C"
{
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
std::ifstream instream(mxArrayToString(prhs[0]));
std::string decryptedString;
CryptoPP::CFB_Mode<CryptoPP::AES>::Decryption cfbDecryption(Key, sizeof(Key), IV);
CryptoPP::FileSource(instream, true, new CryptoPP::StreamTransformationFilter( cfbDecryption, new CryptoPP::StringSink( decryptedString)));
}
}
and the CMakeLists linking
set(PROJECT_NAME CryptoppMinimal)
...
target_link_libraries(${PROJECT_NAME} PRIVATE cryptopp-static)
starting console matlab with gdb
/usr/local/MATLAB/R2020b/bin/matlab -nojvm -Dgdb
and running the test mex function in Matlab
addpath ~/build-o1/bin
CryptoppMinimal('/home/keinkoenig/src/encrypted.dat')
TL;DR; your cryptocpp is getting LD_PRELOADed by Matlab, even if it is being linked statically.
A big hint towards what is going on is the path of the .so being loaded:
#3 0x00007ffff02e7129 [...] /usr/local/MATLAB/R2020b/bin/glnxa64/libmwflcryptocryptopp.so
Compared to the headers that were used during compilation:
#5 [...] /home/keinkoenig/build-o1/vcpkg_installed/x64-linux/include/cryptopp/filters.h:1420
What this shows is that not only is cryptocpp running from a dynamic library, it's running from a different .so than the one you compiled against!
How could this happen? If a static library is compiled with position independent code, calls to functions in the library will still be dispatched in the same way they would for a dynamic library: with a jump table.
And much like dynamic libraries, if the jump table is already populated when the code loaded, the existing function pointers will be reused.
So, if:
crypto-cpp was compiled with -fPIC
Matlab happens to have a crypto-cpp .so loaded prior to your code being loaded.
Then the version of crypto-cpp embedded within your project will be ignored, and Matlab's will be used instead.
Having a quick look at the crypto-cpp project, we find, in the Makefile:
# Add -fPIC for targets *except* X86, X32, Cygwin or MinGW
ifeq ($(IS_X86)$(IS_CYGWIN)$(IS_MINGW),000)
ifeq ($(findstring -fpic,$(CXXFLAGS))$(findstring -fPIC,$(CXXFLAGS)),)
CRYPTOPP_CXXFLAGS += -fPIC
endif
endif
Which appears to confirm this is what's going on.
You can confirm that by loading and running your library from outside Matlab, and it should be calling the static library's code as expected.
I have a .ini file contains below data
[SYSTEM]
num_of_vps = 1
And I have this code to read an element in .ini file. (uint defined as typedef unsigned int uint)
boost::property_tree::ptree pt;
boost::property_tree::ini_parser::read_ini(iniFilePath, pt);
hwCount = pt.get<uint>("SYSTEM.num_of_vps");
I created a from files including above code and called it in a wrapper function in main.cc file. Then I am getting below error
terminate called after throwing an instance of 'boost_1_68_0::exception_detail::clone_impl<boost_1_68_0::exception_detail::error_info_injector<boost_1_68_0::property_tree::ptree_bad_data> >'
what(): conversion of data to type "j" failed
stack trace
#12 0x00002aaab613fcd5 in abort () from /lib64/libc.so.6
#13 0x00002aaab9b29315 in __gnu_cxx::__verbose_terminate_handler () at ../../../../src/gcc-7.3.0/libstdc++-v3/libsupc++/vterminate.cc:95
#14 0x00002aaab9a9e8f6 in __cxxabiv1::__terminate (handler=<optimized out>) at ../../../../src/gcc-7.3.0/libstdc++-v3/libsupc++/eh_terminate.cc:47
#15 0x00002aaab9a9e941 in std::terminate () at ../../../../src/gcc-7.3.0/libstdc++-v3/libsupc++/eh_terminate.cc:57
#16 0x00002aaab9a9ea74 in __cxxabiv1::__cxa_throw (obj=<optimized out>, tinfo=0x2aaab9e1ff60 <typeinfo for boost_1_68_0::exception_detail::clone_impl<boost_1_68_0::exception_detail::error_info_injector<boost_1_68_0::property_tree::ptree_bad_data> >>, dest=0x2aaab99bef18 <boost_1_68_0::exception_detail::clone_impl<boost_1_68_0::exception_detail::error_info_injector<boost_1_68_0::property_tree::ptree_bad_data> >::~clone_impl()>) at ../../../../src/gcc-7.3.0/libstdc++-v3/libsupc++/eh_throw.cc:93
#17 0x00002aaab99bec82 in boost_1_68_0::throw_exception<boost_1_68_0::exception_detail::error_info_injector<boost_1_68_0::property_tree::ptree_bad_data> > (e=...) at throw_exception.hpp:72
#18 0x00002aaab99be576 in boost_1_68_0::exception_detail::throw_exception_<boost_1_68_0::property_tree::ptree_bad_data> (x=..., current_function=0x2aaab9b45fc0 <boost_1_68_0::enable_if<boost_1_68_0::property_tree::detail::is_translator<boost_1_68_0::property_tree::stream_translator<char, std::char_traits<char>, std::allocator<char>, unsigned int> >, unsigned int>::type boost_1_68_0::property_tree::basic_ptree<std::string, std::string, std::less<std::string> >::get_value<unsigned int, boost_1_68_0::property_tree::stream_translator<char, std::char_traits<char>, std::allocator<char>, unsigned int> >(boost_1_68_0::property_tree::stream_translator<char, std::char_traits<char>, std::allocator<char>, unsigned int>) const::__PRETTY_FUNCTION__> "typename boost_1_68_0::enable_if<boost_1_68_0::property_tree::detail::is_translator<Translator>, Type>::type boost_1_68_0::property_tree::basic_ptree<Key, Data, KeyCompare>::get_value(T"..., file=0x2aaab9b45830 "property_tree/detail/ptree_implementation.hpp", line=675) at throw_exception.hpp:89
#19 0x00002aaab99be01e in boost_1_68_0::property_tree::basic_ptree<std::string, std::string, std::less<std::string> >::get_value<unsigned int, boost_1_68_0::property_tree::stream_translator<char, std::char_traits<char>, std::allocator<char>, unsigned int> > (this=0xbc86c8, tr=...) at property_tree/detail/ptree_implementation.hpp:673
#20 0x00002aaab99bd6a5 in boost_1_68_0::property_tree::basic_ptree<std::string, std::string, std::less<std::string> >::get_value<unsigned int> (this=0xbc86c8) at property_tree/detail/ptree_implementation.hpp:683
#21 0x00002aaab99bc545 in boost_1_68_0::property_tree::basic_ptree<std::string, std::string, std::less<std::string> >::get<unsigned int> (this=0x7fffffff9470, path=...) at property_tree/detail/ptree_implementation.hpp:754
#22 0x00002aaab99bba83 in MyRT::DUTConfigFile::readIniFile (this=0xbc5d50, iniFilePath=...) at DUTConfigFile.cpp:231
#23 0x00002aaab99ba8d2 in MyRT::DUTConfigFile::DUTConfigFile (this=0xbc5d50, iniFilePath=..., configFilePath=...) at DUTConfigFile.cpp:26
#24 0x00002aaab99c0839 in setupMyConfigs () at SimXLInterface.cpp:83
#31 0x0000000000408847 in main ()
I tried gdb and it is throwing an exception when converting string to uint using istringstream. Below are the two types of the transition
template<class K, class D, class C>
template<class Type> inline
Type basic_ptree<K, D, C>::get_value() const
{
return get_value<Type>(
typename translator_between<data_type, Type>::type());
}
(gdb) p typeid(Type).name()
$2 = 0x2aaab5c33c91 <typeinfo name for unsigned int> "j"
(gdb) p typeid(data_type).name()
could not find typeinfo symbol for 'std::basic_string<char, std::char_traits<char>, std::allocator<char> >'
Below function in basic_ios.h throws a __throw_bad_cast() exception (here this->_M_num_get = NULL)
const __num_get_type& __ng = __check_facet(this->_M_num_get);
Internal build automation Makefile is addding -D_GLIBCXX_USE_CXX11_ABI=0 to object file creation by default
g++ -I/usr/myboost/boost/boost -Wall -std=c++14 -fopenmp -m64 -msse2 -fPIC \
-D_GLIBCXX_USE_CXX11_ABI=0 -g -o *.o -fPIC -c *.cpp
.so building command
g++ -m64 -msse2 -pthread -shared -static-libstdc++ -static-libgcc \
-Wl,-znoexecstack -g -o runtime.so *.o -ldl -lrt -lz -fopenmp -lcrypto
If I change it to read a std::string type and later convert it to uint, it is working without an exception
std::string strHwCount = pt.get<std::string>("SYSTEM.num_of_vps");
hwCount = static_cast<uint>(std::stoul(strHwCount));
I am using boost-1.68 and gcc-7.3. Dose this have to do with the ABI macro that I'm using? Is there a way to resolve this without above workaround
UPDATE
First of all I am migrating project from cmake to a internal custom build automation (some enhanced version of Makefile). In cmake this worked fine. Based on sehe's answer I did some further analysis.
In a small standalone example this error is not reproducible (using both local string and actual .ini file read).
When going through gdb, In file boost/property_tree/stream_translator.hpp, I could find the read value from the file (changed to num_of_vps = 4 in file) in the both string and istringstream.
optional<Type> basic_ptree<K, D, C>
::get_value_optional(Translator tr) const
{
return tr.get_value(data());
}
(gdb) p data()
$1 = "4"
boost_1_68_0::optional<E> get_value(const internal_type &v) {
std::basic_istringstream<Ch, Traits, Alloc> iss(v);
iss.imbue(m_loc);
E e;
customized::extract(iss, e);
....
}
(gdb) p v
$1 = "4"
(gdb) p iss.str()
$2 = "4"
Inside above customized::extract(iss, e) function there is a string to unsigned int conversion using a basic_istream
static void extract(std::basic_istream<Ch, Traits>& s, E& e) {
s >> e;
if(!s.eof()) { s >> std::ws; }
}
Inside this >> operator there is a _M_extract function and inside that __check_facet function throws the exception (by cheking NULL of this->_M_num_get )
basic_istream<_CharT, _Traits>::
_M_extract(_ValueT& __v) {
__try {
const __num_get_type& __ng = __check_facet(this->_M_num_get);
...
inline const _Facet&
__check_facet(const _Facet* __f) {
if (!__f)
__throw_bad_cast();
return *__f;
}
stack trace above scenario
#0 std::istream::_M_extract<unsigned int>(unsigned int&) (__f=0x0) at gcc-7.3.0/objdir/x86_64-centos-linux/libstdc++-v3/include/bits/basic_ios.h:49
#1 std::istream::operator>>(unsigned int&) (this=0x7fffffff9980, __n=#0x7fffffff997c: 0)
#2 boost_1_68_0::property_tree::customize_stream::extract(std::istream&, unsigned int&) (s=..., e=#0x7fffffff997c: 0)
#3 boost_1_68_0::property_tree::stream_translator<char, std::char_traits<char>, std::allocator<char>, unsigned int>::get_value(std::string const&) (this=0x7fffffff9b58, v="4")
I could see the same behavior in the another place that we are using std::stringstream . Variable ss shows empty even with << operator adding strings
std::stringstream ss;
ss << std::setw(8) << std::setfill('0') << std::hex << firmId;
I had a concern about that the ABI flag causing this, but I was able to remove it and the issue is still there. I searched about this kind of istream issues but couldn't find anything useful.
Your input is corrupt. Likely it's using an encoding or code-points that you didn't expect, but don't stand out for a human reader.
Live On Coliru
#include <boost/property_tree/ini_parser.hpp>
#include <iostream>
int main() {
std::istringstream iss(R"([SYSTEM]
num_of_vps = 1)");
boost::property_tree::ptree pt;
read_ini(iss, pt);
uint hwCount = pt.get<uint>("SYSTEM.num_of_vps");
std::cout << "hwCount: " << hwCount << "\n";
}
Prints
hwCount: 1
However, if you change the input to be e.g.
std::istringstream iss(R"([SYSTEM]
num_of_vps = 1)");
(Note that the space after = is now < > 160, \240, U+00A0 NO-BREAK SPACE, ^KNS, ) it prints:
Live On Coliru
Prints
terminate called after throwing an instance of 'boost::wrapexcept<boost::property_tree::ptree_bad_data>'
what(): conversion of data to type "j" failed
With a tweak to the gcc command I was able to run the executable with the .so with all sstream usages. Previously the gcc linking command was
g++ -m64 -msse2 -pthread -shared -static-libstdc++ -static-libgcc \
-Wl,-znoexecstack -g -o runtime.so *.o -ldl -lrt -lz -fopenmp -lcrypto
Removing -static-libstdc++ and dynamically linking libstdc++.so worked
g++ -m64 -msse2 -pthread -shared -static-libgcc -Wl,-znoexecstack -o runtime.so *.o \
-fPIC -ldl -lrt -lz -fopenmp -lcrypto -Wl,-rpath,/depot/gcc-7.3.0/lib64
I tried to overload QDebug::operator<< for std::string. I know that we can debug (using qDebug()) std::string objects using its std::string::c_str() function but I want to avoid typing .c_str each time.
Here is my attempt
#include <QDebug>
#include <string>
inline const QDebug& operator<< (const QDebug& qDebugObj, const std::string& str) {
return qDebugObj << str.c_str();
}
int main()
{
std::string s = "4444555";
qDebug() << s;
}
This program produces segmentation fault. What is incorrect with this code?
Here is the stack:
#1 0x00000037c407a911 in malloc () from /lib64/libc.so.6
#2 0x00000037ca8bd09d in operator new(unsigned long) () from /usr/lib64/libstdc++.so.6
#3 0x00000037ca89c3c9 in std::basic_string<char, std::char_traits<char>, std::allocator<char> >::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) ()
from /usr/lib64/libstdc++.so.6
#4 0x00000037ca89cde5 in ?? () from /usr/lib64/libstdc++.so.6
#5 0x00000037ca89cf33 in std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&) () from /usr/lib64/libstdc++.so.6
#6 0x00000000004012ca in operator<< (qDebugObj=..., str="4444555") at main.cpp:5
If you look at every overloaded output operator, you will see that none have a const qualifier. Which is your problem, you try to modify a constant object. Remove the const qualification of the qDebugObject and the return value.
You should have compiler warnings screaming about it, and if not then you need to enable more warnings (at least use -Wall when compiling with GCC/clang).
The actual problem, as answered by Mike Seymour in a comment, is that your overload will be called recursively until you get a stack overflow.
A way of bypassing that might be to convert the string to something else, like for example a QString:
return qDebugObj << QString::fromStdString(str);
In addition to your attempt to make an output stream const, you also failed to follow the instructions in the QT documentation
// with the fixed output operator
inline QDebug operator<<(QDebug dbg, const std::string& str)
{
dbg.nospace() << QString::fromStdString(str);
return dbg.space();
}
QT wants the output operator passed by copy (not by reference). There used to be a reason for that, but I cannot remember what it was.
I recently read about using GCC's code generation features (specifically, the -finstrument-functions compiler flag) to easily add instrumentation to my programs. I thought it sounded really cool and went to try it out on a previous C++ project. After several revisions of my patch, I found that any time I tried to use an STL container or print to stdout using C++ stream I/O, my program would immediately crash with a segfault. My first idea was to maintain a std::list of Event structs
typedef struct
{
unsigned char event_code;
intptr_t func_addr;
intptr_t caller_addr;
pthread_t thread_id;
timespec ts;
}Event;
list<Event> events;
which would be written to a file when the program terminated. GDB told me that when I tried to add an Event to the list, calling events.push_back(ev) itself initiated an instrumentation call. This wasn't terrible surprising and made sense after I thought about it for a bit, so on to plan 2.
The example in the blog which got me involved in all this mess didn't do anything crazy, it simply wrote a string to a file using fprintf(). I didn't think there would be any harm in using C++'s stream-based I/O instead of the older (f)printf(), but that assumption proved to be wrong. This time, instead of a nearly-infinite death spiral, GDB reported a fairly normal-looking descent into the standard library... followed by a segfault.
A Short Example
#include <list>
#include <iostream>
#include <stdio.h>
using namespace std;
extern "C" __attribute__ ((no_instrument_function)) void __cyg_profile_func_enter(void*, void*);
list<string> text;
extern "C" void __cyg_profile_func_enter(void* /* unused */, void* /* unused */)
{
// Method 1
text.push_back("NOPE");
// Method 2
cout << "This explodes" << endl;
// Method 3
printf("This works!");
}
Sample GDB Backtrace
Method 1
#0 _int_malloc (av=0x7ffff7380720, bytes=29) at malloc.c:3570
#1 0x00007ffff704ca45 in __GI___libc_malloc (bytes=29) at malloc.c:2924
#2 0x00007ffff7652ded in operator new(unsigned long) ()
from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3 0x00007ffff763ba89 in std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4 0x00007ffff763d495 in char* std::string::_S_construct<char const*>(char const*, char const*, std::allocator<char> const&, std::forward_iterator_tag) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5 0x00007ffff763d5e3 in std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6 0x00000000004028c1 in __cyg_profile_func_enter () at src/instrumentation.cpp:82
#7 0x0000000000402c6f in std::move<std::string&> (__t=...) at /usr/include/c++/4.6/bits/move.h:82
#8 0x0000000000402af5 in std::list<std::string, std::allocator<std::string> >::push_back(std::string&&) (this=0x6055c0, __x=...) at /usr/include/c++/4.6/bits/stl_list.h:993
#9 0x00000000004028d2 in __cyg_profile_func_enter () at src/instrumentation.cpp:82
#10 0x0000000000402c6f in std::move<std::string&> (__t=...) at /usr/include/c++/4.6/bits/move.h:82
#11 0x0000000000402af5 in std::list<std::string, std::allocator<std::string> >::push_back(std::string&&) (this=0x6055c0, __x=...) at /usr/include/c++/4.6/bits/stl_list.h:993
#12 0x00000000004028d2 in __cyg_profile_func_enter () at src/instrumentation.cpp:82
#13 0x0000000000402c6f in std::move<std::string&> (__t=...) at /usr/include/c++/4.6/bits/move.h:82
#14 0x0000000000402af5 in std::list<std::string, std::allocator<std::string> >::push_back(std::string&
...
Method 2
#0 0x00007ffff76307d1 in std::ostream::sentry::sentry(std::ostream&) ()
from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#1 0x00007ffff7630ee9 in std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long) ()
from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#2 0x00007ffff76312ef in std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*) ()
from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3 0x000000000040251e in __cyg_profile_func_enter () at src/instrumentation.cpp:81
#4 0x000000000040216d in _GLOBAL__sub_I__ZN8GLWindow7attribsE () at src/glwindow.cpp:164
#5 0x0000000000402f2d in __libc_csu_init ()
#6 0x00007ffff6feb700 in __libc_start_main (main=0x402cac <main()>, argc=1, ubp_av=0x7fffffffe268,
init=0x402ed0 <__libc_csu_init>, fini=<optimized out>, rtld_fini=<optimized out>,
stack_end=0x7fffffffe258) at libc-start.c:185
#7 0x0000000000401589 in _start ()
Environment:
Ubuntu Linux 12.04 (x64)
GCC 4.6.3
Intel 3750K CPU
8GB RAM
The problem with using cout in the instrumentation function is that the instrumentation function is being called by __libc_csu_init() which is a very early part of the runtime's initialization - before global C++ objects get a chance to be constructed (in fact, I think __libc_csu_init() is responsible for kicking off those constructors - at least indirectly).
So cout hasn't had a chance to be constructed yet and trying to use it doesn't work very well...
And that may well be the problem you run into with trying to use std::List after fixing the infinite recursion (mentioned in Dave S' answer).
If you're willing to lose some instrumentation during initialization, you can do something like:
#include <iostream>
#include <stdio.h>
int initialization_complete = 0;
using namespace std;
extern "C" __attribute__ ((no_instrument_function)) void __cyg_profile_func_enter(void*, void*);
extern "C" void __cyg_profile_func_enter(void* /* unused */, void* /* unused */)
{
if (!initialization_complete) return;
// Method 2
cout << "This explodes" << endl;
// Method 3
printf("This works! ");
}
void foo()
{
cout << "foo()" << endl;
}
int main()
{
initialization_complete = 1;
foo();
}
The first case seems to be an infinite loop, resulting in stack overflow. This is probably because std::list is a template, and it's code is generated as part of the translation unit where you're using it. This causes it to get instrumented as well. So you call push_back, which calls the handler, which calls push_back, ...
The second, if I had to guess, might be similar, though it's harder to tell.
The solution is to compile the instrumentation functions separately, without the -finstrument-functions. Note, the example blog compiled the trace.c separately, without the option.