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) есть еще одна ошибка, не связанная с вашей проблемой, что приводит к сбою связи. Мне пришлось использовать ночной снимок, который в любом случае является довольно стабильным.