In my last post I came back to my debugger project and got set up to build it and tried to get the tests to pass. In this post I’m doing the same with the rust version, rusty_trap.

I want to get in the habit of tracking where I’m starting these posts. So I’m at commit 6c22cba.

When I try to compile I get a few errors:

-*- mode: compilation; default-directory: "/plink:wsl:/home/jkain/projects/rusty_trap/" -*-
Compilation started at Sun Jul  6 08:53:57

/home/jkain/.cargo/bin/cargo b
warning: no edition set: defaulting to the 2015 edition while the latest is 2024
   Compiling nix v0.3.9 (https://github.com/joekain/nix-rust.git?branch=WaitStatus#4ae37a8b)
error[E0591]: can't transmute zero-sized type
   --> /home/jkain/.cargo/git/checkouts/nix-rust-9e641bdbdbe8194f/4ae37a8/src/sched.rs:168:20
	|
168 | ...   ffi::clone(mem::transmute(callback), ptr as *mut c_void, flags, &...
	|                  ^^^^^^^^^^^^^^
	|
	= note: source type: for<'a> extern "C" fn(*mut Box<(dyn FnMut() -> isize + 'a)>) -> i32 {callback}
	= note: target type: *const for<'a> extern "C" fn(*const Box<(dyn FnMut() -> isize + 'a)>) -> i32
	= help: cast with `as` to a pointer instead

For more information about this error, try `rustc --explain E0591`.
error: could not compile `nix` (lib) due to 1 previous error

Compilation exited abnormally with code 101 at Sun Jul  6 08:53:58

This is a problem building the nix crate which seems unexpected. I think I have some old version locked. Now that I think about it, everything must have some old version locked. But, looking at Cargo.toml I have:

[package]
name = "rusty_trap"
version = "0.1.0"
authors = ["Joseph Kain <joekain@gmail.com>"]

[dependencies]
libc     = "0.1.8"

[dependencies.nix]
git = "https://github.com/joekain/nix-rust.git"
version = "0.3.8"
branch = "WaitStatus"

[lib]
name = "rusty_trap"
path = "src/lib.rs"

[[bin]]
name = "twelve"
path = "tests/inferiors/twelve.rs"

[[bin]]
name = "loop"
path = "tests/inferiors/loop.rs"

So i have nix setup to track my repo with my WaitStatus branch, which is woefully out of date.

Now, I think there might have been a change I made that I never created a pull request for in proper nix. But, let’s see what happens if I just list nix as a dependency and pick of the officilal version. And I’ll do a cargo update to get new versions of all the dependencies.

@@ -4,12 +4,8 @@ version = "0.1.0"
 authors = ["Joseph Kain <joekain@gmail.com>"]
 
 [dependencies]
-libc     = "0.1.8"
-
-[dependencies.nix]
-git = "https://github.com/joekain/nix-rust.git"
-version = "0.3.8"
-branch = "WaitStatus"
+libc = "0.2.174"
+nix = "0.30.1"

Ok, this still doesn’t build but at least it’s complaining about problems in rusty_trap now.

-*- mode: compilation; default-directory: "/plink:wsl:/home/jkain/projects/rusty_trap/" -*-
Compilation started at Sun Jul  6 09:08:55

/home/jkain/.cargo/bin/cargo b
warning: no edition set: defaulting to the 2015 edition while the latest is 2024
 Downloading crates ...
  Downloaded cfg_aliases v0.2.1
  Downloaded nix v0.30.1
   Compiling cfg_aliases v0.2.1
   Compiling libc v0.2.174
   Compiling cfg-if v1.0.1
   Compiling bitflags v2.9.1
   Compiling nix v0.30.1
   Compiling rusty_trap v0.1.0 (/home/jkain/projects/rusty_trap)
error[E0432]: unresolved import `nix::unistd::Fork`
 --> src/lib.rs:5:18
  |
5 | use nix::unistd::Fork::*;
  |                  ^^^^ could not find `Fork` in `unistd`

error[E0432]: unresolved import `nix::sys::wait`
 --> src/lib.rs:9:15
  |
9 | use nix::sys::wait::*;
  |               ^^^^ could not find `wait` in `sys`

error[E0433]: failed to resolve: could not find `ptrace` in `sys`
  --> src/ptrace_util/mod.rs:3:15
   |
