diff --git a/utils/hwstub/include/hwstub_gdb.hpp b/utils/hwstub/include/hwstub_gdb.hpp new file mode 100644 index 0000000000..4a9da61de3 --- /dev/null +++ b/utils/hwstub/include/hwstub_gdb.hpp @@ -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 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 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& list, void*& ptr); + virtual void destroy_device_list(void *ptr); + virtual error create_device(ctx_dev_t dev, std::shared_ptr& hwdev); + virtual bool match_device(ctx_dev_t dev, std::shared_ptr 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 ctx); +public: + virtual ~device(); + +protected: + virtual error open_dev(std::shared_ptr& 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 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__ */ + diff --git a/utils/hwstub/include/hwstub_net.hpp b/utils/hwstub/include/hwstub_net.hpp index f6992b5348..6ea36af0eb 100644 --- a/utils/hwstub/include/hwstub_net.hpp +++ b/utils/hwstub/include/hwstub_net.hpp @@ -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 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; }; diff --git a/utils/hwstub/lib/hwstub_gdb.cpp b/utils/hwstub/lib/hwstub_gdb.cpp new file mode 100644 index 0000000000..1c1b453f6c --- /dev/null +++ b/utils/hwstub/lib/hwstub_gdb.cpp @@ -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 +#include + +namespace hwstub { +namespace gdb { + +/** + * Context + */ +context::context(int socket_fd) +{ + m_conn.init(socket_fd); +} + +context::~context() +{ +} + +std::shared_ptr context::create(int socket_fd) +{ + // NOTE: can't use make_shared() because of the protected ctor */ + return std::shared_ptr(new context(socket_fd)); +} + +std::shared_ptr 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(); +} + +error context::fetch_device_list(std::vector& 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& 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 hwdev) +{ + (void) dev; + (void) hwdev; + /* there is only one device, so they all match */ + return true; +} + +/** + * Device + */ +device::device(std::shared_ptr ctx) + :hwstub::device(ctx) +{ +} + +device::~device() +{ +} + +error device::open_dev(std::shared_ptr& 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 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 hctx = get_device()->get_context(); + if(!hctx) + return error::NO_CONTEXT; + context *ctx = dynamic_cast(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 hctx = get_device()->get_context(); + if(!hctx) + return error::NO_CONTEXT; + context *ctx = dynamic_cast(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 + diff --git a/utils/hwstub/lib/hwstub_net.cpp b/utils/hwstub/lib/hwstub_net.cpp index 851723f37d..7009295bd8 100644 --- a/utils/hwstub/lib/hwstub_net.cpp +++ b/utils/hwstub/lib/hwstub_net.cpp @@ -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::create_socket(int socket_fd) -{ - // NOTE: can't use make_shared() because of the protected ctor */ - return std::shared_ptr(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::create_socket(int socket_fd) +{ + // NOTE: can't use make_shared() because of the protected ctor */ + return std::shared_ptr(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::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::create_tcp(const std::string& domain, std::shared_ptr 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::create_unix(const std::string& path, std::stri std::shared_ptr 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 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()); } /** diff --git a/utils/hwstub/lib/hwstub_uri.cpp b/utils/hwstub/lib/hwstub_uri.cpp index da59671540..578f30b2ab 100644 --- a/utils/hwstub/lib/hwstub_uri.cpp +++ b/utils/hwstub/lib/hwstub_uri.cpp @@ -21,6 +21,7 @@ #include "hwstub_usb.hpp" #include "hwstub_virtual.hpp" #include "hwstub_net.hpp" +#include "hwstub_gdb.hpp" #include 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 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 */