c++ - Segfault con asio standalone cuando las clases en archivos separados
boost boost-asio (3)
Creo que la falla de segmentación se produce debido a la falta de coincidencia de las definiciones de tipo asio entre Server.hpp
y Client.hpp
. En su caso, esto solo podría suceder si el impulso cambia las definiciones de tipo según las definiciones establecidas por <string>
, <memory>
o <vector>
.
Lo que sugiero probar es:
Incluya los mismos encabezados en Server / Client.hpp y main.cpp antes de incluir asio.hpp:
#include <string> #include <memory> #include <vector> #define ASIO_STANDALONE #include <asio.hpp>
O simplemente incluya asio.hpp antes de cualquier otra inclusión en sus archivos de encabezado y elimine la inclusión de main.cpp.
El siguiente es el mínimo ejemplo que puedo obtener. Debe estar en archivos separados, ya que parece ser lo que causa el error de falla de segmentación.
Estoy usando Mingw x32 4.8.1 con Asio standalone 1.10.6. También he probado con TDM GCC 4.7.1 y Mingw x64 4.8.1. Todos estos en Windows producen el mismo segfault. No hay tal problema en Linux con la última versión de GCC.
edición: acabo de terminar de probar en Mingw-w64 5.2.0 (la compilación de Mingw-Builds). El mismo problema.
También he probado compilar esto con TDM GCC 4.8.1 en una máquina virtual nueva y obtener el mismo segfault. Edición: También ahora he probado en una máquina completamente diferente con TDM GCC 4.8.1
En todos los casos estoy usando -std=c++11
, -g
y -Wall
. También he compilado con -g
y tengo el mismo resultado. Necesito la marca C ++ 11 porque no quiero una dependencia en el impulso, solo asio.
Con el siguiente código en un solo archivo main.cpp
no hay problemas y el programa parece funcionar como se esperaba. Sin embargo, si pongo cada clase en su propio *.hpp
y *.cpp
, obtengo un segfault en el constructor de clases del Server
.
Luego volví, puse todo de nuevo en main.cpp
y comencé a mover cada clase una por una. El segfault comienza a ocurrir después de la clase final, el Client
se coloca en sus propios archivos.
Además, como estaba poniendo todas las clases en un archivo y moviéndolas, me aseguré de que los archivos de objetos no necesarios no estuvieran vinculados a mi archivo .exe.
Este código comenzó mucho más grande pero se reduce a esto.
Servidor.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;
}
Aquí está el callstack de 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)
Y finalmente, aquí está la salida de 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
Simplemente no puedo ver por qué estoy obteniendo una falta de seguridad. Incluso después de comentar todo el código significativo todavía ocurre.
EDITAR
He editado el código anterior para mostrar que solo el constructor del Server
parece causar un problema y el contenido de cada archivo. (Una vez más me he asegurado de que solo estos archivos de objetos estén compilados y vinculados).
Editar 2
He probado esto con el TDM GCC 4.7.1, Mingw Builds x64 4.8.1 y Mingw Builds x32 4.8.1. El mismo resultado para todos ellos.
EDITAR 3
He reducido aún más el código hacia abajo. Ahora, en el Client
, si asio::io_service&
cualquier objeto asio
que requiera un asio::io_service&
para ser construido, entonces no hay segfault. Pero cualquiera de los tipos de asio
que he probado hasta ahora ha producido el mismo fallo de seguridad. Esto no es un problema en la clase Server
, por ejemplo, que tiene un asio::acceptor
. Lo importante es que no hay una instancia de Client
creado, por lo que el hecho de que afecte al programa y produzca una falta de seguridad en el constructor del Server
es extraño.
EDITAR 4
Ahora he eliminado completamente Server.hpp
y Server.cpp
y he actualizado main.cpp
a esto:
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;
}
Todavía obtengo el segfault y la pila de llamadas refleja la falta del constructor del Server
. El segfault sigue en el mismo lugar. Los resultados de DrMemory se ven casi igual.
Igual que antes, si no vinculo el archivo de objeto de Client
, no tengo ningún problema.
EDITAR 5
Según lo solicitado, aquí está el registro de compilación de 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))
Editar 6
Estoy empezando a salir de mis habilidades ahora, pero he logrado localizar algunos de los problemas (y estoy aprendiendo algo nuevo que es genial).
Parece que cuando se crea un objeto que requiere un asio::io_service&
, agrega "servicios" al io_service
. Estos son estáticos en todas io_service
instancias de io_service
. Entonces, cuando se realiza la solicitud de servicio, hay un bucle que se itera a través del cual parece ser una lista enlazada de servicios ya creados. Si el servicio solicitado no se ha creado ya; entonces se crea
Esta información es de la referencia para io_service
y de mirar service_registry.ipp
(línea 111).
Esto se hace internamente con una llamada a service_registry::do_use_service
. El service_registry
tiene un miembro llamado first_service_
de tipo asio::io_service::service*
. Este primer servicio debe tener un miembro llamado next_
que es la parte de la lista vinculada que mencioné.
Sin embargo, en el momento de la primera llamada a service_registry::do_use_service
(cuando se construye el asio::acceptor
first_service_
), el miembro first_service_
tiene un valor de 0xffffffff
que obviamente no es correcto. Así que creo que esa es la raíz del segfault.
Por qué este miembro tiene el valor 0xffffffff
está más allá de mí. Entendí que solo las máquinas antiguas / peculiares reservaban esta dirección para null
punteros null
... pero admito que podría estar muy lejos.
Simplemente comprobé rápidamente haciendo esto:
int* p = nullptr;
if(p)
std::cout << "something" << std::endl;
y establecer un punto de interrupción para leer el valor. El valor para p
es 0x0
, no 0xffffffff
.
Entonces, establecí un punto de interrupción en el constructor para service_registry
( asio/detail/impl/service_registry.hpp
) y en el destructor (en caso de que se llamara explícitamente en alguna parte) y en los tres métodos do_has_service
, do_use_service
y do_add_service
. Mi pensamiento fue intentar rastrear en qué punto first_service_
obtiene el valor incorrecto.
No he tenido suerte. Estos son los únicos lugares que podrían alterar el valor de first_service_
.
Entiendo que esto significa que algo ha dañado la pila y ha cambiado la dirección de first_service_
. Pero, sólo soy un aficionado ...
Verifiqué que la dirección de this
puntero para el constructor era la misma que la utilizada para la invocación de do_use_service
para asegurarme de que no se crearon dos instancias o algo así.
Editar 7
Bien, ahora he encontrado que si compilo con ASIO_DISABLE_THREADS
¡ya no obtengo una falta de seguridad!
Pero, esto da como resultado que se genere una excepción porque estoy intentando usar subprocesos aunque los haya desactivado. Lo que entiendo es que estaría restringido a llamadas síncronas y no a llamadas asíncronas. (es decir, cual es el punto de usar asio)
El material de referencia aquí dice que ASIO_DISABLE_THREADS
Deshabilita explícitamente el soporte de subprocesos de Asio, independientemente de si Boost admite subprocesos o no.
Así que entiendo que esta definición evita que asio use subprocesos, independientemente de la mejora o no; lo que tiene sentido
¿Por qué roscar causaría un problema, no lo sé. No estoy interesado en ahondar tan lejos.
me doy por vencido
Renuncio a asio. Después de revisar el código y la documentación, parece que se ha desarrollado teniendo en cuenta el impulso más que una biblioteca independiente. Aparentemente, hasta el punto en el que necesitas usar Boost sobre C ++ 11, lo que simplemente no me interesa hacer.
La mejor biblioteca de red C / C ++ parece que hay muchas alternativas.
Para ser honesto, ejecutar mis propias llamadas de socket síncronas en mi propio hilo suena como una mejor idea considerando el control que obtendré. Al menos hasta que asio ingrese a la biblioteca estándar y se implemente en Mingw-w64.
Teniendo en cuenta que asio parece ser un candidato ideal para estar en la biblioteca estándar, o al menos es su sabor, probablemente sea una buena idea seguir con él.
Desde mi punto de vista, es un error gcc. Si usa clang++
lugar de g++.exe
, el bloqueo desaparece incluso sin alterar el orden de #include
. Pasar todos los archivos fuente a g++
en una sola llamada también hace que desaparezca la falla. Esto me lleva a pensar que hay algo mal con la emisión de archivos de objetos COFF de gcc , ya que Clang usa el enlazador de la misma distribución Mingw.
Por lo tanto, use el clang o aplique una solución alternativa de la respuesta de @Wouter Huysentruit.
Tenga en cuenta que la última versión de Clang (3.7.0) tiene otro error, no relacionado con su problema, que hace que el enlace falle. Tuve que usar una instantánea nocturna , que es bastante estable de todos modos.
Tuve un problema similar. Mi aplicación se bloqueó en el constructor win_lock :: lock (), pero nunca se llamó al constructor win_lock :: win_lock ().
Cuando se agregó -D_POSIX_THREADS a los indicadores del compilador, simplemente comenzó a funcionar.
Yo uso mingw-w64-i686-7.1.0-posix-dwarf-rt_v5-rev0 en win7