Segfault с asio автономно, когда классы в отдельных файлах
Ниже приведен минимальный пример, который я могу получить. Это должно быть в отдельных файлах, поскольку это, по-видимому, вызывает ошибку ошибки сегментации.
Я использую Mingw x32 4.8.1 с автономным автономным доступом 1.10.6. Я также тестировал TDM GCC 4.7.1 и Mingw x64 4.8.1. Все они под Windows производят одно и то же segfault. В Linux нет такой проблемы с последней версией GCC.
изменить: Я только что закончил тестирование на Mingw-w64 5.2.0 (его сборка Mingw-Builds). Такая же проблема.
Я также тестировал компиляцию с TDM GCC 4.8.1 на новой виртуальной машине и получал тот же самый segfault. Изменить: я также тестировал на совершенно другой машине с TDM GCC 4.8.1
Во всех случаях я использую -std=c++11
, -g
и -Wall
. Я также скомпилирован с -g
и имеет тот же результат. Мне нужен флаг С++ 11, потому что я не хочу зависимости от boost, просто asio.
При использовании следующего кода в одном файле main.cpp
нет проблем, и программа, похоже, работает как ожидалось. Однако, если я помещаю каждый класс в свой собственный файл *.hpp
и *.cpp
, я получаю segfault в конструкторе классов Server
.
Я снова вернулся и все вернул в main.cpp
и начал перемещать каждый класс по одному. Сегфакт начинается после окончательного класса, Client
помещается в его собственные файлы.
Кроме того, поскольку я помещал все классы в один файл и перемещал их и т.д., я убедился, что любые ненужные объектные файлы не были связаны с моим .exe.
Этот код начал намного больше, но он сузился до этого.
Server.hpp
#ifndef SERVER_HPP_INCLUDED
#define SERVER_HPP_INCLUDED
#include <string>
#include <memory>
#define ASIO_STANDALONE
#include <asio.hpp>
using namespace asio::ip;
namespace network
{
class Server
{
public:
Server(asio::io_service& ioService, uint16_t port);
private:
tcp::acceptor m_acceptor;
};
}
#endif // SERVER_HPP_INCLUDED
server.cpp
#include "Server.hpp"
using namespace network;
#include <iostream>
Server::Server(asio::io_service& ioService, uint16_t port)
: m_acceptor(ioService, tcp::endpoint(tcp::v4(),port))
{
}
Client.hpp
#ifndef CLIENT_HPP_INCLUDED
#define CLIENT_HPP_INCLUDED
#include <vector>
#define ASIO_STANDALONE
#include <asio.hpp>
using namespace asio::ip;
namespace network
{
class Client
{
public:
Client(asio::io_service& ioService);
private:
asio::steady_timer m_timer;
};
}
#endif // CLIENT_HPP_INCLUDED
client.cpp
#include "Client.hpp"
using namespace network;
#include <iostream>
Client::Client(asio::io_service& ioService)
: m_timer(ioService)
{
}
main.cpp
#include <iostream>
#define ASIO_STANDALONE
#include <asio.hpp>
using namespace asio::ip;
#include "Server.hpp"
int main()
{
try
{
uint16_t peerRequestPort = 63000;
asio::io_service io_service;
network::Server server(io_service,peerRequestPort);
}
catch(std::exception& e)
{
std::cout << e.what() << std::endl;
}
return 0;
}
Здесь столбец из GDB:
#0 00406729 asio::detail::service_registry::keys_match(key1=..., key2=...) (F:/GameDev/asio-1.10.6/asio-1.10.6/include/asio/detail/impl/service_registry.ipp:89)
#1 ?? 0x0040696e in asio::detail::service_registry::do_use_service (this=0x5d2f10, key=..., factory=0x406b44 <asio::detail::service_registry::create<asio::socket_acceptor_service<asio::ip::tcp> >(asio::io_service&)>) (F:/GameDev/asio-1.10.6/asio-1.10.6/include/asio/detail/impl/service_registry.ipp:113)
#2 004068B6 asio::detail::service_registry::use_service<asio::socket_acceptor_service<asio::ip::tcp> >(this=0x5d2f10) (F:/GameDev/asio-1.10.6/asio-1.10.6/include/asio/detail/impl/service_registry.hpp:47)
#3 00403857 asio::use_service<asio::socket_acceptor_service<asio::ip::tcp> >(ios=...) (F:/GameDev/asio-1.10.6/asio-1.10.6/include/asio/impl/io_service.hpp:32)
#4 004039B3 asio::basic_io_object<asio::socket_acceptor_service<asio::ip::tcp>, true>::basic_io_object(this=0x28fe48, io_service=...) (F:/GameDev/asio-1.10.6/asio-1.10.6/include/asio/basic_io_object.hpp:182)
#5 00403B29 asio::basic_socket_acceptor<asio::ip::tcp, asio::socket_acceptor_service<asio::ip::tcp> >::basic_socket_acceptor(this=0x28fe48, io_service=..., endpoint=..., reuse_addr=true) (F:/GameDev/asio-1.10.6/asio-1.10.6/include/asio/basic_socket_acceptor.hpp:137)
#6 00401D3B network::Server::Server(this=0x28fe48, ioService=..., port=63000) (F:\GameDev\Dischan\Tests\Server.cpp:7)
#7 004018F1 main() (F:\GameDev\Dischan\Tests\main.cpp:17)
И, наконец, вывод из Dr Memory:
Dr. Memory version 1.8.0 build 8 built on Sep 9 2014 16:27:02
Dr. Memory results for pid 5296: "tests.exe"
Application cmdline: "tests.exe"
Recorded 108 suppression(s) from default C:\Program Files (x86)\Dr. Memory\bin\suppress-default.txt
Error #1: UNADDRESSABLE ACCESS: reading 0x00000007-0x0000000b 4 byte(s)
# 0 asio::detail::service_registry::keys_match [F:/GameDev/asio-1.10.6/asio-1.10.6/include/asio/detail/impl/service_registry.ipp:89]
# 1 asio::detail::service_registry::do_use_service [F:/GameDev/asio-1.10.6/asio-1.10.6/include/asio/detail/impl/service_registry.ipp:113]
# 2 asio::detail::service_registry::use_service<> [F:/GameDev/asio-1.10.6/asio-1.10.6/include/asio/detail/impl/service_registry.hpp:47]
# 3 asio::use_service<> [F:/GameDev/asio-1.10.6/asio-1.10.6/include/asio/impl/io_service.hpp:32]
# 4 asio::basic_io_object<>::basic_io_object [F:/GameDev/asio-1.10.6/asio-1.10.6/include/asio/basic_io_object.hpp:182]
# 5 asio::basic_socket_acceptor<>::basic_socket_acceptor [F:/GameDev/asio-1.10.6/asio-1.10.6/include/asio/basic_socket_acceptor.hpp:137]
# 6 network::Server::Server [F:/GameDev/Dischan/Tests/Server.cpp:7]
# 7 main [F:/GameDev/Dischan/Tests/main.cpp:17]
Note: @0:00:00.780 in thread 7464
Note: instruction: mov 0x04(%eax) -> %eax
Error #2: LEAK 36 direct bytes 0x02530860-0x02530884 + 124 indirect bytes
# 0 replace_operator_new [d:\drmemory_package\common\alloc_replace.c:2609]
# 1 asio::io_service::io_service [F:/GameDev/asio-1.10.6/asio-1.10.6/include/asio/impl/io_service.ipp:39]
# 2 main [F:/GameDev/Dischan/Tests/main.cpp:15]
===========================================================================
FINAL SUMMARY:
DUPLICATE ERROR COUNTS:
SUPPRESSIONS USED:
ERRORS FOUND:
1 unique, 1 total unaddressable access(es)
0 unique, 0 total uninitialized access(es)
0 unique, 0 total invalid heap argument(s)
0 unique, 0 total GDI usage error(s)
0 unique, 0 total handle leak(s)
0 unique, 0 total warning(s)
1 unique, 1 total, 160 byte(s) of leak(s)
0 unique, 0 total, 0 byte(s) of possible leak(s)
ERRORS IGNORED:
14 potential error(s) (suspected false positives)
(details: C:\Users\User\AppData\Roaming\Dr. Memory\DrMemory-tests.exe.5296.000\potential_errors.txt)
12 potential leak(s) (suspected false positives)
(details: C:\Users\User\AppData\Roaming\Dr. Memory\DrMemory-tests.exe.5296.000\potential_errors.txt)
24 unique, 24 total, 2549 byte(s) of still-reachable allocation(s)
(re-run with "-show_reachable" for details)
Details: C:\Users\User\AppData\Roaming\Dr. Memory\DrMemory-tests.exe.5296.000\results.txt
Я просто не понимаю, почему я получаю segfault. Даже после комментирования всего содержательного кода он все еще встречается.
ИЗМЕНИТЬ
Я редактировал приведенный выше код, чтобы показать, что только конструктор Server
вызывает проблему и содержимое каждого файла. (Я снова гарантировал, что только эти объектные файлы скомпилированы и связаны).
РЕДАКТИРОВАТЬ 2
Я тестировал это с помощью TDM GCC 4.7.1, Mingw Builds x64 4.8.1 и Mingw Builds x32 4.8.1. Тот же результат для всех из них.
РЕДАКТИРОВАТЬ 3
Я еще немного снизил код. Теперь, в Client
, если я удалю любые объекты asio
, для которых требуется создать asio::io_service&
, тогда segfault не существует. Но любой из типов asio
, которые я пробовал до сих пор, произвел одно и то же segfault. Это не проблема в классе Server
, например, asio::acceptor
. Самое замечательное в том, что не существует экземпляра Client
, поэтому почему он влияет на программу и создает segfault в конструкторе Server
. Это странно.
EDIT 4
Теперь я полностью удалил Server.hpp
и Server.cpp
и обновил main.cpp
до этого:
main.cpp
#include <iostream>
#define ASIO_STANDALONE
#include <asio.hpp>
using namespace asio::ip;
int main()
{
try
{
uint16_t peerRequestPort = 63000;
asio::io_service io_service;
auto protocol = tcp::v4();
tcp::endpoint endpoint(protocol,peerRequestPort);
tcp::acceptor m_acceptor(io_service, endpoint);
}
catch(std::exception& e)
{
std::cout << e.what() << std::endl;
}
return 0;
}
Я все еще получаю segfault, а callstack отражает отсутствие конструктора Server
. Segfault все еще находится в одном месте. Результаты DrMemory выглядят примерно так же.
Как и раньше, если я не свяжу объектный файл Client
, у меня нет проблем.
РЕДАКТИРОВАТЬ 5
В соответствии с запросом, вот журнал сборки из Code:: Blocks
g++.exe -std=c++11 -Wall -D_WIN32_WINNT=0x0501 -g -I..\..\asio-1.10.6\asio-1.10.6\include -c F:\GameDev\Dischan\Tests\Client.cpp -o obj\Debug\Client.o
g++.exe -std=c++11 -Wall -D_WIN32_WINNT=0x0501 -g -I..\..\asio-1.10.6\asio-1.10.6\include -c F:\GameDev\Dischan\Tests\main.cpp -o obj\Debug\main.o
g++.exe -o Build\Debug\Windows\Tests.exe obj\Debug\Client.o obj\Debug\main.o -lws2_32 -lwsock32
Output file is Build\Debug\Windows\Tests.exe with size 723.02 KB
Process terminated with status 0 (0 minute(s), 3 second(s))
0 error(s), 0 warning(s) (0 minute(s), 3 second(s))
РЕДАКТИРОВАТЬ 6
Теперь я начинаю выходить за пределы своих способностей, но мне удалось выявить некоторые проблемы (и я изучаю некоторые новые вещи, которые круты).
Похоже, что когда объект, требующий asio::io_service&
, создается, он добавляет "сервисы" в io_service
. Они являются статическими во всех экземплярах io_service
. Таким образом, когда запрос службы выполнен, существует цикл, который повторяется, через который представляется связанным списком уже созданных служб. Если запрошенная услуга еще не создана; он был создан.
Эта информация взята из ссылка для io_service
и от просмотра service_registry.ipp
(строка 111).
Это делается внутренне с вызовом service_registry::do_use_service
. service_registry
имеет член с именем first_service_
типа asio::io_service::service*
. Эта первая служба должна иметь член с именем next_
, который является частью связанного списка, о которой я упоминал.
Во время первого вызова service_registry::do_use_service
(когда построено asio::acceptor
) член first_service_
имеет значение 0xffffffff
, что, очевидно, неверно. Поэтому я считаю, что корень segfault.
Почему этот член имеет значение 0xffffffff
находится вне меня. Полагаю, что только старые/изворотливые машины зарезервировали этот адрес для указателей null
... но я соглашаюсь, что я мог бы уйти.
Я просто быстро проверил это, сделав следующее:
int* p = nullptr;
if(p)
std::cout << "something" << std::endl;
и установите точку останова для чтения значения. Значение p
равно 0x0
, а не 0xffffffff
.
Итак, я установил точку останова для конструктора для service_registry
(asio/detail/impl/service_registry.hpp
) и деструктора (в случае, если он был явно вызван где-то), и трех методов do_has_service
, do_use_service
и do_add_service
, Моя мысль заключалась в том, чтобы попытаться отслеживать, в какой момент first_service_
получает плохое значение.
Мне не повезло. Это единственные места, которые могут изменить значение first_service_
.
Я полагаю, это означает, что что-то испортило стек и изменило адрес для first_service_
. Но, я всего лишь любитель...
Я проверял, что адрес указателя this
для конструктора был таким же, как адрес, используемый для вызова do_use_service
, чтобы убедиться, что два экземпляра не были созданы или что-то в этом роде.
EDIT 7
Хорошо, поэтому я обнаружил, что если я скомпилирую с ASIO_DISABLE_THREADS
, я больше не получу segfault!
Но это приводит к тому, что исключение возникает, потому что я пытаюсь использовать потоки, хотя я их отключил. Который я подразумеваю, что я буду ограничен синхронными вызовами и без асинхронных вызовов. (т.е. какой смысл использовать asio?)
справочный материал здесь говорит, что ASIO_DISABLE_THREADS
Явно отключает поддержку потоковой передачи Asio независимо от того, поддерживает ли Boost потоки.
Поэтому я считаю, что это определение останавливает asio от использования потоков независимо от boost или нет; что имеет смысл.
Почему потоки могут вызвать проблему, я не знаю. Я не очень люблю это делать.
Я сдаюсь
Я отказываюсь от asio. Просмотрев код и документацию, он, похоже, был разработан с большим вниманием, а не как отдельная библиотека. По-видимому, до такой степени, когда вам нужно использовать Boost над С++ 11, чего я просто не хочу делать.
Лучшая сетевая библиотека C/С++ выглядит так, что есть много альтернатив.
Честно говоря, запуск моих собственных синхронных вызовов сокетов в моем собственном потоке звучит как лучшая идея с учетом контроля, который я получу. По крайней мере, пока asio не войдет в стандартную библиотеку и не будет реализован в Mingw-w64.
Учитывая, что asio выглядит как главный кандидат, который находится в стандартной библиотеке или, по крайней мере, это привкус, вероятно, хорошая идея придерживаться этого.
Ответы
Ответ 1
Я думаю, что ошибка сегментации возникает из-за несоответствия определения типа asio между Server.hpp
и Client.hpp
. В вашем случае это может произойти только в том случае, если boost изменяет typedefs в зависимости от определений, заданных <string>
, <memory>
или <vector>
.
Я предлагаю попробовать либо:
-
Включите те же заголовки как в Server/Client.hpp, так и в main.cpp перед тем, как включить asio.hpp:
#include <string>
#include <memory>
#include <vector>
#define ASIO_STANDALONE
#include <asio.hpp>
-
Или просто включите asio.hpp, прежде чем какие-либо другие включите в свои файлы заголовков и удалите include из main.cpp.
Ответ 2
Из моего POV это ошибка gcc. Если вы используете clang++
вместо g++.exe
, авария исчезает, даже если она не работает с порядком #include
. Передача всех исходных файлов на g++
в одном вызове также приводит к исчезновению сбоя. Это заставляет меня думать, что что-то не так с эмиссией объектов gcc COFF, поскольку clang использует компоновщик из одного и того же дистрибутива mingw.
Итак, используйте clang или примените обходной путь из ответа @Wouter Huysentruit.
Обратите внимание, что в последнем выпуске clang (3.7.0) есть еще одна ошибка, не связанная с вашей проблемой, что приводит к сбою связи. Мне пришлось использовать ночной снимок, который в любом случае является довольно стабильным.