Поиск по сайту:

Трассировка ядра с помощью Trace-CMD


Trace-cmd — это простая в использовании и многофункциональная утилита для отслеживания функций ядра Linux.

В моей предыдущей статье я объяснил, как использовать ftrace для отслеживания функций ядра. Использование ftrace при записи и чтении файлов может оказаться утомительным, поэтому я использовал его оболочку для запуска команд с параметрами включения и отключения трассировки, установки фильтров, просмотра вывода, очистки вывода и т. д.

Команда трассировки-cmd — это утилита, которая поможет вам сделать это. В этой статье я использую trace-cmd для выполнения тех же задач, что и в своей статье о ftrace. Поскольку я часто возвращаюсь к этой статье, я рекомендую вам прочитать ее, прежде чем читать эту.

Установите трассировку-cmd

Я запускаю команды, описанные в этой статье, от имени пользователя root.

Механизм ftrace встроен в ядро, и вы можете проверить его работу с помощью:

# mount | grep tracefs
none on /sys/kernel/tracing type tracefs (rw,relatime,seclabel)

Однако вам необходимо установить утилиту trace-cmd вручную.

# dnf install trace-cmd -y

Список доступных трассировщиков

При использовании ftrace необходимо просмотреть содержимое файла, чтобы узнать, какие трассировщики доступны. Но с помощью trace-cmd вы можете получить эту информацию с помощью:

# trace-cmd list -t
hwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop

Включить трассировщик функций

В моей предыдущей статье я использовал два трассировщика и сделаю то же самое здесь. Включите свой первый трассировщик, function, с помощью:

$ trace-cmd start -p function
  plugin 'function'

Просмотр результатов трассировки

После включения трассировщика вы можете просмотреть выходные данные, используя аргументы show. Здесь показаны только первые 20 строк, чтобы пример был коротким (см. мою предыдущую статью для объяснения вывода):

# trace-cmd show | head -20
## tracer: function
#
# entries-in-buffer/entries-written: 410142/3380032   #P:8
#
#                                _-----=> irqs-off
#                               / _----=> need-resched
#                              | / _---=> hardirq/softirq
#                              || / _--=> preempt-depth
#                              ||| /     delay
#           TASK-PID     CPU#  ||||   TIMESTAMP  FUNCTION
#              | |         |   ||||      |         |
           gdbus-2606    [004] ..s. 10520.538759: __msecs_to_jiffies <-rebalance_domains
           gdbus-2606    [004] ..s. 10520.538760: load_balance <-rebalance_domains
           gdbus-2606    [004] ..s. 10520.538761: idle_cpu <-load_balance
           gdbus-2606    [004] ..s. 10520.538762: group_balance_cpu <-load_balance
           gdbus-2606    [004] ..s. 10520.538762: find_busiest_group <-load_balance
           gdbus-2606    [004] ..s. 10520.538763: update_group_capacity <-update_sd_lb_stats.constprop.0
           gdbus-2606    [004] ..s. 10520.538763: __msecs_to_jiffies <-update_group_capacity
           gdbus-2606    [004] ..s. 10520.538765: idle_cpu <-update_sd_lb_stats.constprop.0
           gdbus-2606    [004] ..s. 10520.538766: __msecs_to_jiffies <-rebalance_domains

Остановить трассировку и очистить буфер

Трассировка продолжает выполняться в фоновом режиме, и вы можете продолжать просматривать выходные данные с помощью show.

Чтобы остановить трассировку, запустите trace-cmd с аргументом stop:

# trace-cmd stop

Чтобы очистить буфер, запустите его с аргументом clear:

# trace-cmd clear

Включить трассировщик function_graph

Включите второй трассировщик, function_graph, выполнив:

# trace-cmd start -p function_graph
  plugin 'function_graph'

Еще раз просмотрите вывод, используя аргумент show. Как и ожидалось, выходные данные немного отличаются от результатов первой трассировки. На этот раз он включает цепочку вызовов функций:

