【问题标题】:ZeroMQ socket.recv() raised a STACK_OVERFLOW exceptionZeroMQ socket.recv() 引发了 STACK_OVERFLOW 异常
【发布时间】:2017-12-04 08:16:31
【问题描述】:

如果在.dll 中使用此代码, 对socket.recv() 的调用引发了异常STACK_OVERFLOW,但是当此代码编译为为@ 987654326@ 有效。

为什么?

我通过“C:\windows\system32\rundll32.exe myDll.dll StartUp”运行.dll-测试

void StartUp()
{
zmq::context_t context(1);
zmq::socket_t socket(context, ZMQ_REP);

socket.bind("tcp://127.0.0.1:3456");
zmq::message_t msgIN, msgOUT("test", 4);
while (true){


    socket.recv(&msgIN);

    socket.send(msgOUT);
};
}

调用栈:

libzmq-v120-mt-gd-4_2_2.dll!zmq::mailbox_t::recv(zmq::command_t * cmd_=0x0231f700, int timeout_=0x00000000) 

libzmq-v120-mt-gd-4_2_2.dll!zmq::io_thread_t::in_event() 

libzmq-v120-mt-gd-4_2_2.dll!zmq::select_t::loop() 

libzmq-v120-mt-gd-4_2_2.dll!zmq::select_t::worker_routine(void * arg_=0x002f1778) 
libzmq-v120-mt-gd-4_2_2.dll!thread_routine(void * arg_=0x002f17c0) 

主线程调用栈:

libzmq-v120-mt-gd-4_2_2.dll!zmq::signaler_t::wait(int timeout_=0xffffffff)
libzmq-v120-mt-gd-4_2_2.dll!zmq::mailbox_t::recv(zmq::command_t * cmd_=0x0019f3c0, int timeout_=0xffffffff) 
libzmq-v120-mt-gd-4_2_2.dll!zmq::socket_base_t::process_commands(int timeout_, bool throttle_)
libzmq-v120-mt-gd-4_2_2.dll!zmq::socket_base_t::recv(zmq::msg_t * msg_=0x0019f628, int flags_=0x00000000)
libzmq-v120-mt-gd-4_2_2.dll!s_recvmsg(zmq::socket_base_t * s_=0x006f6c70, zmq_msg_t * msg_=0x0019f628, int flags_=0x00000000) 
libzmq-v120-mt-gd-4_2_2.dll!zmq_msg_recv(zmq_msg_t * msg_=0x0019f628, void * s_=0x006f6c70, int flags_=0x00000000)
mydll.dll!zmq::socket_t::recv(zmq::message_t * msg_=0x0019f628, int flags_=0x00000000)
mydll.dll!StartUp() 

更新:

这个例子,也因为同样的原因而崩溃。有人知道异常堆栈溢出的任何原因吗?

zmq::context_t context(1);
zmq::socket_t socket(context, ZMQ_REP);

socket.bind("tcp://*:7712");

while (1){
    Sleep(10);
}

反向问题隔离 MCVE:

这个 myDll.dll-test 是如何工作的,
如果由C:\windows\system32\rundll32.exe myDll.dll StartUp 运行?发布屏幕输出。

void StartUp()
{
     std::cout << "INF:: ENTRY POINT ( C:\windows\system32\rundll32.exe myDll.dll StartUp )" << std::endl;
     std::cout << "INF:: WILL SLEEP  ( C:\windows\system32\rundll32.exe myDll.dll StartUp )" << std::endl;
     Sleep( 10 );
     std::cout << "INF:: SLEPT WELL  ( C:\windows\system32\rundll32.exe myDll.dll StartUp )" << std::endl;
     std::cout << "INF:: WILL RETURN ( C:\windows\system32\rundll32.exe myDll.dll StartUp )" << std::endl;
}

【问题讨论】:

标签: c++ windows zeromq


【解决方案1】:

崩溃的原因是 OPTIONAL_HEADER rundll32 文件中的 SizeOfStackCommit 值。 它太小(0xC000),我将其更改为 0x100000。现在一切正常。

【讨论】:

  • 你不应该使用 RunDll32 来运行任意函数。
【解决方案2】:

ZeroMQ 对象需要一定的尊重才能使用:

正如您已经在屏幕上看到的那样,有许多功能不为人知,可能会造成严重破坏。

最好仔细阅读 ZeroMQ C++ 绑定参考文档和原始 ZeroMQ API(在 C++ 绑定中也经常提到)。

两者都强调不要直接处理zmq::message_t 实例,而是通过使用“服务”函数(通常在 C++ 中重新包装为实例方法)。

zmq::message_t messageIN,
               messageOUT;

bool successFlag;

