macOS Internals Series / Vol. 2

DTRACE
& DTRUSS

Виж всеки system call. Всеки файл. Всяка мрежова операция. В реално време. Без source код. Без Wireshark. Без bullshit.

~800
probe provider-а
0
app рестарти нужни
ns
точност
power
SIP (System Integrity Protection) блокира dtrace по default. Повечето команди изискват или SIP да е частично изключен, или да се ползват с sudo. За пълен dtrace достъп: рестарт в Recovery Mode → Terminal → csrutil enable --without dtrace. Само на dev машини!
01
App / Kernel
DTrace Probe
D Script
Output / Aggregate

Probe = точка на наблюдение

DTrace работи с probes — точки в кода (kernel или userspace), в които може да се "закачи" и да изпълни произволен код при тяхното достигане. Форматът е: provider:module:function:name

Пример: syscall::open:entry — всеки open() system call преди изпълнение.

Zero overhead когато не се използва

DTrace probe-ите са NOP инструкции в нормален режим — буквално нищо не правят. Само когато активираш конкретен probe, ядрото го patch-ва на живо. Това означава: можеш да оставиш probe-ите в production код без никакво penalty.

02
basic usage
$ sudo dtruss -p $(pgrep Safari)
Закача се за вече работещ процес по PID. Виждаш всеки system call в реално време — open, read, write, connect, все.
trace нов процес от старта
$ sudo dtruss /usr/bin/curl https://example.com 2>&1 | head -50
// sample output
open("/etc/resolv.conf\0", 0x0, 0x1B6)     = 3 0
read(0x3, "# Generated by Ne", 0x1000)     = 75 0
connect(0x5, { AF_INET6, ::ffff:93.184.216.34, 443 }, 0x1C) = -1 Err#36
getsockopt(0x5, 0xFFFF, 0x1007, 0x16F, 0x4) = 0 0
connect(0x5, { AF_INET6, ::ffff:93.184.216.34, 443 }, 0x1C) = 0 0
write(0x5, "GET / HTTP/1.1\r\nH", 0x55)   = 85 0
Виждаш точно какво прави curl — чете DNS конфиг, опитва connect (Err#36 = EINPROGRESS = async), после успешен connect, после изпраща HTTP request.
само file operations
$ sudo dtruss -t open -p $(pgrep "Spotify") 2>&1
-t open филтрира само open() calls. Виждаш точно кои файлове отваря Spotify — config файлове, кеш, logs, всичко. Без source код.
запази в файл за анализ
$ sudo dtruss -a -p $(pgrep myapp) 2>&1 | tee trace.log
-a показва всичко — timestamps, PID, thread ID. tee пише едновременно на екрана и в файла. После: grep "open\|connect" trace.log | sort -u
⚠ Честа грешка
dtruss: failed to initialize dtrace: DTrace requires Destructive Actions
Решение: sudo dtruss ... — задължително с sudo. Ако пак не работи, SIP блокира. Виж горния warning banner.
03
Provider Probe Какво трейсва Пример usage
syscall entry / return Всеки kernel system call Файлове, мрежа, процеси
proc exec / exit / fork Създаване/унищожаване на процеси Кой процес стартира кой
io start / done Disk I/O операции Бавни disk writes
profile profile-997hz Timer-based sampling CPU profiling без инструментация
pid$target library:function Userspace функции на конкретен процес Trace конкретна C функция
objc$target ClassName:method Objective-C method calls Trace macOS app методи
vminfo pgfault / cow_fault Virtual memory events Memory pressure диагностика
sched on-cpu / off-cpu CPU scheduling Защо процес е throttled
листни всички probe-и за процес
$ sudo dtrace -l -n 'pid$target:::' -p $(pgrep Safari) | wc -l
Виждаш колко probe точки има Safari. Обикновено десетки хиляди. Всяка функция в всяка библиотека е probe.
04
💡 Синтаксис
probe_description / predicate / { action }
Probe = кога се изпълнява. Predicate = условие (опционално). Action = какво да се направи.
// files_opened.d — всички файлове отворени от процес
#!/usr/sbin/dtrace -s

syscall::open*:entry
/pid == $target/
{
    printf("%s\n", copyinstr(arg0));
}

syscall::open*:return
/pid == $target && arg0 == -1/
{
    printf("FAILED: %s (errno %d)\n", copyinstr(arg0), errno);
}
$target е PID-ът подаден с -p. Виждаш всеки успешен open и всеки неуспешен с errno кода. Стартирай с: sudo dtrace -s files_opened.d -p $(pgrep MyApp)
// slowio.d — намери бавни disk операции (>10ms)
io:::start
{
    start[arg0] = timestamp;
}

io:::done
/start[arg0]/
{
    this->elapsed = (timestamp - start[arg0]) / 1000000;
    /* само операции над 10ms */
    this->elapsed > 10 ?
        printf("%dms  %s  %s\n",
            this->elapsed,
            args[0]->b_flags & B_READ ? "READ" : "WRITE",
            args[2]->fi_pathname) : 1;
    start[arg0] = 0;
}
Всяка disk операция над 10 милисекунди се логва с времето и файла. Незаменимо при диагностика на бавно стартиране на app-ове. Стартирай с: sudo dtrace -s slowio.d
// syscall_count.d — кои syscalls прави процесът най-много
syscall:::entry
/pid == $target/
{
    @calls[probefunc] = count();
}

END
{
    trunc(@calls, 20);
    printf("\nTop 20 syscalls:\n");
    printa("%-30s %@d\n", @calls);
}
// sample output (Slack)
mach_msg_trap                  48291
kevent_qos                     12847
bsdthread_ctl                   8234
read                            6721
psynch_cvsignal                 5102
write                           4891
open                             312
Натисни Ctrl+C след няколко секунди. Виждаш агрегирана статистика — Slack прави почти 50k mach_msg_trap calls (IPC с OS). Това обяснява защо яде CPU в idle.
// network_spy.d — всички мрежови конекции от всички процеси
syscall::connect:entry
{
    printf("%s[%d] connect → %s\n",
        execname,
        pid,
        copyinstr(arg0));
}

syscall::sendto:entry
{
    printf("%s[%d] SEND %d bytes\n",
        execname,
        pid,
        arg2);
}
Виждаш всеки процес, правещ мрежова конекция в цялата система. Незаменимо за: намиране на malware, разбиране какви telemetry данни изпращат app-ове, дебъгване на "кой блокира порта".
// proc_spawn.d — кои процеси стартират кои
proc:::exec-success
{
    printf("%Y  parent=%-20s  new=%-30s\n",
        walltimestamp,
        curpsinfo->pr_psargs,
        execname);
}
Всеки нов процес в системата с timestamp и parent process. Виждаш точно кой стартира кого — идеално за разбиране на build системи, shell скриптове, или подозрително поведение.
05

App се срива — не знаеш защо

sudo dtruss -f -p $(pgrep myapp) 2>&1 | grep "FAILED\|= -1"

Виждаш точно кои файлове не може да отвори, кои system calls фейлват и с кой errno код. Решаваш за минути вместо часове с логове.

Намери hidden telemetry на app

sudo dtruss -t connect -p $(pgrep "SuspiciousApp") 2>&1

Виждаш всяка мрежова конекция. Ако app-ът изпраща данни без да ти каже — ще го видиш тук. IP адреси, портове, всичко.

Защо стартирането на app е бавно

sudo dtrace -s slowio.d & ; open /Applications/SlowApp.app

Виждаш кои файлове се четат бавно по време на startup. Обикновено са липсващи шрифтове, бавен iCloud sync или огромен cache файл.

Reverse engineer непозната binary

sudo dtruss ./unknown_binary 2>&1 | tee binary_trace.txt

Без source код, без IDA Pro — виждаш всеки файл, мрежова конекция, environment variable и argument на binary-то. Основна forensics техника.

CPU spike — кой е виновен

sudo dtrace -n 'profile-997 /arg0/ { @[execname, ksym(arg0)] = count(); } END { trunc(@, 20); printa(@); }'

997Hz CPU profiler — сэмплира всеки 1ms кой код се изпълнява. След 5 секунди Ctrl+C и виждаш top 20 функции, ядящи CPU. Без инструментация, без rebuild.

06
fs_usage — file system usage на живо
$ sudo fs_usage -w -f filesys $(pgrep Chrome)
$ sudo fs_usage -e -w $(pgrep Slack) | grep ".json"
Работи без SIP промени. fs_usage показва всяка file system операция в реално време. Втората команда — само JSON файлове отворени от Slack.
sc_usage — system call summary
$ sudo sc_usage $(pgrep Safari)
Интерактивен ncurses интерфейс — виждаш live count на всеки syscall тип. По-приятен за четене от dtruss при бърза диагностика.
sample — CPU profiling без dtrace
$ sample $(pgrep myapp) 10 -file /tmp/profile.txt
$ cat /tmp/profile.txt | head -60
Сэмплира процеса за 10 секунди и генерира call tree. Виждаш кои функции са се изпълнявали най-дълго. Вграден в macOS, без sudo, без SIP проблеми.
📚 Следваща стъпка
Brendan Gregg (Netflix) е написал DTrace Book — безплатна онлайн версия на dtrace.org. Също: неговите BPF Performance Tools са dtrace-концепциите пренесени в Linux с eBPF.