$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);
+		}
+	}
+}
+
+}
