いつまで経っても親知らずが人間から失われないの、歯医者業界との癒着か?
strace は ptrace(2)
でプロセスを監視することで呼び出されるシステムコールとその引数を表示している。
ここでは簡易的な strace を作成してシステムコールを表示してみる。
どのようにシステムコールを追いかけるか
今回は任意のプロセスにアタッチするのではなく、引数として与えた文字列を execvp
してシステムコールを表示してみる。
システムコールを逐次表示していく手順としては以下のようになる。
fork(2)
して子プロセスの中でexecvp
する。- 子プロセスでは
ptrace(PTRACE_TRACEME, 0, NULL, NULL)
によって親プロセスでトレースを行わせる。 - 親プロセスでは
waitpid
で状態が変化するまで待ち、ptrace(PTRACE_GETREGS, pid, NULL, ®s)
でレジスタの状態を取得する。
基本的には waitpid
→ ptrace(PTRACE_GETREGS, pid, NULL, ®s)
を繰り返していけばよい。
システムコールとレジスタの定義
ptrace(PTRACE_GETREGS, pid, NULL, ®s)
すると regs.orig_rax
にはシステムコール番号が入っている。
そして、 rdi
や rsi
にはそれぞれシステムコールに対する引数が入っている。
このあたりの話は以下の記事を参照されたい。
システムコール番号は環境によって異なるが、x86_64 の場合は ausyscall
でも確認できる。
$ ausyscall x86_64 --dump
Using x86_64 syscall table:
0 read
1 write
2 open
3 close
4 stat
5 fstat
6 lstat
7 poll
8 lseek
(snip)
ausyscall
による出力をいい感じに整形し、システムコールを表示するプログラムが以下。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/user.h>
#include <sys/signal.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <sys/fcntl.h>
#include <syscall.h>
const char *syscalltable[] = {
"read",
"write",
"open",
"close",
"stat",
"fstat",
"lstat",
"poll",
"lseek",
"mmap",
"mprotect",
"munmap",
"brk",
"rt_sigaction",
"rt_sigprocmask",
"rt_sigreturn",
"ioctl",
"pread",
"pwrite",
"readv",
"writev",
"access",
"pipe",
"select",
"sched_yield",
"mremap",
"msync",
"mincore",
"madvise",
"shmget",
"shmat",
"shmctl",
"dup",
"dup2",
"pause",
"nanosleep",
"getitimer",
"alarm",
"setitimer",
"getpid",
"sendfile",
"socket",
"connect",
"accept",
"sendto",
"recvfrom",
"sendmsg",
"recvmsg",
"shutdown",
"bind",
"listen",
"getsockname",
"getpeername",
"socketpair",
"setsockopt",
"getsockopt",
"clone",
"fork",
"vfork",
"execve",
"exit",
"wait4",
"kill",
"uname",
"semget",
"semop",
"semctl",
"shmdt",
"msgget",
"msgsnd",
"msgrcv",
"msgctl",
"fcntl",
"flock",
"fsync",
"fdatasync",
"truncate",
"ftruncate",
"getdents",
"getcwd",
"chdir",
"fchdir",
"rename",
"mkdir",
"rmdir",
"creat",
"link",
"unlink",
"symlink",
"readlink",
"chmod",
"fchmod",
"chown",
"fchown",
"lchown",
"umask",
"gettimeofday",
"getrlimit",
"getrusage",
"sysinfo",
"times",
"ptrace",
"getuid",
"syslog",
"getgid",
"setuid",
"setgid",
"geteuid",
"getegid",
"setpgid",
"getppid",
"getpgrp",
"setsid",
"setreuid",
"setregid",
"getgroups",
"setgroups",
"setresuid",
"getresuid",
"setresgid",
"getresgid",
"getpgid",
"setfsuid",
"setfsgid",
"getsid",
"capget",
"capset",
"rt_sigpending",
"rt_sigtimedwait",
"rt_sigqueueinfo",
"rt_sigsuspend",
"sigaltstack",
"utime",
"mknod",
"uselib",
"personality",
"ustat",
"statfs",
"fstatfs",
"sysfs",
"getpriority",
"setpriority",
"sched_setparam",
"sched_getparam",
"sched_setscheduler",
"sched_getscheduler",
"sched_get_priority_max",
"sched_get_priority_min",
"sched_rr_get_interval",
"mlock",
"munlock",
"mlockall",
"munlockall",
"vhangup",
"modify_ldt",
"pivot_root",
"_sysctl",
"prctl",
"arch_prctl",
"adjtimex",
"setrlimit",
"chroot",
"sync",
"acct",
"settimeofday",
"mount",
"umount2",
"swapon",
"swapoff",
"reboot",
"sethostname",
"setdomainname",
"iopl",
"ioperm",
"create_module",
"init_module",
"delete_module",
"get_kernel_syms",
"query_module",
"quotactl",
"nfsservctl",
"getpmsg",
"putpmsg",
"afs_syscall",
"tuxcall",
"security",
"gettid",
"readahead",
"setxattr",
"lsetxattr",
"fsetxattr",
"getxattr",
"lgetxattr",
"fgetxattr",
"listxattr",
"llistxattr",
"flistxattr",
"removexattr",
"lremovexattr",
"fremovexattr",
"tkill",
"time",
"futex",
"sched_setaffinity",
"sched_getaffinity",
"set_thread_area",
"io_setup",
"io_destroy",
"io_getevents",
"io_submit",
"io_cancel",
"get_thread_area",
"lookup_dcookie",
"epoll_create",
"epoll_ctl_old",
"epoll_wait_old",
"remap_file_pages",
"getdents64",
"set_tid_address",
"restart_syscall",
"semtimedop",
"fadvise64",
"timer_create",
"timer_settime",
"timer_gettime",
"timer_getoverrun",
"timer_delete",
"clock_settime",
"clock_gettime",
"clock_getres",
"clock_nanosleep",
"exit_group",
"epoll_wait",
"epoll_ctl",
"tgkill",
"utimes",
"vserver",
"mbind",
"set_mempolicy",
"get_mempolicy",
"mq_open",
"mq_unlink",
"mq_timedsend",
"mq_timedreceive",
"mq_notify",
"mq_getsetattr",
"kexec_load",
"waitid",
"add_key",
"request_key",
"keyctl",
"ioprio_set",
"ioprio_get",
"inotify_init",
"inotify_add_watch",
"inotify_rm_watch",
"migrate_pages",
"openat",
"mkdirat",
"mknodat",
"fchownat",
"futimesat",
"newfstatat",
"unlinkat",
"renameat",
"linkat",
"symlinkat",
"readlinkat",
"fchmodat",
"faccessat",
"pselect6",
"ppoll",
"unshare",
"set_robust_list",
"get_robust_list",
"splice",
"tee",
"sync_file_range",
"vmsplice",
"move_pages",
"utimensat",
"epoll_pwait",
"signalfd",
"timerfd",
"eventfd",
"fallocate",
"timerfd_settime",
"timerfd_gettime",
"accept4",
"signalfd4",
"eventfd2",
"epoll_create1",
"dup3",
"pipe2",
"inotify_init1",
"preadv",
"pwritev",
"rt_tgsigqueueinfo",
"perf_event_open",
"recvmmsg",
"fanotify_init",
"fanotify_mark",
"prlimit64",
"name_to_handle_at",
"open_by_handle_at",
"clock_adjtime",
"syncfs",
"sendmmsg",
"setns",
"getcpu",
"process_vm_readv",
"process_vm_writev",
"kcmp",
"finit_module",
"sched_setattr",
"sched_getattr",
"renameat2",
"seccomp",
"getrandom",
"memfd_create",
"kexec_file_load",
"bpf",
"execveat"
};
void die (const char *msg)
{
perror(msg);
exit(errno);
}
int main(int argc, char *argv[])
{
int pid, result;
struct user_regs_struct regs;
const char *prog;
if (argc < 2) {
printf("usage: \n%s PROG [ARG]\n", argv[0]);
return 0;
}
prog = argv[1];
switch( (pid = fork()) ) {
case -1: die("Failed fork");
case 0:
// 親プロセスにトレースさせる
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
result = execvp(prog, &argv[1]);
if (result) {
die("execvp");
return result;
}
return 0;
}
while(1) {
int st;
// 子プロセスを再開する
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
if (waitpid(pid, &st, __WALL) == -1) {
break;
}
if (!(WIFSTOPPED(st) && WSTOPSIG(st) == SIGTRAP)) {
break;
}
ptrace(PTRACE_GETREGS, pid, NULL, ®s);
if (regs.rax == - ENOSYS) {
continue;
}
printf("%s = %lld\n", syscalltable[regs.orig_rax], regs.orig_rax);
}
return 0;
}
まずは strace
で pwd
を実行した結果を確認してみる。
vagrant@ubuntu-xenial:~/shared/sandbox/ptrace$ strace pwd
execve("/bin/pwd", ["pwd"], [/* 21 vars */]) = 0
brk(NULL) = 0x19fc000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=45283, ...}) = 0
mmap(NULL, 45283, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fd1abe21000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\t\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fd1abe20000
mmap(NULL, 3971488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fd1ab83e000
mprotect(0x7fd1ab9fe000, 2097152, PROT_NONE) = 0
mmap(0x7fd1abbfe000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c0000) = 0x7fd1abbfe000
mmap(0x7fd1abc04000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fd1abc04000
close(3) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fd1abe1f000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fd1abe1e000
arch_prctl(ARCH_SET_FS, 0x7fd1abe1f700) = 0
mprotect(0x7fd1abbfe000, 16384, PROT_READ) = 0
mprotect(0x606000, 4096, PROT_READ) = 0
mprotect(0x7fd1abe2d000, 4096, PROT_READ) = 0
munmap(0x7fd1abe21000, 45283) = 0
brk(NULL) = 0x19fc000
brk(0x1a1d000) = 0x1a1d000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=1668976, ...}) = 0
mmap(NULL, 1668976, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fd1abc86000
close(3) = 0
getcwd("/home/vagrant/shared/sandbox/ptrace", 4096) = 36
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 4), ...}) = 0
write(1, "/home/vagrant/shared/sandbox/ptr"..., 36/home/vagrant/shared/sandbox/ptrace
) = 36
close(1) = 0
close(2) = 0
exit_group(0) = ?
+++ exited with 0 +++
続いて上記プログラムで pwd
を実行した結果は以下のようになる。
vagrant@ubuntu-xenial:~/shared/sandbox/ptrace$ ./a.out pwd
execve = 59
brk = 12
access = 21
access = 21
open = 2
fstat = 5
mmap = 9
close = 3
access = 21
open = 2
read = 0
fstat = 5
mmap = 9
mmap = 9
mprotect = 10
mmap = 9
mmap = 9
close = 3
mmap = 9
mmap = 9
arch_prctl = 158
mprotect = 10
mprotect = 10
mprotect = 10
munmap = 11
brk = 12
brk = 12
open = 2
fstat = 5
mmap = 9
close = 3
getcwd = 79
fstat = 5
/home/vagrant/shared/sandbox/ptrace
write = 1
close = 3
close = 3
システムコールに関しては strace と同様の結果が取得できている。
引数を表示する
strace ではシステムコールに加えて引数も表示できている。
getcwd("/home/vagrant/shared/sandbox/ptrace", 4096) = 36
前述した通り、引数はレジスタに格納されているので、取得すること自体は ptrace(PTRACE_PEEKDATA, ...
で可能だが、当然システムコールごとに引数は異なっているので色々難しい。
「strace はどうやっているのだろう…何もわからない、俺たちは雰囲気で strace を使っている…」と思いながら見てみたらシステムコールごとに出力する実装を用意している。
例えば getcwd
の実装は これ 。
人間は時として泥臭いことをしないといけないことがある。皆さんは泥臭いですか?
さて、とりあえずここでは getcwd
だけでも表示してみる。
かなり雑だが、getcwd
の引数を表示する print_getcwd()
を書く。
レジスタ rdi
には第一引数である絶対パス名 buf
が、rsi
には第二引数である size
が格納されている。
buf
を PTRACE_PEEKDATA
で取得して表示している。
void print_getcwd(int pid, long long addr, long long size)
{
// %rdi = char *buf, %rsi = unsigned long size
int i;
char* bytes = malloc(size + sizeof(long));
for (i = 0; i < size; i += sizeof(long)) {
long data = ptrace(PTRACE_PEEKDATA, pid, addr + i, NULL);
if (data == -1) {
printf("failed peekdata");
free(bytes);
return;
}
memcpy(bytes + i, &data, sizeof(long));
}
printf("getcwd(\"%s\", %lld) = 79\n", bytes, size);
}
ここもかなり雑だが、getcwd
(システムコール番号が79) のときに print_getcwd
を呼び出すようにする。
if (regs.orig_rax == 79) {
print_getcwd(pid, regs.rdi, (int)regs.rsi);
continue;
}
結果は以下のようになり、ちゃんと引数を表示できていることがわかる。
vagrant@ubuntu-xenial:~/shared/sandbox/ptrace$ ./a.out pwd
execve = 59
brk = 12
access = 21
access = 21
open = 2
fstat = 5
mmap = 9
close = 3
access = 21
open = 2
read = 0
fstat = 5
mmap = 9
mmap = 9
mprotect = 10
mmap = 9
mmap = 9
close = 3
mmap = 9
mmap = 9
arch_prctl = 158
mprotect = 10
mprotect = 10
mprotect = 10
munmap = 11
brk = 12
brk = 12
open = 2
fstat = 5
mmap = 9
close = 3
getcwd("/home/vagrant/shared/sandbox/ptrace", 4096) = 79 # 表示できている
fstat = 5
/home/vagrant/shared/sandbox/ptrace
write = 1
close = 3
close = 3