Skip to content

Fallback to /proc/PID/mem when process_vm_readv not in kernel #240

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

cakemanny
Copy link

@cakemanny cakemanny commented May 24, 2025

Resolves #238

Describe your changes
I've added an alternative implementation of ProcessMemoryManager::readChunk that uses the relevant /proc/PID/mem file and is fallen back to when process_vm_readv fails with ENOSYS.
Once the /proc/PID/mem file is open, subsequent reads are done only from there.

Testing performed

On my dev instance where my kernel does have CONFIG_CROSS_MEMORY_ATTACH set, I did various tests with small source changes. Such as forcing the fallback implementation to always be used.

Details

diff --git a/src/pystack/_pystack/mem.cpp b/src/pystack/_pystack/mem.cpp
index e25c69f..5deda3d 100644
--- a/src/pystack/_pystack/mem.cpp
+++ b/src/pystack/_pystack/mem.cpp
@@ -229,11 +229,15 @@ ProcessMemoryManager::ProcessMemoryManager(pid_t pid)
 ssize_t
 ProcessMemoryManager::readChunk(remote_addr_t addr, size_t len, char* dst) const
 {
+#if 0
     if (d_memfile.is_open()) {
         return readChunkThroughMemFile(addr, len, dst);
     } else {
         return readChunkDirect(addr, len, dst);
     }
+#else
+        return readChunkThroughMemFile(addr, len, dst);
+#endif
 }
 
 ssize_t

dan:~/src/pystack (support-missing-process_vm_readv) % python3 -c $'import time\nwhile True: time.sleep(1)' &
[1] 27161
dan:~/src/pystack (support-missing-process_vm_readv) % pystack remote $(pgrep python3)                       
Traceback for thread 27161 (python3) [] (most recent call last):
    (Python) File "<string>", line 2, in <module>
dan:~/src/pystack (support-missing-process_vm_readv) % pytest
===================================== test session starts =====================================
platform linux -- Python 3.11.2, pytest-8.3.5, pluggy-1.6.0
rootdir: /home/dan/src/pystack
...
=============================== 459 passed, 3 skipped in 15.75s ===============================

I also checked what the various errors might look like, say if the file didn't exist

@@ -273,7 +277,7 @@ ssize_t
 ProcessMemoryManager::readChunkThroughMemFile(remote_addr_t addr, size_t len, char* dst) const
 {
     if (!d_memfile.is_open()) {
-        std::string filepath = "/proc/" + std::to_string(d_pid) + "/mem";
+        std::string filepath = "/proc/" + std::to_string(d_pid) + "/memXXX";
         d_memfile.open(filepath, std::ifstream::binary);
         if (!d_memfile) {
             LOG(ERROR) << "Failed to open file " << filepath;
dan:~/src/pystack (support-missing-process_vm_readv) % pystack remote $(pgrep python3)
ERROR(process_remote): Failed to open file /proc/27161/memXXX
💀 Engine error: No such file or directory 💀

dan:~/src/pystack (support-missing-process_vm_readv) %

Once I was pretty happy that it wasn't too awful, I spun up a flatcar VM and tested the happy path there.

Details (Probably too many)

Per https://www.flatcar.org/docs/latest/setup/releases/verify-images/

curl -L -O https://www.flatcar.org/security/image-signing-key/Flatcar_Image_Signing_Key.asc
gpg --import --keyid-format LONG Flatcar_Image_Signing_Key.asc
cat  << 'EOF' > ssh_config
Host flatcar
HostName localhost
Port 2222
User core
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
EOF
cat << 'EOF' > acquire.sh 
#!/bin/sh

set -eu

#current=4152.2.3
current=current

base_name=flatcar_production_qemu_uefi

for suff in .sh _image.img _efi_code.fd _efi_vars.fd; do
  wget https://lts.release.flatcar-linux.net/arm64-usr/$current/${base_name}${suff}
  wget https://lts.release.flatcar-linux.net/arm64-usr/$current/${base_name}${suff}.sig
  gpg --verify ${base_name}${suff}.sig
done

chmod +x ${base_name}.sh
EOF

sh ./acquire.sh

patch <<'EOF'
diff --git a/flatcar_production_qemu_uefi.sh b/flatcar_production_qemu_uefi.sh
index e97ba31..0e31ba9 100755
--- a/flatcar_production_qemu_uefi.sh
+++ b/flatcar_production_qemu_uefi.sh
@@ -220,6 +220,8 @@ else
             set -- -machine q35 -cpu kvm64 -smp 1 -nographic "$@" ;;
         arm64-usr+aarch64)
             set -- -machine virt,accel=kvm,gic-version=3 -cpu host -smp "${VM_NCPUS}" -nographic "$@" ;;
+        arm64-usr+arm64)
+            set -- -machine virt,accel=hvf -cpu host -smp "${VM_NCPUS}" -nographic "$@" ;;
         arm64-usr+*)
             if test "${VM_NCPUS}" -gt 4 ; then
                 VM_NCPUS=4
EOF
./flatcar_production_qemu_uefi.sh -nographic
ssh -F ssh_config flatcar

core@localhost ~ $ docker run -it --rm python:3.12 bash
Unable to find image 'python:3.12' locally
...
Status: Downloaded newer image for python:3.12
root@946ac0b130f3:/# # fix broken dns
root@946ac0b130f3:/# newresolve=$(sed $'/nameserver/i\\\nnameserver 1.1.1.1' /etc/resolv.conf); echo "$newresolve" > /etc/resolv.conf
root@946ac0b130f3:/# apt-get update && apt-get install libdw-dev libelf-dev pkg-config
...
root@946ac0b130f3:/# python -m pip install "pystack @ git+https://github.com/cakemanny/pystack.git@support-missing-process_vm_readv"
...
root@dce305e0c604:/# pystack
usage: pystack [-h] [-v] [--version] [--no-color] {remote,core} ...
pystack: error: the following arguments are required: command
root@dce305e0c604:/# python3 -c $'import time\nwhile True: time.sleep(1)' &
[1] 260
root@dce305e0c604:/# pystack remote 260
Traceback for thread 260 (python3) [] (most recent call last):
    (Python) File "<string>", line 2, in <module>

root@dce305e0c604:/# pystack remote 260 -vv 2>&1| grep falling
DEBUG(process_remote): process_vm_readv not compiled in kernel, falling back to /proc/PID/mem
root@dce305e0c604:/#

Additional context
I've not written any C++ for about 14 years, so I've probably missed out on some cleverer or prettier ways of doing this.

@codecov-commenter
Copy link

Codecov Report

Attention: Patch coverage is 13.04348% with 20 lines in your changes missing coverage. Please review.

Project coverage is 82.41%. Comparing base (ca83721) to head (3ef98d2).

Files with missing lines Patch % Lines
src/pystack/_pystack/mem.cpp 13.04% 20 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #240      +/-   ##
==========================================
- Coverage   82.66%   82.41%   -0.26%     
==========================================
  Files          46       46              
  Lines        6249     6272      +23     
  Branches      458      465       +7     
==========================================
+ Hits         5166     5169       +3     
- Misses       1083     1103      +20     
Flag Coverage Δ
cpp 82.41% <13.04%> (-0.26%) ⬇️
python_and_cython 82.41% <13.04%> (-0.26%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Kernels may build with the CONFIG_CROSS_MEMORY_ATTACH configuration
option not set, in this case process_vm_readv returns ENOSYS.

The /proc/PID/mem file in the proc(5) filesystem serves the same purpose
but is less efficient as the data must transfer through the kernel.
We use this as a fallback.

Signed-off-by: Daniel Golding <goldingd89@gmail.com>
@cakemanny cakemanny force-pushed the support-missing-process_vm_readv branch from 3ef98d2 to 61713f1 Compare May 25, 2025 21:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support for kernels with CONFIG_CROSS_MEMORY_ATTACH not set
2 participants