std::vector segv 因为不太可能的损坏

std::vector segv because of an improbable corruption

我在以下代码中存在分段错误(在 _answers.push_back(tmp); 上)。

gdb 说

(gdb) p tmp
 = "HTTP/1.0 200 OK\r\nContent-Type: text/plain; charset=UTF-8\r\nSet-Cookie: color=black;path=/\r\nSet-Cookie: code=f69a2d941420d23be97bbb1ae963295647a91c4f3faf9c5fa80727399927d9d5;path=/\r\nSet-Cookie: game=c1e"...
(gdb) call _answers.size()
 = 271275648142580811

所以我猜数组已经损坏了。但是我不知道它发生在哪里。

// Network.hpp
#pragma once

#include <utility>
#include <string>
#include <vector>
#include <boost/asio.hpp>
#include <boost/bind.hpp>

class Network
{
  public:
    Network(std::string const &, std::string const &);
    ~Network() {};

    void init();
    void connect();
    void update();
    void sendQuery(const std::string);
    bool isConnected();
    void reset();

    std::string getAnswer();

    void handleRead(const boost::system::error_code &, size_t);
    void handleWrite(const boost::system::error_code &);

    boost::asio::io_service _io_service;

  private:
    boost::asio::ip::tcp::resolver _resolver;
    boost::asio::ip::tcp::socket _sock;
    boost::asio::ip::tcp::resolver::iterator _it;
    char _buff[2048];
    std::vector<std::string> _answers;
    std::string const &_host;
    std::string const &_port;
    bool _answered;
};

// Network.cpp
Network::Network(std::string const &host, std::string const &port) : _resolver(_io_service), _sock(_io_service), _host(host), _port(port), _answered(true) {}

void Network::connect() {
  _answers.reserve(2048);
  boost::asio::ip::tcp::resolver::query query(_host, _port);
  boost::asio::ip::tcp::resolver::iterator iterator = _resolver.resolve(query);
  boost::asio::connect(_sock.lowest_layer(), iterator);
}

void Network::handleRead(const boost::system::error_code &err, size_t bread) {
  _answered = true;
  if (err && err.value() != 2)
    throw Gomoku::NetworkException(err.message());
  if (bread > 0) {
    std::string tmp(_buff);
    _answers.push_back(tmp);
  }
  memset(_buff, 0, 2048);
}

void Network::handleWrite(const boost::system::error_code &err) {
  if (err)
    throw Gomoku::NetworkException(err.message());
}

void Network::reset() {
  _io_service.poll();
  _io_service.reset();
  _answers.clear();
  _answered = true;
}

void Network::sendQuery(const std::string req) {
  _io_service.poll();
  _io_service.reset();
  if (_answered == 0)
    return;
  _answered = false;
  connect();
  const char *str = new char[req.length()];
  str = req.c_str();
  boost::asio::async_write(_sock, boost::asio::buffer(str, req.length()), boost::bind(&Network::handleWrite, this, boost::asio::placeholders::error));
  boost::asio::async_read(_sock, boost::asio::buffer(_buff, 2048), boost::bind(&Network::handleRead, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}

std::string Network::getAnswer() {
  if (_answers.empty())
    return "";
  std::string tmp = _answers.back();
  _answers.pop_back();
  return tmp;
}

// Player.cpp
Player::Player(std::string const &host, std::string const &port) : _network(host, port) {
  _myTurn = false;
  _whiteScore = _blackScore = 0;
  _host = host + ":" + port;
  initMap();
}

void Player::connect() {
  std::string str = "GET /players/connect/ HTTP/1.0\r\nHost: " + _host + "\r\nAccept: */*\r\n\r\n";
  _network.sendQuery(str);
}

void Player::sendClick(std::pair<int, int> click, std::string const &header) {
  std::stringstream ss;
  ss << "POST /game/play/" << click.first << "/" << click.second << header << _cookie << "\r\n\r\n";
  std::string req = ss.str();
  _network.sendQuery(req);
  _network._io_service.run();
  _network._io_service.reset();
  std::string ans = _network.getAnswer();
  parseAnswer(ans);
  req = "GET /game/map.txt" + header + _cookie + "\r\n\r\n";
  _network.sendQuery(req);
}

我还通过 segv 跟踪 (basic_string.h:400) 看到了以下代码:

: _M_dataplus(_M_local_data(), __str._M_get_allocator()) // TODO A traits

乍一看:std::string tmp(_buff);是错误的,因为_buff可能不是空终止的,因此将271275648142580811字节读入内存。

此外,sendQuery 的参数是一个本地字符串,所以一旦函数退出,字符串就会被释放,并且 async_write 继续从无效内存中读取,这是未定义的行为。什么事都有可能发生。

此外,所有请求都使用相同的传入缓冲区,因此如果多个请求同时发生,则会出现未定义或无用的行为。