如何在调试器下列出在boost :: asio :: io_service中注册的处理程序?

时间:2013-03-14 10:16:18

标签: gdb boost-asio

鉴于正在运行的应用程序,我想提取有关当前注册的完整处理程序的信息。

处理程序已由A类注册。例如:

boost::asio::async_read(s, b, boost::bind(&A::F, this->shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));

在调试器下,我可以访问适当的io_service变量。如何找出(A :: F,this,s,b)尚未完成的操作。

1 个答案:

答案 0 :(得分:22)

我想稍微扩展问题的范围,以涵盖我认为最终目标的替代方案:调试异步处理程序。


示例程序

要通过示例显示调试,请从侦听端口4321的基本UDP echo服务器开始:

#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>

using boost::asio::ip::udp;

class udp_echo
{
public:
  udp_echo(boost::asio::io_service& service,
           unsigned int port)
    : socket_(service, udp::endpoint(udp::v4(), port))
  {
    socket_.async_receive_from(
      boost::asio::buffer(buffer_), sender_,
      boost::bind(&udp_echo::handle_receive, this,
                  boost::asio::placeholders::error,
                  boost::asio::placeholders::bytes_transferred));
  }

  void handle_receive(const boost::system::error_code& error,
                      std::size_t bytes_transferred)
  {
    socket_.async_send_to(
      boost::asio::buffer(buffer_, bytes_transferred), sender_,
      boost::bind(&udp_echo::handle_send, this,
                  boost::asio::placeholders::error,
                  boost::asio::placeholders::bytes_transferred));
  }

  void handle_send(const boost::system::error_code& error,
                   std::size_t bytes_transferred)
  {
    socket_.close();  
  }

private:
  udp::socket socket_;
  boost::array<char, 128> buffer_;
  udp::endpoint sender_;
};

int main()
{
  boost::asio::io_service service;
  udp_echo echo(service, 4321);
  service.run();
}

这个简单的程序只有一个异步调用链:

udp_echo::udp_echo()
{
  socket_.async_receive_from(...); --.
}                                    |
             .-----------------------'
             v
void udp_echo::handle_receive(...)
{
  socket_.async_send_to(...);  ------.
}                                    |
             .-----------------------'
             v
void udp_echo::handle_send()
{
  socket_.close(); 
}

处理程序跟踪

Boost 1.47介绍handler tracking。只需定义BOOST_ASIO_ENABLE_HANDLER_TRACKING和Boost.Asio就会将调试输出(包括时间戳)写入标准错误流。运行编程,并通过UDP发送“hello world”产生以下输出:

@asio|1363273821.846895|0*1|socket@0xbf8c4e3c.async_receive_from // 1
@asio|1363273829.288883|>1|ec=system:0,bytes_transferred=12      // 2
@asio|1363273829.288931|1*2|socket@0xbf8c4e3c.async_send_to      // 3
@asio|1363273829.289013|<1|                                      // 4
@asio|1363273829.289026|>2|ec=system:0,bytes_transferred=12      // 5
@asio|1363273829.289035|2|socket@0xbf8c4e3c.close                // 6
@asio|1363273829.289075|<2|                                      // 7