3  | use nix::sys::ptrace::ptrace::*;
   |               ^^^^^^ could not find `ptrace` in `sys`
   |
note: found an item that was configured out
  --> /home/jkain/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/nix-0.30.1/src/sys/mod.rs:81:13
   |
81 |     pub mod ptrace;
   |             ^^^^^^
note: the item is gated behind the `ptrace` feature
  --> /home/jkain/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/nix-0.30.1/src/sys/mod.rs:79:8
   |
79 |     #![feature = "ptrace"]
   |        ^^^^^^^

error[E0432]: unresolved import `nix::sys::ptrace`
 --> src/ptrace_util/mod.rs:2:15
  |
2 | use nix::sys::ptrace::*;
  |               ^^^^^^ could not find `ptrace` in `sys`

warning: unused import: `nix::unistd::*`
 --> src/lib.rs:4:5
  |
4 | use nix::unistd::*;
  |     ^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: unused import: `handle`
  --> src/lib.rs:22:18
   |
22 | use breakpoint::{handle, TrapBreakpoint};
   |                  ^^^^^^

warning: `extern` declarations without an explicit ABI are deprecated
  --> src/lib.rs:43:5
   |
43 |     extern {
   |     ^^^^^^ help: explicitly specify the "C" ABI: `extern "C"`
   |
   = note: `#[warn(missing_abi)]` on by default

error[E0599]: no variant or associated item named `Sys` found for enum `Errno` in the current scope
  --> src/lib.rs:82:24
   |
82 |             Err(Error::Sys(errno::EAGAIN)) => continue,
   |                        ^^^ variant or associated item not found in `Errno`

warning: variable does not need to be mutable
  --> src/breakpoint/mod.rs:35:34
   |
35 | pub fn handle<F>(inf: Inferior,  mut callback: &mut F) -> InferiorState
   |                                  ----^^^^^^^^
   |                                  |
   |                                  help: remove this `mut`
   |
   = note: `#[warn(unused_mut)]` on by default

Some errors have detailed explanations: E0432, E0433, E0599.
For more information about an error, try `rustc --explain E0432`.
warning: `rusty_trap` (lib) generated 4 warnings
error: could not compile `rusty_trap` (lib) due to 5 previous errors; 4 warnings emitted

Compilation exited abnormally with code 101 at Sun Jul  6 09:08:56

So it seems like either my additions to nix for wait, and ptrace aren’t present or they moved. There is something about fork as well so maybe things just moved. Let’s take a look at the nix docs.

Fork, looks like this should be in lowercase. No idea why it was capitalized before but let’s fix that. Fixed.

Also, this use line gives us some hints about waitpid as well:

use nix::{sys::wait::waitpid,unistd::{fork, ForkResult, write}};

The documentation also poits out

Available on crate feature process only.

This is new, I guess, and also relevant beause just using these doesn’t work:

warning: no edition set: defaulting to the 2015 edition while the latest is 2024
   Compiling rusty_trap v0.1.0 (/home/jkain/projects/rusty_trap)
error[E0432]: unresolved imports `nix::sys::wait`, `nix::unistd::fork`, `nix::unistd::ForkResult`, `nix::unistd::errno`
   --> src/lib.rs:4:16
	|
4   | ...sys::wait::waitpid,unistd::{fork, ForkResult, Error, errno}, sys::si...
	|         ^^^^                   ^^^^  ^^^^^^^^^^         ^^^^^
	|         |                      |     |                  |
	|         |                      |     |                  no `errno` in `unistd`
	|         |                      |     |                  help: a similar name exists in the module: `Errno`
	|         |                      |     no `ForkResult` in `unistd`
	|         |                      no `fork` in `unistd`
	|         could not find `wait` in `sys`
	|
note: found an item that was configured out
   --> /home/jkain/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/nix-0.30.1/src/sys/mod.rs:181:13
	|
181 |     pub mod wait;
	|             ^^^^
note: the item is gated behind the `process` feature
   --> /home/jkain/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/nix-0.30.1/src/sys/mod.rs:180:8
	|
180 |     #![feature = "process"]
	|        ^^^^^^^
note: found an item that was configured out
   --> /home/jkain/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/nix-0.30.1/src/unistd.rs:278:15
	|
278 | pub unsafe fn fork() -> Result<ForkResult> {
	|               ^^^^
note: the item is gated behind the `process` feature
   --> /home/jkain/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/nix-0.30.1/src/unistd.rs:159:4
	|
159 | #![feature = "process"]
	|    ^^^^^^^
note: found an item that was configured out
   --> /home/jkain/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/nix-0.30.1/src/unistd.rs:209:10
	|
209 | pub enum ForkResult {
	|          ^^^^^^^^^^
note: the item is gated behind the `process` feature
   --> /home/jkain/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/nix-0.30.1/src/unistd.rs:159:4
	|
159 | #![feature = "process"]
	|    ^^^^^^^
	= help: consider importing this module instead:
			nix::errno

Oh, I need to add this to the Cargo file on the nix dependency. Looks like I need something similar for ptrace. This is new to me and seems pretty cool that I can pick and choose pieces of nix (and other crates I assume) to include.

modified   Cargo.toml
@@ -5,7 +5,7 @@ authors = ["Joseph Kain <joekain@gmail.com>"]
 
 [dependencies]
 libc = "0.2.174"
-nix = "0.30.1"
+nix = {version = "0.30.1", features = ["process", "ptrace"]}
 
 [lib]
 name = "rusty_trap"

Now, ptrace is interesting. It looks like someone had a similar idea to my idea to create separate functions for each of the different ptrace requests (PTRACE_CONT, PTRACE_PEEKTEXT, etc.) with proper type checking. That’s fantastic.

These are perfect and I’ll use them directly.

Well, in the short term I’ll implement ptrace_util on top of them which will

  1. Let me review the types and see how they compare to the types I choose.
  2. Leave the code that uses ptrace_util alone for now. I like to mimize changes until we get the tests running and passing again.

Hmm, PEEKTEXT and POKETEXT don’t seem to have coresponding functions in the new nix ptrace. However, PEEKDATA and POKEDATA do have functions (read, write) and Linux doesn’t actually separate text and data so I should be able to use these instead.

And ok, now it builds at least. No idea if it works but these seem pretty straightfoward so I hope they do.

modified   Cargo.toml
@@ -5,7 +5,7 @@ authors = ["Joseph Kain <joekain@gmail.com>"]
 
 [dependencies]
 libc = "0.2.174"
-nix = "0.30.1"
+nix = {version = "0.30.1", features = ["process", "ptrace"]}
 
 [lib]
 name = "rusty_trap"

modified   src/ptrace_util/mod.rs
@@ -1,8 +1,6 @@
 use libc::pid_t;
-use nix::sys::ptrace::*;
-use nix::sys::ptrace::ptrace::*;
-use std::ptr;
-use libc::c_void;
+use nix::sys::ptrace;
+use nix::unistd::Pid;
 
 use inferior::InferiorPointer;
 
@@ -39,44 +37,49 @@ pub mod user {
 }
 
 pub fn trace_me() -> () {
-    ptrace(PTRACE_TRACEME, 0, ptr::null_mut(), ptr::null_mut())
+    ptrace::traceme()
         .ok()
         .expect("Failed PTRACE_TRACEME");
 }
 
 pub fn get_instruction_pointer(pid: pid_t) -> InferiorPointer {
-    let raw = ptrace(PTRACE_PEEKUSER, pid, user::regs::RIP as * mut c_void, ptr::null_mut())
+    let raw = ptrace::read_user(Pid::from_raw(pid), user::regs::RIP as ptrace::AddressType)
         .ok()
         .expect("Failed PTRACE_PEEKUSER");
     InferiorPointer(raw as u64)
 }
 
 pub fn set_instruction_pointer(pid: pid_t, ip: InferiorPointer) -> () {
-    ptrace(PTRACE_POKEUSER, pid, user::regs::RIP as * mut c_void, ip.as_voidptr())
+    ptrace::write_user(Pid::from_raw(pid), user::regs::RIP as ptrace::AddressType, ip.as_i64())
         .ok()
         .expect("Failed PTRACE_POKEUSER");
 }
 
 pub fn cont(pid: pid_t) -> () {
-    ptrace(PTRACE_CONT, pid, ptr::null_mut(), ptr::null_mut())
+    ptrace::cont(Pid::from_raw(pid), None)
         .ok()
         .expect("Failed PTRACE_CONTINUE");
 }
 
 pub fn peek_text(pid: pid_t, address: InferiorPointer) -> i64 {
-    ptrace(PTRACE_PEEKTEXT, pid, address.as_voidptr(), ptr::null_mut())
-        .ok()
-        .expect("Failed PTRACE_PEEKTEXT")
+    // From ptrace(2) regarding PTRACE_PEEKTEXT and PTRACE_PEEKDATA
+    //   Linux does not have separate text and data address spaces,
+    //   so these two operations are currently equivalent.
+    // So use ptrace::read which is ptrace(PTRACE_PEEKDATA, ...)
+    // An alterantive would be to use libc::ptrace.
+    ptrace::read(Pid::from_raw(pid),  address.as_voidptr())
+	.ok()
+	.expect("Failed PTRACE_PEEKTEXT")
 }
 
 pub fn poke_text(pid: pid_t, address: InferiorPointer, value: i64) -> () {
-    ptrace(PTRACE_POKETEXT, pid, address.as_voidptr(), value as * mut c_void)
-        .ok()
-        .expect("Failed PTRACE_POKETEXT");
+    ptrace::write(Pid::from_raw(pid), address.as_voidptr(), value)
+	.ok()
+	.expect("Failed PTRACE_POKETEXT")
 }
 
 pub fn single_step(pid: pid_t) -> () {
-    ptrace(PTRACE_SINGLESTEP, pid, ptr::null_mut(), ptr::null_mut())
+    ptrace::step(Pid::from_raw(pid), None)
         .ok()
         .expect("Failed PTRACE_SINGLESTEP");
 }

Now the compilation errors go back to lib.rs, I fixed the problems with use earlier but now it’s looking at the code itself. On of the main issues I faced was that execve changed the types it expects. I had a lot of trouble getting this right and I think part of this is I have forgotten almost all the rust I knew and I might be doing this wrong. But these type conversions to pass to nix::unistd::execve are too much IMHO.

fn exec_inferior(filename: &Path, args: &[&str]) -> () {
    // let c_filename = &CStr::from_ptr(filename.to_str().unwrap().as_ptr() as *const i8);
    let cstring_filename = CString::new(filename.to_str()
					.expect("Failed to get string from filename"))
	.expect("Failed to create CString from filename");
    let cstr_filename = CStr::from_ptr(cstring_filename.as_ptr());

    disable_address_space_layout_randomization();
    ptrace_util::trace_me();
    execve::<CString, CString>(cstr_filename, &[], &[])
        .ok()
        .expect("Failed execve");
    unreachable!();
}

I kind worked through this by brute force / trial and error and am not proud of it. But, let’s move on.

On the bright side, libc now includes personality and I can use that.

@@ -37,35 +34,32 @@ static mut global_breakpoint : Breakpoint = Breakpoint {
 };
 static mut global_inferior : Inferior = Inferior { pid: 0, state: InferiorState::Stopped };
 
-mod ffi {
-    use libc::{c_int, c_long};
-
-    extern {
-        pub fn personality(persona: c_long) -> c_int;
-    }
-}
-
 fn disable_address_space_layout_randomization() -> () {
     unsafe {
-        let old = ffi::personality(0xffffffff);
-        ffi::personality((old | 0x0040000) as i64);
+	let old = libc::personality(0xffffffff);
+	libc::personality((old | libc::ADDR_NO_RANDOMIZE) as u64);
     }
 }
 

Happy to be rid of the hard coded 0x0040000.

The rest were some easier updates to the types. I do wonder if I should push nix::unistd::Pid all the way up and be consistent about using it.

modified   src/lib.rs
@@ -1,15 +1,12 @@
 extern crate nix;
 extern crate libc;
 
-use nix::unistd::*;
-use nix::unistd::Fork::*;
+use nix::{Error, sys::wait::waitpid,unistd::{execve, fork, ForkResult}, sys::signal};
 use libc::pid_t;
-use nix::Error;
-use nix::errno;
 use nix::sys::wait::*;
-use std::ffi::CString;
+use nix::unistd::Pid;
+use std::ffi::{CString, CStr};
 use std::path::Path;
-use nix::sys::signal;
 
 mod ptrace_util;
 
@@ -19,7 +16,7 @@ use inferior::*;
 mod breakpoint;
 
 pub use self::breakpoint::trap_inferior_set_breakpoint;
-use breakpoint::{handle, TrapBreakpoint};
+use breakpoint::{TrapBreakpoint};
 
 #[derive(Copy, Clone)]
 struct Breakpoint {
@@ -37,35 +34,32 @@ static mut global_breakpoint : Breakpoint = Breakpoint {
 };
 static mut global_inferior : Inferior = Inferior { pid: 0, state: InferiorState::Stopped };
 
-mod ffi {
-    use libc::{c_int, c_long};
-
-    extern {
-        pub fn personality(persona: c_long) -> c_int;
-    }
-}
-
 fn disable_address_space_layout_randomization() -> () {
     unsafe {
-        let old = ffi::personality(0xffffffff);
-        ffi::personality((old | 0x0040000) as i64);
+	let old = libc::personality(0xffffffff);
+	libc::personality((old | libc::ADDR_NO_RANDOMIZE) as u64);
     }
 }
 
 fn exec_inferior(filename: &Path, args: &[&str]) -> () {
-    let c_filename = &CString::new(filename.to_str().unwrap()).unwrap();
+    // let c_filename = &CStr::from_ptr(filename.to_str().unwrap().as_ptr() as *const i8);
+    let cstring_filename = CString::new(filename.to_str()
+					.expect("Failed to get string from filename"))
+	.expect("Failed to create CString from filename");
     disable_address_space_layout_randomization();
     ptrace_util::trace_me();
-    execve(c_filename, &[], &[])
+    let cstr_filename = unsafe { CStr::from_ptr(cstring_filename.as_ptr()) };
+    execve::<CString, CString>(cstr_filename, &[], &[])
         .ok()
         .expect("Failed execve");
     unreachable!();
 }
 
-fn attach_inferior(pid: pid_t) -> Result<Inferior, Error> {
-    match waitpid(pid, None) {
-        Ok(WaitStatus::Stopped(pid, signal::SIGTRAP)) =>
-            return Ok(Inferior {pid: pid, state: InferiorState::Running}),
+fn attach_inferior(raw_pid: pid_t) -> Result<Inferior, Error> {
+    let nix_pid = Pid::from_raw(raw_pid);
+    match waitpid(nix_pid, None) {
+        Ok(WaitStatus::Stopped(pid, signal::Signal::SIGTRAP)) =>
+            return Ok(Inferior {pid: pid.into(), state: InferiorState::Running}),
         Ok(_) => panic!("Unexpected stop in attach_inferior"),
         Err(e) => return Err(e)
     }
@@ -73,25 +67,25 @@ fn attach_inferior(pid: pid_t) -> Result<Inferior, Error> {
 
 pub fn trap_inferior_exec(filename: &Path, args: &[&str]) -> Result<TrapInferior, Error> {
     loop {
-        match fork() {
-            Ok(Child)                      => exec_inferior(filename, args),
-            Ok(Parent(pid))                => {
-                unsafe { global_inferior = attach_inferior(pid).ok().unwrap() };
-                return Ok(pid)
+        match unsafe { fork() } {
+            Ok(ForkResult::Child)                      => exec_inferior(filename, args),
+            Ok(ForkResult::Parent{child: pid})         => {
+                unsafe { global_inferior = attach_inferior(pid.into()).ok().unwrap() };
+                return Ok(pid.into())
             },
-            Err(Error::Sys(errno::EAGAIN)) => continue,
-            Err(e)                         => return Err(e)
+            Err(Error::EAGAIN) => continue,
+            Err(e)             => return Err(e)
         }
     }
 }
 
-pub fn trap_inferior_continue<F>(inferior: TrapInferior, callback: &mut F) -> i8
+pub fn trap_inferior_continue<F>(inferior: TrapInferior, callback: &mut F) -> i32
     where F: FnMut(TrapInferior, TrapBreakpoint) -> () {
 
     let mut inf = unsafe { global_inferior };
     ptrace_util::cont(inf.pid);
     loop {
-        inf.state = match waitpid(inf.pid, None) {
+        inf.state = match waitpid(Pid::from_raw(inf.pid), None) {
             Ok(WaitStatus::Exited(_pid, code)) => return code,
             Ok(WaitStatus::Stopped(_pid, signal::SIGTRAP)) =>
                 breakpoint::handle(inf, callback),

Now, it compiles. But the tests fail. Before even running the tests I get some loader issues:

Inconsistency detected by ld.so: ../sysdeps/x86_64/dl-machine.h: 541: elf_machine_rela_relative: Assertion `ELFW(R_TYPE) (reloc->r_info) == R_X86_64_RELATIVE' failed!
Inconsistency detected by ld.so: ../sysdeps/x86_64/dl-machine.h: 541: elf_machine_rela_relative: Assertion `ELFW(R_TYPE) (reloc->r_info) == R_X86_64_RELATIVE' failed!

These are weird, unless I ptrace pokeed something that I shouldn’t have. And, I am suspicious that the breakpoint offsets would have moved, so let’s look into that first.

I used gdb to find the proper address of the breakpoints. And this elimited the inconsistencies detected by ld.so.

But the tests still don’t pass. Well, one of them doesn’t:

running 3 tests
test it_can_exec ... ok
test it_can_handle_a_breakpoint_more_than_once ... FAILED
test it_can_set_breakpoints ... ok

failures:

---- it_can_handle_a_breakpoint_more_than_once stdout ----

thread 'it_can_handle_a_breakpoint_more_than_once' panicked at /home/jkain/projects/rusty_trap/src/lib.rs:93:17:
Unexpected stop on signal SIGSEGV in trap_inferior_continue.  State: 0
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Ok, so inferior_continue found the inferior stopped on SIGSEGV. We could very well cause this by patching/restoring the breakpoint word incorrectly.

Is this the first time the breakpoint triggered?

Using printouts it seems that we’ve handled the breakpoint twice already. But stopping on the breakpoint calls breakpoint::handle once and the single stepping calls it a second time. Let’s confirm with better prints.

in breakpoint::handle inf.state = 0
in breakpoint::handle inf.state = 2

Right, so 0 is Running and 2 is SingleStepping.

So, in breakpoint::handle when single stepping we would attempt to set the breakpoint again and then continue.

Oh, this is the test that only has a breakpoint in main. Why did I think it was the other one?

Given that the test of a breakpoint multiple times works but the one with a single breakpoint occurance does not, it seems that either the address is wrong or something is wrong with trying to write this. Let me look at the main function disassembly in gdb:

I do see one bug and have fixed it but it isn’t the problem I’m looking for.

modified   src/breakpoint/mod.rs
@@ -27,7 +27,7 @@ fn step_over(inferior: TrapInferior, bp: Breakpoint) -> () {

 fn set(inferior: TrapInferior, bp: Breakpoint) -> () {
     let mut modified = bp.original_breakpoint_word;
-    modified &= !0xFFi64 << bp.shift;
+    modified &= !(0xFFi64 << bp.shift);
  
  modified |= 0xCCi64 << bp.shift;
     poke_text(inferior, bp.aligned_address, modified);

I think this was never an issue because the target and aligned address were always the same (and therefor shift was 0) becauze functions are usually aligned to 16 bytes. Either that or I don’t understand the operator precedence here. Eitehr way I think this is a good change but it doesn’t fix the test.

At this point I took a diversion to setup a cleaner development system with a newer version of Ubuntu. And then came back to working on this on the new system. Strangely, the test that was failing changed: The basic breakpoint test worked but the multiple breakpoint test stopped working.

One issue is that again compilers and systems have changed so the breakpoint addresses have moved. Also, the addresses now have namespaces so the symbol name is loop::foo and it gets C++-name-mangled. With this new name I’m able to find the address in gdb and plug it into the test.

After that, I wanted to focus only on the one test that is failing. I commented out the other tests (there must a better way to do this). Then it_can_handle_a_breakpoint_more_than_once passed!?!?

I asked, are these tests run concurrently?

  • Yes, concurrent use of rusty_trap isn’t going to work yet.

I can use cargo test -- --test-threads=1 to run them sequentially.

Now this passes! Well, I learned something about cargo testing and we have new code smell to encourage us to make rusty_trap thread safe. The biggest road block here is the global breakpoint.

Also, from the same documentation I see that there is a better way to run a single test: cargo test it_can_handle_a_breakpoint_more_than_once

Here is the pull request for all of the changes we made for this post.

There are a number of to do items we can carry forward with us.

TODO:

  • Go over the warnings
  • Consider nix::unistd::Pid rather than pid_t.
  • Look at ptrace::AddressType and consider replacing InferiorPointer.
  • Get rid of the global breakpoint and make the system thread safe.
  • Handle multiple breakpoints.

We should be able to tackle some of these in the next post. I hope to see you there.