So i've been struggling with this exercise. I must get al of the System Calls made by any given Linux command of my choice (I.E. ls or cd), list them in a .txt file, and have their unique IDs listed beside them.
So far here's what i got:
strace -o filename.txt ls
This when executed in the Linux shell gives me a "filename.txt" file containing all the system calls of the ls command. Now in my C script:
#include <stdio.h>
#include <stdlib.h>
int main(){
system("strace -o filename.txt ls");
return 0;
}
This should do the same as the previous code, but it's not returning me anything, although the code succesfully compiles. How would i go about fixing this, and then get the IDs? I'm using the "stdlib" library because in my research i found that it has some relation to system call IDs, but haven't found any indication on how to get them. Basically i must read that file i created and have it give each system call its ID.
The exercise is obviously designed to be solved by using the
ptrace()
facility, because thestrace
utility does not have an option to print the syscall number (as far as I know).Technically, you can use something like
to generate a number of
syscall-number syscall-name
lines, to be used for mapping syscall names back to syscall numbers, but this would be silly and error-prone. Silly, because being able to useptrace()
gives you much more control than using thestrace
utility, and using a "clever hack" like above just means you avoid learning how to do that, which in my opinion is by definition self-defeating and therefore utterly silly; and error-prone, because there is absolutely no guarantee that the installed headers match the running architecture. This is especially problematic on multiarch architectures, where you can use-m32
and-m64
compiler options to switch between 32-bit and 64-bit architectures. They typically have completely different syscall numbers.Essentially, your program should:
fork()
a child process.In the child process:
Enable ptracing by calling
prctl(PR_SET_DUMPABLE, 1L)
Make parent process the tracer by calling
ptrace(PTRACE_TRACEME, (pid_t)0, (void *)0, (void *)0)
Optionally, set tracing options. For example, call
ptrace(PTRACE_SETOPTIONS, getpid(), PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXEC | PTRACE_O_TRACEEXIT | PTRACE_O_TRACEFORK)
so that you catch at leastclone()
,fork()
, and exec() family of syscalls.If you do not set the
PTRACE_O_TRACEEXEC
option, you should stop the child process at this point using e.g.raise(SIGSTOP);
, so that the parent process can start tracing this child.Execute the command to be traced using e.g.
execv()
. In particular, if the first command line parameter is the command to run, optionally followed by its options, you can useexecvp(argv[1], argv + 1);
.If you set the
PTRACE_O_TRACEEXEC
option above, then the kernel will auto-pause the child process just before executing the new binary.If the exec fails, the child process should exit. I like to use
exit(127);
, to return exit status 127.In the parent process, use
waitpid(childpid, &status, WUNTRACED | WCONTINUED
in a loop, to catch events in the child process.The very first event should be the initial pause, i.e.
WIFSTOPPED(status)
being true. (If not, something else went wrong.)There are three three different reasons why
waitpid(childpid, &status, WUNTRACED | WCONTINUED)
may return:When the child exits (
WIFEXITED(status)
will be true). This should obviously end the tracing, and have the parent tracer process exit, too.When the child resumes execution (
WIFCONTINUED(status)
will be true).You cannot assume that a
PTRACE_SYSCALL
,PTRACE_SYSEMU
,PTRACE_CONT
etc. commands have actually caused the child process to continue, until the parent gets this signal. In other words, you cannot just fireptrace()
commands to the child process, and expect them to take place in an orderly fashion! Theptrace()
facility is asynchronous, and the call will return immediately; you need towaitpid()
for theWIFCONTINUED(status)
type of event to know that the child process heeded the command.When the kernel stopped the child (with
SIGTRAP
) because the child process is about to execute a syscall. (In the parent,WIFSTOPPED(status)
will be true.)Whenever the child process gets stopped because it is about to execute a syscall, you need to use
ptrace(PTRACE_GETREGS, childpid, (void *)0, ®s)
to obtain the CPU register state in the child process at the point of syscall execution.regs
is of typestruct user
, defined in<sys/user.h>
. For Intel/AMD architectures,regs.regs.eax
(for 32-bit) orregs.regs.rax
(for 64-bit) contains the syscall number (SYS_foo
as defined in<sys/syscall.h>
.You then need to call
ptrace(PTRACE_SYSCALL, childpid, (void *)0, (void *)0)
to tell the kernel to execute that syscall, andwaitpid()
again to wait for theWIFCONTINUED(status)
event notifying that it did.The next
WIFSTOPPED(status)
type event fromwaitpid()
will occur when the syscall is completed. If you want, you can usePTRACE_GETREGS
again to examineregs.regs.eax
orregs.regs.rax
, which contains the syscall return value; on Intel/AMD, if an error occurred, it will be a negative errno value (i.e.-EACCES
,-EINVAL
, or similar.)You need to call
ptrace(PTRACE_SYSCALL, childpid, (void *)0, (void *)0)
to tell the kernel to continue running the child, until the next syscall.There are quite a few examples on-line showing some of the details above, although most that I have personally seen are pretty lax on error checking, and occasionally omit checking the
WIFCONTINUED(status)
waitpid()
events. I've even written an answer detailing how to stop and continue individual threads on StackOverflow. Since the technique can be used as a very powerful custom debugging tool, I do recommend you try to learn the facility so you can leverage it in your work, rather than just copy-paste some existing code to get a passing grade on the exercise.