# trace-cmd show | head -20
## tracer: function_graph
#
# CPU  DURATION                  FUNCTION CALLS
# |     |   |                     |   |   |   |
 4)   0.079 us    |        } /* rcu_all_qs */
 4)   0.327 us    |      } /* __cond_resched */
 4)   0.081 us    |      rcu_read_unlock_strict();
 4)               |      __cond_resched() {
 4)   0.078 us    |        rcu_all_qs();
 4)   0.243 us    |      }
 4)   0.080 us    |      rcu_read_unlock_strict();
 4)               |      __cond_resched() {
 4)   0.078 us    |        rcu_all_qs();
 4)   0.241 us    |      }
 4)   0.080 us    |      rcu_read_unlock_strict();
 4)               |      __cond_resched() {
 4)   0.079 us    |        rcu_all_qs();
 4)   0.235 us    |      }
 4)   0.095 us    |      rcu_read_unlock_strict();
 4)               |      __cond_resched() {

Используйте команды stop и clear, чтобы остановить трассировку и очистить буфер:

# trace-cmd stop
# trace-cmd clear

Настройте трассировку для увеличения глубины

Если вы хотите увидеть больше глубины в вызовах функций, вы можете настроить трассировщик:

# trace-cmd start -p function_graph --max-graph-depth 5
  plugin 'function_graph'

Теперь, когда вы сравните этот вывод с тем, что видели раньше, вы должны увидеть больше вложенных вызовов функций:

# trace-cmd show | head -20
## tracer: function_graph
#
# CPU  DURATION                  FUNCTION CALLS
# |     |   |                     |   |   |   |
 6)               |        __fget_light() {
 6)   0.804 us    |          __fget_files();
 6)   2.708 us    |        }
 6)   3.650 us    |      } /* __fdget */
 6)   0.547 us    |      eventfd_poll();
 6)   0.535 us    |      fput();
 6)               |      __fdget() {
 6)               |        __fget_light() {
 6)   0.946 us    |          __fget_files();
 6)   1.895 us    |        }
 6)   2.849 us    |      }
 6)               |      sock_poll() {
 6)   0.651 us    |        unix_poll();
 6)   1.905 us    |      }
 6)   0.475 us    |      fput();
 6)               |      __fdget() {

Изучите доступные функции для отслеживания

Если вы хотите отслеживать только определенные функции и игнорировать остальные, вам необходимо знать точные имена функций. Вы можете получить их с помощью аргумента list, за которым следует -f. В этом примере выполняется поиск общей функции ядра kmalloc, которая используется для выделения памяти в ядре:

# trace-cmd list -f | grep kmalloc
bpf_map_kmalloc_node
mempool_kmalloc
__traceiter_kmalloc
__traceiter_kmalloc_node
kmalloc_slab
kmalloc_order
kmalloc_order_trace
kmalloc_large_node
__kmalloc
__kmalloc_track_caller
__kmalloc_node
__kmalloc_node_track_caller
[...]

Вот общее количество функций, доступных в моей тестовой системе:

# trace-cmd list -f | wc -l
63165

Функции трассировки модуля ядра

Вы также можете отслеживать функции, связанные с конкретным модулем ядра. Представьте, что вы хотите отслеживать функции, связанные с модулем ядра kvm. Убедитесь, что модуль загружен:

# lsmod  | grep kvm_intel
kvm_intel             335872  0
kvm                   987136  1 kvm_intel

Снова запустите trace-cmd с аргументом list и на выходе grep для строк, оканчивающихся на ] . Это позволит отфильтровать модули ядра. Затем grep модуль ядра kvm_intel, и вы должны увидеть все функции, связанные с этим модулем ядра:

# trace-cmd list -f | grep ]$  | grep kvm_intel
vmx_can_emulate_instruction [kvm_intel]
vmx_update_emulated_instruction [kvm_intel]
vmx_setup_uret_msr [kvm_intel]
vmx_set_identity_map_addr [kvm_intel]
handle_machine_check [kvm_intel]
handle_triple_fault [kvm_intel]
vmx_patch_hypercall [kvm_intel]

[...]

vmx_dump_dtsel [kvm_intel]
vmx_dump_sel [kvm_intel]

Отслеживание конкретных функций

Теперь, когда вы знаете, как находить интересующие функции, примените эти знания на примере. Как и в предыдущей статье, попробуйте отследить функции, связанные с файловой системой. В моей тестовой системе использовалась файловая система ext4.

Эта процедура немного отличается; вместо start вы запускаете команду с аргументом record, за которым следует «шаблон» функций, которые вы хотите отслеживать. Вам также необходимо указать нужный трассировщик; в данном случае это function_graph. Команда продолжает запись трассировки, пока вы не остановите ее нажатием Ctrl+C. Итак, через несколько секунд нажмите Ctrl+C, чтобы остановить отслеживание:

# trace-cmd list -f | grep ^ext4_

# trace-cmd record -l ext4_* -p function_graph
  plugin 'function_graph'
Hit Ctrl^C to stop recording
^C
CPU0 data recorded at offset=0x856000
    8192 bytes in size
[...]

Просмотр записанной трассировки

Чтобы просмотреть записанную ранее трассировку, запустите команду с аргументом report. Из вывода видно, что фильтр сработал, и вы видите только трассировку функции, связанной с ext4:

# trace-cmd report | head -20
[...]
cpus=8
       trace-cmd-12697 [000] 11303.928103: funcgraph_entry:                   |  ext4_show_options() {
       trace-cmd-12697 [000] 11303.928104: funcgraph_entry:        0.187 us   |    ext4_get_dummy_policy();
       trace-cmd-12697 [000] 11303.928105: funcgraph_exit:         1.583 us   |  }
       trace-cmd-12697 [000] 11303.928122: funcgraph_entry:                   |  ext4_create() {
       trace-cmd-12697 [000] 11303.928122: funcgraph_entry:                   |    ext4_alloc_inode() {
       trace-cmd-12697 [000] 11303.928123: funcgraph_entry:        0.101 us   |      ext4_es_init_tree();
       trace-cmd-12697 [000] 11303.928123: funcgraph_entry:        0.083 us   |      ext4_init_pending_tree();
       trace-cmd-12697 [000] 11303.928123: funcgraph_entry:        0.141 us   |      ext4_fc_init_inode();
       trace-cmd-12697 [000] 11303.928123: funcgraph_exit:         0.931 us   |    }
       trace-cmd-12697 [000] 11303.928124: funcgraph_entry:        0.081 us   |    ext4_get_dummy_policy();
       trace-cmd-12697 [000] 11303.928124: funcgraph_entry:        0.133 us   |    ext4_get_group_desc();
       trace-cmd-12697 [000] 11303.928124: funcgraph_entry:        0.115 us   |    ext4_free_inodes_count();
       trace-cmd-12697 [000] 11303.928124: funcgraph_entry:        0.114 us   |    ext4_get_group_desc();

Отслеживание определенного PID

Допустим, вы хотите отслеживать функции, связанные с определенным постоянным идентификатором (PID). Откройте другой терминал и запишите PID работающей оболочки:

# echo $$
10885

Снова запустите команду record и передайте PID, используя опцию -P. На этот раз дайте терминалу поработать (т. е. пока не нажимайте Ctrl+C):

# trace-cmd record -P 10885 -p function_graph
  plugin 'function_graph'
Hit Ctrl^C to stop recording

Запустите некоторые действия в оболочке

Вернитесь к другому терминалу, где у вас была запущена оболочка с определенным PID, и запустите любую команду, например, ls, чтобы просмотреть файлы:

# ls
Temp-9b61f280-fdc1-4512-9211-5c60f764d702
tracker-extract-3-files.1000
v8-compile-cache-1000
[...]

Вернитесь к терминалу, где вы включили трассировку, и нажмите Ctrl+C, чтобы остановить трассировку:

# trace-cmd record -P 10885 -p function_graph
  plugin 'function_graph'
Hit Ctrl^C to stop recording
^C
CPU1 data recorded at offset=0x856000
    618496 bytes in size
[...]

В выводе трассировки вы можете увидеть PID и оболочку Bash слева, а связанные с ними вызовы функций — справа. Это может быть очень удобно, чтобы сузить трассировку:

# trace-cmd report  | head -20

cpus=8
          <idle>-0     [001] 11555.380581: funcgraph_entry:                   |  switch_mm_irqs_off() {
          <idle>-0     [001] 11555.380583: funcgraph_entry:        1.703 us   |    load_new_mm_cr3();
          <idle>-0     [001] 11555.380586: funcgraph_entry:        0.493 us   |    switch_ldt();
          <idle>-0     [001] 11555.380587: funcgraph_exit:         7.235 us   |  }
            bash-10885 [001] 11555.380589: funcgraph_entry:        1.046 us   |  finish_task_switch.isra.0();
            bash-10885 [001] 11555.380591: funcgraph_entry:                   |  __fdget() {
            bash-10885 [001] 11555.380592: funcgraph_entry:        2.036 us   |    __fget_light();
            bash-10885 [001] 11555.380594: funcgraph_exit:         3.256 us   |  }
            bash-10885 [001] 11555.380595: funcgraph_entry:                   |  tty_poll() {
            bash-10885 [001] 11555.380597: funcgraph_entry:                   |    tty_ldisc_ref_wait() {
            bash-10885 [001] 11555.380598: funcgraph_entry:                   |      ldsem_down_read() {
            bash-10885 [001] 11555.380598: funcgraph_entry:                   |        __cond_resched() {

Попробуйте

Эти краткие примеры показывают, как использование trace-cmd вместо базового механизма ftrace одновременно просто в использовании и богато возможностями, включая многие, которые я здесь не рассмотрел. Чтобы узнать больше и стать лучше, обратитесь к его справочной странице и попробуйте другие полезные команды.

Статьи по данной тематике: