Данный раздел посвящён трассировке системных вызовов и работе работе с утилитой Strace. Также будет рассмотрен алгоритм предзагрузки библиотек
Общая теория
Разберёмся, что из себя вообще представляет трассировка. В разных областях это понятие может интерпретироваться по-разному.
Трассировка кода, а именно ей мы будем заниматься глобально — это метод тестирования программного обеспечения, при котором отслеживается исполнение инструкций программы, а также состояния переменных и объектов во время её работы. По сути, это тот механизм, который помогает нам отлаживать программу, выявлять проблемы, просматривать непосредственное исполнение кода. Подробнее про отладку в целом можно почитать тут. Трассировка системных вызовов, как ясно из названия, отслеживает выполнение обращений программы к ОС, параметры вызовов и результат их выполнения.
Strace
Проект Strace — один из классических инструментов Linux, который позволяет трассировать системные вызовы. При этом сама утилита тоже работает за счёт системных вызовов, а, точнее, с системным вызовом ptrace. Основная задача strace — это отслеживание возвращаемых значений от ptrace и их обработка в соответствии с поставленными фильтрами и действиями. Для того, чтобы разобраться с работой strace, мы рекомендуем поисследовать man, изучить статьи с Хабра (про сам strace, и про его реальное использование)
Попробуем с его помощью поизучать разные Linux — утилиты. Например, сколько системных вызовов в утилите date
user@localhost:~> date
Tue Oct 22 04:32:30 PM MSK 2024
user@localhost:~> strace date
execve("/usr/bin/date", ["date"], 0x7fff61594470 /* 83 vars */) = 0
brk(NULL) = 0x56286a0db000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc5e25ad000
<...>
write(1, "Tue Oct 22 04:32:37 PM MSK 2024\n", 32Tue Oct 22 04:32:37 PM MSK 2024
) = 32
close(1) = 0
close(2) = 0
exit_group(0) = ?
+++ exited with 0 +++
user@localhost:~> strace date |& wc # Смотрим количество
120 735 8213
user@localhost:~>
Современные утилиты, вообще говоря, дёргают множество системных вызовов: проверки доступа, кеширование, локализация, подтягивание библиотек и т. д.
user@localhost:~> strace ls |& wc
174 1028 11490
user@localhost:~>
Во всём этом множестве вызовов довольно трудно ориентироваться, поэтому одна из основных функций strace — сокращать количество выводов информации о системных вызовах путём указания области фильтрования. Ключ фильтрации -e позволяет выделить группу, системные вызовы связанные с которой и будут отображаться.
Отследим системные вызовы, связанные с обработкой файлов, в процессе выполнения date
user@localhost:~> strace -e file date
execve("/usr/bin/date", ["date"], 0x7ffe15ffef28 /* 83 vars */) = 0
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
<...>
openat(AT_FDCWD, "/usr/lib/locale/en_US.utf8/LC_CTYPE", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/etc/localtime", O_RDONLY|O_CLOEXEC) = 3
Tue Oct 22 04:36:27 PM MSK 2024
+++ exited with 0 +++
user@localhost:~> strace -e file date |& wc
35 264 3171
user@localhost:~>
Также можно отслеживать возвращаемые значения системных вызовов. Ключи -z и -Z фильтруют успешные и неуспешные вызовы соответственно.
user@localhost:~> strace -z -e file date
execve("/usr/bin/date", ["date"], 0x7fff03ace970 /* 83 vars */) = 0
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
<...>
O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/etc/localtime", O_RDONLY|O_CLOEXEC) = 3
Tue Oct 22 04:37:22 PM MSK 2024
+++ exited with 0 +++
user@localhost:~> strace -z -e file date |& wc
21 111 1547
user@localhost:~>
Для трассировки вызовов, связанных с конкретными файлами, можно воспользоваться ключом -P для задания пути до исследуемого объекта. При этом strace отследит всю работу с ним, в том числе и в рамках работы с описывающим его файловым дескриптором.
user@localhost:~> strace -P /usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES date
openat(AT_FDCWD, "/usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=57, ...}) = 0
mmap(NULL, 57, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f9fc9acf000
close(3) = 0
Sat Oct 26 08:49:38 PM MSK 2024
+++ exited with 0 +++
user@localhost:~>
Как мы видим, strace срезает часть вывода больших данных об объекте. Чтобы получать информацию без сокращения, можно воспользоваться ключом -v.
user@localhost:~> strace -P /usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES -v date
openat(AT_FDCWD, "/usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_dev=makedev(0, 0x2a), st_ino=5991, st_mode=S_IFREG|0644, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=8, st_size=57, st_atime=1729957009 /* 2024-10-26T18:36:49.302689708+0300 */, st_atime_nsec=302689708, st_mtime=1722239432 /* 2024-07-29T10:50:32+0300 */, st_mtime_nsec=0, st_ctime=1725309643 /* 2024-09-02T23:40:43.066310486+0300 */, st_ctime_nsec=66310486}) = 0
mmap(NULL, 57, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f6a459a4000
close(3) = 0
Sat Oct 26 08:51:37 PM MSK 2024
+++ exited with 0 +++
user@localhost:~>
Чтобы не отслеживать каждый файл отдельно, под каким дескриптором и когда он обрабатывался, или чтобы просто иметь понимание, какие объекты под какими дескрипторами или переменными окружения скрыты, можно воспользоваться ключами -y и -yy, которые раскрывают информацию о соответствии дескрипторов файлам.
user@localhost:~> strace -P /usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES date 2> info_not_y
Sat Oct 26 09:00:26 PM MSK 2024
user@localhost:~> strace -P /usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES -y date 2> info_with_y
Sat Oct 26 09:00:37 PM MSK 2024
user@localhost:~> cat info_not_y | grep -E "(openat)|(fstat)"
openat(AT_FDCWD, "/usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=57, ...}) = 0
user@localhost:~> cat info_with_y | grep -E "(openat)|(fstat)"
openat(AT_FDCWD</home/user>, "/usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES", O_RDONLY|O_CLOEXEC) = 3</usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES>
fstat(3</usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES>, {st_mode=S_IFREG|0644, st_size=57, ...}) = 0
user@localhost:~>
Чтобы отследить время выполнения системных вызовов, их количество и количество ошибок выполнения, можно воспользоваться ключом -w, показывающим таблицу выполнения системных вызовов. Ключ идёт в обязательной паре с ключами -С/-c, которые указывают, выводить или не выводить при этом саму трассировку.
user@localhost:~> LC_ALL=en_US.utf8 strace -P /usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES -w -C date
openat(AT_FDCWD, "/usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=57, ...}) = 0
mmap(NULL, 57, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f9e556cc000
close(3) = 0
Sat Oct 26 09:03:54 PM MSK 2024
+++ exited with 0 +++
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
29.31 0.000007 7 1 mmap
29.07 0.000007 7 1 openat
21.59 0.000005 5 1 fstat
20.03 0.000005 5 1 close
------ ----------- ----------- --------- --------- ----------------
100.00 0.000025 6 4 total
user@localhost:~> strace -w -c date
Sat Oct 26 09:04:58 PM MSK 2024
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
35.47 0.000554 553 1 execve
16.76 0.000262 8 31 13 openat
14.02 0.000219 10 21 mmap
9.34 0.000146 7 20 close
7.71 0.000120 6 20 fstat
3.39 0.000053 17 3 mprotect
2.81 0.000044 43 1 write
2.24 0.000035 6 5 read
1.70 0.000027 8 3 brk
1.15 0.000018 17 1 munmap
0.67 0.000010 5 2 pread64
0.67 0.000010 10 1 set_tid_address
0.64 0.000010 10 1 rseq
0.57 0.000009 8 1 prlimit64
0.57 0.000009 8 1 futex
0.54 0.000008 8 1 1 access
0.53 0.000008 8 1 set_robust_list
0.52 0.000008 8 1 getrandom
0.37 0.000006 5 1 lseek
0.33 0.000005 5 1 arch_prctl
------ ----------- ----------- --------- --------- ----------------
100.00 0.001561 13 117 14 total
user@localhost:~>
Ключ -o позволит сохранить результаты трассировки в отдельный файл (если вы хотите делать перенаправление вывода вручную, необходимо помнить, что трассировочная информация направляется в stderr, соответственно перенаправлять надо поток ошибок через 2>{cм. пример в описании ключа -y}).
user@localhost:~> ls | grep Date
user@localhost:~> strace -w -c -o Date_exec_wall date
Sat Oct 26 09:12:11 PM MSK 2024
user@localhost:~> ls | grep Date
Date_exec_wall
user@localhost:~> cat Date_exec_wall
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
41.26 0.000619 619 1 execve
15.70 0.000236 11 21 mmap
15.58 0.000234 7 31 13 openat
7.50 0.000113 5 20 fstat
7.23 0.000109 5 20 close
2.12 0.000032 6 5 read
1.75 0.000026 8 3 mprotect
1.65 0.000025 8 3 brk
1.05 0.000016 7 2 pread64
1.05 0.000016 15 1 write
0.95 0.000014 14 1 munmap
0.66 0.000010 9 1 1 access
0.49 0.000007 7 1 set_tid_address
0.47 0.000007 7 1 arch_prctl
0.46 0.000007 6 1 rseq
0.46 0.000007 6 1 set_robust_list
0.45 0.000007 6 1 futex
0.41 0.000006 6 1 lseek
0.38 0.000006 5 1 prlimit64
0.37 0.000005 5 1 getrandom
------ ----------- ----------- --------- --------- ----------------
100.00 0.001502 12 117 14 total
user@localhost:~>
Раз уж мы упоминали трассировку в качестве одного из механизмов, которым пользуются отладчики, будет неправильным не рассказать про возможности чтения стека и данных, над которыми оперируют системные вызовы.
Для просмотра стека в момент исполнения какого-либо из системных вызовом можно воспользоваться ключом -k.
user@localhost:~> strace -P /usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES -k date
openat(AT_FDCWD, "/usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES", O_RDONLY|O_CLOEXEC) = 3
> /usr/lib64/libc.so.6(__open_nocancel+0x44) [0x107102]
> /usr/lib64/libc.so.6(_nl_load_locale+0x2b9) [0x37317]
> /usr/lib64/libc.so.6(_nl_find_locale+0x773) [0x360f1]
> /usr/lib64/libc.so.6(setlocale+0x128) [0x38586]
> /usr/bin/date() [0x3567]
> /usr/lib64/libc.so.6(__libc_start_call_main+0x81) [0x2a33f]
> /usr/lib64/libc.so.6(__libc_start_main@@GLIBC_2.34+0x8a) [0x2a408]
> /usr/bin/date() [0x4254]
fstat(3, {st_mode=S_IFREG|0644, st_size=57, ...}) = 0
> /usr/lib64/libc.so.6(__fstat64+0xd) [0x101ffb]
> /usr/lib64/libc.so.6(_nl_load_locale+0x2cd) [0x3732b]
> /usr/lib64/libc.so.6(_nl_find_locale+0x773) [0x360f1]
> /usr/lib64/libc.so.6(setlocale+0x128) [0x38586]
> /usr/bin/date() [0x3567]
> /usr/lib64/libc.so.6(__libc_start_call_main+0x81) [0x2a33f]
> /usr/lib64/libc.so.6(__libc_start_main@@GLIBC_2.34+0x8a) [0x2a408]
> /usr/bin/date() [0x4254]
mmap(NULL, 57, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f313281d000
> /usr/lib64/libc.so.6(__mmap+0x28) [0x10fd86]
> /usr/lib64/libc.so.6(_nl_load_locale+0xaa) [0x37108]
> /usr/lib64/libc.so.6(_nl_find_locale+0x773) [0x360f1]
> /usr/lib64/libc.so.6(setlocale+0x128) [0x38586]
> /usr/bin/date() [0x3567]
> /usr/lib64/libc.so.6(__libc_start_call_main+0x81) [0x2a33f]
> /usr/lib64/libc.so.6(__libc_start_main@@GLIBC_2.34+0x8a) [0x2a408]
> /usr/bin/date() [0x4254]
close(3) = 0
> /usr/lib64/libc.so.6(__close_nocancel+0xd) [0x106ddb]
> /usr/lib64/libc.so.6(_nl_load_locale+0xba) [0x37118]
> /usr/lib64/libc.so.6(_nl_find_locale+0x773) [0x360f1]
> /usr/lib64/libc.so.6(setlocale+0x128) [0x38586]
> /usr/bin/date() [0x3567]
> /usr/lib64/libc.so.6(__libc_start_call_main+0x81) [0x2a33f]
> /usr/lib64/libc.so.6(__libc_start_main@@GLIBC_2.34+0x8a) [0x2a408]
> /usr/bin/date() [0x4254]
Sat Oct 26 09:19:09 PM MSK 2024
+++ exited with 0 +++
user@localhost:~>
Для просмотра данных (которые, например, считаны системным вызовом read) можно воспользоваться ключом -s с указанием количества выводимых байт.
user@localhost:~> cat > Hello.txt
Try to read me, programmer, HAHAHA
user@localhost:~> strace -e read -s100 cat Hello.txt
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0N\245\2\0\0\0\0\0@\0\0\0\0\0\0\0\310x$\0\0\0\0\0\0\0\0\0@\08\0\16\0@\0C\0B\0\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0\20\3\0\0"..., 832) = 832
read(3, "# Locale name alias data base.\n# Copyright (C) 1996-2024 Free Software Foundation, Inc.\n#\n# This pro"..., 4096) = 2940
read(3, "", 4096) = 0
read(3, "Try to read me, programmer, HAHAHA\n", 262144) = 35
Try to read me, programmer, HAHAHA
read(3, "", 262144) = 0
+++ exited with 0 +++
user@localhost:~>
Управление вызовами
Одна из важных задач отладки — исследование поведения программы в случаях некорректной работы системных вызовов. У нас может не быть прав доступа к файлам, может произойти ошибка при считывании или записи данных. Правильно написанная программа должна уметь обрабатывать такие случаи, даже если на практике за всё время использования она с ними не столкнётся. Хуже того, такую ситуацию — завершение определённого системного вызова с заданным кодом ошибки — далеко не всегда легко создать даже в чисто тестовых целях. Соответственно, для проверки нам необходим механизм управления системными вызовами, чтобы собственноручно менять результат их работы и проверять обрабатываемость тех или иных ошибок.
ptrace, на основе работы которого построен strace, отлавливает процессы дважды — при вызове и при возврате из системного вызова. И в момент возврата мы можем подменить результат его работы, причём вплоть до указания пришедшего в процесс сигнала или конкретного кода возврата.
Поисследуем подмену результатов. Возьмём команду cat /dev/null, обращающуюся к пустому устройству, и ничего не возвращающую на вывод (Чтение из /dev/null эквивалентно считыванию конца файла EOF ) . Вызовем её три раза (с помощью развёртки имён Shell {,,}) и будем трассировать обращение на открытие файла.
user@localhost:~> cat /dev/null{,,}
user@localhost:~> strace -P /dev/null -e openat cat /dev/null{,,}
openat(AT_FDCWD, "/dev/null", O_RDONLY) = 3
openat(AT_FDCWD, "/dev/null", O_RDONLY) = 3
openat(AT_FDCWD, "/dev/null", O_RDONLY) = 3
+++ exited with 0 +++
user@localhost:~>
Теперь воспользуемся ключом -e fault(или --fault) для подмены результата системного вызова.
user@localhost:~> strace -P /dev/null -e openat -e fault=openat:error=EACCES cat /dev/null{,,}
openat(AT_FDCWD, "/dev/null", O_RDONLY) = -1 EACCES (Permission denied) (INJECTED)
cat: /dev/null: Permission denied
openat(AT_FDCWD, "/dev/null", O_RDONLY) = -1 EACCES (Permission denied) (INJECTED)
cat: /dev/null: Permission denied
openat(AT_FDCWD, "/dev/null", O_RDONLY) = -1 EACCES (Permission denied) (INJECTED)
cat: /dev/null: Permission denied
+++ exited with 1 +++
user@localhost:~>
-e fault \ --fault являются упрощёнными флагами более масштабного флага подмены -e inject \ --inject. Базовый синтаксис подмены выглядит как $<место\ подмены>:<error=ошибка>:<when=какой\ по\ счёту\ системный\ вызов\ подменяется>$
inject также позволяет указать сигнал, который получил процесс.
user@localhost:~> strace -P /dev/null -e openat --fault=openat:error=EACCES:when=3 cat /dev/null{,,}
openat(AT_FDCWD, "/dev/null", O_RDONLY) = 3
openat(AT_FDCWD, "/dev/null", O_RDONLY) = 3
openat(AT_FDCWD, "/dev/null", O_RDONLY) = -1 EACCES (Permission denied) (INJECTED)
cat: /dev/null: Permission denied
+++ exited with 1 +++
user@localhost:~> strace -P /dev/null -e openat -e fault=openat:error=EACCES:when=2 cat /dev/null{,,}
openat(AT_FDCWD, "/dev/null", O_RDONLY) = 3
openat(AT_FDCWD, "/dev/null", O_RDONLY) = -1 EACCES (Permission denied) (INJECTED)
cat: /dev/null: Permission denied
openat(AT_FDCWD, "/dev/null", O_RDONLY) = 3
+++ exited with 1 +++
user@localhost:~> strace -P /dev/null -e openat --inject=openat:error=EACCES:when=2:signal=SIGINT cat /dev/null{,,}
openat(AT_FDCWD, "/dev/null", O_RDONLY) = 3
openat(AT_FDCWD, "/dev/null", O_RDONLY) = -1 EACCES (Permission denied) (INJECTED)
--- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---
+++ killed by SIGINT +++
user@localhost:~>
Существует ещё множество возможностей для применения strace, и некоторые из них сама утилита предлагает в подсказках:
user@localhost:~> strace --tips
______________________________________________ ____
/ \ / \
| It is possible to use regular expressions | |-. .-.|
| for syscall names in the -e trace \ (_@)(_@)
| expression, for example: \ .---_ \
| _\ /.. \_/
| strace -e trace=/^sched_.* / |__.-^ /
| | } |
| will trace all scheduling-related syscalls. | | [
\______________________________________________/ [ ]
] |
user@localhost:~> strace --tips
______________________________________________ ____
/ \ / \
| You can control what columns are shown | |-. .-.|
| in the call summary table produced by -c/-C \ (_@)(_@)
| options with -U/--summary-columns option. \ .---_ \
| It is a way to get minimum/maximum call _\ /.. \_/
| duration printed, for example: / |__.-^ /
| | } |
| strace -c -U name,min-time,max-time ls | | [
\______________________________________________/ [ ]
] |
user@localhost:~>
Предзагрузка библиотек
Немного отвлечёмся от трассировки и изучим другую тему — предзагрузку библиотек.
Напишем программу, которая просто выводит псевдослучайные числа и что-то ещё:
rand.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
int i;
char *s;
s = strdup(argv[argc-1]);
for(i=0; i<4; i++)
printf("%s: %d, %d\n", s, rand(), i*2+1);
return 0;
}
user@localhost:~/LinuxAppDev/06_Tracing/Lection> cc rand.c -o rand
user@localhost:~/LinuxAppDev/06_Tracing/Lection> ./rand
./rand: 1804289383, 1
./rand: 846930886, 3
./rand: 1681692777, 5
./rand: 1714636915, 7
user@localhost:~/LinuxAppDev/06_Tracing/Lection> ./rand q w e
e: 1804289383, 1
e: 846930886, 3
e: 1681692777, 5
e: 1714636915, 7
user@localhost:~/LinuxAppDev/06_Tracing/Lection>
Вспомним, как работает динамический загрузчик: при компоновке программы собирается пространство имён используемых в ней объектов, а при запуске программы с использованием переменной окружения LD_LIBRARY_PATH, указывающей на расположение динамических библиотек, программа собирает сами объекты.
user@localhost:~/LinuxAppDev/06_Tracing/Lection> ldd rand
linux-vdso.so.1 (0x00007f7e00de3000)
libc.so.6 => /lib64/libc.so.6 (0x00007f7e00a00000)
/lib64/ld-linux-x86-64.so.2 (0x00007f7e00de5000)
user@localhost:~/LinuxAppDev/06_Tracing/Lection>
Кроме используемого нами LD_LIBRARY_PATH есть также переменная LD_PRELOAD, заранее подгружающая библиотеку для монтирования объектов из неё. И подгруженные таким образом символы оказываются более приоритетными, благодаря чему мы можем провернуть один трюк.
Опишем программу, в которой напишем функцию с сигнатурой, совпадающей с rand()
ura.c
int rand() {
return 42;
}
Смонтируем из неё динамическую библиотеку и скомандуем системе загрузить эту библиотеку в память перед тем, как будет загружена основная программа. Это приведёт к тому, что в процессе динамической компоновки программы со всеми нужными ей библиотеками (например, libc), символ rand из нашей библиотеки — если он нужен программе — будет найден первым.
user@localhost:~/LinuxAppDev/06_Tracing/Lection> cc -shared -fPIC ura.c -o ura.so
user@localhost:~/LinuxAppDev/06_Tracing/Lection> LD_PRELOAD=`pwd`/ura.so ./rand
./rand: 42, 1
./rand: 42, 3
./rand: 42, 5
./rand: 42, 7
user@localhost:~/LinuxAppDev/06_Tracing/Lection>
Мы ничего не меняли в основной программе, при этом получили работу того rand(), который сами написали, поскольку при поиске объекта по имени программа поймала предзагруженную функцию, а не изначально задумывающуюся.
Для того, чтобы контролировать механизм загрузки объектов динамических библиотек, стоит воспользоваться функциями libc, описанными в dlfcn.h. Функция dlsym применяется для поиска символа по имени, а с описателем RTLD_NEXT — находит следующий такой символ после текущего.
ura.c
#include <dlfcn.h>
typedef int (*rnd_type)(void);
int rand(void) {
int ret;
// найти ещё один символ с таким же именем
ret = ((rnd_type)(dlsym(RTLD_NEXT, "rand")))();
return ret % 2 ? ret : 42;
}
Предполагается, что наша функция сначала вызовет «настоящую» rand(), а затем подменит её результат на 42, если он был чётный.
user@localhost:~/LinuxAppDev/06_Tracing/Lection> cc -shared -fPIC ura.c -o ura.so
user@localhost:~/LinuxAppDev/06_Tracing/Lection> LD_PRELOAD=`pwd`/ura.so ./rand
./rand: 1804289383, 1
./rand: 42, 3
./rand: 1681692777, 5
./rand: 1714636915, 7
user@localhost:~/LinuxAppDev/06_Tracing/Lection> ./rand
./rand: 1804289383, 1
./rand: 846930886, 3
./rand: 1681692777, 5
./rand: 1714636915, 7
user@localhost:~/LinuxAppDev/06_Tracing/Lection>
Домашнее задание
Написать программу move.c, принимающую два параметра в командной строке: move infile outfile, которая перемещает содержимое файла infile в файл outfile (если таковой был, его содержимое удаляется), и в случае успеха удаляет infile.
- Программа должна проверять по возможности все возможные ошибки и сообщать о них, в том числе выставляя различный код ошибки
- Программа должна по возможности работать безопасно — не удалять исходный файл, пока целевой не закрыт; удалять целевой файл и сохранять исходный, если есть подозрение, что что-то пошло не так
- Написать тесты с помощью
strace error injection, которые позволяют проверить как можно больше ошибочных ситуаций (проверяют код ошибки и наличие того файла, который должен был остаться ☺) - Написать
.soбиблиотеку дляLD_PRELOAD, при подгрузке которой файл, в имени которого есть строка"PROTECT", не удаляется (как минимум — не удаляется при помощи вашегоmove). Написать тест, который это проверяет - Как обычно, написать
Makefileсо сборкой, запусками тестов и очисткой генератов