Skip to content
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

nix::sys::ptrace desperately needs documentation #649

Open
marmistrz opened this issue Jul 6, 2017 · 14 comments
Open

nix::sys::ptrace desperately needs documentation #649

marmistrz opened this issue Jul 6, 2017 · 14 comments
Labels

Comments

@marmistrz
Copy link
Contributor

Hi!

First of all - I'm eager to contribute the documentation, once I've understood, how the module should be used.

I was quite new to ptrace and I'm still learning, so I might have missed something obvious, keep this in mind please :)

I had this small piece of code, which simply printed a message, whenever a syscall was entered or exited.

int main() {
    pid_t child = fork();
    if (child == 0) {
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        execl("/bin/ls", "ls", NULL);
    } else {
        for (;;) {
            int status;
            wait(&status);
            if (WIFEXITED(status))
                break;
            long orig_rax = ptrace(PTRACE_PEEKUSER, child, 8 * ORIG_RAX, NULL);
            printf("Syscall %ld\n", orig_rax);
            ptrace(PTRACE_SYSCALL, child, NULL, NULL);
        }
    }
    return 0;
}

This worked very well. Now I translated this into Rust.

extern crate colored;
extern crate spawn_ptrace;
extern crate nix;

use std::process::Command;
use nix::sys::ptrace::ptrace;
use nix::sys::ptrace::ptrace::*;
use nix::sys::wait::{wait, WaitStatus};
use std::ptr;
use nix::libc::c_void;

const RAX: i64 = 8 * 15;

fn main() {

    use spawn_ptrace::CommandPtraceSpawn;
    let child = Command::new("ls").arg("-l").spawn_ptrace().unwrap();
    let pid = child.id() as i32;

    loop {
        match wait() {
            Ok(WaitStatus::Exited(_, code)) => assert_eq!(code, 0),
            Ok(WaitStatus::Stopped(_, sig)) => {
                println!("Stopped with a signal: {:?}", sig);
            }
            Ok(s) => panic!("Unexpected stop reason: {:?}", s),
            Err(e) => panic!("Unexpected waitpid error: {:?}", e),
        }

        let orig_rax = ptrace(PTRACE_PEEKUSER, pid, RAX as *mut c_void, ptr::null_mut()).unwrap();
        println!("We've got syscall: {}", orig_rax);

        ptrace(PTRACE_SYSCALL, pid, ptr::null_mut(), ptr::null_mut()).unwrap();
    }
}

No good. Nothing displays, the process is stuck at the wait() forever.

I took a look at the spawn-ptrace crate, which doesn't call wait() before, so next try:

extern crate colored;
extern crate spawn_ptrace;
extern crate nix;

use std::process::Command;
use nix::sys::ptrace::ptrace;
use nix::sys::ptrace::ptrace::*;
use nix::sys::wait::{wait, WaitStatus};
use std::ptr;
use nix::libc::c_void;

const RAX: i64 = 8 * 15;

fn main() {

    use spawn_ptrace::CommandPtraceSpawn;
    let child = Command::new("ls").arg("-l").spawn_ptrace().unwrap();
    let pid = child.id() as i32;

    loop {
        let orig_rax = ptrace(PTRACE_PEEKUSER, pid, RAX as *mut c_void, ptr::null_mut()).unwrap();
        println!("We've got syscall: {}", orig_rax);

        ptrace(PTRACE_SYSCALL, pid, ptr::null_mut(), ptr::null_mut()).unwrap();

        match wait() {
            Ok(WaitStatus::Exited(_, code)) => assert_eq!(code, 0),
            Ok(WaitStatus::Stopped(_, sig)) => {
                println!("Stopped with a signal: {:?}", sig);
            }
            Ok(s) => panic!("Unexpected stop reason: {:?}", s),
            Err(e) => panic!("Unexpected waitpid error: {:?}", e),
        }
    }
}

This displays all syscalls, compared to the C version, but panicks at the end, because the process is not in the stopped state.

We've got syscall: 3
Stopped with a signal: SIGTRAP
We've got syscall: 3
Stopped with a signal: SIGTRAP
We've got syscall: 231
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Sys(ESRCH)', src/libcore/result.rs:859
stack backtrace:
   0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace
   1: std::sys_common::backtrace::_print
   2: std::panicking::default_hook::{{closure}}
   3: std::panicking::default_hook
   4: std::panicking::rust_panic_with_hook
   5: std::panicking::begin_panic
   6: std::panicking::begin_panic_fmt
   7: rust_begin_unwind
   8: core::panicking::panic_fmt
   9: core::result::unwrap_failed
             at /build/rust/src/rustc-1.18.0-src/src/libcore/macros.rs:29
  10: <core::result::Result<T, E>>::unwrap
             at /build/rust/src/rustc-1.18.0-src/src/libcore/result.rs:737
  11: ptrace_rust::main
             at ./src/main.rs:21
  12: __rust_maybe_catch_panic
  13: std::rt::lang_start
  14: main
  15: __libc_start_main
  16: _start

Could anyone explain, how the nix::sys::ptrace module should be used? I'll be happy to contribute the documentation in return.

@Susurrus Susurrus added the A-docs label Jul 6, 2017
@Susurrus
Copy link
Contributor

Susurrus commented Jul 6, 2017

First of all, thanks for offering to contribute docs, unfortunately they're severely lacking for nix!

My first questions are what is your platform and what version of nix are you using, as it looks like you're using a released version and not git.

Looking at your examples, your code looks correct to me from what I understand about ptrace. One thing to note is that nix uses its own definition of ptrace, which I think is incorrect for some platforms. It should really be using libc's version. I suggest you actually try using libc::ptrace directly and see if that solves your issue. If so, we might just need to switch to use the libc version here.

And your first issue, if we removed the external dependencies, would be a great match for our local test suite. I'd love to get this issue resolved to the point where we could get some ptrace tests into our testsuite. Would you be willing to contribute that as well once we resolve this?

@Susurrus
Copy link
Contributor

Susurrus commented Jul 6, 2017

Additionally, I'd also love to have safe functions for all the ptrace() (standard) use cases. So for your case I'd like to add a ptrace_peekuser() that takes just a Pid type and a register, which will be way more usable than calling the general ptrace(). I'd be willing to mentor you through filing a PR to add that if you're willing (one of the first steps would be to add ORIG_RAX to libc for the appropriate platforms.

@marmistrz
Copy link
Contributor Author

Yeah, I'll eagerly contribite the example to the testsuite. I'll try using the libc version tomorrow.

To be sure - you mean this crate: https://github.com/rust-lang/libc

Yeah, I wanted to say the same. I think I'd do them anyway, so why not contribute them here.

But let's start small. I'll see if I can utilize libc::ptrace tomorrow

@Susurrus
Copy link
Contributor

Susurrus commented Jul 6, 2017

@marmistrz Yes, that's the main libc crate and it's what nix builds on top of. I suggest using the git version of that as well when playing with it as it doesn't release too often.

@marmistrz
Copy link
Contributor Author

What's the best way to add a git version as a dependency? cargo will only download from crates.io

@Susurrus
Copy link
Contributor

Susurrus commented Jul 6, 2017

cargo can use local paths or git directly (see here)

@vincenthage
Copy link

Hi,
Try this PR, which for me is the only branch that's currently working for ptracing processes.

@marmistrz
Copy link
Contributor Author

marmistrz commented Jul 7, 2017

Oh, so there was a mistake on my side, that I did not exit the loop after the process has exited :P
Another problem was that the spawn-ptrace crate issued an initial waitpid, what was not documented at all.

So, nix::sys::ptrace::ptrace works correctly. I'll add my wrappers as a PR soon - I have to write them anyway, usage of raw ptrace would be atrocious, and nix is where the wrappers should belong.

Btw. what's the difference between nix::libc::ptrace and libc::ptrace?

Do we want to include something similar to spawn_ptrace into nix?

EDIT: I've already created the register data for x64, would such a thing be ok to include into nix::sys::ptrace (so the type would be nix::sys::ptrace::Register): https://pastebin.com/28HF8wSA

@Susurrus
Copy link
Contributor

Susurrus commented Jul 7, 2017

Btw. what's the difference between nix::libc::ptrace and libc::ptrace?

There isn't one. We just expose the version of libc used by nix to avoid type breakage if a project uses another version of libc somewhere.

Do we want to include something similar to spawn_ptrace into nix?

I don't know what it does, but probably not. We're trying to be a thin and safe wrapper over libc. But of course, i'm not completely familiar with spawn_ptrace or even ptrace.

EDIT: I've already created the register data for x64, would such a thing be ok to include into nix::sys::ptrace (so the type would be nix::sys::ptrace::Register): https://pastebin.com/28HF8wSA

Yes, but you shouldn't declare values directly in nix, you should use the libc types directly, but most of them should already exist as I checked when you first opened this PR.

@marmistrz
Copy link
Contributor Author

Oh, I didn't notice these types are exported vla the libc crate.

But the approach that the end user (i.e. the programmer) only uses enum Register and we translate them to integers without user's knowledge is ok to you?

@Susurrus
Copy link
Contributor

Susurrus commented Jul 9, 2017

We shouldn't do any "translation", as the enum values should be the proper numeric values to start. So you'll want to write the enum like:

pub enum Register {
    R15 = libc::R15,
    R14 = libc::R14,
    R13 = libc::R13,
    ...
}

But I searched through the libc sources again and didn't see any of these register values. You'll need to get them merged into libc first and then we can export them as part of the Register enum for use with ptrace.

@marmistrz
Copy link
Contributor Author

Because they're here: https://doc.rust-lang.org/regex/libc/struct.user_regs_struct.html

I didn't know Rust's enums are so clever. And is it possible to do something like this:

enum Register {
    use libc::user_regs_struct::*
}

which would be equivalent to your snippet?

I'll contribute that on Monday, besides I already have 3 clean wrappers around ptrace without superfluous args, I'll contribute them too.

@Susurrus
Copy link
Contributor

Susurrus commented Jul 9, 2017

No that syntax won't work. But looking forward to a PR with your contributions!

@marmistrz
Copy link
Contributor Author

As for the registry of syscalls: raised that in libc. Let's see what they say, I'm not sure where it belongs better - here or libc
rust-lang/libc#662

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants