hwstub: add stub gdb backend

Change-Id: I1e3fe2bec3f5d926a02027c13b5056d461da2f9d
This commit is contained in:
Amaury Pouly 2016-10-05 14:43:05 +01:00
parent eb39277d4a
commit cea6fca67b
5 changed files with 561 additions and 89 deletions

View file

@ -0,0 +1,112 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2016 by Amaury Pouly
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef __HWSTUB_GDB_HPP__
#define __HWSTUB_GDB_HPP__
#include "hwstub.hpp"
#include "hwstub_net.hpp"
namespace hwstub {
namespace gdb {
/** GDB context
*
* A GDB context connects to a GDB server and has only one device. */
class context : public hwstub::context
{
friend class device;
friend class handle;
protected:
context(int socke_fd);
public:
virtual ~context();
/** Create a context from an existing socket */
static std::shared_ptr<context> create(int socket_fd);
/** Create a TCP socket context with a domain name and a port. If port is empty,
* a default port is used. */
static std::shared_ptr<context> create_tcp(const std::string& domain,
const std::string& port, std::string *error = nullptr);
protected:
/** Send a message to the server. Context will always serialize calls to send()
* so there is no need to worry about concurrency issues. */
error send(void *buffer, size_t& sz);
/** Receive a message from the server, sz is updated with the received size.
* Context will always serialize calls to recv() so there is no need to
* worry about concurrency issues. */
error recv(void *buffer, size_t& sz);
/* NOTE ctx_dev_t is always 0 */
virtual error fetch_device_list(std::vector<ctx_dev_t>& list, void*& ptr);
virtual void destroy_device_list(void *ptr);
virtual error create_device(ctx_dev_t dev, std::shared_ptr<device>& hwdev);
virtual bool match_device(ctx_dev_t dev, std::shared_ptr<device> hwdev);
hwstub::net::socket_connection m_conn;
};
/** GDB device
*
* Device based on gdb context. */
class device : public hwstub::device
{
friend class context; /* for ctor */
protected:
device(std::shared_ptr<hwstub::context> ctx);
public:
virtual ~device();
protected:
virtual error open_dev(std::shared_ptr<handle>& handle);
virtual bool has_multiple_open() const;
};
/** GDB handle
*
* Handle based on gdb device. */
class handle : public hwstub::handle
{
friend class device; /* for ctor */
protected:
handle(std::shared_ptr<hwstub::device> dev);
public:
virtual ~handle();
protected:
virtual error read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic);
virtual error write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic);
virtual error get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz);
virtual error get_dev_log(void *buf, size_t& buf_sz);
virtual error exec_dev(uint32_t addr, uint16_t flags);
virtual error status() const;
virtual size_t get_buffer_size();
protected:
struct hwstub_version_desc_t m_desc_version;
struct hwstub_layout_desc_t m_desc_layout;
struct hwstub_target_desc_t m_desc_target;
};
} // namespace virt
} // namespace hwstub
#endif /* __HWSTUB_GDB_HPP__ */

View file

@ -29,6 +29,58 @@
namespace hwstub {
namespace net {
/** Net helper
*
* This class provides helpers to implement a socket based context. It abstracts
* send/recv to return sensible error codes, print debug messages, and has helpers
* to create unix and tcp sockets.
*
* NOTE: this class is NOT thread-safe */
class socket_connection
{
public:
socket_connection();
~socket_connection();
/** Return socket file descriptor, or -1 if none */
int get_socket_fd();
/** Init a socket connection with an existing file descriptor. Note that the
* file descriptor will be closed when the class will be destroyed or init
* again. If a previous socket was associated with this class before, it will
* be closed. */
void init(int socket_fd);
/** Close the socket connection now */
void close();
/** Create a TCP connection with a domain name and a port.
* In case of error, return -1 */
static int create_tcp(const std::string& domain,
const std::string& port, std::string *error = nullptr);
/** Init a UNIX socket connection with a file system path (see man for details)
* In case of error, the previous connection is NOT closed and
* the function returns false, otherwise it returns true. */
static int create_unix(const std::string& path,
std::string *error = nullptr);
/** Init a UNIX socket connection with an abstract name (see man for details)
* In case of error, the previous connection is NOT closed and
* the function returns false, otherwise it returns true. */
static int create_unix_abstract(const std::string& path,
std::string *error = nullptr);
/** Useful functions for network byte order conversion */
static uint32_t to_net_order(uint32_t u);
static uint32_t from_net_order(uint32_t u);
/** set operation timeout */
void set_timeout(std::chrono::milliseconds ms);
/** Send a message to the server. */
error send(void *buffer, size_t& sz, std::ostream& debug = cnull);
/** Receive a message from the server, sz is updated with the received size. */
error recv(void *buffer, size_t& sz, std::ostream& debug = cnull);
protected:
int m_socketfd; /* socket file descriptor */
};
/** Net context
*
* A socket context provides access to another context through a network. This
@ -56,6 +108,7 @@ public:
/** Create a UNIX socket context with an abstract name (see man for details) */
static std::shared_ptr<context> create_unix_abstract(const std::string& path,
std::string *error = nullptr);
/** Useful functions for network byte order conversion */
uint32_t to_net_order(uint32_t u);
uint32_t from_net_order(uint32_t u);
@ -144,7 +197,7 @@ protected:
virtual error send(void *buffer, size_t& sz);
virtual error recv(void *buffer, size_t& sz);
int m_socketfd; /* socket file descriptor */
socket_connection m_conn;
};

View file

@ -0,0 +1,224 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2016 by Amaury Pouly
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "hwstub_gdb.hpp"
#include <algorithm>
#include <cstring>
namespace hwstub {
namespace gdb {
/**
* Context
*/
context::context(int socket_fd)
{
m_conn.init(socket_fd);
}
context::~context()
{
}
std::shared_ptr<context> context::create(int socket_fd)
{
// NOTE: can't use make_shared() because of the protected ctor */
return std::shared_ptr<context>(new context(socket_fd));
}
std::shared_ptr<context> context::create_tcp(const std::string& domain,
const std::string& port, std::string *error)
{
int fd = hwstub::net::socket_connection::create_tcp(domain, port, error);
if(fd >= 0)
return context::create(fd);
else
return std::shared_ptr<context>();
}
error context::fetch_device_list(std::vector<ctx_dev_t>& list, void*& ptr)
{
(void) ptr;
list.resize(1);
list[0] = 0;
return error::SUCCESS;
}
void context::destroy_device_list(void *ptr)
{
(void) ptr;
}
error context::create_device(ctx_dev_t dev, std::shared_ptr<hwstub::device>& hwdev)
{
/* there is only one device, so we don't use dev */
(void) dev;
// NOTE: can't use make_shared() because of the protected ctor */
hwdev.reset(new device(shared_from_this()));
return error::SUCCESS;
}
bool context::match_device(ctx_dev_t dev, std::shared_ptr<hwstub::device> hwdev)
{
(void) dev;
(void) hwdev;
/* there is only one device, so they all match */
return true;
}
/**
* Device
*/
device::device(std::shared_ptr<hwstub::context> ctx)
:hwstub::device(ctx)
{
}
device::~device()
{
}
error device::open_dev(std::shared_ptr<hwstub::handle>& h)
{
// NOTE: can't use make_shared() because of the protected ctor */
h.reset(new handle(shared_from_this()));
return error::SUCCESS;
}
bool device::has_multiple_open() const
{
/* we want to serialize accesses, otherwise multiple concurrent messages to
* the server over the same connection will confuse the server */
return false;
}
/**
* Handle
*/
handle::handle(std::shared_ptr<hwstub::device> dev)
:hwstub::handle(dev)
{
m_desc_version.bLength = sizeof(m_desc_version);
m_desc_version.bDescriptorType = HWSTUB_DT_VERSION;
m_desc_version.bMajor = HWSTUB_VERSION_MAJOR;
m_desc_version.bMinor = HWSTUB_VERSION_MINOR;
m_desc_version.bRevision = 0;
m_desc_layout.bLength = sizeof(m_desc_layout);
m_desc_layout.bDescriptorType = HWSTUB_DT_LAYOUT;
m_desc_layout.dCodeStart = 0;
m_desc_layout.dCodeSize = 0;
m_desc_layout.dStackStart = 0;
m_desc_layout.dStackSize = 0;
m_desc_layout.dBufferStart = 0;
m_desc_layout.dBufferSize = 1;
m_desc_target.bLength = sizeof(m_desc_target);
m_desc_target.bDescriptorType = HWSTUB_DT_TARGET;
m_desc_target.dID = HWSTUB_TARGET_UNK;
strncpy(m_desc_target.bName, "GDB server", sizeof(m_desc_target.bName));
m_desc_target.bName[sizeof(m_desc_target.bName) - 1] = 0;
}
handle::~handle()
{
}
error handle::read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic)
{
std::shared_ptr<hwstub::context> hctx = get_device()->get_context();
if(!hctx)
return error::NO_CONTEXT;
context *ctx = dynamic_cast<context*>(hctx.get());
ctx->debug() << "[gdb::handle] --> READ(0x" << std::hex
<< addr << "," << sz << "," << atomic << ")\n";
ctx->debug() << "[gdb::handle] <-- READ unimplemented\n";
/* implement read here using ctx->send() and ctx->recv() */
(void) addr;
(void) buf;
(void) sz;
(void) atomic;
return error::DUMMY;
}
error handle::write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic)
{
std::shared_ptr<hwstub::context> hctx = get_device()->get_context();
if(!hctx)
return error::NO_CONTEXT;
context *ctx = dynamic_cast<context*>(hctx.get());
ctx->debug() << "[gdb::handle] --> WRITE(0x" << std::hex
<< addr << "," << sz << "," << atomic << ")\n";
ctx->debug() << "[gdb::handle] <-- WRITE unimplemented\n";
/* implement read here using ctx.send() and ctx.recv() */
(void) addr;
(void) buf;
(void) sz;
(void) atomic;
return error::DUMMY;
}
error handle::get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz)
{
void *p = nullptr;
switch(desc)
{
case HWSTUB_DT_VERSION: p = &m_desc_version; break;
case HWSTUB_DT_LAYOUT: p = &m_desc_layout; break;
case HWSTUB_DT_TARGET: p = &m_desc_target; break;
default: break;
}
if(p == nullptr)
return error::ERROR;
/* size is in the bLength field of the descriptor */
size_t desc_sz = *(uint8_t *)p;
buf_sz = std::min(buf_sz, desc_sz);
memcpy(buf, p, buf_sz);
return error::SUCCESS;
}
error handle::get_dev_log(void *buf, size_t& buf_sz)
{
(void) buf;
(void) buf_sz;
return error::DUMMY;
}
error handle::exec_dev(uint32_t addr, uint16_t flags)
{
(void) addr;
(void) flags;
return error::DUMMY;
}
error handle::status() const
{
return hwstub::handle::status();
}
size_t handle::get_buffer_size()
{
return 2048;
}
} // namespace gdb
} // namespace net

View file

@ -29,39 +29,6 @@
namespace hwstub {
namespace net {
/**
* Context
*/
context::context()
:m_state(state::HELLO), m_error(error::SUCCESS)
{
}
context::~context()
{
}
std::shared_ptr<context> context::create_socket(int socket_fd)
{
// NOTE: can't use make_shared() because of the protected ctor */
return std::shared_ptr<socket_context>(new socket_context(socket_fd));
}
std::string context::default_unix_path()
{
return "hwstub";
}
std::string context::default_tcp_domain()
{
return "localhost";
}
std::string context::default_tcp_port()
{
return "6666";
}
namespace
{
/* len is the total length, including a 0 character if any */
@ -171,10 +138,163 @@ namespace
}
}
/**
* Socket connection
*/
socket_connection::socket_connection()
:m_socketfd(-1)
{
}
socket_connection::~socket_connection()
{
close();
}
int socket_connection::get_socket_fd()
{
return m_socketfd;
}
void socket_connection::init(int socket_fd)
{
close();
m_socketfd = socket_fd;
}
void socket_connection::close()
{
if(m_socketfd >= 0)
::close(m_socketfd);
m_socketfd = -1;
}
int socket_connection::create_tcp(const std::string& domain,
const std::string& port, std::string *error)
{
return create_tcp_low(domain, port, false, error);
}
int socket_connection::create_unix(const std::string& path,
std::string *error)
{
return create_unix_low(false, path.c_str(), path.size() + 1, true, error);
}
int socket_connection::create_unix_abstract(const std::string& path,
std::string *error)
{
std::string fake_path = "#" + path; /* the # will be overriden by 0 */
return create_unix_low(true, fake_path.c_str(), fake_path.size(), true, error);
}
uint32_t socket_connection::to_net_order(uint32_t u)
{
return htonl(u);
}
uint32_t socket_connection::from_net_order(uint32_t u)
{
return ntohl(u);
}
void socket_connection::set_timeout(std::chrono::milliseconds ms)
{
struct timeval tv;
tv.tv_usec = 1000 * (ms.count() % 1000);
tv.tv_sec = ms.count() / 1000;
/* set timeout for the client operations */
setsockopt(m_socketfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
setsockopt(m_socketfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv));
}
error socket_connection::send(void *buffer, size_t& sz, std::ostream& debug)
{
debug << "[net::sock] send(" << sz << "): ";
int ret = ::send(m_socketfd, buffer, sz, MSG_NOSIGNAL);
if(ret >= 0)
{
debug << "good(" << ret << ")\n";
sz = (size_t)ret;
return error::SUCCESS;
}
/* convert some errors */
debug << "fail(" << errno << "," << strerror(errno) << ")\n";
switch(errno)
{
#if EAGAIN != EWOULDBLOCK
case EAGAIN:
#endif
case EWOULDBLOCK: return error::TIMEOUT;
case ECONNRESET: case EPIPE: return error::SERVER_DISCONNECTED;
default: return error::NET_ERROR;
}
}
error socket_connection::recv(void *buffer, size_t& sz, std::ostream& debug)
{
debug << "[net::sock] recv(" << sz << "): ";
int ret = ::recv(m_socketfd, buffer, sz, MSG_WAITALL);
if(ret > 0)
{
debug << "good(" << ret << ")\n";
sz = (size_t)ret;
return error::SUCCESS;
}
if(ret == 0)
{
debug << "disconnected\n";
return error::SERVER_DISCONNECTED;
}
debug << "fail(" << errno << "," << strerror(errno) << ")\n";
switch(errno)
{
#if EAGAIN != EWOULDBLOCK
case EAGAIN:
#endif
case EWOULDBLOCK: return error::TIMEOUT;
default: return error::NET_ERROR;
}
}
/**
* Context
*/
context::context()
:m_state(state::HELLO), m_error(error::SUCCESS)
{
}
context::~context()
{
}
std::shared_ptr<context> context::create_socket(int socket_fd)
{
// NOTE: can't use make_shared() because of the protected ctor */
return std::shared_ptr<socket_context>(new socket_context(socket_fd));
}
std::string context::default_unix_path()
{
return "hwstub";
}
std::string context::default_tcp_domain()
{
return "localhost";
}
std::string context::default_tcp_port()
{
return "6666";
}
std::shared_ptr<context> context::create_tcp(const std::string& domain,
const std::string& port, std::string *error)
{
int fd = create_tcp_low(domain, port, false, error);
int fd = socket_connection::create_tcp(domain, port, error);
if(fd >= 0)
return context::create_socket(fd);
else
@ -183,7 +303,7 @@ std::shared_ptr<context> context::create_tcp(const std::string& domain,
std::shared_ptr<context> context::create_unix(const std::string& path, std::string *error)
{
int fd = create_unix_low(false, path.c_str(), path.size() + 1, true, error);
int fd = socket_connection::create_unix(path, error);
if(fd >= 0)
return context::create_socket(fd);
else
@ -192,8 +312,7 @@ std::shared_ptr<context> context::create_unix(const std::string& path, std::stri
std::shared_ptr<context> context::create_unix_abstract(const std::string& path, std::string *error)
{
std::string fake_path = "#" + path; /* the # will be overriden by 0 */
int fd = create_unix_low(true, fake_path.c_str(), fake_path.size(), true, error);
int fd = socket_connection::create_unix_abstract(path, error);
if(fd >= 0)
return context::create_socket(fd);
else
@ -264,12 +383,12 @@ bool context::match_device(ctx_dev_t dev, std::shared_ptr<hwstub::device> hwdev)
uint32_t context::to_net_order(uint32_t u)
{
return htonl(u);
return socket_connection::to_net_order(u);
}
uint32_t context::from_net_order(uint32_t u)
{
return ntohl(u);
return socket_connection::from_net_order(u);
}
error context::send_cmd(uint32_t cmd, uint32_t args[HWSTUB_NET_ARGS], uint8_t *send_data,
@ -492,74 +611,29 @@ void context::stop_context()
* Socket context
*/
socket_context::socket_context(int socket_fd)
:m_socketfd(socket_fd)
{
m_conn.init(socket_fd);
set_timeout(std::chrono::milliseconds(1000));
}
socket_context::~socket_context()
{
stop_context();
close(m_socketfd);
}
void socket_context::set_timeout(std::chrono::milliseconds ms)
{
struct timeval tv;
tv.tv_usec = 1000 * (ms.count() % 1000);
tv.tv_sec = ms.count() / 1000;
/* set timeout for the client operations */
setsockopt(m_socketfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
setsockopt(m_socketfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv));
m_conn.set_timeout(ms);
}
error socket_context::send(void *buffer, size_t& sz)
{
debug() << "[net::ctx::sock] send(" << sz << "): ";
int ret = ::send(m_socketfd, buffer, sz, MSG_NOSIGNAL);
if(ret >= 0)
{
debug() << "good(" << ret << ")\n";
sz = (size_t)ret;
return error::SUCCESS;
}
/* convert some errors */
debug() << "fail(" << errno << "," << strerror(errno) << ")\n";
switch(errno)
{
#if EAGAIN != EWOULDBLOCK
case EAGAIN:
#endif
case EWOULDBLOCK: return error::TIMEOUT;
case ECONNRESET: case EPIPE: return error::SERVER_DISCONNECTED;
default: return error::NET_ERROR;
}
return m_conn.send(buffer, sz, debug());
}
error socket_context::recv(void *buffer, size_t& sz)
{
debug() << "[net::ctx::sock] recv(" << sz << "): ";
int ret = ::recv(m_socketfd, buffer, sz, MSG_WAITALL);
if(ret > 0)
{
debug() << "good(" << ret << ")\n";
sz = (size_t)ret;
return error::SUCCESS;
}
if(ret == 0)
{
debug() << "disconnected\n";
return error::SERVER_DISCONNECTED;
}
debug() << "fail(" << errno << "," << strerror(errno) << ")\n";
switch(errno)
{
#if EAGAIN != EWOULDBLOCK
case EAGAIN:
#endif
case EWOULDBLOCK: return error::TIMEOUT;
default: return error::NET_ERROR;
}
return m_conn.recv(buffer, sz, debug());
}
/**

View file

@ -21,6 +21,7 @@
#include "hwstub_usb.hpp"
#include "hwstub_virtual.hpp"
#include "hwstub_net.hpp"
#include "hwstub_gdb.hpp"
#include <cctype>
namespace hwstub {
@ -41,6 +42,8 @@ void print_usage(FILE *f, bool client, bool server)
if(client)
fprintf(f, " usb USB context (using libusb)\n");
fprintf(f, " tcp TCP context\n");
if(client)
fprintf(f, " gdb A GDB context over tcp\n");
fprintf(f, " unix Local unix domain context\n");
if(client)
fprintf(f, " virt A virtual context (testing and debugging mostly)\n");
@ -63,6 +66,8 @@ void print_usage(FILE *f, bool client, bool server)
fprintf(f, "in which case the domain must start with a '#':\n");
fprintf(f, " unix:///path/to/socket\n");
fprintf(f, " unix://#hwstub\n");
fprintf(f, "When creating a GDB context, the domain and port are the usual TCP parameters:\n");
fprintf(f, " gdb://localhost:4141\n");
if(client)
{
fprintf(f, "When creating a virtual context, the domain must contain a specification of the devices.\n");
@ -288,6 +293,10 @@ std::shared_ptr<context> create_context(const uri& uri, std::string *error)
{
return hwstub::net::context::create_tcp(uri.domain(), uri.port(), error);
}
else if(uri.scheme() == "gdb")
{
return hwstub::gdb::context::create_tcp(uri.domain(), uri.port(), error);
}
else if(uri.scheme() == "unix")
{
/* port must be empty */