From 1fe1dbb3ed7a0f4e4bcfa81b029fc590de461101 Mon Sep 17 00:00:00 2001 From: Martin Hoffmann Date: Tue, 5 Mar 2013 21:14:16 +0100 Subject: [PATCH] util: Added disassembler using objdump tool. The disassembler disassembles an elf file with an external objdump tool. The architecture specific objdump must be configured via cmake (ARCH_TOOL_PREFIX), e.g. arm-none-eabi- for arm-none-eabi-objdump. --- src/core/config/VariantConfig.hpp.in | 2 + src/core/sal/CMakeLists.txt | 2 + src/core/util/CMakeLists.txt | 44 +- src/core/util/Disassembler.cc | 100 + src/core/util/Disassembler.hpp | 68 + src/core/util/pstream.h | 2100 +++++++++++++++++++ src/experiments/kesorefs/experiment.cc | 2 + src/experiments/kesorefs/experiment.hpp | 1 + src/experiments/vezs-example/experiment.cc | 62 +- src/experiments/vezs-example/experiment.hpp | 8 +- 10 files changed, 2340 insertions(+), 49 deletions(-) create mode 100644 src/core/util/Disassembler.cc create mode 100644 src/core/util/Disassembler.hpp create mode 100644 src/core/util/pstream.h diff --git a/src/core/config/VariantConfig.hpp.in b/src/core/config/VariantConfig.hpp.in index 54de68d8..9b819df5 100644 --- a/src/core/config/VariantConfig.hpp.in +++ b/src/core/config/VariantConfig.hpp.in @@ -11,4 +11,6 @@ #cmakedefine BUILD_X86 #cmakedefine BUILD_ARM +#define ARCH_TOOL_PREFIX "@ARCH_TOOL_PREFIX@" + #endif // __VARIANT_CONFIG_HPP__ diff --git a/src/core/sal/CMakeLists.txt b/src/core/sal/CMakeLists.txt index 631bb9ba..fe5abddb 100644 --- a/src/core/sal/CMakeLists.txt +++ b/src/core/sal/CMakeLists.txt @@ -72,10 +72,12 @@ if(BUILD_X86) set(SRCS ${SRCS} x86/Architecture.cc ) + set(ARCH_TOOL_PREFIX "" CACHE PATH "Setup prefix for binutils, e.g., arm-none-eabi- or tricore-, ..") elseif(BUILD_ARM) set(SRCS ${SRCS} arm/Architecture.cc ) + set(ARCH_TOOL_PREFIX "arm-none-eabi-" CACHE PATH "Setup prefix for binutils, e.g., arm-none-eabi- or tricore-, ..") endif(BUILD_X86) # Don't include these sources if perf-stuff is disabled diff --git a/src/core/util/CMakeLists.txt b/src/core/util/CMakeLists.txt index 6c550436..a514c5a4 100644 --- a/src/core/util/CMakeLists.txt +++ b/src/core/util/CMakeLists.txt @@ -1,23 +1,25 @@ set(SRCS - ElfReader.cc - ElfReader.hpp - Demangler.hpp - Demangler.cc - elfinfo/elfinfo.cc - elfinfo/elfinfo.h - gzstream/gzstream.C - gzstream/gzstream.h - Logger.cc - Logger.hpp - MemoryMap.hpp - ProtoStream.cc - ProtoStream.hpp - SynchronizedCounter.cc - SynchronizedCounter.hpp - SynchronizedMap.hpp - SynchronizedQueue.hpp - WallclockTimer.cc - WallclockTimer.hpp + ElfReader.cc + ElfReader.hpp + Demangler.hpp + Demangler.cc + Disassembler.hpp + Disassembler.cc + elfinfo/elfinfo.cc + elfinfo/elfinfo.h + gzstream/gzstream.C + gzstream/gzstream.h + Logger.cc + Logger.hpp + MemoryMap.hpp + ProtoStream.cc + ProtoStream.hpp + SynchronizedCounter.cc + SynchronizedCounter.hpp + SynchronizedMap.hpp + SynchronizedQueue.hpp + WallclockTimer.cc + WallclockTimer.hpp ) # required by ProtoStream.cc: @@ -26,7 +28,7 @@ include_directories(${PROTOBUF_INCLUDE_DIRS}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) # required by Synchronized*.cc: -find_package(Boost 1.42 COMPONENTS thread REQUIRED) +find_package(Boost 1.42 COMPONENTS thread regex REQUIRED) include_directories(${Boost_INCLUDE_DIRS}) link_directories(${Boost_LIBRARY_DIRS}) @@ -38,4 +40,4 @@ if(${LIB_IBERTY} STREQUAL LIB_IBERTY-NOTFOUND) endif() add_library(fail-util ${SRCS}) -target_link_libraries(fail-util ${PROTOBUF_LIBRARY} ${Boost_THREAD_LIBRARY} ${LIB_IBERTY}) +target_link_libraries(fail-util ${PROTOBUF_LIBRARY} ${Boost_LIBRARIES} ${LIB_IBERTY} ) diff --git a/src/core/util/Disassembler.cc b/src/core/util/Disassembler.cc new file mode 100644 index 00000000..3645031e --- /dev/null +++ b/src/core/util/Disassembler.cc @@ -0,0 +1,100 @@ +#include "Disassembler.hpp" +#include +#include "pstream.h" +#include +#include +#include +#include +#ifndef __puma +#include +#include +#endif + +namespace fail { + + const std::string DISASSEMBLER::FAILED = "[Disassembler] Disassemble failed."; + + Disassembler::Disassembler() : m_log("Fail*Disassembler", false){ } + + int Disassembler::init() { + // try to open elf file from environment variable + char * elfpath = getenv("FAIL_ELF_PATH"); + if(elfpath == NULL){ + m_log << "FAIL_ELF_PATH not set :(" << std::endl; + return 0; + }else{ + return init(elfpath); + } + } + + int Disassembler::init(const char* path){ + // Disassemble ELF +#ifndef __puma + std::string command = std::string(ARCH_TOOL_PREFIX) + std::string("objdump -d ") + std::string(path); + m_log << "Executing: " << command << std::endl; + redi::ipstream objdump( command ); + std::string str; + while(std::getline(objdump, str)){ + evaluate(str); + } + + objdump.close(); + if(objdump.rdbuf()->exited()){ + int ex = objdump.rdbuf()->status(); + if(ex != 0){ + m_code.clear(); + m_log << "Could not disassemble!" << std::endl; + return 0; + } + } + m_log << "disassembled " << m_code.size() << " lines." << std::endl; +#endif + return m_code.size(); + } + + std::ostream& operator <<(std::ostream & os, const fail::Instruction & i) { + os << i.opcode << "\t" << i.instruction << "\t" << i.comment; + return os; + } + + void Disassembler::evaluate(const std::string& line){ +#ifndef __puma + // Only read in real code lines: + // Code lines start with a leading whitespace! (hopefully in each objdump implementation!) + if(line.size() > 0 && isspace(line[0])){ + // a line looks like: 800156c:\tdd14 \tble.n 8001598 <_ZN2hw3hal7T32Term8PutBlockEPci+0x30> + boost::regex expr("\\s+([A-Fa-f0-9]+):((?:\\s+[A-Fa-f0-9]+)+)\\s+(.+?)(;.*)?$"); + boost::smatch res; + if(boost::regex_search(line, res, expr)){ + std::string address = res[1]; + std::stringstream ss; + ss << std::hex << address; + address_t addr = 0; + ss >> addr; + std::string opcode = res[2]; + boost::trim(opcode); + std::string instruction = res[3]; + boost::trim(instruction); + std::string comment = res[4]; + boost::trim(comment); + + m_code.insert(std::make_pair(addr, Instruction(opcode, instruction, comment))); + } + } +#endif + } + + static Instruction g_InstructionNotFound; + + const Instruction& Disassembler::disassemble(address_t address){ + InstructionMap_t::const_iterator it = m_code.find(address); + if(it == m_code.end()){ + return g_InstructionNotFound; + }else{ + return it->second; + } + } + + +} // end of namespace + diff --git a/src/core/util/Disassembler.hpp b/src/core/util/Disassembler.hpp new file mode 100644 index 00000000..b15dca17 --- /dev/null +++ b/src/core/util/Disassembler.hpp @@ -0,0 +1,68 @@ +#ifndef __DISASSEMBLER_HPP +#define __DISASSEMBLER_HPP + +#include +#include "Logger.hpp" +#include "sal/SALConfig.hpp" +#include +#include + +namespace fail { + + //! Inform about failed disassembly + struct DISASSEMBLER { + static const std::string FAILED; + }; + + /** + * @class Instruction + * @brief An Instruction represents an disassembled opcode + */ + struct Instruction { + std::string opcode; // TODO convert to integer, size? + std::string instruction; //!< The disassembled instruction + std::string comment; //!< Comment (rest of line after ; ) + Instruction(std::string opcode = "", const std::string& instr = DISASSEMBLER::FAILED, const std::string& comment = "") + : opcode(opcode), instruction(instr), comment(comment) { }; + }; + // InstructionMap_t; + InstructionMap_t m_code; + void evaluate(const std::string &); + }; +} // end of namespace + +#endif // DISASSEMBLER_HPP diff --git a/src/core/util/pstream.h b/src/core/util/pstream.h new file mode 100644 index 00000000..10b2ec82 --- /dev/null +++ b/src/core/util/pstream.h @@ -0,0 +1,2100 @@ +/* +PStreams - POSIX Process I/O for C++ +Copyright (C) 2001-2012 Jonathan Wakely + +This file is part of PStreams. + +PStreams is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. + +PStreams is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . +*/ + +/** + * @file pstream.h + * @brief Declares all PStreams classes. + * @author Jonathan Wakely + * + * Defines classes redi::ipstream, redi::opstream, redi::pstream + * and redi::rpstream. + */ + +#ifndef REDI_PSTREAM_H_SEEN +#define REDI_PSTREAM_H_SEEN + +#include +#include +#include +#include +#include +#include +#include // for min() +#include // for errno +#include // for size_t, NULL +#include // for exit() +#include // for pid_t +#include // for waitpid() +#include // for ioctl() and FIONREAD +#if defined(__sun) +# include // for FIONREAD on Solaris 2.5 +#endif +#include // for pipe() fork() exec() and filedes functions +#include // for kill() +#include // for fcntl() +#if REDI_EVISCERATE_PSTREAMS +# include // for FILE, fdopen() +#endif + + +/// The library version. +#define PSTREAMS_VERSION 0x0072 // 0.7.2 + +/** + * @namespace redi + * @brief All PStreams classes are declared in namespace redi. + * + * Like the standard iostreams, PStreams is a set of class templates, + * taking a character type and traits type. As with the standard streams + * they are most likely to be used with @c char and the default + * traits type, so typedefs for this most common case are provided. + * + * The @c pstream_common class template is not intended to be used directly, + * it is used internally to provide the common functionality for the + * other stream classes. + */ +namespace redi +{ + /// Common base class providing constants and typenames. + struct pstreams + { + /// Type used to specify how to connect to the process. + typedef std::ios_base::openmode pmode; + + /// Type used to hold the arguments for a command. + typedef std::vector argv_type; + + /// Type used for file descriptors. + typedef int fd_type; + + static const pmode pstdin = std::ios_base::out; ///< Write to stdin + static const pmode pstdout = std::ios_base::in; ///< Read from stdout + static const pmode pstderr = std::ios_base::app; ///< Read from stderr + + protected: + enum { bufsz = 32 }; ///< Size of pstreambuf buffers. + enum { pbsz = 2 }; ///< Number of putback characters kept. + }; + + /// Class template for stream buffer. + template > + class basic_pstreambuf + : public std::basic_streambuf + , public pstreams + { + public: + // Type definitions for dependent types + typedef CharT char_type; + typedef Traits traits_type; + typedef typename traits_type::int_type int_type; + typedef typename traits_type::off_type off_type; + typedef typename traits_type::pos_type pos_type; + /** @deprecated use pstreams::fd_type instead. */ + typedef fd_type fd_t; + + /// Default constructor. + basic_pstreambuf(); + + /// Constructor that initialises the buffer with @a command. + basic_pstreambuf(const std::string& command, pmode mode); + + /// Constructor that initialises the buffer with @a file and @a argv. + basic_pstreambuf( const std::string& file, + const argv_type& argv, + pmode mode ); + + /// Destructor. + ~basic_pstreambuf(); + + /// Initialise the stream buffer with @a command. + basic_pstreambuf* + open(const std::string& command, pmode mode); + + /// Initialise the stream buffer with @a file and @a argv. + basic_pstreambuf* + open(const std::string& file, const argv_type& argv, pmode mode); + + /// Close the stream buffer and wait for the process to exit. + basic_pstreambuf* + close(); + + /// Send a signal to the process. + basic_pstreambuf* + kill(int signal = SIGTERM); + + /// Close the pipe connected to the process' stdin. + void + peof(); + + /// Change active input source. + bool + read_err(bool readerr = true); + + /// Report whether the stream buffer has been initialised. + bool + is_open() const; + + /// Report whether the process has exited. + bool + exited(); + +#if REDI_EVISCERATE_PSTREAMS + /// Obtain FILE pointers for each of the process' standard streams. + std::size_t + fopen(FILE*& in, FILE*& out, FILE*& err); +#endif + + /// Return the exit status of the process. + int + status() const; + + /// Return the error number (errno) for the most recent failed operation. + int + error() const; + + protected: + /// Transfer characters to the pipe when character buffer overflows. + int_type + overflow(int_type c); + + /// Transfer characters from the pipe when the character buffer is empty. + int_type + underflow(); + + /// Make a character available to be returned by the next extraction. + int_type + pbackfail(int_type c = traits_type::eof()); + + /// Write any buffered characters to the stream. + int + sync(); + + /// Insert multiple characters into the pipe. + std::streamsize + xsputn(const char_type* s, std::streamsize n); + + /// Insert a sequence of characters into the pipe. + std::streamsize + write(const char_type* s, std::streamsize n); + + /// Extract a sequence of characters from the pipe. + std::streamsize + read(char_type* s, std::streamsize n); + + /// Report how many characters can be read from active input without blocking. + std::streamsize + showmanyc(); + + protected: + /// Enumerated type to indicate whether stdout or stderr is to be read. + enum buf_read_src { rsrc_out = 0, rsrc_err = 1 }; + + /// Initialise pipes and fork process. + pid_t + fork(pmode mode); + + /// Wait for the child process to exit. + int + wait(bool nohang = false); + + /// Return the file descriptor for the output pipe. + fd_type& + wpipe(); + + /// Return the file descriptor for the active input pipe. + fd_type& + rpipe(); + + /// Return the file descriptor for the specified input pipe. + fd_type& + rpipe(buf_read_src which); + + void + create_buffers(pmode mode); + + void + destroy_buffers(pmode mode); + + /// Writes buffered characters to the process' stdin pipe. + bool + empty_buffer(); + + bool + fill_buffer(bool non_blocking = false); + + /// Return the active input buffer. + char_type* + rbuffer(); + + buf_read_src + switch_read_buffer(buf_read_src); + + private: + basic_pstreambuf(const basic_pstreambuf&); + basic_pstreambuf& operator=(const basic_pstreambuf&); + + void + init_rbuffers(); + + pid_t ppid_; // pid of process + fd_type wpipe_; // pipe used to write to process' stdin + fd_type rpipe_[2]; // two pipes to read from, stdout and stderr + char_type* wbuffer_; + char_type* rbuffer_[2]; + char_type* rbufstate_[3]; + /// Index into rpipe_[] to indicate active source for read operations. + buf_read_src rsrc_; + int status_; // hold exit status of child process + int error_; // hold errno if fork() or exec() fails + }; + + /// Class template for common base class. + template > + class pstream_common + : virtual public std::basic_ios + , virtual public pstreams + { + protected: + typedef basic_pstreambuf streambuf_type; + + typedef pstreams::pmode pmode; + typedef pstreams::argv_type argv_type; + + /// Default constructor. + pstream_common(); + + /// Constructor that initialises the stream by starting a process. + pstream_common(const std::string& command, pmode mode); + + /// Constructor that initialises the stream by starting a process. + pstream_common(const std::string& file, const argv_type& argv, pmode mode); + + /// Pure virtual destructor. + virtual + ~pstream_common() = 0; + + /// Start a process. + void + do_open(const std::string& command, pmode mode); + + /// Start a process. + void + do_open(const std::string& file, const argv_type& argv, pmode mode); + + public: + /// Close the pipe. + void + close(); + + /// Report whether the stream's buffer has been initialised. + bool + is_open() const; + + /// Return the command used to initialise the stream. + const std::string& + command() const; + + /// Return a pointer to the stream buffer. + streambuf_type* + rdbuf() const; + +#if REDI_EVISCERATE_PSTREAMS + /// Obtain FILE pointers for each of the process' standard streams. + std::size_t + fopen(FILE*& in, FILE*& out, FILE*& err); +#endif + + protected: + std::string command_; ///< The command used to start the process. + streambuf_type buf_; ///< The stream buffer. + }; + + + /** + * @class basic_ipstream + * @brief Class template for Input PStreams. + * + * Reading from an ipstream reads the command's standard output and/or + * standard error (depending on how the ipstream is opened) + * and the command's standard input is the same as that of the process + * that created the object, unless altered by the command itself. + */ + + template > + class basic_ipstream + : public std::basic_istream + , public pstream_common + , virtual public pstreams + { + typedef std::basic_istream istream_type; + typedef pstream_common pbase_type; + + using pbase_type::buf_; // declare name in this scope + + pmode readable(pmode mode) + { + if (!(mode & (pstdout|pstderr))) + mode |= pstdout; + return mode; + } + + public: + /// Type used to specify how to connect to the process. + typedef typename pbase_type::pmode pmode; + + /// Type used to hold the arguments for a command. + typedef typename pbase_type::argv_type argv_type; + + /// Default constructor, creates an uninitialised stream. + basic_ipstream() + : istream_type(NULL), pbase_type() + { } + + /** + * @brief Constructor that initialises the stream by starting a process. + * + * Initialises the stream buffer by calling do_open() with the supplied + * arguments. + * + * @param command a string containing a shell command. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, pmode) + */ + basic_ipstream(const std::string& command, pmode mode = pstdout) + : istream_type(NULL), pbase_type(command, readable(mode)) + { } + + /** + * @brief Constructor that initialises the stream by starting a process. + * + * Initialises the stream buffer by calling do_open() with the supplied + * arguments. + * + * @param file a string containing the pathname of a program to execute. + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + basic_ipstream( const std::string& file, + const argv_type& argv, + pmode mode = pstdout ) + : istream_type(NULL), pbase_type(file, argv, readable(mode)) + { } + + /** + * @brief Destructor. + * + * Closes the stream and waits for the child to exit. + */ + ~basic_ipstream() + { } + + /** + * @brief Start a process. + * + * Calls do_open( @a %command , @a mode|pstdout ). + * + * @param command a string containing a shell command. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, pmode) + */ + void + open(const std::string& command, pmode mode = pstdout) + { + this->do_open(command, readable(mode)); + } + + /** + * @brief Start a process. + * + * Calls do_open( @a file , @a argv , @a mode|pstdout ). + * + * @param file a string containing the pathname of a program to execute. + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + void + open( const std::string& file, + const argv_type& argv, + pmode mode = pstdout ) + { + this->do_open(file, argv, readable(mode)); + } + + /** + * @brief Set streambuf to read from process' @c stdout. + * @return @c *this + */ + basic_ipstream& + out() + { + this->buf_.read_err(false); + return *this; + } + + /** + * @brief Set streambuf to read from process' @c stderr. + * @return @c *this + */ + basic_ipstream& + err() + { + this->buf_.read_err(true); + return *this; + } + }; + + + /** + * @class basic_opstream + * @brief Class template for Output PStreams. + * + * Writing to an open opstream writes to the standard input of the command; + * the command's standard output is the same as that of the process that + * created the pstream object, unless altered by the command itself. + */ + + template > + class basic_opstream + : public std::basic_ostream + , public pstream_common + , virtual public pstreams + { + typedef std::basic_ostream ostream_type; + typedef pstream_common pbase_type; + + using pbase_type::buf_; // declare name in this scope + + public: + /// Type used to specify how to connect to the process. + typedef typename pbase_type::pmode pmode; + + /// Type used to hold the arguments for a command. + typedef typename pbase_type::argv_type argv_type; + + /// Default constructor, creates an uninitialised stream. + basic_opstream() + : ostream_type(NULL), pbase_type() + { } + + /** + * @brief Constructor that initialises the stream by starting a process. + * + * Initialises the stream buffer by calling do_open() with the supplied + * arguments. + * + * @param command a string containing a shell command. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, pmode) + */ + basic_opstream(const std::string& command, pmode mode = pstdin) + : ostream_type(NULL), pbase_type(command, mode|pstdin) + { } + + /** + * @brief Constructor that initialises the stream by starting a process. + * + * Initialises the stream buffer by calling do_open() with the supplied + * arguments. + * + * @param file a string containing the pathname of a program to execute. + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + basic_opstream( const std::string& file, + const argv_type& argv, + pmode mode = pstdin ) + : ostream_type(NULL), pbase_type(file, argv, mode|pstdin) + { } + + /** + * @brief Destructor + * + * Closes the stream and waits for the child to exit. + */ + ~basic_opstream() { } + + /** + * @brief Start a process. + * + * Calls do_open( @a %command , @a mode|pstdin ). + * + * @param command a string containing a shell command. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, pmode) + */ + void + open(const std::string& command, pmode mode = pstdin) + { + this->do_open(command, mode|pstdin); + } + + /** + * @brief Start a process. + * + * Calls do_open( @a file , @a argv , @a mode|pstdin ). + * + * @param file a string containing the pathname of a program to execute. + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + void + open( const std::string& file, + const argv_type& argv, + pmode mode = pstdin) + { + this->do_open(file, argv, mode|pstdin); + } + }; + + + /** + * @class basic_pstream + * @brief Class template for Bidirectional PStreams. + * + * Writing to a pstream opened with @c pmode @c pstdin writes to the + * standard input of the command. + * Reading from a pstream opened with @c pmode @c pstdout and/or @c pstderr + * reads the command's standard output and/or standard error. + * Any of the process' @c stdin, @c stdout or @c stderr that is not + * connected to the pstream (as specified by the @c pmode) + * will be the same as the process that created the pstream object, + * unless altered by the command itself. + */ + template > + class basic_pstream + : public std::basic_iostream + , public pstream_common + , virtual public pstreams + { + typedef std::basic_iostream iostream_type; + typedef pstream_common pbase_type; + + using pbase_type::buf_; // declare name in this scope + + public: + /// Type used to specify how to connect to the process. + typedef typename pbase_type::pmode pmode; + + /// Type used to hold the arguments for a command. + typedef typename pbase_type::argv_type argv_type; + + /// Default constructor, creates an uninitialised stream. + basic_pstream() + : iostream_type(NULL), pbase_type() + { } + + /** + * @brief Constructor that initialises the stream by starting a process. + * + * Initialises the stream buffer by calling do_open() with the supplied + * arguments. + * + * @param command a string containing a shell command. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, pmode) + */ + basic_pstream(const std::string& command, pmode mode = pstdout|pstdin) + : iostream_type(NULL), pbase_type(command, mode) + { } + + /** + * @brief Constructor that initialises the stream by starting a process. + * + * Initialises the stream buffer by calling do_open() with the supplied + * arguments. + * + * @param file a string containing the pathname of a program to execute. + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + basic_pstream( const std::string& file, + const argv_type& argv, + pmode mode = pstdout|pstdin ) + : iostream_type(NULL), pbase_type(file, argv, mode) + { } + + /** + * @brief Destructor + * + * Closes the stream and waits for the child to exit. + */ + ~basic_pstream() { } + + /** + * @brief Start a process. + * + * Calls do_open( @a %command , @a mode ). + * + * @param command a string containing a shell command. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, pmode) + */ + void + open(const std::string& command, pmode mode = pstdout|pstdin) + { + this->do_open(command, mode); + } + + /** + * @brief Start a process. + * + * Calls do_open( @a file , @a argv , @a mode ). + * + * @param file a string containing the pathname of a program to execute. + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + void + open( const std::string& file, + const argv_type& argv, + pmode mode = pstdout|pstdin ) + { + this->do_open(file, argv, mode); + } + + /** + * @brief Set streambuf to read from process' @c stdout. + * @return @c *this + */ + basic_pstream& + out() + { + this->buf_.read_err(false); + return *this; + } + + /** + * @brief Set streambuf to read from process' @c stderr. + * @return @c *this + */ + basic_pstream& + err() + { + this->buf_.read_err(true); + return *this; + } + }; + + + /** + * @class basic_rpstream + * @brief template for Restricted PStreams. + * + * Writing to an rpstream opened with @c pmode @c pstdin writes to the + * standard input of the command. + * It is not possible to read directly from an rpstream object, to use + * an rpstream as in istream you must call either basic_rpstream::out() + * or basic_rpstream::err(). This is to prevent accidental reads from + * the wrong input source. If the rpstream was not opened with @c pmode + * @c pstderr then the class cannot read the process' @c stderr, and + * basic_rpstream::err() will return an istream that reads from the + * process' @c stdout, and vice versa. + * Reading from an rpstream opened with @c pmode @c pstdout and/or + * @c pstderr reads the command's standard output and/or standard error. + * Any of the process' @c stdin, @c stdout or @c stderr that is not + * connected to the pstream (as specified by the @c pmode) + * will be the same as the process that created the pstream object, + * unless altered by the command itself. + */ + + template > + class basic_rpstream + : public std::basic_ostream + , private std::basic_istream + , private pstream_common + , virtual public pstreams + { + typedef std::basic_ostream ostream_type; + typedef std::basic_istream istream_type; + typedef pstream_common pbase_type; + + using pbase_type::buf_; // declare name in this scope + + public: + /// Type used to specify how to connect to the process. + typedef typename pbase_type::pmode pmode; + + /// Type used to hold the arguments for a command. + typedef typename pbase_type::argv_type argv_type; + + /// Default constructor, creates an uninitialised stream. + basic_rpstream() + : ostream_type(NULL), istream_type(NULL), pbase_type() + { } + + /** + * @brief Constructor that initialises the stream by starting a process. + * + * Initialises the stream buffer by calling do_open() with the supplied + * arguments. + * + * @param command a string containing a shell command. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, pmode) + */ + basic_rpstream(const std::string& command, pmode mode = pstdout|pstdin) + : ostream_type(NULL) , istream_type(NULL) , pbase_type(command, mode) + { } + + /** + * @brief Constructor that initialises the stream by starting a process. + * + * Initialises the stream buffer by calling do_open() with the supplied + * arguments. + * + * @param file a string containing the pathname of a program to execute. + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + basic_rpstream( const std::string& file, + const argv_type& argv, + pmode mode = pstdout|pstdin ) + : ostream_type(NULL), istream_type(NULL), pbase_type(file, argv, mode) + { } + + /// Destructor + ~basic_rpstream() { } + + /** + * @brief Start a process. + * + * Calls do_open( @a %command , @a mode ). + * + * @param command a string containing a shell command. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, pmode) + */ + void + open(const std::string& command, pmode mode = pstdout|pstdin) + { + this->do_open(command, mode); + } + + /** + * @brief Start a process. + * + * Calls do_open( @a file , @a argv , @a mode ). + * + * @param file a string containing the pathname of a program to execute. + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + void + open( const std::string& file, + const argv_type& argv, + pmode mode = pstdout|pstdin ) + { + this->do_open(file, argv, mode); + } + + /** + * @brief Obtain a reference to the istream that reads + * the process' @c stdout. + * @return @c *this + */ + istream_type& + out() + { + this->buf_.read_err(false); + return *this; + } + + /** + * @brief Obtain a reference to the istream that reads + * the process' @c stderr. + * @return @c *this + */ + istream_type& + err() + { + this->buf_.read_err(true); + return *this; + } + }; + + + /// Type definition for common template specialisation. + typedef basic_pstreambuf pstreambuf; + /// Type definition for common template specialisation. + typedef basic_ipstream ipstream; + /// Type definition for common template specialisation. + typedef basic_opstream opstream; + /// Type definition for common template specialisation. + typedef basic_pstream pstream; + /// Type definition for common template specialisation. + typedef basic_rpstream rpstream; + + + /** + * When inserted into an output pstream the manipulator calls + * basic_pstreambuf::peof() to close the output pipe, + * causing the child process to receive the end-of-file indicator + * on subsequent reads from its @c stdin stream. + * + * @brief Manipulator to close the pipe connected to the process' stdin. + * @param s An output PStream class. + * @return The stream object the manipulator was invoked on. + * @see basic_pstreambuf::peof() + * @relates basic_opstream basic_pstream basic_rpstream + */ + template + inline std::basic_ostream& + peof(std::basic_ostream& s) + { + typedef basic_pstreambuf pstreambuf; + if (pstreambuf* p = dynamic_cast(s.rdbuf())) + p->peof(); + return s; + } + + + /* + * member definitions for pstreambuf + */ + + + /** + * @class basic_pstreambuf + * Provides underlying streambuf functionality for the PStreams classes. + */ + + /** Creates an uninitialised stream buffer. */ + template + inline + basic_pstreambuf::basic_pstreambuf() + : ppid_(-1) // initialise to -1 to indicate no process run yet. + , wpipe_(-1) + , wbuffer_(NULL) + , rsrc_(rsrc_out) + , status_(-1) + , error_(0) + { + init_rbuffers(); + } + + /** + * Initialises the stream buffer by calling open() with the supplied + * arguments. + * + * @param command a string containing a shell command. + * @param mode the I/O mode to use when opening the pipe. + * @see open() + */ + template + inline + basic_pstreambuf::basic_pstreambuf(const std::string& command, pmode mode) + : ppid_(-1) // initialise to -1 to indicate no process run yet. + , wpipe_(-1) + , wbuffer_(NULL) + , rsrc_(rsrc_out) + , status_(-1) + , error_(0) + { + init_rbuffers(); + open(command, mode); + } + + /** + * Initialises the stream buffer by calling open() with the supplied + * arguments. + * + * @param file a string containing the name of a program to execute. + * @param argv a vector of argument strings passsed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see open() + */ + template + inline + basic_pstreambuf::basic_pstreambuf( const std::string& file, + const argv_type& argv, + pmode mode ) + : ppid_(-1) // initialise to -1 to indicate no process run yet. + , wpipe_(-1) + , wbuffer_(NULL) + , rsrc_(rsrc_out) + , status_(-1) + , error_(0) + { + init_rbuffers(); + open(file, argv, mode); + } + + /** + * Closes the stream by calling close(). + * @see close() + */ + template + inline + basic_pstreambuf::~basic_pstreambuf() + { + close(); + } + + /** + * Starts a new process by passing @a command to the shell (/bin/sh) + * and opens pipes to the process with the specified @a mode. + * + * If @a mode contains @c pstdout the initial read source will be + * the child process' stdout, otherwise if @a mode contains @c pstderr + * the initial read source will be the child's stderr. + * + * Will duplicate the actions of the shell in searching for an + * executable file if the specified file name does not contain a slash (/) + * character. + * + * @warning + * There is no way to tell whether the shell command succeeded, this + * function will always succeed unless resource limits (such as + * memory usage, or number of processes or open files) are exceeded. + * This means is_open() will return true even if @a command cannot + * be executed. + * Use pstreambuf::open(const std::string&, const argv_type&, pmode) + * if you need to know whether the command failed to execute. + * + * @param command a string containing a shell command. + * @param mode a bitwise OR of one or more of @c out, @c in, @c err. + * @return NULL if the shell could not be started or the + * pipes could not be opened, @c this otherwise. + * @see execl(3) + */ + template + basic_pstreambuf* + basic_pstreambuf::open(const std::string& command, pmode mode) + { + const char * shell_path = "/bin/sh"; +#if 0 + const std::string argv[] = { "sh", "-c", command }; + return this->open(shell_path, argv_type(argv, argv+3), mode); +#else + basic_pstreambuf* ret = NULL; + + if (!is_open()) + { + switch(fork(mode)) + { + case 0 : + // this is the new process, exec command + ::execl(shell_path, "sh", "-c", command.c_str(), (char*)NULL); + + // can only reach this point if exec() failed + + // parent can get exit code from waitpid() + ::_exit(errno); + // using std::exit() would make static dtors run twice + + case -1 : + // couldn't fork, error already handled in pstreambuf::fork() + break; + + default : + // this is the parent process + // activate buffers + create_buffers(mode); + ret = this; + } + } + return ret; +#endif + } + + /** + * @brief Helper function to close a file descriptor. + * + * Inspects @a fd and calls close(3) if it has a non-negative value. + * + * @param fd a file descriptor. + * @relates basic_pstreambuf + */ + inline void + close_fd(pstreams::fd_type& fd) + { + if (fd >= 0 && ::close(fd) == 0) + fd = -1; + } + + /** + * @brief Helper function to close an array of file descriptors. + * + * Calls @c close_fd() on each member of the array. + * The length of the array is determined automatically by + * template argument deduction to avoid errors. + * + * @param fds an array of file descriptors. + * @relates basic_pstreambuf + */ + template + inline void + close_fd_array(pstreams::fd_type (&fds)[N]) + { + for (std::size_t i = 0; i < N; ++i) + close_fd(fds[i]); + } + + /** + * Starts a new process by executing @a file with the arguments in + * @a argv and opens pipes to the process with the specified @a mode. + * + * By convention @c argv[0] should be the file name of the file being + * executed. + * + * If @a mode contains @c pstdout the initial read source will be + * the child process' stdout, otherwise if @a mode contains @c pstderr + * the initial read source will be the child's stderr. + * + * Will duplicate the actions of the shell in searching for an + * executable file if the specified file name does not contain a slash (/) + * character. + * + * Iff @a file is successfully executed then is_open() will return true. + * Otherwise, pstreambuf::error() can be used to obtain the value of + * @c errno that was set by execvp(3) in the child process. + * + * The exit status of the new process will be returned by + * pstreambuf::status() after pstreambuf::exited() returns true. + * + * @param file a string containing the pathname of a program to execute. + * @param argv a vector of argument strings passed to the new program. + * @param mode a bitwise OR of one or more of @c out, @c in and @c err. + * @return NULL if a pipe could not be opened or if the program could + * not be executed, @c this otherwise. + * @see execvp(3) + */ + template + basic_pstreambuf* + basic_pstreambuf::open( const std::string& file, + const argv_type& argv, + pmode mode ) + { + basic_pstreambuf* ret = NULL; + + if (!is_open()) + { + // constants for read/write ends of pipe + enum { RD, WR }; + + // open another pipe and set close-on-exec + fd_type ck_exec[] = { -1, -1 }; + if (-1 == ::pipe(ck_exec) + || -1 == ::fcntl(ck_exec[RD], F_SETFD, FD_CLOEXEC) + || -1 == ::fcntl(ck_exec[WR], F_SETFD, FD_CLOEXEC)) + { + error_ = errno; + close_fd_array(ck_exec); + } + else + { + switch(fork(mode)) + { + case 0 : + // this is the new process, exec command + { + char** arg_v = new char*[argv.size()+1]; + for (std::size_t i = 0; i < argv.size(); ++i) + { + const std::string& src = argv[i]; + char*& dest = arg_v[i]; + dest = new char[src.size()+1]; + dest[ src.copy(dest, src.size()) ] = '\0'; + } + arg_v[argv.size()] = NULL; + + ::execvp(file.c_str(), arg_v); + + // can only reach this point if exec() failed + + // parent can get error code from ck_exec pipe + error_ = errno; + + ssize_t wrote = ::write(ck_exec[WR], &error_, sizeof(error_)); + if (wrote == -1) + { + // Suppress warn_unused_result attribute with _FORTIFY_SOURCE. + // Process is about to exit anyway, no need for a better check. + } + + ::close(ck_exec[WR]); + ::close(ck_exec[RD]); + + ::_exit(error_); + // using std::exit() would make static dtors run twice + } + + case -1 : + // couldn't fork, error already handled in pstreambuf::fork() + close_fd_array(ck_exec); + break; + + default : + // this is the parent process + + // check child called exec() successfully + ::close(ck_exec[WR]); + switch (::read(ck_exec[RD], &error_, sizeof(error_))) + { + case 0: + // activate buffers + create_buffers(mode); + ret = this; + break; + case -1: + error_ = errno; + break; + default: + // error_ contains error code from child + // call wait() to clean up and set ppid_ to 0 + this->wait(); + break; + } + ::close(ck_exec[RD]); + } + } + } + return ret; + } + + /** + * Creates pipes as specified by @a mode and calls @c fork() to create + * a new process. If the fork is successful the parent process stores + * the child's PID and the opened pipes and the child process replaces + * its standard streams with the opened pipes. + * + * If an error occurs the error code will be set to one of the possible + * errors for @c pipe() or @c fork(). + * See your system's documentation for these error codes. + * + * @param mode an OR of pmodes specifying which of the child's + * standard streams to connect to. + * @return On success the PID of the child is returned in the parent's + * context and zero is returned in the child's context. + * On error -1 is returned and the error code is set appropriately. + */ + template + pid_t + basic_pstreambuf::fork(pmode mode) + { + pid_t pid = -1; + + // Three pairs of file descriptors, for pipes connected to the + // process' stdin, stdout and stderr + // (stored in a single array so close_fd_array() can close all at once) + fd_type fd[] = { -1, -1, -1, -1, -1, -1 }; + fd_type* const pin = fd; + fd_type* const pout = fd+2; + fd_type* const perr = fd+4; + + // constants for read/write ends of pipe + enum { RD, WR }; + + // N.B. + // For the pstreambuf pin is an output stream and + // pout and perr are input streams. + + if (!error_ && mode&pstdin && ::pipe(pin)) + error_ = errno; + + if (!error_ && mode&pstdout && ::pipe(pout)) + error_ = errno; + + if (!error_ && mode&pstderr && ::pipe(perr)) + error_ = errno; + + if (!error_) + { + pid = ::fork(); + switch (pid) + { + case 0 : + { + // this is the new process + + // for each open pipe close one end and redirect the + // respective standard stream to the other end + + if (*pin >= 0) + { + ::close(pin[WR]); + ::dup2(pin[RD], STDIN_FILENO); + ::close(pin[RD]); + } + if (*pout >= 0) + { + ::close(pout[RD]); + ::dup2(pout[WR], STDOUT_FILENO); + ::close(pout[WR]); + } + if (*perr >= 0) + { + ::close(perr[RD]); + ::dup2(perr[WR], STDERR_FILENO); + ::close(perr[WR]); + } + break; + } + case -1 : + { + // couldn't fork for some reason + error_ = errno; + // close any open pipes + close_fd_array(fd); + break; + } + default : + { + // this is the parent process, store process' pid + ppid_ = pid; + + // store one end of open pipes and close other end + if (*pin >= 0) + { + wpipe_ = pin[WR]; + ::close(pin[RD]); + } + if (*pout >= 0) + { + rpipe_[rsrc_out] = pout[RD]; + ::close(pout[WR]); + } + if (*perr >= 0) + { + rpipe_[rsrc_err] = perr[RD]; + ::close(perr[WR]); + } + } + } + } + else + { + // close any pipes we opened before failure + close_fd_array(fd); + } + return pid; + } + + /** + * Closes all pipes and calls wait() to wait for the process to finish. + * If an error occurs the error code will be set to one of the possible + * errors for @c waitpid(). + * See your system's documentation for these errors. + * + * @return @c this on successful close or @c NULL if there is no + * process to close or if an error occurs. + */ + template + basic_pstreambuf* + basic_pstreambuf::close() + { + const bool running = is_open(); + + sync(); // this might call wait() and reap the child process + + // rather than trying to work out whether or not we need to clean up + // just do it anyway, all cleanup functions are safe to call twice. + + destroy_buffers(pstdin|pstdout|pstderr); + + // close pipes before wait() so child gets EOF/SIGPIPE + close_fd(wpipe_); + close_fd_array(rpipe_); + + do + { + error_ = 0; + } while (wait() == -1 && error() == EINTR); + + return running ? this : NULL; + } + + /** + * Called on construction to initialise the arrays used for reading. + */ + template + inline void + basic_pstreambuf::init_rbuffers() + { + rpipe_[rsrc_out] = rpipe_[rsrc_err] = -1; + rbuffer_[rsrc_out] = rbuffer_[rsrc_err] = NULL; + rbufstate_[0] = rbufstate_[1] = rbufstate_[2] = NULL; + } + + template + void + basic_pstreambuf::create_buffers(pmode mode) + { + if (mode & pstdin) + { + delete[] wbuffer_; + wbuffer_ = new char_type[bufsz]; + this->setp(wbuffer_, wbuffer_ + bufsz); + } + if (mode & pstdout) + { + delete[] rbuffer_[rsrc_out]; + rbuffer_[rsrc_out] = new char_type[bufsz]; + rsrc_ = rsrc_out; + this->setg(rbuffer_[rsrc_out] + pbsz, rbuffer_[rsrc_out] + pbsz, + rbuffer_[rsrc_out] + pbsz); + } + if (mode & pstderr) + { + delete[] rbuffer_[rsrc_err]; + rbuffer_[rsrc_err] = new char_type[bufsz]; + if (!(mode & pstdout)) + { + rsrc_ = rsrc_err; + this->setg(rbuffer_[rsrc_err] + pbsz, rbuffer_[rsrc_err] + pbsz, + rbuffer_[rsrc_err] + pbsz); + } + } + } + + template + void + basic_pstreambuf::destroy_buffers(pmode mode) + { + if (mode & pstdin) + { + this->setp(NULL, NULL); + delete[] wbuffer_; + wbuffer_ = NULL; + } + if (mode & pstdout) + { + if (rsrc_ == rsrc_out) + this->setg(NULL, NULL, NULL); + delete[] rbuffer_[rsrc_out]; + rbuffer_[rsrc_out] = NULL; + } + if (mode & pstderr) + { + if (rsrc_ == rsrc_err) + this->setg(NULL, NULL, NULL); + delete[] rbuffer_[rsrc_err]; + rbuffer_[rsrc_err] = NULL; + } + } + + template + typename basic_pstreambuf::buf_read_src + basic_pstreambuf::switch_read_buffer(buf_read_src src) + { + if (rsrc_ != src) + { + char_type* tmpbufstate[] = {this->eback(), this->gptr(), this->egptr()}; + this->setg(rbufstate_[0], rbufstate_[1], rbufstate_[2]); + for (std::size_t i = 0; i < 3; ++i) + rbufstate_[i] = tmpbufstate[i]; + rsrc_ = src; + } + return rsrc_; + } + + /** + * Suspends execution and waits for the associated process to exit, or + * until a signal is delivered whose action is to terminate the current + * process or to call a signal handling function. If the process has + * already exited (i.e. it is a "zombie" process) then wait() returns + * immediately. Waiting for the child process causes all its system + * resources to be freed. + * + * error() will return EINTR if wait() is interrupted by a signal. + * + * @param nohang true to return immediately if the process has not exited. + * @return 1 if the process has exited and wait() has not yet been called. + * 0 if @a nohang is true and the process has not exited yet. + * -1 if no process has been started or if an error occurs, + * in which case the error can be found using error(). + */ + template + int + basic_pstreambuf::wait(bool nohang) + { + int exited = -1; + if (is_open()) + { + int status; + switch(::waitpid(ppid_, &status, nohang ? WNOHANG : 0)) + { + case 0 : + // nohang was true and process has not exited + exited = 0; + break; + case -1 : + error_ = errno; + break; + default : + // process has exited + ppid_ = 0; + status_ = status; + exited = 1; + // Close wpipe, would get SIGPIPE if we used it. + destroy_buffers(pstdin); + close_fd(wpipe_); + // Must free read buffers and pipes on destruction + // or next call to open()/close() + break; + } + } + return exited; + } + + /** + * Sends the specified signal to the process. A signal can be used to + * terminate a child process that would not exit otherwise. + * + * If an error occurs the error code will be set to one of the possible + * errors for @c kill(). See your system's documentation for these errors. + * + * @param signal A signal to send to the child process. + * @return @c this or @c NULL if @c kill() fails. + */ + template + inline basic_pstreambuf* + basic_pstreambuf::kill(int signal) + { + basic_pstreambuf* ret = NULL; + if (is_open()) + { + if (::kill(ppid_, signal)) + error_ = errno; + else + { +#if 0 + // TODO call exited() to check for exit and clean up? leave to user? + if (signal==SIGTERM || signal==SIGKILL) + this->exited(); +#endif + ret = this; + } + } + return ret; + } + + /** + * This function can call pstreambuf::wait() and so may change the + * object's state if the child process has already exited. + * + * @return True if the associated process has exited, false otherwise. + * @see basic_pstreambuf::wait() + */ + template + inline bool + basic_pstreambuf::exited() + { + return ppid_ == 0 || wait(true)==1; + } + + + /** + * @return The exit status of the child process, or -1 if wait() + * has not yet been called to wait for the child to exit. + * @see basic_pstreambuf::wait() + */ + template + inline int + basic_pstreambuf::status() const + { + return status_; + } + + /** + * @return The error code of the most recently failed operation, or zero. + */ + template + inline int + basic_pstreambuf::error() const + { + return error_; + } + + /** + * Closes the output pipe, causing the child process to receive the + * end-of-file indicator on subsequent reads from its @c stdin stream. + */ + template + inline void + basic_pstreambuf::peof() + { + sync(); + destroy_buffers(pstdin); + close_fd(wpipe_); + } + + /** + * Unlike pstreambuf::exited(), this function will not call wait() and + * so will not change the object's state. This means that once a child + * process is executed successfully this function will continue to + * return true even after the process exits (until wait() is called.) + * + * @return true if a previous call to open() succeeded and wait() has + * not been called and determined that the process has exited, + * false otherwise. + */ + template + inline bool + basic_pstreambuf::is_open() const + { + return ppid_ > 0; + } + + /** + * Toggle the stream used for reading. If @a readerr is @c true then the + * process' @c stderr output will be used for subsequent extractions, if + * @a readerr is false the the process' stdout will be used. + * @param readerr @c true to read @c stderr, @c false to read @c stdout. + * @return @c true if the requested stream is open and will be used for + * subsequent extractions, @c false otherwise. + */ + template + inline bool + basic_pstreambuf::read_err(bool readerr) + { + buf_read_src src = readerr ? rsrc_err : rsrc_out; + if (rpipe_[src]>=0) + { + switch_read_buffer(src); + return true; + } + return false; + } + + /** + * Called when the internal character buffer is not present or is full, + * to transfer the buffer contents to the pipe. + * + * @param c a character to be written to the pipe. + * @return @c traits_type::eof() if an error occurs, otherwise if @a c + * is not equal to @c traits_type::eof() it will be buffered and + * a value other than @c traits_type::eof() returned to indicate + * success. + */ + template + typename basic_pstreambuf::int_type + basic_pstreambuf::overflow(int_type c) + { + if (!empty_buffer()) + return traits_type::eof(); + else if (!traits_type::eq_int_type(c, traits_type::eof())) + return this->sputc(c); + else + return traits_type::not_eof(c); + } + + + template + int + basic_pstreambuf::sync() + { + return !exited() && empty_buffer() ? 0 : -1; + } + + /** + * @param s character buffer. + * @param n buffer length. + * @return the number of characters written. + */ + template + std::streamsize + basic_pstreambuf::xsputn(const char_type* s, std::streamsize n) + { + std::streamsize done = 0; + while (done < n) + { + if (std::streamsize nbuf = this->epptr() - this->pptr()) + { + nbuf = std::min(nbuf, n - done); + traits_type::copy(this->pptr(), s + done, nbuf); + this->pbump(nbuf); + done += nbuf; + } + else if (!empty_buffer()) + break; + } + return done; + } + + /** + * @return true if the buffer was emptied, false otherwise. + */ + template + bool + basic_pstreambuf::empty_buffer() + { + const std::streamsize count = this->pptr() - this->pbase(); + if (count > 0) + { + const std::streamsize written = this->write(this->wbuffer_, count); + if (written > 0) + { + if (const std::streamsize unwritten = count - written) + traits_type::move(this->pbase(), this->pbase()+written, unwritten); + this->pbump(-written); + return true; + } + } + return false; + } + + /** + * Called when the internal character buffer is is empty, to re-fill it + * from the pipe. + * + * @return The first available character in the buffer, + * or @c traits_type::eof() in case of failure. + */ + template + typename basic_pstreambuf::int_type + basic_pstreambuf::underflow() + { + if (this->gptr() < this->egptr() || fill_buffer()) + return traits_type::to_int_type(*this->gptr()); + else + return traits_type::eof(); + } + + /** + * Attempts to make @a c available as the next character to be read by + * @c sgetc(). + * + * @param c a character to make available for extraction. + * @return @a c if the character can be made available, + * @c traits_type::eof() otherwise. + */ + template + typename basic_pstreambuf::int_type + basic_pstreambuf::pbackfail(int_type c) + { + if (this->gptr() != this->eback()) + { + this->gbump(-1); + if (!traits_type::eq_int_type(c, traits_type::eof())) + *this->gptr() = traits_type::to_char_type(c); + return traits_type::not_eof(c); + } + else + return traits_type::eof(); + } + + template + std::streamsize + basic_pstreambuf::showmanyc() + { + int avail = 0; + if (sizeof(char_type) == 1) + avail = fill_buffer(true) ? this->egptr() - this->gptr() : -1; +#ifdef FIONREAD + else + { + if (::ioctl(rpipe(), FIONREAD, &avail) == -1) + avail = -1; + else if (avail) + avail /= sizeof(char_type); + } +#endif + return std::streamsize(avail); + } + + /** + * @return true if the buffer was filled, false otherwise. + */ + template + bool + basic_pstreambuf::fill_buffer(bool non_blocking) + { + const std::streamsize pb1 = this->gptr() - this->eback(); + const std::streamsize pb2 = pbsz; + const std::streamsize npb = std::min(pb1, pb2); + + char_type* const rbuf = rbuffer(); + + traits_type::move(rbuf + pbsz - npb, this->gptr() - npb, npb); + + std::streamsize rc = -1; + + if (non_blocking) + { + const int flags = ::fcntl(rpipe(), F_GETFL); + if (flags != -1) + { + const bool blocking = !(flags & O_NONBLOCK); + if (blocking) + ::fcntl(rpipe(), F_SETFL, flags | O_NONBLOCK); // set non-blocking + + error_ = 0; + rc = read(rbuf + pbsz, bufsz - pbsz); + + if (rc == -1 && error_ == EAGAIN) // nothing available + rc = 0; + else if (rc == 0) // EOF + rc = -1; + + if (blocking) + ::fcntl(rpipe(), F_SETFL, flags); // restore + } + } + else + rc = read(rbuf + pbsz, bufsz - pbsz); + + if (rc > 0 || (rc == 0 && non_blocking)) + { + this->setg( rbuf + pbsz - npb, + rbuf + pbsz, + rbuf + pbsz + rc ); + return true; + } + else + { + this->setg(NULL, NULL, NULL); + return false; + } + } + + /** + * Writes up to @a n characters to the pipe from the buffer @a s. + * + * @param s character buffer. + * @param n buffer length. + * @return the number of characters written. + */ + template + inline std::streamsize + basic_pstreambuf::write(const char_type* s, std::streamsize n) + { + std::streamsize nwritten = 0; + if (wpipe() >= 0) + { + nwritten = ::write(wpipe(), s, n * sizeof(char_type)); + if (nwritten == -1) + error_ = errno; + else + nwritten /= sizeof(char_type); + } + return nwritten; + } + + /** + * Reads up to @a n characters from the pipe to the buffer @a s. + * + * @param s character buffer. + * @param n buffer length. + * @return the number of characters read. + */ + template + inline std::streamsize + basic_pstreambuf::read(char_type* s, std::streamsize n) + { + std::streamsize nread = 0; + if (rpipe() >= 0) + { + nread = ::read(rpipe(), s, n * sizeof(char_type)); + if (nread == -1) + error_ = errno; + else + nread /= sizeof(char_type); + } + return nread; + } + + /** @return a reference to the output file descriptor */ + template + inline pstreams::fd_type& + basic_pstreambuf::wpipe() + { + return wpipe_; + } + + /** @return a reference to the active input file descriptor */ + template + inline pstreams::fd_type& + basic_pstreambuf::rpipe() + { + return rpipe_[rsrc_]; + } + + /** @return a reference to the specified input file descriptor */ + template + inline pstreams::fd_type& + basic_pstreambuf::rpipe(buf_read_src which) + { + return rpipe_[which]; + } + + /** @return a pointer to the start of the active input buffer area. */ + template + inline typename basic_pstreambuf::char_type* + basic_pstreambuf::rbuffer() + { + return rbuffer_[rsrc_]; + } + + + /* + * member definitions for pstream_common + */ + + /** + * @class pstream_common + * Abstract Base Class providing common functionality for basic_ipstream, + * basic_opstream and basic_pstream. + * pstream_common manages the basic_pstreambuf stream buffer that is used + * by the derived classes to initialise an iostream class. + */ + + /** Creates an uninitialised stream. */ + template + inline + pstream_common::pstream_common() + : std::basic_ios(NULL) + , command_() + , buf_() + { + this->std::basic_ios::rdbuf(&buf_); + } + + /** + * Initialises the stream buffer by calling + * do_open( @a command , @a mode ) + * + * @param command a string containing a shell command. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, pmode) + */ + template + inline + pstream_common::pstream_common(const std::string& command, pmode mode) + : std::basic_ios(NULL) + , command_(command) + , buf_() + { + this->std::basic_ios::rdbuf(&buf_); + do_open(command, mode); + } + + /** + * Initialises the stream buffer by calling + * do_open( @a file , @a argv , @a mode ) + * + * @param file a string containing the pathname of a program to execute. + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + template + inline + pstream_common::pstream_common( const std::string& file, + const argv_type& argv, + pmode mode ) + : std::basic_ios(NULL) + , command_(file) + , buf_() + { + this->std::basic_ios::rdbuf(&buf_); + do_open(file, argv, mode); + } + + /** + * This is a pure virtual function to make @c pstream_common abstract. + * Because it is the destructor it will be called by derived classes + * and so must be defined. It is also protected, to discourage use of + * the PStreams classes through pointers or references to the base class. + * + * @sa If defining a pure virtual seems odd you should read + * http://www.gotw.ca/gotw/031.htm (and the rest of the site as well!) + */ + template + inline + pstream_common::~pstream_common() + { + } + + /** + * Calls rdbuf()->open( @a command , @a mode ) + * and sets @c failbit on error. + * + * @param command a string containing a shell command. + * @param mode the I/O mode to use when opening the pipe. + * @see basic_pstreambuf::open(const std::string&, pmode) + */ + template + inline void + pstream_common::do_open(const std::string& command, pmode mode) + { + if (!buf_.open((command_=command), mode)) + this->setstate(std::ios_base::failbit); + } + + /** + * Calls rdbuf()->open( @a file, @a argv, @a mode ) + * and sets @c failbit on error. + * + * @param file a string containing the pathname of a program to execute. + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see basic_pstreambuf::open(const std::string&, const argv_type&, pmode) + */ + template + inline void + pstream_common::do_open( const std::string& file, + const argv_type& argv, + pmode mode ) + { + if (!buf_.open((command_=file), argv, mode)) + this->setstate(std::ios_base::failbit); + } + + /** Calls rdbuf->close() and sets @c failbit on error. */ + template + inline void + pstream_common::close() + { + if (!buf_.close()) + this->setstate(std::ios_base::failbit); + } + + /** + * @return rdbuf()->is_open(). + * @see basic_pstreambuf::is_open() + */ + template + inline bool + pstream_common::is_open() const + { + return buf_.is_open(); + } + + /** @return a string containing the command used to initialise the stream. */ + template + inline const std::string& + pstream_common::command() const + { + return command_; + } + + /** @return a pointer to the private stream buffer member. */ + // TODO document behaviour if buffer replaced. + template + inline typename pstream_common::streambuf_type* + pstream_common::rdbuf() const + { + return const_cast(&buf_); + } + + +#if REDI_EVISCERATE_PSTREAMS + /** + * @def REDI_EVISCERATE_PSTREAMS + * If this macro has a non-zero value then certain internals of the + * @c basic_pstreambuf template class are exposed. In general this is + * a Bad Thing, as the internal implementation is largely undocumented + * and may be subject to change at any time, so this feature is only + * provided because it might make PStreams useful in situations where + * it is necessary to do Bad Things. + */ + + /** + * @warning This function exposes the internals of the stream buffer and + * should be used with caution. It is the caller's responsibility + * to flush streams etc. in order to clear any buffered data. + * The POSIX.1 function fdopen(3) is used to obtain the + * @c FILE pointers from the streambuf's private file descriptor + * members so consult your system's documentation for + * fdopen(3). + * + * @param in A FILE* that will refer to the process' stdin. + * @param out A FILE* that will refer to the process' stdout. + * @param err A FILE* that will refer to the process' stderr. + * @return An OR of zero or more of @c pstdin, @c pstdout, @c pstderr. + * + * For each open stream shared with the child process a @c FILE* is + * obtained and assigned to the corresponding parameter. For closed + * streams @c NULL is assigned to the parameter. + * The return value can be tested to see which parameters should be + * @c !NULL by masking with the corresponding @c pmode value. + * + * @see fdopen(3) + */ + template + std::size_t + basic_pstreambuf::fopen(FILE*& in, FILE*& out, FILE*& err) + { + in = out = err = NULL; + std::size_t open_files = 0; + if (wpipe() > -1) + { + if ((in = ::fdopen(wpipe(), "w"))) + { + open_files |= pstdin; + } + } + if (rpipe(rsrc_out) > -1) + { + if ((out = ::fdopen(rpipe(rsrc_out), "r"))) + { + open_files |= pstdout; + } + } + if (rpipe(rsrc_err) > -1) + { + if ((err = ::fdopen(rpipe(rsrc_err), "r"))) + { + open_files |= pstderr; + } + } + return open_files; + } + + /** + * @warning This function exposes the internals of the stream buffer and + * should be used with caution. + * + * @param in A FILE* that will refer to the process' stdin. + * @param out A FILE* that will refer to the process' stdout. + * @param err A FILE* that will refer to the process' stderr. + * @return A bitwise-or of zero or more of @c pstdin, @c pstdout, @c pstderr. + * @see basic_pstreambuf::fopen() + */ + template + inline std::size_t + pstream_common::fopen(FILE*& fin, FILE*& fout, FILE*& ferr) + { + return buf_.fopen(fin, fout, ferr); + } + +#endif // REDI_EVISCERATE_PSTREAMS + + +} // namespace redi + +/** + * @mainpage PStreams Reference + * @htmlinclude mainpage.html + */ + +#endif // REDI_PSTREAM_H_SEEN + +// vim: ts=2 sw=2 expandtab + diff --git a/src/experiments/kesorefs/experiment.cc b/src/experiments/kesorefs/experiment.cc index 352fa2b8..3b7b9a08 100644 --- a/src/experiments/kesorefs/experiment.cc +++ b/src/experiments/kesorefs/experiment.cc @@ -20,6 +20,7 @@ #include "campaign.hpp" #include "kesoref.pb.h" +#include "util/Disassembler.hpp" using namespace std; using namespace fail; @@ -79,6 +80,7 @@ void handleMemoryAccessEvent(KesoRefExperimentData& param, const fail::MemAccess bool KESOrefs::run() { + m_dis.init(); //******* Boot, and store state *******// m_log << "STARTING EXPERIMENT" << endl; #if SAFESTATE // define SS (SafeState) when building: make -DSS diff --git a/src/experiments/kesorefs/experiment.hpp b/src/experiments/kesorefs/experiment.hpp index 88b306b6..0f3f5b25 100644 --- a/src/experiments/kesorefs/experiment.hpp +++ b/src/experiments/kesorefs/experiment.hpp @@ -14,6 +14,7 @@ class KESOrefs : public fail::ExperimentFlow { fail::Logger m_log; fail::MemoryManager& m_mm; fail::ElfReader m_elf; + fail::Disassembler m_dis; void printEIP(); void setupExitBPs(const std::string&); diff --git a/src/experiments/vezs-example/experiment.cc b/src/experiments/vezs-example/experiment.cc index 1c29895c..287846c9 100644 --- a/src/experiments/vezs-example/experiment.cc +++ b/src/experiments/vezs-example/experiment.cc @@ -15,13 +15,14 @@ #include "sal/Listener.hpp" #include - using namespace std; using namespace fail; - bool VEZSExperiment::run() { + MemoryManager& mm = simulator.getMemoryManager(); + + //m_elf.printDemangled(); m_log << "STARTING EXPERIMENT" << endl; m_log << "Instruction Pointer: 0x" << hex << simulator.getCPU(0).getInstructionPointer() << endl; // Test register access @@ -30,34 +31,30 @@ bool VEZSExperiment::run() reg = simulator.getCPU(0).getRegister(RI_R2); m_log << "Register R2: 0x" << hex << simulator.getCPU(0).getRegisterContent(reg) << endl; - simulator.getCPU(0).setRegisterContent(reg, 0x23); - reg = simulator.getCPU(0).getRegister(RI_R3); - m_log << "Register R3: 0x" << hex << simulator.getCPU(0).getRegisterContent(reg) << endl; - - simulator.terminate(); - -// STOP HERE - - - -// Test Memory access - address_t targetaddress = 0x12345678; - MemoryManager& mm = simulator.getMemoryManager(); - mm.setByte(targetaddress, 0x42); - mm.getByte(targetaddress); - - uint8_t tb[] = {0xab, 0xbb, 0xcc, 0xdd}; - mm.setBytes(targetaddress, 4, tb); - *((uint32_t*)(tb)) = 0; // clear array. - // read back bytes - mm.getBytes(targetaddress, 4, tb); // Test Listeners - address_t address = 0xee; - BPSingleListener bp(address); - simulator.addListener(&bp); + address_t address = m_elf.getSymbol("incfoo").getAddress(); + address &= ~1; // Cortex M3 Thumb Mode has the first bit set.. + m_log << "incfoo() @ 0x" << std::hex << address << std::endl; + address_t pfoo = m_elf.getSymbol("foo").getAddress(); + //BPSingleListener bp(address); + BPRangeListener bp(address-32, address + 32); + MemWriteListener l_foo( pfoo ); + simulator.addListener(&l_foo); + reg = simulator.getCPU(0).getRegister(RI_R4); + unsigned foo = 23; + for(int i = 0; i < 15; i++){ + simulator.addListenerAndResume(&bp); + if(i == 0) mm.setBytes(pfoo, 4, (void*)&foo); + m_log << " Breakpoint hit! @ 0x" << std::hex << simulator.getCPU(0).getInstructionPointer() << std::endl; + m_log << " Register R3: 0x" << hex << simulator.getCPU(0).getRegisterContent(reg) << endl; + mm.getBytes(pfoo, 4, (void*)&foo); + m_log << " foo @ 0x"<< std::hex << pfoo << " = " << foo << std::endl; + } + +/* BPRangeListener rbp(0xef, 0xff); simulator.addListener(&rbp); @@ -77,6 +74,19 @@ bool VEZSExperiment::run() // resume backend. // simulator.resume(); +// Test Memory access + address_t targetaddress = 0x12345678; + MemoryManager& mm = simulator.getMemoryManager(); + mm.setByte(targetaddress, 0x42); + mm.getByte(targetaddress); + + uint8_t tb[] = {0xab, 0xbb, 0xcc, 0xdd}; + mm.setBytes(targetaddress, 4, tb); + *((uint32_t*)(tb)) = 0; // clear array. + // read back bytes + mm.getBytes(targetaddress, 4, tb); + +*/ // Explicitly terminate, or the simulator will continue to run. simulator.terminate(); } diff --git a/src/experiments/vezs-example/experiment.hpp b/src/experiments/vezs-example/experiment.hpp index b3eb8ad8..5045cba4 100644 --- a/src/experiments/vezs-example/experiment.hpp +++ b/src/experiments/vezs-example/experiment.hpp @@ -5,14 +5,18 @@ #include "efw/JobClient.hpp" #include "util/Logger.hpp" +#include "util/Disassembler.hpp" +#include "util/ElfReader.hpp" + class VEZSExperiment : public fail::ExperimentFlow { fail::JobClient m_jc; fail::Logger m_log; + fail::ElfReader m_elf; public: - VEZSExperiment() : m_log("VEZS-example", false) {}; - bool run(); + VEZSExperiment() : m_log("VEZS-example", false) {}; + bool run(); }; #endif // __CHECKSUM_OOSTUBS_EXPERIMENT_HPP__