它可以逐行读取:

  1. 非处理程序(0)调用socket.async_receive_from(),创建处理程序1。
  2. 输入处理程序1 socket.async_receive_from(),没有错误,已收到12个字节。
  3. 处理程序1 socket.async_receive_from()调用socket.async_send_to(),创建处理程序2。
  4. 退出处理程序1 socket.async_receive_from()
  5. 输入处理程序2 socket.async_send_to(),没有错误,已发送12个字节。
  6. 处理程序2调用了socket.close()
  7. 退出处理程序2 socket.async_send_to()
  8. 并直观地映射到以下内容:

    udp_echo::udp_echo()
    {
      socket_.async_receive_from(...); --. // 1
    }                                    |
                 .-----------------------'
                 v
    void udp_echo::handle_receive(...)
    {                                      // 2
      socket_.async_send_to(...);  ------. // 3
    }                                    | // 4
                 .-----------------------'
                 v
    void udp_echo::handle_send()
    {                                      // 5
      socket_.close();                     // 6
    }                                      // 7
    

    GDB

    通过GDB进行调试需要挖掘多个层。它有助于了解Boost.Asio的一些实现细节。以下是一些概念:

    • io_service仅包含准备运行的处理程序。
    • reactor通常包含工作操作,以及未准备好运行的完成处理程序的句柄。
    • reactor将使用io_service注册。

    这是一个调试会话:

    (gdb) bt
    #0  0x00ab1402 in __kernel_vsyscall ()
    #1  0x00237ab8 in __epoll_wait_nocancel () from /lib/libc.so.6
    #2  0x080519c3 in boost::asio::detail::epoll_reactor::run (this=0x80560b0, 
        block=true, ops=...)
        at /opt/boost/include/boost/asio/detail/impl/epoll_reactor.ipp:392
    #3  0x08051c2d in boost::asio::detail::task_io_service::do_run_one (
        this=0x8056030, lock=..., this_thread=..., ec=...)
        at /opt/boost/include/boost/asio/detail/impl/task_io_service.ipp:396
    #4  0x08051e8a in boost::asio::detail::task_io_service::run (this=0x8056030, 
        ec=...)
        at /opt/boost/include/boost/asio/detail/impl/task_io_service.ipp:153
    #5  0x08051f50 in boost::asio::io_service::run (this=0xbfffe818)
        at /opt/boost/include/boost/asio/impl/io_service.ipp:59
    #6  0x08049a44 in main () at example.cpp:48
    (gdb) frame 6
    #6  0x08049a44 in main () at example.cpp:48
    48        service.run();
    

    首先,需要找到反应堆服务。需要进行向下转换,因此我们使用调试器来定位某些类型:

    (gdb) p service.service_registry_.init_keytab
    init_key
    init_key<boost::asio::datagram_socket_service<boost::asio::ip::udp> >
    init_key<boost::asio::detail::epoll_reactor>
    init_key<boost::asio::detail::task_io_service>

    每个密钥都与特定服务相关联,所有服务都在service.service_registry_内的链接列表中维护。类型信息与它们相关联,使我们能够识别所需的服务。

    (gdb) set $service = service.service_registry_.first_service_
    (gdb) p $service.key_.type_info_.__name
    $1 = 0x8052b60
    "N5boost4asio6detail14typeid_wrapperINS0_23datagram_socket_serviceINS0_2ip3udpEEEEE"
    

    那是boost::asio::datagram_socket_service<boost::asio::ip::udp>,所以继续下一个:

    (gdb) set $service = $service.next_
    (gdb) p $service.key_.type_info_.__name
    $2 = 0x8052cc0 "N5boost4asio6detail14typeid_wrapperINS1_13epoll_reactorEEE"
    

    $service现在指向反应堆服务。根据{{​​1}}类型参数向下转发服务:

    (gdb) set $service = *('boost::asio::detail::epoll_reactor'*) $service

    有工作的优秀处理人员在反应堆内的运作链表中列出:

    (gdb) set $ops = $service.registered_descriptors_.live_list_.op_queue_
    (gdb) set $op = $ops.front_
    (gdb) p *$op
    $3 = {<boost::asio::detail::task_io_service_operation> = {next_ = 0x0, 
        func_ = 0x804c256
        <boost::asio::detail::reactive_socket_recvfrom_op<
        boost::asio::mutable_buffers_1, boost::asio::ip::basic_endpoint<
        boost::asio::ip::udp>, boost::_bi::bind_t<void, 
        boost::_mfi::mf2<void, udp_echo, boost::system::error_code const&, 
        unsigned int>, boost::_bi::list3<boost::_bi::value<udp_echo*>,
        boost::arg<1> (*)(), boost::arg<2> (*)()> > >::
        do_complete(boost::asio::io_service::io_service_impl*, 
        boost::asio::detail::epoll_reactor::descriptor_state::operation*, 
        boost::system::error_code const&, size_t)>, task_result_ = 0}, ec_ = {
        m_val = 11, m_cat = 0x13b2c8}, bytes_transferred_ = 0, perform_func_ =
        0x80514c8 <boost::asio::detail::reactive_socket_recvfrom_op_base<
        boost::asio::mutable_buffers_1,  
        boost::asio::ip::basic_endpoint<boost::asio::ip::udp> 
        >::do_perform(boost::asio::detail::reactor_op*)>}

    需要另一个垂头丧气。将init_key强制转换为$op成员函数指针所属的类。

    (gdb) set $op = *('boost::asio::detail::reactive_socket_recvfrom_op<
    boost::asio::mutable_buffers_1, boost::asio::ip::basic_endpoint<
    boost::asio::ip::udp>, boost::_bi::bind_t<void, boost::_mfi::mf2<
    void, udp_echo, boost::system::error_code const&, unsigned int>,
     boost::_bi::list3<boost::_bi::value<udp_echo*>, 
     boost::arg<1> (*)(), boost::arg<2> (*)()> > >'*) $op

    此操作包含所需信息。

    缓冲区:

    func_

    (gdb) p $op.buffers_ $4 = {<boost::asio::mutable_buffer> = {data_ = 0xbfffe77c, size_ = 128}, <No data fields>} (gdb) p &echo.buffer_ $5 = (boost::array<char, 128u> *) 0xbfffe77c 实例:

    this

    成员函数指针:

    (gdb) p $op.handler_.l_.a1_.t_ 
    $6 = (udp_echo *) 0xbfffe768
    (gdb) p &echo
    $7 = (udp_echo *) 0xbfffe768
    

    套接字信息:

    (gdb) p $op.handler_.f_.f_
    $8 = (void (udp_echo::*)(udp_echo *, const boost::system::error_code &, 
        unsigned int)) 0x80505b0 <
        udp_echo::handle_receive(boost::system::error_code const&, size_t)>
    

    在这种情况下,操作只知道本机套接字表示(文件描述符)。确定它是什么套接字的一种有用方法是查询lsof。

    (gdb) p $op.socket_ 
    $9 = 10
    (gdb) p echo.socket_.implementation.socket_ 
    $10 = 10
    

    因此,文件描述符10正在侦听UDP 4321。