include/asm/ptrace.h:
struct pt_regs {
long ebx;
long ecx;
long edx;
long esi;
long edi;
long ebp;
long eax;
int xds;
int xes;
long orig_eax;
long eip;
int xcs;
long eflags;
long esp;
int xss;
};
I'm wondering how exactly I can view/access a system call, given a pt_regs struct?
ptrace allows a tracer to run a traced program (tracee), but automatically stop (pause) at every entry and exit from a syscall. The ptrace(PTRACE_GETREGS, pid, ®s, ®s)
allows the tracer to obtain the parameters to a syscall on entry, and the result value on exit from a syscall, to a struct user_regs_struct regs;
as defined in <sys/user.h>
header file. (Specifying the pointer to that as both addr
and data
pointers avoids special-casing SPARC systems. See the above man page for background.)
System call arguments are in registers, but it depends on the architecture which registers are used. See man 2 syscall for details.
The C library defines the struct user_regs_struct
in <sys/user.h>
for the currently installed architecture. For architecture-independent code, you'll need to either find a library that will abstract the details for you, or write a shim yourself. (It's not difficult; it's just that there are fifteen architectures to support, and gathering the details for each, and testing them, and combining it all to an #if defined()
.. #elif defined()
.. #else #error ... #endif` sequence is a lot of work, details to get absolutely right. So straightforward hard work.)
For example, on i386, the system call number (see /usr/include/bits/syscall.h
) is in the eax
register, with the return value also in eax
. Up to six parameters can be supplied to each syscall; starting from the first, they are stored in the ebx
, ecx
, edx
, esi
, edi
, and ebp
registers, reading from left to right, in that order. (The Linux kernel does not support floating-point arguments to system calls, so basically all syscall arguments are promoted to long
, which is the same size as a pointer on i386. Userspace virtual memory is also flat, i.e. pointers are just straightforward 32-bit addresses, and don't contain segment register references.)
As a detailed example, let us consider the case where the tracee is stopped on entry to say the read
syscall. This syscall in Linux uses the same interface as the POSIX.1 read()
function, except that instead of returning -1 with errno
set when an error occurs, it will return -errornumber
instead; i.e. -EAGAIN
, -EINTR
, and so on.
The tracer notices (via wait()
or waitpid()
) that the tracee is stopped, and obtains the registers using the aforementioned ptrace(PTRACE_GETREGS,pid,®s,®s)
call. The file descriptor is in regs.ebx
, the pointer to the buffer is in regs.ecx
, and the number of bytes to be written is in regs.edx
.
If the tracer then continues and waits for the tracee to stop at the exit of said syscall, regs.eax
would contain the result value. If it is zero, the other end of the descriptor has closed (shut down) the connection. If it is positive, it is the number of bytes read to the buffer. If it is negative, it is the error number (as would be stored in errno
) negated. (Yes, errno values in Linux are guaranteed to be small positive (nonzero) integers.)
On x86, syscall arguments are in (ebx, ecx, edx, esi, edi, ebp) and syscall number should be in orig_eax if I recall correctly.
If you want to view or access them in userspace, check ptrace(2), and maybe also strace(1) source.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With