Unix-แแก แแกแแแแก แแแแ แแชแแฃแ แกแแกแขแแแแแจแ แแ แแแ แแแแก แแแแฃแแแแแชแแ แแแ แ แกแแแงแแ แแกแแแ แแ แแแแ แแชแแฃแ แกแแกแขแแแแกแแแ แฎแแแแ แคแฃแแฅแชแแแแแก แแชแแ แ แแแแ แแแแก - แกแแกแขแแแฃแ แ แแแ แแแแก แแแจแแแแแแ. แแก แแแจแแแแก, แ แแ แแแแแ แแแแก แแแแแแแแกแแแแก แจแแแซแแแแ แกแแกแแ แแแแแ แแงแแก แแ แแชแแกแแแแก แแแแ แจแแกแ แฃแแแแฃแแ แกแแกแขแแแฃแ แ แแแ แแแแก แแแแแแแแแ.
แแ แแแ แแแ แแแแแฎแแแ แแแแ แแแแแขแ แแแแ แแ แแแ แแแแแแก โแแแขแแแฃแ แ แชแฎแแแ แแแโ Linux-แแ strace
, แ แแแแแแช แแ แกแขแแขแแแก แแแแแ. แฏแแจแฃแจแฃแ แ แแฆแญแฃแ แแแแแแแก แแแแแงแแแแแแก แแแแแแแแแแก แแฎแแแแก แแแแแ แแกแขแแ แแ strace
แแ แแกแแแ แแ แแแ แแแแแแก แแแแแแแแก แแฆแฌแแ แ.
แแแคแแ แแแชแแแก
แกแแฎแแแแแแแก แฌแแ แแแจแแแ แกแขแ แแแก แแแฌแงแแแแแแแ แแแแแแ: Piglet Trace แกแแคแฃแซแแแแแ: แแแจแแแแฃแแ แแ แแแ แแแแก แแแจแแแแ strace แกแแคแฃแซแแแแแ: แแ แแชแแกแแก แแแฌแแแ แแแแแแ แแแแแแแแ: แแแแจแแแก แแ แแชแแกแแแแก แแแแแงแฃแ แแก แแแแแแแ แแแแแแแแ: แคแแแแแก แแแแแแแแ แกแแฎแแแฃแ แแแแก แแแชแแแแ แแแแแแแแ: แคแแแแแ แฌแแแแแแก แแแแแงแฃแ แแก แแแแแแแ แแแแแแแแ: แแ แแแแแซแแแแแแ แแ แแแ แแแแแ แแแกแขแแ แแแแกแ: แแแแฃแจแแแแแแก แแแกแขแ แกแแกแขแแแฃแ แ แแแ แแก แแ แแก แแแกแขแแ แแแแกแ: แจแแชแแแแแก แแแแฅแชแแ Afterword
แกแแฎแแแแแแแก แฌแแ แแแจแแแ
Unix-แจแ แแ แแแ แแแแแกแ แแ OS แแแ แแแก แจแแ แแก แแแแแแ แ แแแขแแ แคแแแกแ แแ แแก แกแแกแขแแแฃแ แ แแแ แแแ. แกแแกแขแแแฃแ แ แแแ แแแ, syscals), แแ แแแ แแแแแแก แฃแ แแแแ แแฅแแแแแแ แแแ แ แกแแแงแแ แแกแแแ แฎแแแแ แแฅแกแแแฃแแแฃแ แแ แแแแ แแแจแแแแแแ.
แแแแ แแ Unix-แแก แแแ แแแ แกแแฏแแ แ แแแ แกแแแจแ (ptrace
.
ptrace แจแแแฅแแแ แซแแ แแแแแแ แแแขแแ แแฅแขแแฃแแ แแแแแ แแแแกแแแแก, แแแแ แแ 80-แแแแ แฌแแแแแก แแแแแก (แแแแแ แชแแฃแแ แแแแฅแแจแ
trace
แแแแกแแแ. แแ แแแ แแแแแ แแ แแ แแแแแแแ แแแแแฃแแแแแแ แแงแ SunOS-แแกแแแแก, แแแแ แแ 1994 แฌแแแกแแแแก strace
แแแ แขแแ แแแฃแแ แแงแ System V-แแ, Solaris-แแ แแ แกแฃแ แฃแคแ แ แแแแฃแแแ แฃแ Linux-แแ.
แแฆแแก strace แแฎแแ แก แฃแญแแ แก แแฎแแแแ Linux-แก แแ แแงแ แแแแแ แแแก ptrace
, แแแแแแ แแแแ แแ แแแแแ แแแคแแ แแแแแแ.
แแแแแแแแ แแแ (แแ แซแแแแแ แแฅแขแแฃแ แ) แจแแแแแฎแแแแ strace
-
แแกแแแ แแแแจแแแแแแแแแแ, แ แแ ptrace แกแแกแขแแแฃแ แ แแแ แ แแ แขแ แแกแแ แแแ แแ แแกแแแแก แงแแคแแแ แฉแแ แแฃแแ POSIX-แจแ, แแแฃแฎแแแแแแ แฎแแแแ แซแแแแ แแกแขแแ แแแกแ แแ แแแแแแแแแขแแชแแแกแ Linux-แจแ, FreeBSD-แจแ, OpenBSD-แกแ แแ แขแ แแแแชแแฃแ Unix-แจแ.
แกแขแ แแแก แแแฌแงแแแแแแแ แแแแแแ: Piglet Trace
"แแฅแแแ แแ แแแกแแแ แแแแก แแแแแแ" (แแแแแก แ แแฉแ, แแแแแแขแแ แ Unix-แแก 6-แ แแแ แกแแแจแ)
แแแ แแฃแแ แแแแจแแแแแแแ แแแ แแแขแแ แจแแ แงแฃแแแแก: แแ แแ แแแแแแจแแแแ แกแแแแแแจแแแแก, แแแแ แแ แแชแแแแแแแ แแแแแแ แแแแ แกแขแ แฃแฅแขแฃแ แ (แแแแแ แแแแ แแงแแแแแแแแ แกแแขแงแแแก "แแแขแแฎแ", แแแแ แแ แแ แแฏแแ แ แแแ แแขแ แแแแแแก). แแแแแ แแแแขแแแแ, แ แแ แแแ แแแแ Unix-แแก แแ แแแแแแแแ แแแ แฆแแ แฌแงแแ แแก แแแซแ แแแแแก แแ แแคแแ แแแแฃแ แ แแฃแแขแฃแ แ แแกแ แแฎแแแกแแ แฉแแแแแ.
แแ แกแขแแขแแแก แแแแแแแแกแแแแก, แแแแแแจแแฌแแแแแ แแ แแ แแก แแแฌแแแฃแแแแแก แแแแแแแแแแแจแ แแแแ แแแแ แกแขแ แแกแแก แฌแงแแ แแก แแแแแก แแแจแแ. แแแแ แแ แแแแแฎแแแแแกแแแแก แกแแแแฃแแแ แแ แฃแแแ แแแ แฉแแก. แแแจแแกแแแแแ, แแกแแแ แกแขแ แแขแแก แแ แแแ แแแแแแก แแฃแจแแแแแก แแ แแแชแแแแก แกแแฉแแแแแแแแ, แแ แแแแชแแ แแแแก แแแแแแขแฃแ แฃแแ แขแ แแกแแ แแก -
$ gcc examples/piglet-trace.c -o ptr
$ ptr echo test > /dev/null
BRK(12) -> 94744690540544
ACCESS(21) -> 18446744073709551614
ACCESS(21) -> 18446744073709551614
unknown(257) -> 3
FSTAT(5) -> 0
MMAP(9) -> 140694657216512
CLOSE(3) -> 0
ACCESS(21) -> 18446744073709551614
unknown(257) -> 3
READ(0) -> 832
FSTAT(5) -> 0
MMAP(9) -> 140694657208320
MMAP(9) -> 140694650953728
MPROTECT(10) -> 0
MMAP(9) -> 140694655045632
MMAP(9) -> 140694655070208
CLOSE(3) -> 0
unknown(158) -> 0
MPROTECT(10) -> 0
MPROTECT(10) -> 0
MPROTECT(10) -> 0
MUNMAP(11) -> 0
BRK(12) -> 94744690540544
BRK(12) -> 94744690675712
unknown(257) -> 3
FSTAT(5) -> 0
MMAP(9) -> 140694646390784
CLOSE(3) -> 0
FSTAT(5) -> 0
IOCTL(16) -> 18446744073709551591
WRITE(1) -> 5
CLOSE(3) -> 0
CLOSE(3) -> 0
unknown(231)
Tracee terminated
Piglet Trace แแแแแชแแแแก Linux-แแก แแกแแแแ แกแแกแขแแแฃแ แแแ แก (แแฎ.
แแแแแ แจแแแฎแแแแ แฉแแแแ แแแแแแก แแฃแจแแแแแก. Linux-แแก แจแแแแฎแแแแแจแ, debugger แแ tracers แแงแแแแแแ, แ แแแแ แช แแแแแ แแฆแแแแจแแ, ptrace แกแแกแขแแแแก แแแ แก. แแก แแฃแจแแแแก แแแ แแแ แแ แแฃแแแแขแจแ แแ แซแแแแแแก แแแแแขแแคแแแแขแแ แแแแก แแแแแชแแแแ, แ แแแแแแแแแ แแฎแแแแ แฉแแแ แแแญแแ แแแแ PTRACE_TRACEME
, PTRACE_SYSCALL
ะธ PTRACE_GETREGS
.
แขแ แแกแแ แ แแฌแงแแแ แฉแแแฃแแแแ แแแ Unix แกแขแแแแ: fork(2)
แแฌแงแแแก แแแแจแแแก แแ แแชแแกแก, แ แแแแแแช แแแแแก แแฎแ แแ แแงแแแแแก exec(3)
แแฌแงแแแก แจแแกแแกแฌแแแ แแ แแแ แแแแก. แแ แแแแแ แแ แแแฎแแแฌแแแแแ แแฅ แแ แแก แแแแแฌแแแแ ptrace(PTRACE_TRACEME)
แแแ แ exec
: แแแแจแแแก แแ แแชแแกแ แแแแแแก, แ แแ แแจแแแแแ แแ แแชแแกแ แแแแแขแ แแแแแก แแแก:
pid_t child_pid = fork();
switch (child_pid) {
case -1:
err(EXIT_FAILURE, "fork");
case 0:
/* Child here */
/* A traced mode has to be enabled. A parent will have to wait(2) for it
* to happen. */
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
/* Replace itself with a program to be run. */
execvp(argv[1], argv + 1);
err(EXIT_FAILURE, "exec");
}
แแจแแแแแ แแ แแชแแกแ แแฎแแ แฃแแแ แแแแแแซแแฎแแก wait(2)
แแแแจแแแก แแ แแชแแกแจแ, แแแฃ แแแ แฌแแฃแแแแ, แ แแ แแแฎแแ แแแแแแก แ แแแแแจแ แแแแแกแแแ:
/* Parent */
/* First we wait for the child to set the traced mode (see
* ptrace(PTRACE_TRACEME) above) */
if (waitpid(child_pid, NULL, 0) == -1)
err(EXIT_FAILURE, "traceme -> waitpid");
แแ แแขแแแแ, แแแแแแแ แแแกแ แฃแแแแฃแแแ แแ แจแแแแซแแแแ แแแ แแแแแ แแแแแ แซแแแแ แกแแกแขแแแแก แแแ แแแแก แแแแแงแฃแ แแก แแแแแแแ แแแฃแแแแแแแ แชแแแแจแ.
แแแแแฌแแแแ ptrace(PTRACE_SYSCALL)
แแแ แแแขแแแก แแซแแแแ, แ แแ แจแแแแแแแจแ wait
แแจแแแแแ แแแกแ แฃแแแแแ แแ แกแแกแขแแแฃแ แ แแแ แแก แจแแกแ แฃแแแแแแแ แแ แแแกแ แแแกแ แฃแแแแแกแแแแแแ. แแ แแแ แก แจแแ แแก แจแแแแซแแแแ แจแแแกแ แฃแแแ แแแแแกแแแแ แ แแแฅแแแแแแ: แจแแชแแแแแ แแแ แ แแแขแแ แแแขแแฃแแแ, แจแแชแแแแแ แแ แแฃแแแแขแแแ แแ แแแแ แฃแแแแแก แแแแจแแแแแแแ.
แฉแแแ แฃแแ แแแแ แฃแแแ แแแแแแแซแแฎแแ แแ แซแแแแแ แแ แฏแแ ptrace(PTRACE_GETREGS)
แ แแแกแขแ แแก แแแแแแแ แแแแแก แแแกแแฆแแแแ rax
แแแ แแแแ (แกแแกแขแแแฃแ แ แแแ แแก แแแแแ แ) แแ แแแจแแแแ (แแแแ แฃแแแแแก แฆแแ แแแฃแแแแ).
แกแแแแแแแแแแจแ, แชแแแแ:
/* A system call tracing loop, one interation per call. */
for (;;) {
/* A non-portable structure defined for ptrace/GDB/strace usage mostly.
* It allows to conveniently dump and access register state using
* ptrace. */
struct user_regs_struct registers;
/* Enter syscall: continue execution until the next system call
* beginning. Stop right before syscall.
*
* It's possible to change the system call number, system call
* arguments, return value or even avoid executing the system call
* completely. */
if (ptrace(PTRACE_SYSCALL, child_pid, NULL, NULL) == -1)
err(EXIT_FAILURE, "enter_syscall");
if (waitpid(child_pid, NULL, 0) == -1)
err(EXIT_FAILURE, "enter_syscall -> waitpid");
/* According to the x86-64 system call convention on Linux (see man 2
* syscall) the number identifying a syscall should be put into the rax
* general purpose register, with the rest of the arguments residing in
* other general purpose registers (rdi,rsi, rdx, r10, r8, r9). */
if (ptrace(PTRACE_GETREGS, child_pid, NULL, ®isters) == -1)
err(EXIT_FAILURE, "enter_syscall -> getregs");
/* Note how orig_rax is used here. That's because on x86-64 rax is used
* both for executing a syscall, and returning a value from it. To
* differentiate between the cases both rax and orig_rax are updated on
* syscall entry/exit, and only rax is updated on exit. */
print_syscall_enter(registers.orig_rax);
/* Exit syscall: execute of the syscall, and stop on system
* call exit.
*
* More system call tinkering possible: change the return value, record
* time it took to finish the system call, etc. */
if (ptrace(PTRACE_SYSCALL, child_pid, NULL, NULL) == -1)
err(EXIT_FAILURE, "exit_syscall");
if (waitpid(child_pid, NULL, 0) == -1)
err(EXIT_FAILURE, "exit_syscall -> waitpid");
/* Retrieve register state again as we want to inspect system call
* return value. */
if (ptrace(PTRACE_GETREGS, child_pid, NULL, ®isters) == -1) {
/* ESRCH is returned when a child terminates using a syscall and no
* return value is possible, e.g. as a result of exit(2). */
if (errno == ESRCH) {
fprintf(stderr, "nTracee terminatedn");
break;
}
err(EXIT_FAILURE, "exit_syscall -> getregs");
}
/* Done with this system call, let the next iteration handle the next
* one */
print_syscall_exit(registers.rax);
}
แแก แแ แแก แแแแแ แขแ แแกแแ แ. แแฎแแ แแฅแแแ แแชแแ, แกแแ แฃแแแ แแแแฌแงแแ แจแแแแแแ แแแ แขแแ แแแ
แกแแคแฃแซแแแแแ: แแแจแแแแฃแแ แแ แแแ แแแแก แแแจแแแแ strace
แ แแแแ แช แแแ แแแแ แแแแแงแแแแแแก แจแแแแฎแแแแ strace
, แแแแแ แฆแแ แก แฃแแแ แขแแแแกแ แฎแแ แฎแแก แแแแแแแแ - แแแแแแแชแแแก แแแจแแแแ strace
.
แแแแกแแแแแก, แ แแ แแ แฉแแแฃแฆแ แแแแแแ แขแแแแฃแ แ แแ แแแ แแแแก แแแ แแแแก แแแฃแแแแแแแ แฉแแแแแแแแแแก, แฉแแแ แแฌแแ แ write
:
int main(int argc, char *argv[])
{
char str[] = "write me to stdoutn";
/* write(2) is a simple wrapper around a syscall so it should be easy to
* find in the syscall trace. */
if (sizeof(str) != write(STDOUT_FILENO, str, sizeof(str))){
perror("write");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
แแแแแ แแแแจแแแแ แแ แแแ แแแ แแ แแแแ แฌแแฃแแแแ, แ แแ แแก แแฃแจแแแแก:
$ gcc examples/write-simple.c -o write-simple
$ ./write-simple
write me to stdout
แแ แแแแแก, แแแแแ แแแแฃแจแแแ แแแ แกแขแ แแแก แแแแขแ แแแแก แฅแแแจ:
$ strace ./write-simple
pexecve("./write", ["./write"], 0x7ffebd6145b0 /* 71 vars */) = 0
brk(NULL) = 0x55ff5489e000
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)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=197410, ...}) = 0
mmap(NULL, 197410, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f7a2a633000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "177ELF21133>1260342"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2030544, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f7a2a631000
mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f7a2a04c000
mprotect(0x7f7a2a233000, 2097152, PROT_NONE) = 0
mmap(0x7f7a2a433000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f7a2a433000
mmap(0x7f7a2a439000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f7a2a439000
close(3) = 0
arch_prctl(ARCH_SET_FS, 0x7f7a2a6324c0) = 0
mprotect(0x7f7a2a433000, 16384, PROT_READ) = 0
mprotect(0x55ff52b52000, 4096, PROT_READ) = 0
mprotect(0x7f7a2a664000, 4096, PROT_READ) = 0
munmap(0x7f7a2a633000, 197410) = 0
write(1, "write me to stdoutn", 20write me to stdout
) = 20
exit_group(0) = ?
แซแแแแแ "แกแแขแงแแแแ แ" แแ แแ แช แแกแ แกแแแแแแแแแแแแแแ. แแฅ แแ แ แแ แแแแแแแ: แแ แแแ แแแแก แแแแแแแแแแ แจแแ แแฃแแแ แแแแแกแแแแแแแ strace
แแ แกแแกแขแแแฃแ แ แแแ แแแแก แกแแแ แแแแ, แ แแแแแแแช แแ แแแแแแขแแ แแกแแแก.
แแฅแแแ แจแแแแซแแแแ แแแแแกแฎแแแแแ แแ แแแ แแแแก แกแขแแแแแ แขแฃแแ แแแแแแแแแแ แแแแแแ แแ strace แจแแชแแแแแก แแแแแแแแแแ -o แแแแแแ แแแแแแก แแแแแงแแแแแแ, แ แแแแแแช แแแแแแแกแแแแ แแแแก แกแแกแขแแแแก แแแ แแแแก แกแแแก แแ แแฃแแแแขแแก แคแแแแแ.
แ แฉแแแ "แแแแแขแแแแแ" แแแ แแแแก แแ แแแแแแแกแแแ แแแแแแแแแแ. แแแแฃแจแแแ, แ แแ แฉแแแ แแฎแแแแ แแแ แแแ แแแแแแขแแ แแกแแแก write
. แฒแแกแแฆแแแ -e
แกแแจแฃแแแแแแก แแแซแแแแ แแแฃแแแแแ แแแแแแแแฅแแแแแแ, แ แแแแแแแแแช แกแแกแขแแแแก แแแ แแแ แแคแแแขแ แแแ. แงแแแแแแ แแแแฃแแแ แฃแแ แแแ แแแแก แแแ แแแแขแ, แแฃแแแแ แแแแ, แแ แแก, trace=*
, แ แแแแแแแช แจแแแแซแแแแ แแแขแแแแ แแฎแแแแ แฉแแแแแแแก แกแแแแขแแ แแกแ แแแ แแแ.
แ แแแแกแแช แแแแแแงแแแแแ แแ แแแ แแฃแแแ -o
ะธ -e
แฉแแแ แแแแแฆแแแ:
$ strace -e trace=write -owrite-simple.log ./write-simple
write me to stdout
$ cat write-simple.log
write(1, "write me to stdoutn", 20
) = 20
+++ exited with 0 +++
แแกแ แ แแ, แฎแแแแแ, แฌแแแแแฎแแ แแแแ แแ แฃแคแ แ แแแแแแแ.
แแฅแแแ แแกแแแ แจแแแแซแแแแ แฌแแจแแแแ แกแแกแขแแแฃแ แ แแแ แแแ, แแแแแแแแแ, แแแฎแกแแแ แแแแก แแแแแฌแแแแแแกแแแ แแ แแแแแแแแกแฃแคแแแแแกแแแ:
$ strace -e trace=!brk,mmap,mprotect,munmap -owrite-simple.log ./write-simple
write me to stdout
$ cat write-simple.log
execve("./write-simple", ["./write-simple"], 0x7ffe9972a498 /* 69 vars */) = 0
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)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=124066, ...}) = 0
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "177ELF21133>1260342"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2030544, ...}) = 0
close(3) = 0
arch_prctl(ARCH_SET_FS, 0x7f00f0be74c0) = 0
write(1, "write me to stdoutn", 20) = 20
exit_group(0) = ?
+++ exited with 0 +++
แแแแแแแแแกแฌแแแแ แแแแแ แแชแฎแฃแแ แซแแฎแแแแก แแแจแแแ แแแแแ แแชแฎแฃแแ แแแ แแแแก แกแแแจแ: แแแแก แแแแแฎแแแก แแ แซแแแแแแก แแแ แกแ. shell).
glibc-แแก แฉแแแก แแแ แกแแแจแ แกแแกแขแแแฃแ แ แแแ แ แฌแงแแแขแก แแ แแชแแกแก exit_group
, แแ แ แขแ แแแแชแแฃแแ _exit
. แแก แแ แแก แกแแกแขแแแฃแ แแแ แแแแแ แแฃแจแแแแแก แกแแ แแฃแแ: แแแขแแ แคแแแกแ, แ แแแแแแแช แแฃแจแแแแก แแ แแแ แแแแกแขแ, แแแ แแแแแ แแ แแ แแก แแแแแแจแแ แแแฃแแ แกแแกแขแแแฃแ แแแ แแแแแ. แฃแคแ แ แแแขแแช, แแก แ แแแฃแแแ แฃแแแ แแชแแแแแ แแแแฎแแ แชแแแแแแแกแ แแ แแแแขแคแแ แแแก แแแฎแแแแแ.
แกแแคแฃแซแแแแแ: แแ แแชแแกแแก แแแฌแแแ แแแแแแ
แแแแแแแแ แแแแแ, ptrace แกแแกแขแแแแก แแแ แ, แ แแแแแแแช แแก แแจแแแแ strace
, แจแแแซแแแแ แแแแแงแแแแแฃแ แแฅแแแก แแฎแแแแ แแ แแแ แแแแก แกแแแชแแแแฃแ แ แแแแแจแ แแแจแแแแแกแแก. แแก แจแแแฆแฃแแแ แจแแแซแแแแ แแแแแแ แฃแแแ แแฆแแ แแ Unix 6 แแแ แกแแแก แแฆแแแแจแ. แแฆแแกแแฆแแแแแ แแก แกแแแแแ แแกแ แแฆแแ แแ แแก: แฎแแแแแฎแแ แกแแญแแ แแ แกแแแฃแจแแ แแ แแแ แแแแก แแ แแแแแแแแแก แแแแแแแแแแ. แขแแแแฃแ แ แแแแแแแแแ แกแแฎแแแฃแ แแ แแแแแแแแแ แแ แแชแแกแ แแ แซแแแ. แแแแขแแ แแแแแแแแ แแแ strace
แจแแฃแซแแแ แจแแฃแแ แแแแก แแ แแชแแกแแแก แคแ แแแแก แแ แแก.
แแแงแแแแแก แแแแแแแแ
int main(int argc, char *argv[])
{
(void) argc; (void) argv;
char str[] = "write men";
write(STDOUT_FILENO, str, sizeof(str));
/* Sleep indefinitely or until a signal arrives */
pause();
write(STDOUT_FILENO, str, sizeof(str));
return EXIT_SUCCESS;
}
แแแแแ แแแแจแแแแ แแ แแแ แแแ แแ แแแแ แฌแแฃแแแแ, แ แแ แแก แแแงแแแฃแแแ:
$ gcc examples/write-sleep.c -o write-sleep
$ ./write-sleep
./write-sleep
write me
^C
$
แแฎแแ แแชแแแแ แแแกแจแ แจแแแ แแแแ:
$ ./write-sleep &
[1] 15329
write me
$ strace -p 15329
strace: Process 15329 attached
pause(
^Cstrace: Process 15329 detached
<detached ...>
แแ แแแ แแแ แแแแแแแแแแ แแแ แแ pause
. แแแแฎแแ, แ แแแแ แ แแแแแ แแแก แแแ แกแแแแแแแแแ:
$ strace -o write-sleep.log -p 15329 &
strace: Process 15329 attached
$
$ kill -CONT 15329
$ cat write-sleep.log
pause() = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGCONT {si_signo=SIGCONT, si_code=SI_USER, si_pid=14989, si_uid=1001} ---
pause(
$
$ kill -TERM 15329
$ cat write-sleep.log
pause() = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGCONT {si_signo=SIGCONT, si_code=SI_USER, si_pid=14989, si_uid=1001} ---
pause() = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=14989, si_uid=1001} ---
+++ killed by SIGTERM +++
แฉแแแ แแแแฃแจแแแ แแแงแแแฃแแ แแ แแแ แแแ แแ แจแแแฃแแ แแแแ แแแก แแแแแงแแแแแแ strace
. แแ แ แ แแ แชแฎแแแ แแแฎแแ: แแแฃแแแก แกแแกแขแแแฃแ แ แแแ แ แฃแแฃแแแแแแงแแคแก แกแแแแแแแแก แแแแแฃแจแแแแแแแแแก แแแ แแจแ แแ แ แแช แฃแคแ แ แกแแแแขแแ แแกแแ, แกแขแ แแแกแ แแแแแขแแ แแ แแแก แแ แ แแฎแแแแ แกแแกแขแแแฃแ แแแ แแแก, แแ แแแแ แจแแแแแแแแ แกแแแแแแแแกแแช.
แแแแแแแแ: แแแแจแแแก แแ แแชแแกแแแแก แแแแแงแฃแ แแก แแแแแแแ
แแ แแชแแกแแแแแ แแฃแจแแแแ แแแ แแก แกแแจแฃแแแแแแ fork
- แงแแแแ แฃแแแฅแกแแก แกแแคแฃแซแแแแ. แแแแฎแแ, แ แแแแ แแฃแจแแแแก แกแขแ แแกแ แแ แแชแแกแแก แฎแแกแแแ แแแ แขแแแ โแแแแ แแแแแแแกโ แแแแแแแแแก แแแแแงแแแแแแ.
int main(int argc, char *argv[])
{
pid_t parent_pid = getpid();
pid_t child_pid = fork();
if (child_pid == 0) {
/* A child is born! */
child_pid = getpid();
/* In the end of the day printf is just a call to write(2). */
printf("child (self=%d)n", child_pid);
exit(EXIT_SUCCESS);
}
printf("parent (self=%d, child=%d)n", parent_pid, child_pid);
wait(NULL);
exit(EXIT_SUCCESS);
}
แแฅ แแ แแแแแแแฃแ แ แแ แแชแแกแ แฅแแแแก แแแแจแแแก แแ แแชแแกแก, แแ แแแ แแฌแแ แแแ แกแขแแแแแ แขแฃแ แแแแแแแแแแแ:
$ gcc examples/fork-write.c -o fork-write
$ ./fork-write
parent (self=11274, child=11275)
child (self=11275)
แแแแฃแแแกแฎแแแแแ, แฉแแแ แแแฎแแแแแ แแฎแแแแ แกแแกแขแแแฃแ แแแ แแแก แกแแฌแงแแกแ แแ แแชแแกแแแแ:
$ strace -e trace=write -ofork-write.log ./fork-write
child (self=22049)
parent (self=22048, child=22049)
$ cat fork-write.log
write(1, "parent (self=22048, child=22049)"..., 33) = 33
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=22049, si_uid=1001, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++
แแ แแจแ แแแฎแแแ แแแแ แแแแแขแ แแแแ แแแแแ แแ แแชแแกแแก แฎแ -f
แ แแแแแแแแแช strace
แแแแแขแ แแแแแก แกแแกแขแแแฃแ แแแ แแแก แแแแจแแแก แแ แแชแแกแแแจแ. แแก แแแแขแแแ แแแแแแแแแแ แแแแแแฃแ แฎแแแก pid
แแ แแชแแกแ, แ แแแแแแช แฅแแแแก แกแแกแขแแแแก แแแแแแแแแแก:
$ strace -f -e trace=write -ofork-write.log ./fork-write
parent (self=22710, child=22711)
child (self=22711)
$ cat fork-write.log
22710 write(1, "parent (self=22710, child=22711)"..., 33) = 33
22711 write(1, "child (self=22711)n", 19) = 19
22711 +++ exited with 0 +++
22710 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=22711, si_uid=1001, si_status=0, si_utime=0, si_stime=0} ---
22710 +++ exited with 0 +++
แแ แแแแขแแฅแกแขแจแ, แกแแกแขแแแฃแ แ แแแ แแแแก แฏแแฃแคแแก แแแฎแแแแแ แคแแแขแ แแชแแ แจแแแซแแแแ แกแแกแแ แแแแแ แแงแแก:
$ strace -f -e trace=%process -ofork-write.log ./fork-write
parent (self=23610, child=23611)
child (self=23611)
$ cat fork-write.log
23610 execve("./fork-write", ["./fork-write"], 0x7fff696ff720 /* 63 vars */) = 0
23610 arch_prctl(ARCH_SET_FS, 0x7f3d03ba44c0) = 0
23610 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f3d03ba4790) = 23611
23610 wait4(-1, <unfinished ...>
23611 exit_group(0) = ?
23611 +++ exited with 0 +++
23610 <... wait4 resumed> NULL, 0, NULL) = 23611
23610 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=23611, si_uid=1001, si_status=0, si_utime=0, si_stime=0} ---
23610 exit_group(0) = ?
23610 +++ exited with 0 +++
แกแฎแแแแ แจแแ แแก, แ แ แกแแกแขแแแฃแ แ แแแ แ แแแแแแงแแแแแ แแฎแแแ แแ แแชแแกแแก แจแแกแแฅแแแแแแ?
แแแแแแแแ: แคแแแแแก แแแแแแแแ แกแแฎแแแฃแ แแแแก แแแชแแแแ
แคแแแแแก แแฆแฌแแ แแก แชแแแแ, แ แ แแฅแแ แฃแแแ, แกแแกแแ แแแแแแ, แแแแ แแ แแแแแ แแขแฃแแ แคแแแแแแแก แกแแฎแแแแแ, แ แแแแแแแแช แแ แแแ แแแ แฌแแแแแ, แแกแแแ แจแแแซแแแแ แแแแแแแแแแ.
แจแแแแแแ
void do_write(int out_fd)
{
char str[] = "write me to a filen";
if (sizeof(str) != write(out_fd, str, sizeof(str))){
perror("write");
exit(EXIT_FAILURE);
}
}
int main(int argc, char *argv[])
{
char tmp_filename_template[] = "/tmp/output_fileXXXXXX";
int out_fd = mkstemp(tmp_filename_template);
if (out_fd == -1) {
perror("mkstemp");
exit(EXIT_FAILURE);
}
do_write(out_fd);
return EXIT_SUCCESS;
}
แฉแแแฃแแแแ แแแ แแแ แแก แแ แแก strace
แแฉแแแแแแก แกแแกแขแแแฃแ แแแ แแ แแแแแชแแแฃแแ แแฆแฌแแ แแก แแแแ แแก แแแแจแแแแแแแแก:
$ strace -e trace=write -o write-tmp-file.log ./write-tmp-file
$ cat write-tmp-file.log
write(3, "write me to a filen", 20) = 20
+++ exited with 0 +++
แแ แแจแแ -y
แแ แแแ แแแ แแฉแแแแแแก แคแแแแแก แแแแก, แ แแแแแกแแช แจแแแกแแแแแแแ แแฆแฌแแ แแแแแ:
$ strace -y -e trace=write -o write-tmp-file.log ./write-tmp-file
$ cat write-tmp-file.log
write(3</tmp/output_fileCf5MyW>, "write me to a filen", 20) = 20
+++ exited with 0 +++
แแแแแแแแ: แคแแแแแ แฌแแแแแแก แแแแแงแฃแ แแก แแแแแแแ
แแแแแ แแ แแ แกแแกแแ แแแแแ แคแฃแแฅแชแแ: แแฉแแแแแ แแฎแแแแ แกแแกแขแแแฃแ แ แแแ แแแ, แ แแแแแแแช แแแแแแจแแ แแแฃแแแ แแแแแ แแขแฃแ แคแแแแแแ. แจแแแแแแ
void do_write(int out_fd)
{
char str[] = "write me to a filen";
if (sizeof(str) != write(out_fd, str, sizeof(str))){
perror("write");
exit(EXIT_FAILURE);
}
}
int main(int argc, char *argv[])
{
/*
* Path will be provided by the first program argument.
* */
const char *path = argv[1];
/*
* Open an existing file for writing in append mode.
* */
int out_fd = open(path, O_APPEND | O_WRONLY);
if (out_fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
do_write(out_fd);
return EXIT_SUCCESS;
}
แกแขแแแแแ แขแฃแแแ strace
แแฉแแแแแแก แฃแแแ แแ แแ แแกแแญแแ แ แแแคแแ แแแชแแแก. แแ แแจแ -P
แแ แแฃแแแแขแแ แแฌแแแแก strace แแแแแญแแแก แแฎแแแแ แแแ แแแ แแแแแแแแฃแ แคแแแแแ:
$ strace -y -P/tmp/test_file.log -o write-file.log ./write-file /tmp/test_file.log
$ cat write-file.log
openat(AT_FDCWD, "/tmp/test_file.log", O_WRONLY|O_APPEND) = 3</tmp/test_file.log>
write(3</tmp/test_file.log>, "write me to a filen", 20) = 20
+++ exited with 0 +++
แแแแแแแแ: แแ แแแแแซแแแแแแ แแ แแแ แแแแแ
แแแแฃแแแแฃแ แ strace
แแกแแแ แจแแฃแซแแแ แแแแฎแแแ แแก แแ แแแแแซแแคแแแแแแ แแฃแจแแแแแกแแก
void *thread(void *arg)
{
(void) arg;
printf("Secondary thread: workingn");
sleep(1);
printf("Secondary thread: donen");
return NULL;
}
int main(int argc, char *argv[])
{
printf("Initial thread: launching a threadn");
pthread_t thr;
if (0 != pthread_create(&thr, NULL, thread, NULL)) {
fprintf(stderr, "Initial thread: failed to create a thread");
exit(EXIT_FAILURE);
}
printf("Initial thread: joining a threadn");
if (0 != pthread_join(thr, NULL)) {
fprintf(stderr, "Initial thread: failed to join a thread");
exit(EXIT_FAILURE);
};
printf("Initial thread: done");
exit(EXIT_SUCCESS);
}
แแฃแแแแ แแแแ, แแก แฃแแแ แแงแแก แจแแแแแแแแ แแแแแแ แแกแแแแ แกแแแชแแแแฃแ แ แแแกแแแแแแแ - -pthread flag:
$ gcc examples/thread-write.c -pthread -o thread-write
$ ./thread-write
/thread-write
Initial thread: launching a thread
Initial thread: joining a thread
Secondary thread: working
Secondary thread: done
Initial thread: done
$
แแ แแจแ -f
, แ แแแแ แช แ แแแฃแแแ แฃแแ แแ แแชแแกแแแแก แจแแแแฎแแแแแจแ, แแแแแแขแแแก แแ แแชแแกแแก แแแแก แแแแแแฃแแ แฎแแแแก แแแกแแฌแงแแกแจแ.
แแฃแแแแ แแแแ, แฉแแแ แแ แแกแแฃแแ แแแ แซแแคแแแแก แแแแแขแแคแแแแขแแ แแ POSIX Threads แกแขแแแแแ แขแแก แแแแฎแแ แชแแแแแแแก แแแแแแแ, แแ แแแแ แแแแฃแฅแกแแก แแแแแแแแแแแก แแแแแแแแแแก แแแแ แแแแแงแแแแแฃแแ แ แแชแฎแแแ. แแ แฃแแแแแกแแแแแแก แแแแแกแแแ แแกแแ, แแ แแ แกแแแแแก แแ แแชแแกแแแ แแ แซแแคแแแ - แแ แแก แแแแแแแแแแ, แ แแแแแแแช แฃแแแ แแแแแแแฌแแแแแก แแแแ แแขแแก แแ แกแแแฃแ แแแ แแแแแก แจแแ แแก.
แแ แแแแ แซแแคแจแ แแฃแจแแแแแกแแก, แกแแกแขแแแฃแ แ แแแ แแแ แซแแแแแ แแแแ แ แฎแแแแ:
$ strace -f -othread-write.log ./thread-write
$ wc -l thread-write.log
60 thread-write.log
แแแ แ แแฅแแก แจแแแแแคแแ แแแแ แแฎแแแแ แแ แแชแแกแแก แแแแแฏแแแแขแแ แแ แกแแกแขแแแฃแ แ แแแ แแแแ write
:
$ strace -f -e trace="%process,write" -othread-write.log ./thread-write
$ cat thread-write.log
18211 execve("./thread-write", ["./thread-write"], 0x7ffc6b8d58f0 /* 64 vars */) = 0
18211 arch_prctl(ARCH_SET_FS, 0x7f38ea3b7740) = 0
18211 write(1, "Initial thread: launching a thre"..., 35) = 35
18211 clone(child_stack=0x7f38e9ba2fb0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7f38e9ba39d0, tls=0x7f38e9ba3700, child_tidptr=0x7f38e9ba39d0) = 18212
18211 write(1, "Initial thread: joining a thread"..., 33) = 33
18212 write(1, "Secondary thread: workingn", 26) = 26
18212 write(1, "Secondary thread: donen", 23) = 23
18212 exit(0) = ?
18212 +++ exited with 0 +++
18211 write(1, "Initial thread: done", 20) = 20
18211 exit_group(0) = ?
18211 +++ exited with 0 +++
แกแฎแแแแ แจแแ แแก, แแแแฎแแแแ. แ แ แกแแกแขแแแฃแ แ แแแ แ แแแแแแงแแแแแ แแฎแแแ แแแแแก แจแแกแแฅแแแแแแ? แ แแ แแแแกแฎแแแแแแแ แแก แซแแคแแแแก แแแฌแแแแแ แแ แแชแแกแแแแก แแแฌแแแแแแกแแแ?
แแแกแขแแ แแแแกแ: แแแแฃแจแแแแแแก แแแกแขแ แกแแกแขแแแฃแ แ แแแ แแก แแ แแก
แแ แ-แแ แแ แชแแขแ แฎแแแก แฌแแ แแแแแฉแแแ strace
แจแแกแแซแแแแแแแแแ - แกแแกแขแแแฃแ แ แแแ แแก แแ แแก แคแฃแแฅแชแแแก แแแ แแแแก แแแกแขแแก แฉแแแแแแ. แแแ แขแแแ
void do_write(void)
{
char str[] = "write me to stdoutn";
if (sizeof(str) != write(STDOUT_FILENO, str, sizeof(str))){
perror("write");
exit(EXIT_FAILURE);
}
}
int main(int argc, char *argv[])
{
do_write();
return EXIT_SUCCESS;
}
แแฃแแแแ แแแแ, แแ แแแ แแแแก แแแแแแแแแแ แฎแแแแ แซแแแแแ แแแชแฃแแแแแแ แแ, แแแ แแ แแ แแจแแกแ -k
(แแแ แแก แกแขแแแแก แฉแแแแแแ), แแแ แ แแฅแแก แกแแกแขแแแแก แแแ แแแแก แแแคแแแขแแ แแก แกแแฎแแแแ:
$ gcc examples/write-simple.c -o write-simple
$ strace -k -e trace=write -o write-simple.log ./write-simple
write me to stdout
$ cat write-simple.log
write(1, "write me to stdoutn", 20) = 20
> /lib/x86_64-linux-gnu/libc-2.27.so(__write+0x14) [0x110154]
> /home/vkazanov/projects-my/strace-post/write-simple(do_write+0x50) [0x78a]
> /home/vkazanov/projects-my/strace-post/write-simple(main+0x14) [0x7d1]
> /lib/x86_64-linux-gnu/libc-2.27.so(__libc_start_main+0xe7) [0x21b97]
> /home/vkazanov/projects-my/strace-post/write-simple(_start+0x2a) [0x65a]
+++ exited with 0 +++
แแแกแขแแ แแแแกแ: แจแแชแแแแแก แแแแฅแชแแ
แแ แแแแแ แแ แแ แแฎแแแ แแ แซแแแแแ แกแแกแแ แแแแแ แคแฃแแฅแชแแ: แจแแชแแแแแก แแแแฅแชแแ. แฒแฅ
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
void do_write(const char *str, ssize_t len)
{
if (len != write(STDOUT_FILENO, str, (size_t)len)){
perror("write");
exit(EXIT_FAILURE);
}
}
int main(int argc, char *argv[])
{
(void) argc; (void) argv;
char str1[] = "write me 1n";
do_write(str1, sizeof(str1));
char str2[] = "write me 2n";
do_write(str2, sizeof(str2));
return EXIT_SUCCESS;
}
แแแแแ แแแแงแแแ แแ แแแ แฉแแฌแแ แแก แแแ แก:
$ gcc examples/write-twice.c -o write-twice
$ ./write-twice
write me 1
write me 2
$ strace -e trace=write -owrite-twice.log ./write-twice
write me 1
write me 2
$ cat write-twice.log
write(1, "write me 1n", 12) = 12
write(1, "write me 2n", 12) = 12
+++ exited with 0 +++
แแฎแแ แฉแแแ แแแงแแแแแ แแแแแแแแฅแแแแก inject
แจแแชแแแแแก แฉแแกแแ EBADF
แงแแแแ แแแ แจแ แฉแแฌแแ แแ:
$ strace -e trace=write -e inject=write:error=EBADF -owrite-twice.log ./write-twice
$ cat write-twice.log
write(1, "write me 1n", 12) = -1 EBADF (Bad file descriptor) (INJECTED)
write(3, "write: Bad file descriptorn", 27) = -1 EBADF (Bad file descriptor) (INJECTED)
+++ exited with 1 +++
แกแแแแขแแ แแกแแ แ แ แจแแชแแแแแแ แแแแ แฃแแแ แงแแแแ แแแแแฌแแแแแแ write
, แแแ แจแแ แแก แแแ แ, แ แแแแแแช แแแแแแแ แขแแ แแ แแก แแแฆแแ. แแแ แ แแฅแแก แจแแชแแแแแก แแแแ แฃแแแแแก แแฎแแแแ แแแ แแแแ แแแ แแกแแแแก:
$ strace -e trace=write -e inject=write:error=EBADF:when=1 -owrite-twice.log ./write-twice
write: Bad file descriptor
$ cat write-twice.log
write(1, "write me 1n", 12) = -1 EBADF (Bad file descriptor) (INJECTED)
write(3, "write: Bad file descriptorn", 27) = 27
+++ exited with 1 +++
แแ แแแแ แ:
$ strace -e trace=write -e inject=write:error=EBADF:when=2 -owrite-twice.log ./write-twice
write me 1
write: Bad file descriptor
$ cat write-twice.log
write(1, "write me 1n", 12) = 12
write(1, "write me 2n", 12) = -1 EBADF (Bad file descriptor) (INJECTED)
write(3, "write: Bad file descriptorn", 27) = 27
+++ exited with 1 +++
แแ แแ แแก แกแแญแแ แ แจแแชแแแแแก แขแแแแก แแแแแแแแ:
$ strace -e trace=write -e fault=write:when=1 -owrite-twice.log ./write-twice
$ cat write-twice.log
write(1, "write me 1n", 12) = -1 ENOSYS (Function not implemented) (INJECTED)
write(3, "write: Function not implementedn", 32) = 32
+++ exited with 1 +++
แกแฎแแ แแ แแจแแแแแ แแ แแแ, แจแแแแซแแแแ โแแแขแแฎแแโ แฌแแแแแ แแแแแ แแขแฃแ แคแแแแแ. แแแแแแแแ:
$ strace -y -P/tmp/test_file.log -e inject=file:error=ENOENT -o write-file.log ./write-file /tmp/test_file.log
open: No such file or directory
$ cat write-file.log
openat(AT_FDCWD, "/tmp/test_file.log", O_WRONLY|O_APPEND) = -1 ENOENT (No such file or directory) (INJECTED)
+++ exited with 1 +++
แจแแชแแแแแก แแแแฅแชแแแก แแแ แแ,
Afterword
แแแแฃแแแแฃแ แ strace
- แแแ แขแแแ แแ แกแแแแแแ แแแกแขแ แฃแแแแขแ. แแแแ แแ แกแแกแขแแแฃแ แ แแแ แแแแก แแแ แแ, แแ แแแ แแแแแแกแ แแ แแแแ แแชแแฃแแ แกแแกแขแแแแก แแฃแจแแแแแก แกแฎแแ แแกแแแฅแขแแแ แจแแแซแแแแ แแแแแ แแฃแแ แแงแแก. แแแแแแแแแ, แแแก แจแแฃแซแแแ แแแแแงแฃแ แ แแแแแแแก แแแ แแแก แแแแแแแฃแ แแ แแแแแแจแแ แแแฃแ แแแแแแแแแแแแจแ. strace
- แแแแแแชแแแก แแแ แแแแ แฎแแแ แกแแแฃแแแ แแ แกแฎแแแก แแ แแแ แแแแแแแ แแแแแแจแแ แแแแ แแ แแแแแแแแแก แจแแแแฎแแแแแจแ แแ แแแแ แแจแ แแ แฏแแ แแแแแช แแแงแแแแ.
แแแแแแ, แแฃ แแแงแแแ แ Unix, แฌแแแแแแฎแแ man 1 strace
แแ แแแแแกแฃแคแแแ แแแแแฎแแแแ แแฅแแแแก แแ แแแ แแแแแก!
แฌแงแแ แ: www.habr.com