pkgsrc-wip/edb-debugger-git/patches/patch-plugins_DebuggerCore_...

773 lines
23 KiB
C++

$NetBSD$
--- plugins/DebuggerCore/unix/netbsd/PlatformProcess.cpp.orig 2017-02-19 02:09:05.379589420 +0000
+++ plugins/DebuggerCore/unix/netbsd/PlatformProcess.cpp
@@ -0,0 +1,767 @@
+/*
+Copyright (C) 2015 - 2015 Evan Teran
+ evan.teran@gmail.com
+
+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 program 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#define _LARGEFILE64_SOURCE
+#include "PlatformProcess.h"
+#include "DebuggerCore.h"
+#include "PlatformCommon.h"
+#include "PlatformRegion.h"
+#include "MemoryRegions.h"
+#include "edb.h"
+#include "linker.h"
+
+#include <QByteArray>
+#include <QFile>
+#include <QFileInfo>
+#include <QTextStream>
+#include <QDateTime>
+
+#include <boost/functional/hash.hpp>
+#include <fstream>
+
+#include <sys/mman.h>
+#include <sys/ptrace.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <elf.h>
+
+namespace DebuggerCorePlugin {
+namespace {
+
+// Used as size of ptrace word
+#define EDB_WORDSIZE sizeof(long)
+
+void set_ok(bool &ok, long value) {
+ ok = (value != -1) || (errno == 0);
+}
+
+QStringList split_max(const QString &str, const int &maxparts) {
+ int prev_idx = 0, idx = 0;
+ QStringList items;
+ for (const QChar &c : str) {
+ if (c == ' ') {
+ if (prev_idx < idx) {
+ if (items.size() < maxparts - 1)
+ items << str.mid(prev_idx, idx - prev_idx);
+ else {
+ items << str.right(str.size() - prev_idx);
+ break;
+ }
+ }
+ prev_idx = idx + 1;
+ }
+ ++idx;
+ }
+ if (prev_idx < str.size() && items.size() < maxparts) {
+ items << str.right(str.size() - prev_idx);
+ }
+ return items;
+}
+
+//------------------------------------------------------------------------------
+// Name: process_map_line
+// Desc: parses the data from a line of a memory map file
+//------------------------------------------------------------------------------
+IRegion::pointer process_map_line(const QString &line) {
+
+ edb::address_t start;
+ edb::address_t end;
+ edb::address_t base;
+ IRegion::permissions_t permissions;
+ QString name;
+
+ const QStringList items = split_max(line, 6);
+ if(items.size() >= 3) {
+ bool ok;
+ const QStringList bounds = items[0].split("-");
+ if(bounds.size() == 2) {
+ start = edb::address_t::fromHexString(bounds[0],&ok);
+ if(ok) {
+ end = edb::address_t::fromHexString(bounds[1],&ok);
+ if(ok) {
+ base = edb::address_t::fromHexString(items[2],&ok);
+ if(ok) {
+ const QString perms = items[1];
+ permissions = 0;
+ if(perms[0] == 'r') permissions |= PROT_READ;
+ if(perms[1] == 'w') permissions |= PROT_WRITE;
+ if(perms[2] == 'x') permissions |= PROT_EXEC;
+
+ if(items.size() >= 6) {
+ name = items[5];
+ }
+
+ return std::make_shared<PlatformRegion>(start, end, base, name, permissions);
+ }
+ }
+ }
+ }
+ }
+ return nullptr;;
+}
+
+//------------------------------------------------------------------------------
+// Name:
+// Desc:
+//------------------------------------------------------------------------------
+template<class Addr>
+QList<Module> loaded_modules_(const IProcess* process, const std::unique_ptr<IBinary> &binary_info_) {
+ QList<Module> ret;
+
+ if(binary_info_) {
+ edb::linux::r_debug<Addr> dynamic_info;
+ if(const edb::address_t debug_pointer = binary_info_->debug_pointer()) {
+ if(process) {
+ if(process->read_bytes(debug_pointer, &dynamic_info, sizeof(dynamic_info))) {
+ if(dynamic_info.r_map) {
+
+ auto link_address = edb::address_t::fromZeroExtended(dynamic_info.r_map);
+
+ while(link_address) {
+
+ edb::linux::link_map<Addr> map;
+ if(process->read_bytes(link_address, &map, sizeof(map))) {
+ char path[PATH_MAX];
+ if(!process->read_bytes(edb::address_t::fromZeroExtended(map.l_name), &path, sizeof(path))) {
+ path[0] = '\0';
+ }
+
+ if(map.l_addr) {
+ Module module;
+ module.name = path;
+ module.base_address = map.l_addr;
+ ret.push_back(module);
+ }
+
+ link_address = edb::address_t::fromZeroExtended(map.l_next);
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // fallback
+ if(ret.isEmpty()) {
+ const QList<IRegion::pointer> r = edb::v1::memory_regions().regions();
+ QSet<QString> found_modules;
+
+ for(const IRegion::pointer &region: r) {
+
+ // we assume that modules will be listed by absolute path
+ if(region->name().startsWith("/")) {
+ if(!found_modules.contains(region->name())) {
+ Module module;
+ module.name = region->name();
+ module.base_address = region->start();
+ found_modules.insert(region->name());
+ ret.push_back(module);
+ }
+ }
+ }
+ }
+
+ return ret;
+}
+
+}
+
+//------------------------------------------------------------------------------
+// Name: PlatformProcess
+// Desc:
+//------------------------------------------------------------------------------
+PlatformProcess::PlatformProcess(DebuggerCore *core, edb::pid_t pid) : core_(core), pid_(pid), ro_mem_file_(0), rw_mem_file_(0) {
+ if (!core_->proc_mem_read_broken_) {
+ QFile* memory_file = new QFile(QString("/proc/%1/mem").arg(pid_));
+ auto flags = QIODevice::ReadOnly | QIODevice::Unbuffered;
+ if (!core_->proc_mem_write_broken_) {
+ flags |= QIODevice::WriteOnly;
+ }
+ if (memory_file->open(flags)) {
+ ro_mem_file_ = memory_file;
+ if (!core_->proc_mem_write_broken_) {
+ rw_mem_file_ = memory_file;
+ }
+ } else {
+ delete memory_file;
+ }
+ }
+}
+
+//------------------------------------------------------------------------------
+// Name: ~PlatformProcess
+// Desc:
+//------------------------------------------------------------------------------
+PlatformProcess::~PlatformProcess() {
+ if (ro_mem_file_) {
+ delete ro_mem_file_;
+ }
+}
+
+//------------------------------------------------------------------------------
+// Name: seek_addr
+// Desc: seeks memory file to given address, taking possible negativity of the
+// address into account
+//------------------------------------------------------------------------------
+void seek_addr(QFile& file, edb::address_t address) {
+ if(address <= UINT64_MAX/2) {
+ file.seek(address);
+ } else {
+ const int fd=file.handle();
+ // Seek in two parts to avoid specifying negative offset: off64_t is a signed type
+ const off64_t halfAddressTruncated=address>>1;
+ lseek64(fd,halfAddressTruncated,SEEK_SET);
+ const off64_t secondHalfAddress=address-halfAddressTruncated;
+ lseek64(fd,secondHalfAddress,SEEK_CUR);
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// Name: read_bytes
+// Desc: reads <len> bytes into <buf> starting at <address>
+// Note: returns the number of bytes read <N>
+// Note: if the read is short, only the first <N> bytes are defined
+//------------------------------------------------------------------------------
+std::size_t PlatformProcess::read_bytes(edb::address_t address, void* buf, std::size_t len) const {
+ quint64 read = 0;
+
+ Q_ASSERT(buf);
+ Q_ASSERT(core_->process_ == this);
+
+ auto ptr = reinterpret_cast<char *>(buf);
+
+ if(len != 0) {
+
+ // small reads take the fast path
+ if(len == 1) {
+
+ auto it = core_->breakpoints_.find(address);
+ if(it != core_->breakpoints_.end()) {
+ *ptr = (*it)->original_byte();
+ return 1;
+ }
+
+ if(ro_mem_file_) {
+ seek_addr(*ro_mem_file_, address);
+ read = ro_mem_file_->read(ptr, 1);
+ if (read == 1) {
+ return 1;
+ }
+ return 0;
+ } else {
+ bool ok;
+ quint8 x = read_byte_via_ptrace(address, &ok);
+ if(ok) {
+ *ptr = x;
+ return 1;
+ }
+ return 0;
+ }
+ }
+
+ if(ro_mem_file_) {
+ seek_addr(*ro_mem_file_, address);
+ read = ro_mem_file_->read(ptr, len);
+ if(read == 0 || read == quint64(-1)) {
+ return 0;
+ }
+ } else {
+ for(std::size_t index = 0; index < len; ++index) {
+
+ // read a byte, if we failed, we are done
+ bool ok;
+ const quint8 x = read_byte_via_ptrace(address + index, &ok);
+ if(!ok) {
+ break;
+ }
+
+ // store it
+ reinterpret_cast<char*>(buf)[index] = x;
+
+ ++read;
+ }
+ }
+
+ // replace any breakpoints
+ for(const IBreakpoint::pointer &bp: core_->breakpoints_) {
+ if(bp->address() >= address && bp->address() < (address + read)) {
+ // show the original bytes in the buffer..
+ ptr[bp->address() - address] = bp->original_byte();
+ }
+ }
+ }
+
+ return read;
+}
+
+//------------------------------------------------------------------------------
+// Name: write_bytes
+// Desc: writes <len> bytes from <buf> starting at <address>
+//------------------------------------------------------------------------------
+std::size_t PlatformProcess::write_bytes(edb::address_t address, const void *buf, std::size_t len) {
+ quint64 written = 0;
+
+ Q_ASSERT(buf);
+ Q_ASSERT(core_->process_ == this);
+
+ if(len != 0) {
+ if(rw_mem_file_) {
+ seek_addr(*rw_mem_file_,address);
+ written = rw_mem_file_->write(reinterpret_cast<const char *>(buf), len);
+ if(written == 0 || written == quint64(-1)) {
+ return 0;
+ }
+ }
+ else {
+ // TODO write whole words at a time using ptrace_poke.
+ for(std::size_t byteIndex=0;byteIndex<len;++byteIndex) {
+ bool ok=false;
+ write_byte_via_ptrace(address+byteIndex, *(reinterpret_cast<const char*>(buf)+byteIndex), &ok);
+ if(!ok) return written;
+ ++written;
+ }
+ }
+ }
+
+ return written;
+}
+
+//------------------------------------------------------------------------------
+// Name: read_pages
+// Desc: reads <count> pages from the process starting at <address>
+// Note: buf's size must be >= count * core_->page_size()
+// Note: address should be page aligned.
+//------------------------------------------------------------------------------
+std::size_t PlatformProcess::read_pages(edb::address_t address, void *buf, std::size_t count) const {
+ Q_ASSERT(buf);
+ Q_ASSERT(core_->process_ == this);
+ return read_bytes(address, buf, count * core_->page_size()) / core_->page_size();
+}
+
+//------------------------------------------------------------------------------
+// Name:
+// Desc:
+//------------------------------------------------------------------------------
+QDateTime PlatformProcess::start_time() const {
+ QFileInfo info(QString("/proc/%1/stat").arg(pid_));
+ return info.created();
+}
+
+//------------------------------------------------------------------------------
+// Name:
+// Desc:
+//------------------------------------------------------------------------------
+QList<QByteArray> PlatformProcess::arguments() const {
+ QList<QByteArray> ret;
+
+ if(pid_ != 0) {
+ const QString command_line_file(QString("/proc/%1/cmdline").arg(pid_));
+ QFile file(command_line_file);
+
+ if(file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ QTextStream in(&file);
+
+ QByteArray s;
+ QChar ch;
+
+ while(in.status() == QTextStream::Ok) {
+ in >> ch;
+ if(ch == '\0') {
+ if(!s.isEmpty()) {
+ ret << s;
+ }
+ s.clear();
+ } else {
+ s += ch;
+ }
+ }
+
+ if(!s.isEmpty()) {
+ ret << s;
+ }
+ }
+ }
+ return ret;
+}
+
+//------------------------------------------------------------------------------
+// Name:
+// Desc:
+//------------------------------------------------------------------------------
+QString PlatformProcess::current_working_directory() const {
+ return edb::v1::symlink_target(QString("/proc/%1/cwd").arg(pid_));
+}
+
+//------------------------------------------------------------------------------
+// Name:
+// Desc:
+//------------------------------------------------------------------------------
+QString PlatformProcess::executable() const {
+ return edb::v1::symlink_target(QString("/proc/%1/exe").arg(pid_));
+}
+
+//------------------------------------------------------------------------------
+// Name:
+// Desc:
+//------------------------------------------------------------------------------
+edb::pid_t PlatformProcess::pid() const {
+ return pid_;
+}
+
+//------------------------------------------------------------------------------
+// Name:
+// Desc:
+//------------------------------------------------------------------------------
+IProcess::pointer PlatformProcess::parent() const {
+
+ struct user_stat user_stat;
+ int n = get_user_stat(pid_, &user_stat);
+ if(n >= 4) {
+ return std::make_shared<PlatformProcess>(core_, user_stat.ppid);
+ }
+
+ return nullptr;
+}
+
+//------------------------------------------------------------------------------
+// Name:
+// Desc:
+//------------------------------------------------------------------------------
+edb::address_t PlatformProcess::code_address() const {
+ struct user_stat user_stat;
+ int n = get_user_stat(pid_, &user_stat);
+ if(n >= 26) {
+ return user_stat.startcode;
+ }
+ return 0;
+}
+
+//------------------------------------------------------------------------------
+// Name:
+// Desc:
+//------------------------------------------------------------------------------
+edb::address_t PlatformProcess::data_address() const {
+ struct user_stat user_stat;
+ int n = get_user_stat(pid_, &user_stat);
+ if(n >= 27) {
+ return user_stat.endcode + 1; // endcode == startdata ?
+ }
+ return 0;
+}
+
+//------------------------------------------------------------------------------
+// Name:
+// Desc:
+//------------------------------------------------------------------------------
+QList<IRegion::pointer> PlatformProcess::regions() const {
+ static QList<IRegion::pointer> regions;
+ static size_t totalHash = 0;
+
+ const QString map_file(QString("/proc/%1/maps").arg(pid_));
+
+ // hash the region file to see if it changed or not
+ {
+ std::ifstream mf(map_file.toStdString());
+ size_t newHash = 0;
+ std::string line;
+
+ while(std::getline(mf,line)) {
+ boost::hash_combine(newHash, line);
+ }
+
+ if(totalHash == newHash) {
+ return regions;
+ }
+
+ totalHash = newHash;
+ regions.clear();
+ }
+
+ // it changed, so let's process it
+ QFile file(map_file);
+ if(file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+
+ QTextStream in(&file);
+ QString line = in.readLine();
+
+ while(!line.isNull()) {
+ if(IRegion::pointer region = process_map_line(line)) {
+ regions.push_back(region);
+ }
+ line = in.readLine();
+ }
+ }
+
+ return regions;
+}
+
+//------------------------------------------------------------------------------
+// Name: read_byte
+// Desc: the base implementation of reading a byte
+//------------------------------------------------------------------------------
+quint8 PlatformProcess::read_byte_via_ptrace(edb::address_t address, bool *ok) const {
+ // TODO(eteran): assert that we are paused
+
+ Q_ASSERT(ok);
+ Q_ASSERT(core_->process_ == this);
+
+ *ok = false;
+
+ // if this spot is unreadable, then just return 0xff, otherwise
+ // continue as normal.
+
+ // core_->page_size() - 1 will always be 0xf* because pagesizes
+ // are always 0x10*, so the masking works
+ // range of nBytesToNextPage is [1..n] where n=pagesize, and we have to adjust
+ // if nByteToNextPage < wordsize
+ const edb::address_t nBytesToNextPage = core_->page_size() - (address & (core_->page_size() - 1));
+
+ // Avoid crossing page boundary, since next page may be unreadable
+ const edb::address_t addressShift = nBytesToNextPage < EDB_WORDSIZE ? EDB_WORDSIZE - nBytesToNextPage : 0;
+ address -= addressShift;
+
+ const long value = ptrace_peek(address, ok);
+
+ if(*ok) {
+ quint8 result;
+ // We aren't interested in `value` as in number, it's just a buffer, so no endianness magic.
+ // Just have to compensate for `addressShift` when reading it.
+ std::memcpy(&result,reinterpret_cast<const char*>(&value)+addressShift,sizeof result);
+ return result;
+ }
+
+ return 0xff;
+}
+
+
+//------------------------------------------------------------------------------
+// Name: write_byte
+// Desc: writes a single byte at a given address via ptrace API.
+// Note: assumes the this will not trample any breakpoints, must be handled
+// in calling code!
+//------------------------------------------------------------------------------
+void PlatformProcess::write_byte_via_ptrace(edb::address_t address, quint8 value, bool *ok) {
+ // TODO(eteran): assert that we are paused
+
+ Q_ASSERT(ok);
+ Q_ASSERT(core_->process_ == this);
+
+ *ok = false;
+
+ // core_->page_size() - 1 will always be 0xf* because pagesizes
+ // are always 0x10*, so the masking works
+ // range of nBytesToNextPage is [1..n] where n=pagesize, and we have to adjust
+ // if nBytesToNextPage < wordsize
+ const edb::address_t nBytesToNextPage = core_->page_size() - (address & (core_->page_size() - 1));
+
+ // Avoid crossing page boundary, since next page may be inaccessible
+ const edb::address_t addressShift = nBytesToNextPage < EDB_WORDSIZE ? EDB_WORDSIZE - nBytesToNextPage : 0;
+ address -= addressShift;
+
+ long word = ptrace_peek(address, ok);
+ if(!*ok) return;
+
+ // We aren't interested in `value` as in number, it's just a buffer, so no endianness magic.
+ // Just have to compensate for `addressShift` when writing it.
+ std::memcpy(reinterpret_cast<char*>(&word)+addressShift,&value,sizeof value);
+
+ *ok = ptrace_poke(address, word);
+}
+
+//------------------------------------------------------------------------------
+// Name: ptrace_peek
+// Desc:
+// Note: this will fail on newer versions of linux if called from a
+// different thread than the one which attached to process
+//------------------------------------------------------------------------------
+long PlatformProcess::ptrace_peek(edb::address_t address, bool *ok) const {
+ Q_ASSERT(ok);
+ Q_ASSERT(core_->process_ == this);
+
+ if (EDB_IS_32_BIT && address > 0xffffffffULL) {
+ // 32 bit ptrace can't handle such long addresses
+ *ok = false;
+ return 0;
+ }
+
+ errno = 0;
+ // NOTE: on some Linux systems ptrace prototype has ellipsis instead of third and fourth arguments
+ // Thus we can't just pass address as is on IA32 systems: it'd put 64 bit integer on stack and cause UB
+ auto nativeAddress=reinterpret_cast<const void* const>(address.toUint());
+ const long v = ptrace(PTRACE_PEEKTEXT, pid_, nativeAddress, 0);
+ set_ok(*ok, v);
+ return v;
+}
+
+//------------------------------------------------------------------------------
+// Name: ptrace_poke
+// Desc:
+//------------------------------------------------------------------------------
+bool PlatformProcess::ptrace_poke(edb::address_t address, long value) {
+
+ Q_ASSERT(core_->process_ == this);
+
+ if (EDB_IS_32_BIT && address > 0xffffffffULL) {
+ // 32 bit ptrace can't handle such long addresses
+ return 0;
+ }
+
+ // NOTE: on some Linux systems ptrace prototype has ellipsis instead of third and fourth arguments
+ // Thus we can't just pass address as is on IA32 systems: it'd put 64 bit integer on stack and cause UB
+ auto nativeAddress=reinterpret_cast<const void* const>(address.toUint());
+ return ptrace(PTRACE_POKETEXT, pid_, nativeAddress, value) != -1;
+}
+
+//------------------------------------------------------------------------------
+// Name: threads
+// Desc:
+//------------------------------------------------------------------------------
+QList<IThread::pointer> PlatformProcess::threads() const {
+
+ Q_ASSERT(core_->process_ == this);
+
+ QList<IThread::pointer> threadList;
+
+ for(auto &thread : core_->threads_) {
+ threadList.push_back(thread);
+ }
+
+ return threadList;
+}
+
+//------------------------------------------------------------------------------
+// Name: current_thread
+// Desc:
+//------------------------------------------------------------------------------
+IThread::pointer PlatformProcess::current_thread() const {
+
+ Q_ASSERT(core_->process_ == this);
+
+ auto it = core_->threads_.find(core_->active_thread_);
+ if(it != core_->threads_.end()) {
+ return it.value();
+ }
+ return IThread::pointer();
+}
+
+//------------------------------------------------------------------------------
+// Name:
+// Desc:
+//------------------------------------------------------------------------------
+edb::uid_t PlatformProcess::uid() const {
+
+ const QFileInfo info(QString("/proc/%1").arg(pid_));
+ return info.ownerId();
+}
+
+//------------------------------------------------------------------------------
+// Name:
+// Desc:
+//------------------------------------------------------------------------------
+QString PlatformProcess::user() const {
+ if(const struct passwd *const pwd = ::getpwuid(uid())) {
+ return pwd->pw_name;
+ }
+
+ return QString();
+}
+
+//------------------------------------------------------------------------------
+// Name:
+// Desc:
+//------------------------------------------------------------------------------
+QString PlatformProcess::name() const {
+ struct user_stat user_stat;
+ const int n = get_user_stat(pid_, &user_stat);
+ if(n >= 2) {
+ return user_stat.comm;
+ }
+
+ return QString();
+}
+
+//------------------------------------------------------------------------------
+// Name:
+// Desc:
+//------------------------------------------------------------------------------
+QList<Module> PlatformProcess::loaded_modules() const {
+ if(edb::v1::debuggeeIs64Bit()) {
+ return loaded_modules_<Elf64_Addr>(this, core_->binary_info_);
+ } else if(edb::v1::debuggeeIs32Bit()) {
+ return loaded_modules_<Elf32_Addr>(this, core_->binary_info_);
+ } else {
+ return QList<Module>();
+ }
+}
+
+//------------------------------------------------------------------------------
+// Name: pause
+// Desc: stops *all* threads of a process
+//------------------------------------------------------------------------------
+void PlatformProcess::pause() {
+ // belive it or not, I belive that this is sufficient for all threads
+ // this is because in the debug event handler above, a SIGSTOP is sent
+ // to all threads when any event arrives, so no need to explicitly do
+ // it here. We just need any thread to stop. So we'll just target the
+ // pid_ which will send it to any one of the threads in the process.
+ ::kill(pid_, SIGSTOP);
+}
+
+//------------------------------------------------------------------------------
+// Name: resume
+// Desc: resumes ALL threads
+//------------------------------------------------------------------------------
+void PlatformProcess::resume(edb::EVENT_STATUS status) {
+ // TODO: assert that we are paused
+ Q_ASSERT(core_->process_ == this);
+
+ if(status != edb::DEBUG_STOP) {
+ if(IThread::pointer thread = current_thread()) {
+ thread->resume(status);
+
+ // resume the other threads passing the signal they originally reported had
+ for(auto &other_thread : threads()) {
+ if(core_->waited_threads_.contains(other_thread->tid())) {
+ other_thread->resume();
+ }
+ }
+ }
+ }
+}
+
+//------------------------------------------------------------------------------
+// Name: step
+// Desc: steps the currently active thread
+//------------------------------------------------------------------------------
+void PlatformProcess::step(edb::EVENT_STATUS status) {
+ // TODO: assert that we are paused
+ Q_ASSERT(core_->process_ == this);
+
+ if(status != edb::DEBUG_STOP) {
+ if(IThread::pointer thread = current_thread()) {
+ thread->step(status);
+ }
+ }
+}
+
+}