Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/240.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support kernels that don't have CONFIG_CROSS_MEMORY_ATTACH set.
38 changes: 37 additions & 1 deletion src/pystack/_pystack/mem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
namespace pystack {

using elf_unique_ptr = std::unique_ptr<Elf, std::function<void(Elf*)>>;
using file_unique_ptr = std::unique_ptr<FILE, std::function<int(FILE*)>>;

static ssize_t
_process_vm_readv(
Expand Down Expand Up @@ -228,6 +227,16 @@ ProcessMemoryManager::ProcessMemoryManager(pid_t pid)

ssize_t
ProcessMemoryManager::readChunk(remote_addr_t addr, size_t len, char* dst) const
{
if (d_memfile || getenv("_PYSTACK_NO_PROCESS_VM_READV") != nullptr) {
return readChunkThroughMemFile(addr, len, dst);
} else {
return readChunkDirect(addr, len, dst);
}
}

ssize_t
ProcessMemoryManager::readChunkDirect(remote_addr_t addr, size_t len, char* dst) const
{
struct iovec local[1];
struct iovec remote[1];
Expand All @@ -246,6 +255,9 @@ ProcessMemoryManager::readChunk(remote_addr_t addr, size_t len, char* dst) const
throw InvalidRemoteAddress();
} else if (errno == EPERM) {
throw std::runtime_error(PERM_MESSAGE);
} else if (errno == ENOSYS) {
LOG(DEBUG) << "process_vm_readv not compiled in kernel, falling back to /proc/PID/mem";
return readChunkThroughMemFile(addr, len, dst);
}
throw std::system_error(errno, std::generic_category());
}
Expand All @@ -256,6 +268,30 @@ ProcessMemoryManager::readChunk(remote_addr_t addr, size_t len, char* dst) const
return result;
}

ssize_t
ProcessMemoryManager::readChunkThroughMemFile(remote_addr_t addr, size_t len, char* dst) const
{
if (!d_memfile) {
std::string filepath = "/proc/" + std::to_string(d_pid) + "/mem";
d_memfile = file_unique_ptr(fopen(filepath.c_str(), "r"), fclose);
if (!d_memfile) {
if (errno == EPERM || errno == EACCES) {
LOG(ERROR) << "Permission denied opening file " << filepath;
throw std::runtime_error(PERM_MESSAGE);
}
LOG(ERROR) << "Failed to open file " << filepath << ": " << std::strerror(errno);
throw std::runtime_error("Failed to open " + filepath);
}
}
fseeko(d_memfile.get(), addr, SEEK_SET);
if (static_cast<off_t>(addr) != ftello(d_memfile.get())
|| len != fread(dst, 1, len, d_memfile.get()))
{
throw InvalidRemoteAddress();
}
return static_cast<ssize_t>(len);
}

ssize_t
ProcessMemoryManager::copyMemoryFromProcess(remote_addr_t addr, size_t len, void* dst) const
{
Expand Down
7 changes: 6 additions & 1 deletion src/pystack/_pystack/mem.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
#include <sys/stat.h>
#include <vector>

#include <elf_common.h>
#include "elf_common.h"

namespace pystack {

using file_unique_ptr = std::unique_ptr<FILE, std::function<int(FILE*)>>;
typedef uintptr_t remote_addr_t;

struct RemoteMemCopyError : public std::exception
Expand Down Expand Up @@ -174,9 +176,12 @@ class ProcessMemoryManager : public AbstractRemoteMemoryManager
pid_t d_pid;
std::vector<VirtualMap> d_vmaps;
mutable LRUCache d_lru_cache;
mutable file_unique_ptr d_memfile;

// Methods
ssize_t readChunk(remote_addr_t addr, size_t len, char* dst) const;
ssize_t readChunkDirect(remote_addr_t addr, size_t len, char* dst) const;
ssize_t readChunkThroughMemFile(remote_addr_t addr, size_t len, char* dst) const;
};

struct SimpleVirtualMap
Expand Down
12 changes: 11 additions & 1 deletion tests/integration/test_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,23 @@

@pytest.mark.parametrize("method", STACK_METHODS)
@pytest.mark.parametrize("blocking", [True, False], ids=["blocking", "non-blocking"])
def test_simple_execution(method, blocking, tmpdir):
@pytest.mark.parametrize(
"use_process_vm_readv", [True, False], ids=["process_vm_readv", "/proc/PID/mem"]
)
def test_simple_execution(method, blocking, use_process_vm_readv, tmpdir, monkeypatch):
"""Test that we can retrieve the thread state of a single process.

This test is specially useful to run under valgrind or similar tools and to
quickly check that the very basic functionality works as expected.
"""

# GIVEN
env_var = "_PYSTACK_NO_PROCESS_VM_READV"
if use_process_vm_readv:
monkeypatch.delenv(env_var, raising=False)
else:
monkeypatch.setenv(env_var, "1")

# WHEN
with spawn_child_process(
sys.executable, TEST_SINGLE_THREAD_FILE, tmpdir
Expand Down