Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mumble-voip/mumble.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavide Beatrici <git@davidebeatrici.dev>2020-10-28 21:26:53 +0300
committerDavide Beatrici <git@davidebeatrici.dev>2020-10-28 21:26:56 +0300
commit253b814ba56dbfe22927428857e3db3a1c92dc5d (patch)
treed28055d22aad1b81bc19492efd1d88b001d73df1 /plugins
parent5debd105e33835d5e7f8681cecae5ccf225c19a7 (diff)
FEAT(plugins): Add classes for process-related functions
Since most of the functions are already using C++, why not use classes as well? This commit introduces the following classes: - HostLinux: can only be compiled on Linux. Implements peek() and module(). - HostWindows: can only be compiled on Windows. Implements peek() and module(). - Process: abstract (cannot be instantiated directly). Inherits from HostLinux on Linux and from HostWindows on Windows. Provides functions that can be used with both Linux and Windows processes. Pure virtual functions are implemented in the following classes: - ProcessLinux: meant to be used with Linux processes, inherits from Process. Only implements exportedSymbol(), due to the other functions being universal. The constructor detects the architecture through the ELF header. - ProcessWindows: meant to be used with Windows processes, inherits from Process. Only implements exportedSymbol(), due to the other functions being universal. The constructor detects the architecture through the NT header.
Diffstat (limited to 'plugins')
-rw-r--r--plugins/HostLinux.cpp162
-rw-r--r--plugins/HostLinux.h33
-rw-r--r--plugins/HostWindows.cpp52
-rw-r--r--plugins/HostWindows.h32
-rw-r--r--plugins/Process.cpp77
-rw-r--r--plugins/Process.h102
-rw-r--r--plugins/ProcessLinux.cpp98
-rw-r--r--plugins/ProcessLinux.h20
-rw-r--r--plugins/ProcessWindows.cpp88
-rw-r--r--plugins/ProcessWindows.h20
10 files changed, 684 insertions, 0 deletions
diff --git a/plugins/HostLinux.cpp b/plugins/HostLinux.cpp
new file mode 100644
index 000000000..146e4f086
--- /dev/null
+++ b/plugins/HostLinux.cpp
@@ -0,0 +1,162 @@
+// Copyright 2020 The Mumble Developers. All rights reserved.
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file at the root of the
+// Mumble source tree or at <https://www.mumble.info/LICENSE>.
+
+#include "HostLinux.h"
+
+#include "mumble_plugin_utils.h"
+
+#include <cstring>
+#include <sstream>
+
+#include <sys/uio.h>
+
+HostLinux::HostLinux(const procid_t pid) : m_ok(true), m_pid(pid) {
+}
+
+HostLinux::~HostLinux() {
+}
+
+bool HostLinux::peek(const procptr_t address, void *dst, const size_t size) const {
+ iovec in;
+ in.iov_base = reinterpret_cast< void * >(address);
+ in.iov_len = size;
+
+ iovec out;
+ out.iov_base = dst;
+ out.iov_len = size;
+
+ const auto ret = process_vm_readv(m_pid, &out, 1, &in, 1, 0);
+
+ return (ret != -1 && static_cast< size_t >(ret) == in.iov_len);
+}
+
+procptr_t HostLinux::module(const std::string &module) const {
+ std::stringstream ss;
+ ss << "/proc/";
+ ss << m_pid;
+ ss << "/maps";
+ const auto maps = readFile(ss.str());
+
+ if (maps.size() == 0) {
+ return 0;
+ }
+
+ std::stringstream ssPath(maps);
+ while (ssPath.good()) {
+ std::string baseaddr;
+
+ int ch;
+ while (1) {
+ ch = ssPath.get();
+ if (ch == '-') {
+ break;
+ } else if (ch == EOF) {
+ return 0;
+ }
+ baseaddr.push_back(static_cast< char >(ch));
+ }
+
+ // seek to perms
+ do {
+ ch = ssPath.get();
+ if (ch == EOF) {
+ return 0;
+ }
+ } while (ch != ' ');
+
+ // seek to offset
+ do {
+ ch = ssPath.get();
+ if (ch == EOF) {
+ return 0;
+ }
+ } while (ch != ' ');
+
+ // seek to dev
+ do {
+ ch = ssPath.get();
+ if (ch == EOF) {
+ return 0;
+ }
+ } while (ch != ' ');
+
+ // seek to inode
+ do {
+ ch = ssPath.get();
+ if (ch == EOF) {
+ return 0;
+ }
+ } while (ch != ' ');
+
+ // seek to pathname
+ do {
+ ch = ssPath.get();
+ if (ch == EOF) {
+ return 0;
+ }
+ } while (ch != ' ');
+
+ // eat spaces until we're at the beginning of pathname.
+ while (ch == ' ') {
+ if (ch == EOF) {
+ return 0;
+ }
+ ch = ssPath.get();
+ }
+ ssPath.unget();
+
+ std::string pathname;
+ while (1) {
+ ch = ssPath.get();
+ if (ch == '\n') {
+ break;
+ } else if (ch == EOF) {
+ return 0;
+ }
+ pathname.push_back(static_cast< char >(ch));
+ };
+
+ // OK, we found 'em!
+ // Only treat path as a real path if it starts with /.
+ if (pathname.size() > 0 && pathname.at(0) == '/') {
+ // Find the basename.
+ size_t lastSlash = pathname.find_last_of('/');
+ if (pathname.size() > lastSlash + 1) {
+ std::string basename = pathname.substr(lastSlash + 1);
+ if (basename == module) {
+ unsigned long addr = strtoul(baseaddr.c_str(), nullptr, 16);
+ return addr;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+bool HostLinux::isWine(const procid_t id) {
+ std::stringstream ss;
+ ss << "/proc/";
+ ss << id;
+ ss << "/exe";
+
+ char *path = realpath(ss.str().c_str(), nullptr);
+ if (!path) {
+ return false;
+ }
+
+ const char *filename = basename(path);
+ if (!filename) {
+ return false;
+ }
+
+ const auto ret = strcmp(filename, "wine-preloader") == 0 || strcmp(filename, "wine64-preloader") == 0;
+
+ // basename() returns a pointer to the basename's position in the string passed as argument.
+ // For that reason we cannot free 'path' before performing the check.
+ free(path);
+
+ return ret;
+}
diff --git a/plugins/HostLinux.h b/plugins/HostLinux.h
new file mode 100644
index 000000000..ec24b61f9
--- /dev/null
+++ b/plugins/HostLinux.h
@@ -0,0 +1,33 @@
+// Copyright 2020 The Mumble Developers. All rights reserved.
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file at the root of the
+// Mumble source tree or at <https://www.mumble.info/LICENSE>.
+
+#ifndef HOSTLINUX_H_
+#define HOSTLINUX_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <string>
+
+typedef uint32_t procid_t;
+typedef uint64_t procptr_t;
+
+class HostLinux {
+protected:
+ bool m_ok;
+ procid_t m_pid;
+
+public:
+ inline bool isOk() const { return m_ok; }
+
+ bool peek(const procptr_t address, void *dst, const size_t size) const;
+ procptr_t module(const std::string &module) const;
+
+ static bool isWine(const procid_t id);
+
+ HostLinux(const procid_t pid);
+ virtual ~HostLinux();
+};
+
+#endif
diff --git a/plugins/HostWindows.cpp b/plugins/HostWindows.cpp
new file mode 100644
index 000000000..61c64a02f
--- /dev/null
+++ b/plugins/HostWindows.cpp
@@ -0,0 +1,52 @@
+// Copyright 2020 The Mumble Developers. All rights reserved.
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file at the root of the
+// Mumble source tree or at <https://www.mumble.info/LICENSE>.
+
+#include "HostWindows.h"
+
+#include "mumble_plugin_utils.h"
+
+#include <windows.h>
+#include <tlhelp32.h>
+
+HostWindows::HostWindows(const procid_t pid) : m_ok(false), m_pid(pid) {
+ m_handle = OpenProcess(PROCESS_VM_READ, false, m_pid);
+ if (m_handle) {
+ m_ok = true;
+ }
+}
+
+HostWindows::~HostWindows() {
+ if (m_handle) {
+ CloseHandle(m_handle);
+ }
+}
+
+bool HostWindows::peek(const procptr_t address, void *dst, const size_t size) const {
+ SIZE_T read;
+ const BOOL ok = ReadProcessMemory(m_handle, reinterpret_cast< void * >(address), dst, size, &read);
+ return (ok && read == size);
+}
+
+procptr_t HostWindows::module(const std::string &module) const {
+ const auto handle = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, m_pid);
+ if (handle == INVALID_HANDLE_VALUE) {
+ return 0;
+ }
+
+ procptr_t ret = 0;
+
+ MODULEENTRY32 me;
+ me.dwSize = sizeof(me);
+ for (BOOL ok = Module32First(handle, &me); ok; ok = Module32Next(handle, &me)) {
+ if (me.szModule == utf8ToUtf16(module)) {
+ ret = reinterpret_cast< procptr_t >(me.modBaseAddr);
+ break;
+ }
+ }
+
+ CloseHandle(handle);
+
+ return ret;
+}
diff --git a/plugins/HostWindows.h b/plugins/HostWindows.h
new file mode 100644
index 000000000..a8253669e
--- /dev/null
+++ b/plugins/HostWindows.h
@@ -0,0 +1,32 @@
+// Copyright 2020 The Mumble Developers. All rights reserved.
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file at the root of the
+// Mumble source tree or at <https://www.mumble.info/LICENSE>.
+
+#ifndef HOSTWINDOWS_H_
+#define HOSTWINDOWS_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <string>
+
+typedef uint32_t procid_t;
+typedef uint64_t procptr_t;
+
+class HostWindows {
+protected:
+ bool m_ok;
+ procid_t m_pid;
+ void *m_handle;
+
+public:
+ inline bool isOk() const { return m_ok; }
+
+ bool peek(const procptr_t address, void *dst, const size_t size) const;
+ procptr_t module(const std::string &module) const;
+
+ HostWindows(const procid_t pid);
+ virtual ~HostWindows();
+};
+
+#endif
diff --git a/plugins/Process.cpp b/plugins/Process.cpp
new file mode 100644
index 000000000..46c0c40dc
--- /dev/null
+++ b/plugins/Process.cpp
@@ -0,0 +1,77 @@
+// Copyright 2020 The Mumble Developers. All rights reserved.
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file at the root of the
+// Mumble source tree or at <https://www.mumble.info/LICENSE>.
+
+#include "Process.h"
+
+#include "mumble_plugin_utils.h"
+
+#include <chrono>
+
+Process::Process(const procid_t id, const std::string &name) : Host(id), m_name(name), m_pointerSize(0) {
+}
+
+Process::~Process() {
+}
+
+procptr_t Process::peekPtr(const procptr_t address) const {
+ procptr_t v = 0;
+
+ if (!peek(address, &v, m_pointerSize)) {
+ return 0;
+ }
+
+ return v;
+}
+
+std::string Process::peekString(const procptr_t address, const size_t length) const {
+ std::string string;
+
+ if (length > 0) {
+ string.resize(length);
+
+ if (!peek(address, &string[0], length)) {
+ return std::string();
+ }
+ } else {
+ auto now = std::chrono::steady_clock::now();
+ const auto end = now + std::chrono::seconds(3);
+
+ for (procptr_t i = 0; now < end; ++i) {
+ char ch = 0;
+ if (!peek(address + i, &ch, sizeof(ch)) || ch == '\0') {
+ break;
+ }
+
+ string += ch;
+
+ // Update current time.
+ now = std::chrono::steady_clock::now();
+ }
+ }
+
+ return string;
+}
+
+procptr_t Process::virtualFunction(const procptr_t classObject, const size_t index) const {
+ const auto vTable = peekPtr(classObject);
+ if (!vTable) {
+ return 0;
+ }
+
+ return peekPtr(vTable + (index * m_pointerSize));
+}
+
+procid_t Process::find(const std::string &name, const std::multimap< std::wstring, unsigned long long int > &pids) {
+ if (pids.empty()) {
+ return 0;
+ }
+
+ const auto iter = pids.find(utf8ToUtf16(name));
+ if (iter == pids.cend()) {
+ return 0;
+ }
+
+ return static_cast< procid_t >(iter->second);
+}
diff --git a/plugins/Process.h b/plugins/Process.h
new file mode 100644
index 000000000..5ca184bd7
--- /dev/null
+++ b/plugins/Process.h
@@ -0,0 +1,102 @@
+// Copyright 2020 The Mumble Developers. All rights reserved.
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file at the root of the
+// Mumble source tree or at <https://www.mumble.info/LICENSE>.
+
+#ifndef PROCESS_H_
+#define PROCESS_H_
+
+#ifdef OS_WINDOWS
+# include "HostWindows.h"
+using Host = HostWindows;
+#else
+# include "HostLinux.h"
+using Host = HostLinux;
+#endif
+
+#include <map>
+#include <vector>
+
+/// Abstract class.
+/// Only defines stuff that can be used with both Linux and Windows processes.
+class Process : public Host {
+protected:
+ std::string m_name;
+ uint8_t m_pointerSize;
+
+public:
+ using Host::module;
+ using Host::peek;
+
+ template< typename T > inline bool peek(const procptr_t address, T &dst) const {
+ return peek(address, &dst, sizeof(T));
+ }
+
+ template< typename T > T inline peek(const procptr_t address) const {
+ T ret;
+ if (!peek(address, &ret, sizeof(T))) {
+ ret = {};
+ }
+
+ return ret;
+ }
+
+ procptr_t peekPtr(const procptr_t address) const;
+
+ /// Reads the specified amount of data at the specified address and returns it as std::string.
+ /// An empty std::string is returned in case of error.
+ ///
+ /// If \p length is 0, the function reads one byte at a time and stops when either '\0' is found or 3 seconds have
+ /// passed. The successfully read data is returned, also in case of error.
+ std::string peekString(const procptr_t address, const size_t length = 0) const;
+
+ template< typename T > std::vector< T > peekVector(const procptr_t address, const size_t elements) const {
+ try {
+ std::vector< T > var(elements);
+ peek(address, &var[0], sizeof(T) * elements);
+ return var;
+ } catch (std::bad_alloc &) {
+ return std::vector< T >();
+ }
+ }
+
+ /// This function is very useful when there is an array of structures and we only need to store a part of each.
+ /// The Source Engine plugin needs it because there is a structure that happens to be larger in the Linux version of
+ /// Left 4 Dead 2. Since the extra members are unknown, we simply discard the extra bytes when reading the array of
+ /// structures.
+ template< typename T >
+ std::vector< T > peekVector(const procptr_t address, const size_t elements, const size_t realStructSize) const {
+ if (realStructSize == sizeof(T)) {
+ // If the structure's size is correct, there is no need for this special function.
+ return peekVector< T >(address, elements);
+ } else if (realStructSize < sizeof(T)) {
+ return std::vector< T >();
+ }
+
+ // Read the full structures into a vector.
+ const auto fullStructs = peekVector< uint8_t >(address, realStructSize * elements);
+
+ // Allocate vector for the known structures.
+ std::vector< T > ret(elements);
+
+ // Copy elements from the vector containing the full structures, discarding extra bytes.
+ auto seek = &fullStructs[0];
+
+ for (size_t i = 0; i < elements; ++i, seek += realStructSize) {
+ memcpy(&ret[i], seek, sizeof(T));
+ }
+
+ return ret;
+ }
+
+ virtual procptr_t exportedSymbol(const std::string &symbol, const procptr_t module) const = 0;
+
+ procptr_t virtualFunction(const procptr_t classObject, const size_t index) const;
+
+ static procid_t find(const std::string &name, const std::multimap< std::wstring, unsigned long long int > &pids);
+
+ Process(const procid_t id, const std::string &name);
+ virtual ~Process();
+};
+
+#endif
diff --git a/plugins/ProcessLinux.cpp b/plugins/ProcessLinux.cpp
new file mode 100644
index 000000000..d42c0b57e
--- /dev/null
+++ b/plugins/ProcessLinux.cpp
@@ -0,0 +1,98 @@
+// Copyright 2020 The Mumble Developers. All rights reserved.
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file at the root of the
+// Mumble source tree or at <https://www.mumble.info/LICENSE>.
+
+#include "ProcessLinux.h"
+
+#include <elf.h>
+
+ProcessLinux::ProcessLinux(const procid_t id, const std::string &name) : Process(id, name) {
+ if (!m_ok) {
+ return;
+ }
+
+ m_ok = false;
+
+ const auto address = module(name);
+ if (!address) {
+ return;
+ }
+
+ // We can know the process architecture by looking at its ELF header.
+ const auto elf = peekVector< int8_t >(address, 5);
+
+ // The first 4 bytes constitute the magical number in ASCII: 0x7F 45 4c 46.
+ if (!(elf[0] == 0x7f && elf[1] == 'E' && elf[2] == 'L' && elf[3] == 'F')) {
+ return;
+ }
+
+ // The fifth byte is 1 in case the process is 32 bit or 2 in case it's 64 bit.
+ m_pointerSize = elf[4] == 1 ? 4 : 8;
+
+ m_ok = true;
+}
+
+ProcessLinux::~ProcessLinux() {
+}
+
+template< typename Elf_Ehdr, typename Elf_Phdr, typename Elf_Dyn, typename Elf_Sym >
+static procptr_t exportedSymbol(const Process &proc, const std::string &symbol, const procptr_t module) {
+ procptr_t hashTable = 0;
+ procptr_t strTable = 0;
+ procptr_t symTable = 0;
+
+ const auto ehdr = proc.peek< Elf_Ehdr >(module);
+ const auto phdrs = proc.peekVector< Elf_Phdr >(module + ehdr.e_phoff, ehdr.e_phnum);
+
+ for (const auto &phdr : phdrs) {
+ if (phdr.p_type == PT_DYNAMIC) {
+ const auto dyns = proc.peekVector< Elf_Dyn >(module + phdr.p_vaddr, phdr.p_memsz / sizeof(Elf_Dyn));
+ for (const auto &dyn : dyns) {
+ switch (dyn.d_tag) {
+ case DT_HASH:
+ hashTable = dyn.d_un.d_ptr;
+ break;
+ case DT_STRTAB:
+ strTable = dyn.d_un.d_ptr;
+ break;
+ case DT_SYMTAB:
+ symTable = dyn.d_un.d_ptr;
+ break;
+ }
+
+ if (hashTable && strTable && symTable) {
+ break;
+ }
+ }
+
+ break;
+ }
+ }
+
+ // Hash table pseudo-struct:
+ // uint32_t nBucket;
+ // uint32_t nChain;
+ // uint32_t bucket[nBucket];
+ // uint32_t chain[nChain];
+ const auto nChain = proc.peek< uint32_t >(hashTable + sizeof(uint32_t));
+
+ for (uint32_t i = 0; i < nChain; ++i) {
+ const auto sym = proc.peek< Elf_Sym >(symTable + sizeof(Elf_Sym) * i);
+ const auto name = proc.peekString(strTable + sym.st_name, symbol.size());
+
+ if (name == symbol) {
+ return module + sym.st_value;
+ }
+ }
+
+ return 0;
+}
+
+procptr_t ProcessLinux::exportedSymbol(const std::string &symbol, const procptr_t module) const {
+ if (m_pointerSize > 4) {
+ return ::exportedSymbol< Elf64_Ehdr, Elf64_Phdr, Elf64_Dyn, Elf64_Sym >(*this, symbol, module);
+ } else {
+ return ::exportedSymbol< Elf32_Ehdr, Elf32_Phdr, Elf32_Dyn, Elf32_Sym >(*this, symbol, module);
+ }
+}
diff --git a/plugins/ProcessLinux.h b/plugins/ProcessLinux.h
new file mode 100644
index 000000000..73fd9007a
--- /dev/null
+++ b/plugins/ProcessLinux.h
@@ -0,0 +1,20 @@
+// Copyright 2020 The Mumble Developers. All rights reserved.
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file at the root of the
+// Mumble source tree or at <https://www.mumble.info/LICENSE>.
+
+#ifndef PROCESSLINUX_H_
+#define PROCESSLINUX_H_
+
+#include "Process.h"
+
+/// Meant to be used with Linux processes.
+class ProcessLinux : public Process {
+public:
+ procptr_t exportedSymbol(const std::string &symbol, const procptr_t module) const override;
+
+ ProcessLinux(const procid_t id, const std::string &name);
+ virtual ~ProcessLinux();
+};
+
+#endif
diff --git a/plugins/ProcessWindows.cpp b/plugins/ProcessWindows.cpp
new file mode 100644
index 000000000..d94a9157a
--- /dev/null
+++ b/plugins/ProcessWindows.cpp
@@ -0,0 +1,88 @@
+// Copyright 2020 The Mumble Developers. All rights reserved.
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file at the root of the
+// Mumble source tree or at <https://www.mumble.info/LICENSE>.
+
+#include "ProcessWindows.h"
+
+#include "mumble_plugin_win32_internals.h"
+
+ProcessWindows::ProcessWindows(const procid_t id, const std::string &name) : Process(id, name) {
+ if (!m_ok) {
+ return;
+ }
+
+ m_ok = false;
+
+ const auto address = module(name);
+ if (!address) {
+ return;
+ }
+
+ const auto dos = peek< ImageDosHeader >(address);
+ if (!(dos.magic[0] == 'M' && dos.magic[1] == 'Z')) {
+ return;
+ }
+
+ const auto nt = peek< ImageNtHeadersNoOptional >(address + dos.addressOfNtHeader);
+ if (!(nt.signature[0] == 'P' && nt.signature[1] == 'E' && nt.signature[2] == '\0' && nt.signature[3] == '\0')) {
+ return;
+ }
+
+ switch (nt.fileHeader.machine) {
+ case 0x14c: // IMAGE_FILE_MACHINE_I386
+ m_pointerSize = 4;
+ break;
+ default:
+ m_pointerSize = 8;
+ }
+
+ m_ok = true;
+}
+
+ProcessWindows::~ProcessWindows() {
+}
+
+template< typename ImageNtHeaders >
+static procptr_t exportedSymbol(const Process &proc, const std::string &symbol, const procptr_t module) {
+ const auto dos = proc.peek< ImageDosHeader >(module);
+ if (!(dos.magic[0] == 'M' && dos.magic[1] == 'Z')) {
+ return 0;
+ }
+
+ const auto nt = proc.peek< ImageNtHeaders >(module + dos.addressOfNtHeader);
+ if (!(nt.signature[0] == 'P' && nt.signature[1] == 'E' && nt.signature[2] == '\0' && nt.signature[3] == '\0')) {
+ return 0;
+ }
+
+ const auto dataAddress = nt.optionalHeader.dataDirectory[0].virtualAddress;
+ if (!dataAddress) {
+ return 0;
+ }
+
+ const auto exportDir = proc.peek< ImageExportDirectory >(module + dataAddress);
+
+ const auto funcs = proc.peekVector< uint32_t >(module + exportDir.addressOfFunctions, exportDir.numberOfFunctions);
+ const auto names = proc.peekVector< uint32_t >(module + exportDir.addressOfNames, exportDir.numberOfNames);
+ const auto ords = proc.peekVector< uint16_t >(module + exportDir.addressOfNameOrdinals, exportDir.numberOfNames);
+
+ for (uint32_t i = 0; i < exportDir.numberOfNames; ++i) {
+ if (names[i]) {
+ const auto name = proc.peekString(module + names[i], symbol.size());
+
+ if (name == symbol) {
+ return module + funcs[ords[i]];
+ }
+ }
+ }
+
+ return 0;
+}
+
+procptr_t ProcessWindows::exportedSymbol(const std::string &symbol, const procptr_t module) const {
+ if (m_pointerSize > 4) {
+ return ::exportedSymbol< ImageNtHeaders64 >(*this, symbol, module);
+ } else {
+ return ::exportedSymbol< ImageNtHeaders32 >(*this, symbol, module);
+ }
+}
diff --git a/plugins/ProcessWindows.h b/plugins/ProcessWindows.h
new file mode 100644
index 000000000..663d06cfa
--- /dev/null
+++ b/plugins/ProcessWindows.h
@@ -0,0 +1,20 @@
+// Copyright 2020 The Mumble Developers. All rights reserved.
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file at the root of the
+// Mumble source tree or at <https://www.mumble.info/LICENSE>.
+
+#ifndef PROCESSWINDOWS_H_
+#define PROCESSWINDOWS_H_
+
+#include "Process.h"
+
+/// Meant to be used with Windows processes.
+class ProcessWindows : public Process {
+public:
+ procptr_t exportedSymbol(const std::string &symbol, const procptr_t module) const override;
+
+ ProcessWindows(const procid_t id, const std::string &name);
+ virtual ~ProcessWindows();
+};
+
+#endif