Єдина Країна! Единая Страна!

Archive for the 'Зневадження' Category

Зневадження коду за допомогою strace

Неділя, Лютий 8th, 2009

Ви коли небуть ломали собі голову над пошуком помилки? Помилки, яку ніяк не вдається знайти у джерельних текстах, але яка часто проявляється після компіляції і запуску програми. Знайомтесь: strace. strace — це утиліта, яка дозволяє вам трасувати системні виклики і сигнали конкретної команди. Якої команди? А які у вас є?

strace — вільне програмне забезпечення, що розповсюджується на умовах ліцензії, подібної до ліцензії BSD. Спершу утиліта була написана Полом Краненбургом (Paul Kranenburg) для SunOS за мотивами іншої утиліти для SunOS, trace. На Linux її портував Бранко Ланкестер (Branko Lankester), котрий, окрім цього, реалізував її підтримку в ядрі. У 1993, Рік Следкі (Rick Sladkey) об’єднав strace 2.5 для SunOS з другою версією strace для Linux, додавши при цьому багато можливостей truss(1) з SVR4. В результаті, з’явилася strace, котра працювала на обох платформах. Сьогодні, strace підтримується Уічертом Аккерманом (Wichert Akkerman) і Роландом МакҐрасом (Roland McGrath).

Трасування helloworld

Робота strace полягає у перехопленні і записі системних викликів, виконаних процесом, а також отриманих ним сигналів. Для кожного системного виклику, до стандартного файлу помилок, або ж до будь якого іншого заданого файлу, виводиться його ім’я, аргументи і код виклику. Як щодо кількох прикладів?

Щоб не ускладнювати роботу strace, я використав найпростішу з можливих програм.

#include 
 
main()
{
    printf("Hello World n");
}

Компілюємо її:

# gcc -o helloworld helloworld.c

Готово! Запускаємо: ./helloworld, і отримаємо на стандартний вихід:

Hello World

Виглядає надзвичайно просто. Тепер запустимо strace. strace протрасує, і виведе на stderr усі системні виклики і сигнали:

# strace ./helloworld

Вивід команди `strace ./helloworld`:

 1  execve("./helloworld", ["./helloworld"], [/* 22 vars */]) = 0
 2  uname({sys="Linux", node="knuth", ...}) = 0
 3  brk(0)                                  = 0x81f7000
 4  access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
 5  open("/etc/ld.so.cache", O_RDONLY)      = 3
 6  fstat64(3, {st_mode=S_IFREG|0644, st_size=73856, ...}) = 0
 7  old_mmap(NULL, 73856, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7fed000
 8  close(3)                                = 0
 9  open("/lib/tls/libc.so.6", O_RDONLY)    = 3
10  read(3, "177ELF111���������3�3�1��� ?B�00"..., 512) = 512
11  fstat64(3, {st_mode=S_IFREG|0755, st_size=1454835, ...}) = 0
12  old_mmap(0x40f000, 1215644, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x40f000
13  old_mmap(0x532000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x123000) = 0x532000
14  old_mmap(0x536000, 7324, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x536000
15  close(3)                                = 0
16  old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fec000
17  mprotect(0x532000, 4096, PROT_READ)     = 0
18  mprotect(0x40b000, 4096, PROT_READ)     = 0
19  set_thread_area({entry_number:-1 -> 6, base_addr:0xb7fec940, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
20  munmap(0xb7fed000, 73856)               = 0
21  fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0
22  mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fff000
23  write(1, "Hello World n", 13)          = 13
24  munmap(0xb7fff000, 4096)                = 0
25  exit_group(13)                          = ?

Перше, що ми бачимо — це execve. execve() виконує програму, задану ім’ям файлу. Далі, uname() отримує ім’я і тип системи, яку я використовував для тесту. Після цього, наша програма запитує певну кількість пам’яті і мапи розміщення розділюваних бібліотек, необхідних для роботи, наприклад, динамічний завантажувач і libc. До всієї цієї інформації ми скоро повернемося, а поки подивимося, що відбувається далі. Рядок 23 виводу трасування, нарешті відкриває нам призначення нашої програми helloworld (як би ми не бачили джерельного коду): вивести у файловий дескриптор 1 (STDOUT), повідомити кількість байт у виведеній інформації та їх значення. Не складно здогадатися, що застосування трасування може допомогти вам відстежити у програмі конкретний виклик або сигнал. Чим же це корисно? Якщо ви пишете програму трохи більш складну, ніж helloworld.c, і вона дає збій, видаючи мінімум, або взагалі не видаючи інформації про те, що з нею сталося, трасування може вам допомогти виявити причину, яка викликає збій у процесі роботи.

Read the rest of this entry »