Debugging TUI programs with rust-gdb

I'd like to debug a thread crash in a TUI program. When I do it with rust-gdb the normal way, gdb's stdout and the TUI's stdout interfere with one another. When the crash happens, I'm unable to execute commands like down to examine what happened. I see things like this when I type:

How can I properly TUI programs with gdb? Is there, for example, a trick to launch the process in one terminal window and attach gdb to it in another?

You should be able to attach to a process id using the attach command: Attach (Debugging with GDB)

Not sure if attaching a debugger will automatically capture stdout/stderr from the debugged process though. Not currently on a system with gdb, so I can't check.

Interesting angle, but it's not quite working for me yet. This is what I tried:

  • Start the debug build of the program via target/debug/hx
  • Get the PID of the program via ps aux | grep hx
  • Start rust-gdb
  • Enter attach 203795 (where 203795 was the PID)

In response, I see this:

Attaching to process 203795
ptrace: Operation not permitted.

I think this might help you: https://stackoverflow.com/questions/19215177/how-to-solve-ptrace-operation-not-permitted-when-trying-to-attach-gdb-to-a-pro

1 Like

Another way to do it is to run gdbserver in the window where you want the TUI, then attach to that from a separate gdb session. The server will be the direct parent for ptrace-security considerations.

Regarding the gdbserver approach, I tried this but the connection isn't working:

If I understand the sources correctly, there's a security policy preventing processes from debugging arbitrary processes unless I somehow manage to run the debugger as a child process of the debug target?

It has been years since I used it, and I'm on my phone so can't easily check right now. But I believe another option is to tell gdb to use a different TTY for the child program. I seem to remember doing this many years ago.

However, on a developer computer it is not unreasonable to run with (or occasionally switch to) the laxer policy that allows tracing any other program from the same user.

Thank you.:heart:

I've also read that in C it's possible to let your debug target do a special call at the start of execution to allow other processes to debug it, but I haven't found a Rust equivalent to it yet.

For now I suppose I can work around it by temporarily disabling the security as Vorpal suggests. For completeness' sake, this is the command:

echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope

Remember to set it back to its original value afterwards. For me this was 1. Quoting the man page:

   0 ("classic ptrace permissions")
          No additional restrictions on operations that perform PTRACE_MODE_ATTACH checks (beyond those imposed by the commoncap and other LSMs).

          The use of PTRACE_TRACEME is unchanged.

   1 ("restricted ptrace") [default value]
          When performing an operation that requires a PTRACE_MODE_ATTACH check, the calling process must either have the CAP_SYS_PTRACE  capability
          in the user namespace of the target process or it must have a predefined relationship with the target process.  By default, the predefined
          relationship is that the target process must be a descendant of the caller.

          A  target process can employ the prctl(2) PR_SET_PTRACER operation to declare an additional PID that is allowed to perform PTRACE_MODE_AT‐
          TACH operations on the target.  See the kernel source file Documentation/admin-guide/LSM/Yama.rst (or Documentation/security/Yama.txt  be‐
          fore Linux 4.13) for further details.

          The use of PTRACE_TRACEME is unchanged.

These things tend to live in the nix crate, but I haven't checked if that is the case here.

You could do the exact same call. Just use the libc crate or maybe nix or rustix. I would expect at least one of them have mapped the specific flag values and prctl will be mapped for sure. For libc you will need unsafe for sure, you might not need it for the other ones.

1 Like

That's an interesting idea, although I haven't really bridged from Rust to C before. I've seen the official code example but I'm not sure how this works with the #include <sys/prctl.h> dependency. The LLM I asked about it outputted something fairly convoluted.

The nix crate has a prctl module. I don't see a function in there that seems to allow any ptracer though.

You wouldn't need the include. Instead you would go looking in the libc crate for the relevant function (prctl) and the relevant constants.

Nix might not have a wrapper, it tries to be higher level, so it a nice option when it has it. And rustix doesn't seem to have much in this case at all (it is narrower in scope and usually somewhere in between libc and nix in terms of abstraction level).

1 Like

Awesome, thanks! So for people finding this thread in the future:

  1. Run cargo add libc
  2. In the main function, add unsafe { prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY) };
  3. Run the debug binary in terminal 1
  4. Get its PID from ps aux | grep [your binary filename here]
  5. Start rust-gdb in terminal 2
  6. At the GDB prompt, enter attach [your PID here]
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.