while (true){

             successFlag  = socket.recv( &messageIN );
     assert( successFlag && "EXC: .recv( &messageIN )" );
                        /* The zmq_recv() function shall receive a message
                           from the socket referenced by the socket argument
                           and store it in the message referenced by the msg
                           argument.
                           Any content previously stored in msg shall be
                           properly deallocated.
                           If there are no messages available on the specified
                           socket the zmq_recv() function shall block
                           until the request can be satisfied.
                           */

                       messageOUT.copy(  messageIN );

             successFlag  = socket.send(  messageOUT );
     assert( successFlag && "EXC: .send(  messageOUT )" );
                        /* The zmq_send() function shall queue the message
                           referenced by the msg argument to be sent to
                           the socket referenced by the socket argument.
                           The flags argument is a combination of the flags
                           defined { ZMQ_NOBLOCK, ZMQ_SNDMORE }

                           The zmq_msg_t structure passed to zmq_send()
                           is nullified during the call.

                           If you want to send the same message to multiple
                           sockets you have to copy it using (e.g.
                           using zmq_msg_copy() ).

                           A successful invocation of zmq_send()
                           does not indicate that the message
                           has been transmitted to the network,
                           only that it has been queued on the socket
                           and ØMQ has assumed responsibility for the message.
                           */
};

我的怀疑是引用计数,添加越来越多的实例,由无限 while( true ){...}-loop 中的 zmq::message_t message; 构造函数生成,其中没有一个遇到过它自己的公平析构函数。具有物理上有限容量且 DLL 内部没有堆栈管理的堆栈迟早会失败。

zmq::message_t 实例是相当昂贵的玩具,因此专业代码始终欢迎良好的资源管理实践(预分配、重用、受控销毁)。

Q.E.D.


为了清楚起见的尾注:

稍微解释一下 Dijkstra 关于错误追踪和软件测试的观点:“如果我没有看到错误,那并不意味着这段代码中没有错误(如果除了它之外还链接了任何外部函数,则更少)。 "

没有堆栈分配?

是的,没有可见个。

ZeroMQ API 为它增添了更多亮点:

zmq_msg_init_size() 函数应分配存储消息大小字节长所需的任何资源,并初始化 msg 引用的消息对象以表示新分配的消息。

实现应选择将消息内容存储在堆栈上(小消息)还是堆上(大消息)。出于性能原因zmq_msg_init_size() 不得清除消息数据。”

多年来,我一直在使用基于 ZeroMQ API v.2.1+ 的跨平台分布式系统,这教会了我在显式资源控制方面要小心谨慎。一旦您没有为原生 API 开发自己的语言绑定,就会越多。


在所有不受支持的批评之后,让我们再添加一条来自 ZeroMQ 的引用:

这增加了一个视图,即如何通过库 C++ 绑定本身对 message_t 内容进行适当的间接操作,并包装到琐碎的辅助函数中:

来自zhelpers.hpp

//  Receive 0MQ string from socket and convert into string
static std::string
s_recv (zmq::socket_t & socket) {

    zmq::message_t message;
    socket.recv(&message);

    return std::string(static_cast<char*>(message.data()), message.size());
}

//  Convert string to 0MQ string and send to socket
static bool
s_send (zmq::socket_t & socket, const std::string & string) {

    zmq::message_t message(string.size());
    memcpy (message.data(), string.data(), string.size());

    bool rc = socket.send (message);
    return (rc);
}

//  Sends string as 0MQ string, as multipart non-terminal
static bool
s_sendmore (zmq::socket_t & socket, const std::string & string) {

    zmq::message_t message(string.size());
    memcpy (message.data(), string.data(), string.size());

    bool rc = socket.send (message, ZMQ_SNDMORE);
    return (rc);
}

【讨论】:

  • 堆栈不应循环增长。 message_t 对象在其作用域的末尾被破坏(这是 OP 代码的循环体的末尾)。
  • @interjay 我可以理解 message_t 实例的可见部分的隐式释放,一旦超出范围(实际上从未发生过),但还有一个隐藏部分 - 实现 - 它在构造函数“内部”ZeroMQ 内部(指针密集型,交叉引用)资源的每次传递时分配额外的资源,这些资源应该总是显式的 .close()-ed / ~destructed 以便让所有底层分配也被释放.简而言之,message_t 实例不是任何可消耗的一次性用品,这里需要资源管理关怀。
  • message_t 析构函数将释放它使用的所有资源。并且这些资源在任何情况下都不会存储在堆栈中。
  • @interjay 恕我直言,先生,ZeroMQ API 明确警告堆栈分配。
  • 您从文档中引用的内容中没有这样的警告。当他们说“将消息内容存储在堆栈上”时,它仅仅意味着该结构有一个固定大小的数组成员,如果合适的话,他们会将数据放在那里。在这种情况下没有任何分配。事实上,函数在调用函数的堆栈中分配数据是完全不可能的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-01-14
  • 2013-08-28
  • 2019-12-08
  • 2015-07-18
相关资源
最近更新 更多