Наблюдать за изменением (адресом памяти) в ядре Linux и выводить трассировку стека при его изменении?

Я хотел бы как-то "посмотреть" переменную (или, скорее, адрес памяти) в ядре Linux (точнее, модуль / драйвер ядра); и выяснить, что изменило его - в основном, распечатать трассировку стека при изменении переменной.

Например, в модуле ядра testjiffy-hr.c в конце этого ответа, я хотел бы распечатать трассировку стека каждый раз, когда runcount переменные изменения; надеюсь, что след стека будет содержать упоминание о testjiffy_timer_function, которая действительно является функцией, которая изменяет эту переменную.

Теперь я знаю, что могу использовать kgdb подключиться к отладочному ядру Linux, работающему, скажем, на виртуальной машине, и даже установить точки останова (надеюсь, также точки наблюдения), но проблема в том, что я действительно хочу отладить драйвер ALSA, в частности воспроизведение dma_area буфер (где я получаю некоторые неожиданные данные) - который очень чувствителен к времени; и запуск самого отладочного ядра может испортить время (не говоря уже о запуске его на виртуальной машине).

Еще большая проблема здесь заключается в том, что воспроизведение dma_area указатель существует только во время операции воспроизведения (или, другими словами, между _start а также _stop обработчики) - так что я должен был бы записать dma_area адрес на каждом _start обратный вызов, а затем каким-то образом "запланировать" его для "просмотра" во время операции воспроизведения.

Так что я надеялся, что есть способ сделать что-то подобное прямо в коде драйвера - например, добавить немного кода в это _start обратный вызов, который записывает dma_area указатель и использовать его в качестве аргумента команды, которая инициирует "наблюдение" за изменением; с трассировкой стека, напечатанной из соответствующей функции обратного вызова. (Я знаю, что это тоже повлияет на время, но я надеялся, что оно будет "легким", чтобы не слишком влиять на работу "живого" драйвера).

Итак, мой вопрос: существует ли такая техника отладки в ядре Linux?

Если нет: возможно ли установить аппаратное (или программное) прерывание, которое реагирует на изменение определенного адреса памяти? Тогда мог бы я установить такой обработчик прерываний, который мог бы распечатать трассировку стека? (хотя, я думаю, весь контекст меняется при запуске обработчиков IRQ, так что, возможно, получение трассировки стека было бы неправильным)?

Если нет: остались ли другие методы, которые позволили бы мне напечатать трассировку стека процесса, который изменил значение, хранящееся в данной ячейке памяти в ядре (возможно, в живом, не отлаженном ядре)?

2 ответа

Решение

Большое спасибо за ответы @CosminRatiu и Eugene; благодаря этим я нашел:

... с помощью которого я мог бы развить пример, который я публикую здесь, testhrarr.c модуль ядра / драйвер и Makefile (ниже). Это демонстрирует, что аппаратное отслеживание точек наблюдения может быть достигнуто двумя способами: либо с использованием perf программа, которая может проверять драйвер без изменений; или путем добавления некоторого аппаратного кода точки останова в драйвер (в примере, заключенном в HWDEBUG_STACK определить переменную).

По сути, отладка содержимого стандартных типов атомарных переменных, таких как int (например, runcount переменные) являются прямыми, если они определены как глобальная переменная в модуле ядра, поэтому они в конечном итоге отображаются как символ ядра в глобальном масштабе. Из-за этого код ниже добавляет testhrarr_ в качестве префикса для переменных (чтобы избежать конфликтов имен). Тем не менее, отладка содержимого массивов может быть немного сложнее, из-за необходимости разыменования - так это демонстрирует этот пост, отладка первого байта testhrarr_arr массив. Это было сделано на:

$ echo `cat /etc/lsb-release` 
DISTRIB_ID=Ubuntu DISTRIB_RELEASE=11.04 DISTRIB_CODENAME=natty DISTRIB_DESCRIPTION="Ubuntu 11.04"
$ uname -a
Linux mypc 2.6.38-16-generic #67-Ubuntu SMP Thu Sep 6 18:00:43 UTC 2012 i686 i686 i386 GNU/Linux
$ cat /proc/cpuinfo | grep "model name"
model name  : Intel(R) Atom(TM) CPU N450   @ 1.66GHz
model name  : Intel(R) Atom(TM) CPU N450   @ 1.66GHz

testhrarr модуль в основном выделяет память для небольшого массива при инициализации модуля, устанавливает функцию таймера и предоставляет /proc/testhrarr_proc файл (используя более новый proc_create интерфейс). Затем, пытаясь прочитать из /proc/testhrarr_proc файл (скажем, используя cat) запустит функцию таймера, которая изменит testhrarr_arr значения массива и дамп сообщений в /var/log/syslog, Мы ожидаем, что testhrarr_arr[0] будет меняться три раза во время операции; однажды в testhrarr_startup и дважды в testhrarr_timer_function (из-за упаковки).

с помощью perf

После сборки модуля с make Вы можете загрузить его с помощью:

sudo insmod ./testhrarr.ko

В таком случае, /var/log/syslog будет содержать:

kernel: [40277.199913] Init testhrarr: 0 ; HZ: 250 ; 1/HZ (ms): 4 ; hrres: 0.000000001
kernel: [40277.199930]  Addresses: _runcount 0xf84be22c ; _arr 0xf84be2a0 ; _arr[0] 0xed182a80 (0xed182a80) ; _timer_function 0xf84bc1c3 ; my_hrtimer 0xf84be260; my_hrt.f 0xf84be27c
kernel: [40277.220329] HW Breakpoint for testhrarr_arr write installed (0xf84be2a0)

Обратите внимание, что просто прохождение testhrarr_arr как символ для аппаратной точки наблюдения сканирует адрес этой переменной (0xf84be2a0), а не адрес первого элемента массива (0xed182a80)! Из-за этого аппаратная точка останова не будет срабатывать - поэтому поведение будет таким, как если бы код аппаратной точки останова вообще не присутствовал (что может быть достигнуто путем отмены определения). HWDEBUG_STACK)!

Таким образом, даже без аппаратной точки останова, установленной через код модуля ядра, мы все равно можем использовать perf наблюдать за изменением адреса памяти - в perf, мы указываем адрес, который мы хотим посмотреть (здесь адрес первого элемента testhrarr_arr, 0xed182a80), и процесс, который должен быть запущен: здесь мы бежим bash так что мы можем выполнить cat /proc/testhrarr_proc который запустит таймер модуля ядра, а затем sleep 0.5 что позволит таймеру завершить. -a Параметр также необходим, в противном случае некоторые события могут быть пропущены:

$ sudo perf record -a --call-graph --event=mem:0xed182a80:w bash -c 'cat /proc/testhrarr_proc ; sleep 0.5'
testhrarr proc: startup
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.485 MB perf.data (~21172 samples) ]

С этой точки зрения, /var/log/syslog также будет содержать что-то вроде:

[40822.114964] testhrarr_timer_function: testhrarr_runcount 0 [40822.114980] testhrarr jiffies 10130528; ret: 1; ktnsec: 40822114975062 [40822.118956] testhrarr_timer_function: testhrarr_runcount 1 [40822.118977] testhrarr jiffies 10130529; ret: 1; ktnsec: 40822118973195 [40822.122940] testhrarr_timer_function: testhrarr_runcount 2 [40822.122956] testhrarr jiffies 10130530; ret: 1; ktnsec: 40822122951143 [40822.126962] testhrarr_timer_function: testhrarr_runcount 3 [40822.126978] testhrarr jiffies 10130531; ret: 1; ktnsec: 40822126973583 [40822.130941] testhrarr_timer_function: testhrarr_runcount 4 [40822.130961] testhrarr jiffies 10130532; ret: 1; ktnsec: 40822130955167 [40822.134940] testhrarr_timer_function: testhrarr_runcount 5 [40822.134962] testhrarr jiffies 10130533; ret: 1; ktnsec: 40822134958888 [40822.138936] testhrarr_timer_function: testhrarr_runcount 6 [40822.138958] testhrarr jiffies 10130534; ret: 1; ktnsec: 40822138955693 [40822.142940] testhrarr_timer_function: testhrarr_runcount 7 [40822.142962] testhrarr jiffies 10130535; ret: 1; ktnsec: 40822142959345 [40822.146936] testhrarr_timer_function: testhrarr_runcount 8 [40822.146957] testhrarr jiffies 10130536; ret: 1; ktnsec: 40822146954479 [40822.150949] testhrarr_timer_function: testhrarr_runcount 9 [40822.150970] testhrarr jiffies 10130537; ret: 1; ktnsec: 40822150963438 [40822.154974] testhrarr_timer_function: testhrarr_runcount 10 [40822.154988] testhrarr [5, 7, 9, 11, 13,] 

Читать захват perf (файл называется perf.data) мы можем использовать:

$ sudo perf report --call-graph flat --stdio Не найдено ни kallsyms, ни vmlinux с идентификатором сборки 5031df4d8668bcc45a7bdb4023909c6f8e2d3d34 [testhrarr] с идентификатором сборки 5031df4d8668bcc45a7bdb4023909 с продолжением символов продолжения, без продолжения, не найден, не найден, но не найден открыть /usr/lib/libpixman-1.so.0.20.2, продолжить без символов Не удалось открыть /usr/lib/xorg/modules/drivers/intel_drv.so, продолжить без символов Не удалось открыть /usr/bin/Xorg, продолжение без символов # События: 5 неизвестно #
# Команда общего объекта Символ общего объекта # ........  .......  .............  ....................................
#
    87.50%     Xorg  [testhrarr]    [k] testhrarr_timer_function
            87.50%
                testhrarr_timer_function
                __run_hrtimer
                hrtimer_interrupt
                smp_apic_timer_interrupt
                apic_timer_interrupt
                0x30185d
                0x2ed701
                0x2ed8cc
                0x2edba0
                0x9d0386
                0x8126fc8
                0x81217a1
                0x811bdd3
                0x8070aa7
                0x806281c
                __libc_start_main
                0x8062411

     6,25%      cat  [testhrarr]    [k] testhrarr_timer_function
             6,25 Функция testhrarr_proc_show
                seq_read
                proc_reg_read
                vfs_read
                sys_read
                syscall_call
                0xaa2416
                0x8049f4d
                __libc_start_main
                0x8049081

     3.12% Swapper  [testhrarr]    [к] testhrarr_timer_function
             3.12%
                testhrarr_timer_function
                __run_hrtimer
                hrtimer_interrupt
                smp_apic_timer_interrupt
                apic_timer_interrupt
                cpuidle_idle_call
                cpu_idle
                start_secondary

     3.12% кошка [testhrarr] [к] 0x356   
             3.12%
                0xf84bc356
                0xf84bc3a7
                seq_read
                proc_reg_read
                vfs_read
                sys_read
                syscall_call
                0xaa2416
                0x8049f4d
                __libc_start_main
                0x8049081



#
# (для более высокого уровня обзора попробуйте: perf report --sort comm,dso)
# 

Итак, поскольку мы собираем модуль ядра с отладкой на -g в Makefile), это не проблема для perf найти символы этого модуля, даже если живое ядро ​​не является ядром отладки. Так что это правильно интерпретирует testhrarr_timer_function как установщик большую часть времени, хотя он не сообщает testhrarr_startup (но это действительно отчет testhrarr_proc_show который называет это). Есть также ссылки на 0xf84bc3a7 а также 0xf84bc356 который он не мог решить; Тем не менее, обратите внимание, что модуль загружен в 0xf84bc000:

$ sudo cat /proc/modules | grep testhr
testhrarr 13433 0 - Live 0xf84bc000

... и эта запись также начинается с ...[k] 0x356; и если мы посмотрим в objdump модуля ядра:

 $ objdump -S testhrarr.ko | Меньше
... 
 00000323: 

 static void testhrarr_startup (void) 
 { 
 ... 
     testhrarr_arr [0] = 0; // просто первый элемент 
  34b: a1 80 00 00 00 mov 0x80,% eax 
  350: c7 00 00 00 00 00 movl $ 0x0, (% eax) 
     hrtimer_start (& my_hrtimer, ktime_period_ns, HRTIMER_MODE_REL); 
  356: c7 04 24 01 00 00 00 movl $ 0x1, (% esp) ********** 
  35d: 8b 15 1c 00 00 00 mov 0x1c,% edx 
 ... 
 00000375: 


 static int testhrarr_proc_show (struct seq_file * m, void * v) { 
 ... 
     seq_printf (m, "testhrarr proc: startup \ n"); 
  38f: c7 44 24 04 79 00 00 movl $ 0x79,0x4 (% esp) 
  396: 00  
  397: 8b 45 fc mov -0x4 (% ebp),% eax 
  39a: 89 04 24 mov% eax, (% esp) 
  39d: e8 fc ff ff ff call 39e  
     testhrarr_startup (); 
  3a2: e8 7c ff ff ff call 323  
  3a7: eb 1c jmp 3c5 ********** 
   } еще { 
     seq_printf (m, "testhrarr proc: (выполняется,% d) \ n", testhrarr_runcount); 
  3a9: a1 0c 00 00 00 mov 0xc,% eax 
 ... 
 

... так 0xf84bc356 по-видимому, относится к hrtimer_start; а также 0xf84bc3a7 -> 3a7 относится к своему призванию testhrarr_proc_show функция; что, к счастью, имеет смысл. (Обратите внимание, что я сталкивался с различными версиями драйвера, что _start мог показать, а timer_function быть выраженным простыми адресами; не уверен, с чем это связано).

Одна проблема с perf Тем не менее, это дает мне статистические "накладные расходы" на эти функции (не уверен, что это означает - вероятно, время, потраченное между входом и выходом функции?) - но на самом деле мне нужен журнал стека следы, которые являются последовательными. Не уверен если perf может быть настроен для этого - но это определенно будет сделано с кодом модуля ядра для аппаратных точек останова.

с помощью модуля ядра HW точка останова

Код, который находится в HWDEBUG_STACK реализует настройку и обработку точки останова HW. Как уже отмечалось, по умолчанию настроен для символа ksym_name (если не указано), testhrarr_arr, который не вызывает аппаратную точку останова вообще. ksym_name параметр может быть указан в командной строке во время insmod; Здесь мы можем отметить, что:

$ sudo rmmod testhrarr    # remove module if still loaded
$ sudo insmod ./testhrarr.ko ksym=testhrarr_arr[0]

... результаты с HW Breakpoint for testhrarr_arr[0] write installed (0x (null)) в /var/log/syslog; - это означает, что мы не можем использовать символы с обозначениями в скобках для доступа к массиву; К счастью, нулевой указатель здесь просто означает, что точка останова HW снова не сработает; это не разрушает ОС полностью :)

Существует, однако, глобальная переменная, созданная для ссылки на первый элемент testhrarr_arr массив называется testhrarr_arr_first - обратите внимание, как эта глобальная переменная специально обрабатывается в коде и требует разыменования, чтобы получить правильный адрес. Итак, мы делаем:

$ sudo rmmod testhrarr    # remove module if still loaded
$ sudo insmod ./testhrarr.ko ksym=testhrarr_arr_first

... и системный журнал сообщает:

kernel: [43910.509726] Init testhrarr: 0 ; HZ: 250 ; 1/HZ (ms): 4 ; hrres: 0.000000001
kernel: [43910.509765]  Addresses: _runcount 0xf84be22c ; _arr 0xf84be2a0 ; _arr[0] 0xedf6c5c0 (0xedf6c5c0) ; _timer_function 0xf84bc1c3 ; my_hrtimer 0xf84be260; my_hrt.f 0xf84be27c
kernel: [43910.538535] HW Breakpoint for testhrarr_arr_first write installed (0xedf6c5c0)

... и мы видим, что точка останова HW установлена ​​на 0xedf6c5c0, который является адресом testhrarr_arr[0], Теперь, если мы запустим драйвер через /proc файл:

$ cat /proc/testhrarr_proc 
testhrarr proc: startup

... мы получаем в syslog:

 ядро: [44069.735695] значение testhrarr_arr_first изменено 
 [44069.735711] Pid: 29320, комм.: cat Не испорчен 2.6.38-16-generiC# 67-Ubuntu 
 [44069.735719] Отслеживание вызовов: 
 [44069.735737] []? sample_hbp_handler + 0x2d / 0x3b [testhrarr] 
 [44069.735755] []? __perf_event_overflow + 0x90 / 0x240 
 [44069.735768] []? proc_alloc_inode + 0x23 / 0x90 
 [44069.735778] []? proc_alloc_inode + 0x23 / 0x90 
 [44069.735790] []? perf_swevent_event + 0x136 / 0x140 
 [44069.735801] []? perf_bp_event + 0x70 / 0x80 
 [44069.735812] []? prep_new_page + 0x110 / 0x1a0 
 [44069.735824] []? get_page_from_freelist + 0x12e / 0x320 
 [44069.735836] []? seq_open + 0x3d / 0xa0 
 [44069.735848] []? hw_breakpoint_handler.clone.0 + 0x102 / 0x130 
 [44069.735861] []? hw_breakpoint_exceptions_notify + 0x22 / 0x30 
 [44069.735872] []? notifier_call_chain + 0x45 / 0x60 
 [44069.735883] []? atomic_notifier_call_chain + 0x22 / 0x30 
 [44069.735894] []? notify_die + 0x2d / 0x30 
 [44069.735904] []? do_debug + 0x88 / 0x180 
 [44069.735915] []? debug_stack_correct + 0x30 / 0x38 
 [44069.735928] []? testhrarr_startup + 0x33 / 0x52 [testhrarr] 
 [44069.735940] []? testhrarr_proc_show + 0x32 / 0x57 [testhrarr] 
 [44069.735952] []? seq_read + 0x145 / 0x390 
 [44069.735963] []? seq_read + 0x0 / 0x390 
 [44069.735973] []? proc_reg_read + 0x64 / 0xa0 
 [44069.735985] []? vfs_read + 0x9F / 0x160 
 [44069.735995] []? proc_reg_read + 0x0 / 0xa0 
 [44069.736003] []? sys_read + 0x42 / 0x70 
 [44069.736013] []? syscall_call + 0x7 / 0xB 
 [44069.736019] Стек дампов из sample_hbp_handler 
 [44069.740132] testhrarr_timer_function: testhrarr_runcount 0  
 [44069.740146] testhrarr jiffies 10942435; ret: 1; ktnsec: 44069740142485 
 [44069.740159] Значение testhrarr_arr_first изменено 
 [44069.740169] Pid: 4302, комм: gnome-терминал Не испорчен 2.6.38-16-generiC# 67-Ubuntu 
 [44069.740176] Отслеживание вызовов: 
 [44069.740195] []? sample_hbp_handler + 0x2d / 0x3b [testhrarr] 
 [44069.740213] []? __perf_event_overflow + 0x90 / 0x240 
 [44069.740227] []? perf_swevent_event + 0x136 / 0x140 
 [44069.740239] []? perf_bp_event + 0x70 / 0x80 
 [44069.740253] []? sched_clock_local + 0xd3 / 0x1c0 
 [44069.740267] []? format_decode + 0x323 / 0x380 
 [44069.740280] []? hw_breakpoint_handler.clone.0 + 0x102 / 0x130 
 [44069.740292] []? hw_breakpoint_exceptions_notify + 0x22 / 0x30 
 [44069.740302] []? notifier_call_chain + 0x45 / 0x60 
 [44069.740313] []? atomic_notifier_call_chain + 0x22 / 0x30 
 [44069.740324] []? notify_die + 0x2d / 0x30 
 [44069.740335] []? do_debug + 0x88 / 0x180 
 [44069.740345] []? debug_stack_correct + 0x30 / 0x38 
 [44069.740364] []? init_intel_cacheinfo + 0x103 / 0x394 
 [44069.740379] []? testhrarr_timer_function + 0xed / 0x160 [testhrarr] 
 [44069.740391] []? __run_hrtimer + 0x6F / 0x190 
 [44069.740404] []? testhrarr_timer_function + 0x0 / 0x160 [testhrarr] 
 [44069.740416] []? hrtimer_interrupt + 0x108 / 0x240 
 [44069.740430] []? smp_apic_timer_interrupt + 0x56 / 0x8a 
 [44069.740441] []? apic_timer_interrupt + 0x31 / 0x38 
 [44069.740453] []? _raw_spin_unlock_irqrestore + 0x15 / 0x20 
 [44069.740465] []? try_to_del_timer_sync + 0x67 / 0xb0 
 [44069.740476] []? del_timer_sync + 0x29 / 0x50 
 [44069.740486] []? flush_delayed_work + 0x13 / 0x40 
 [44069.740500] []? tty_flush_to_ldisc + 0x12 / 0x20 
 [44069.740510] []? n_tty_poll + 0x4F / 0x190 
 [44069.740523] []? tty_poll + 0x6d / 0x90 
 [44069.740531] []? n_tty_poll + 0x0 / 0x190 
 [44069.740542] []? do_poll.clone.3 + 0xd0 / 0x210 
 [44069.740553] []? do_sys_poll + 0x134 / 0x1e0 
 [44069.740563] []? __pollwait + 0x0 / 0xd0 
 [44069.740572] []? pollwake + 0x0 / 0x60 
 ... 
 [44069.740742] []? pollwake + 0x0 / 0x60 
 [44069.740757] []? rw_verify_area + 0x6c / 0x130 
 [44069.740770] []? ktime_get_ts + 0xf8 / 0x120 
 [44069.740781] []? poll_select_set_timeout + 0x64 / 0x70 
 [44069.740793] []? sys_poll + 0x5a / 0xd0 
 [44069.740804] []? syscall_call + 0x7 / 0xB 
 [44069.740815] []? init_intel_cacheinfo + 0x23 / 0x394 
 [44069.740822] Стек дампов из sample_hbp_handler 
 [44069.744130] testhrarr_timer_function: testhrarr_runcount 1  
 [44069.744143] testhrarr jiffies 10942436; ret: 1; ktnsec: 44069744140055 
 [44069.748132] testhrarr_timer_function: testhrarr_runcount 2  
 [44069.748145] testhrarr jiffies 10942437; ret: 1; ktnsec: 44069748141271 
 [44069.752131] testhrarr_timer_function: testhrarr_runcount 3  
 [44069.752145] testhrarr jiffies 10942438; ret: 1; ktnsec: 44069752141164 
 [44069.756131] testhrarr_timer_function: testhrarr_runcount 4  
 [44069.756141] testhrarr jiffies 10942439; ret: 1; ktnsec: 44069756138318 
 [44069.760130] testhrarr_timer_function: testhrarr_runcount 5  
 [44069.760141] testhrarr jiffies 10942440; ret: 1; ktnsec: 44069760138469 
 [44069.760154] Значение testhrarr_arr_first изменено 
 [44069.760164] Pid: 4302, комм: gnome-терминал Не испорчен 2.6.38-16-generiC# 67-Ubuntu 
 [44069.760170] Отслеживание вызовов: 
 [44069.760187] []? sample_hbp_handler + 0x2d / 0x3b [testhrarr] 
 [44069.760202] []? __perf_event_overflow + 0x90 / 0x240 
 [44069.760213] []? perf_swevent_event + 0x136 / 0x140 
 [44069.760224] []? perf_bp_event + 0x70 / 0x80 
 [44069.760235] []? sched_clock_local + 0xd3 / 0x1c0 
 [44069.760247] []? format_decode + 0x323 / 0x380 
 [44069.760258] []? hw_breakpoint_handler.clone.0 + 0x102 / 0x130 
 [44069.760269] []? hw_breakpoint_exceptions_notify + 0x22 / 0x30 
 [44069.760279] []? notifier_call_chain + 0x45 / 0x60 
 [44069.760289] []? atomic_notifier_call_chain + 0x22 / 0x30 
 [44069.760299] []? notify_die + 0x2d / 0x30 
 [44069.760308] []? do_debug + 0x88 / 0x180 
 [44069.760318] []? debug_stack_correct + 0x30 / 0x38 
 [44069.760334] []? init_intel_cacheinfo + 0x103 / 0x394 
 [44069.760345] []? testhrarr_timer_function + 0xed / 0x160 [testhrarr] 
 [44069.760356] []? __run_hrtimer + 0x6F / 0x190 
 [44069.760366] []? send_to_group.clone.1 + 0xf8 / 0x150 
 [44069.760376] []? testhrarr_timer_function + 0x0 / 0x160 [testhrarr] 
 [44069.760387] []? hrtimer_interrupt + 0x108 / 0x240 
 [44069.760396] []? fsnotify + 0x1a5 / 0x290 
 [44069.760407] []? smp_apic_timer_interrupt + 0x56 / 0x8a 
 [44069.760416] []? apic_timer_interrupt + 0x31 / 0x38 
 [44069.760428] []? mem_cgroup_resize_limit + 0x108 / 0x1c0 
 [44069.760437] []? fput + 0x0 / 0x30 
 [44069.760446] []? sys_write + 0x67 / 0x70 
 [44069.760455] []? syscall_call + 0x7 / 0xB 
 [44069.760464] []? init_intel_cacheinfo + 0x23 / 0x394 
 [44069.760470] Стек дампов из sample_hbp_handler 
 [44069.764134] testhrarr_timer_function: testhrarr_runcount 6  
 [44069.764147] testhrarr jiffies 10942441; ret: 1; ktnsec: 44069764144141 
 [44069.768133] testhrarr_timer_function: testhrarr_runcount 7  
 [44069.768146] testhrarr jiffies 10942442; ret: 1; ktnsec: 44069768142976 
 [44069.772134] testhrarr_timer_function: testhrarr_runcount 8  
 [44069.772148] testhrarr jiffies 10942443; ret: 1; ktnsec: 44069772144121 
 [44069.776132] testhrarr_timer_function: testhrarr_runcount 9  
 [44069.776145] testhrarr jiffies 10942444; ret: 1; ktnsec: 44069776141971 
 [44069.780133] testhrarr_timer_function: testhrarr_runcount 10  
 [44069.780141] testhrarr [5, 7, 9, 11, 13,] 

... мы получаем трассировку стека ровно три раза - один раз за testhrarr_startup и дважды в testhrarr_timer_function: однажды для runcount==0 и один раз для runcount==5, как и ожидалось.

Ну, надеюсь, это поможет кому-то,
Ура!


Makefile

CONFIG_MODULE_FORCE_UNLOAD=y

# debug build:
# "CFLAGS was changed ... Fix it to use EXTRA_CFLAGS."
override EXTRA_CFLAGS+=-g -O0 

obj-m += testhrarr.o
#testhrarr-objs  := testhrarr.o

all:
    @echo EXTRA_CFLAGS = $(EXTRA_CFLAGS)
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

testhrarr.c

/*
 * [http://www.tldp.org/LDP/lkmpg/2.6/html/lkmpg.html#AEN189 The Linux Kernel Module Programming Guide]
 * https://stackru.com/questions/16920238/reliability-of-linux-kernel-add-timer-at-resolution-of-one-jiffy/17055867#17055867
 * https://stackru.com/questions/8516021/proc-create-example-for-kernel-module/18924359#18924359
 * http://lxr.free-electrons.com/source/samples/hw_breakpoint/data_breakpoint.c
 */


#include <linux/module.h>   /* Needed by all modules */
#include <linux/kernel.h>   /* Needed for KERN_INFO */
#include <linux/init.h>     /* Needed for the macros */
#include <linux/jiffies.h>
#include <linux/time.h>
#include <linux/proc_fs.h>  /* /proc entry */
#include <linux/seq_file.h> /* /proc entry */
#define ARRSIZE 5
#define MAXRUNS 2*ARRSIZE

#include <linux/hrtimer.h>

#define HWDEBUG_STACK 1

#if (HWDEBUG_STACK == 1)
#include <linux/perf_event.h>
#include <linux/hw_breakpoint.h>

struct perf_event * __percpu *sample_hbp;
static char ksym_name[KSYM_NAME_LEN] = "testhrarr_arr";
module_param_string(ksym, ksym_name, KSYM_NAME_LEN, S_IRUGO);
MODULE_PARM_DESC(ksym, "Kernel symbol to monitor; this module will report any"
      " write operations on the kernel symbol");
#endif

static volatile int testhrarr_runcount = 0;
static volatile int testhrarr_isRunning = 0;

static unsigned long period_ms;
static unsigned long period_ns;
static ktime_t ktime_period_ns;
static struct hrtimer my_hrtimer;

static int* testhrarr_arr;
static int* testhrarr_arr_first;

static enum hrtimer_restart testhrarr_timer_function(struct hrtimer *timer)
{
  unsigned long tjnow;
  ktime_t kt_now;
  int ret_overrun;

  printk(KERN_INFO
    " %s: testhrarr_runcount %d \n",
    __func__, testhrarr_runcount);

  if (testhrarr_runcount < MAXRUNS) {
    tjnow = jiffies;
    kt_now = hrtimer_cb_get_time(&my_hrtimer);
    ret_overrun = hrtimer_forward(&my_hrtimer, kt_now, ktime_period_ns);
    printk(KERN_INFO
      " testhrarr jiffies %lu ; ret: %d ; ktnsec: %lld\n",
      tjnow, ret_overrun, ktime_to_ns(kt_now));
    testhrarr_arr[(testhrarr_runcount % ARRSIZE)] += testhrarr_runcount;
    testhrarr_runcount++;
    return HRTIMER_RESTART;
  }
  else {
    int i;
    testhrarr_isRunning = 0;
    // do not use KERN_DEBUG etc, if printk buffering until newline is desired!
    printk("testhrarr_arr [ ");
    for(i=0; i<ARRSIZE; i++) {
      printk("%d, ", testhrarr_arr[i]);
    }
    printk("]\n");
    return HRTIMER_NORESTART;
  }
}

static void testhrarr_startup(void)
{
  if (testhrarr_isRunning == 0) {
    testhrarr_isRunning = 1;
    testhrarr_runcount = 0;
    testhrarr_arr[0] = 0; //just the first element
    hrtimer_start(&my_hrtimer, ktime_period_ns, HRTIMER_MODE_REL);
  }
}


static int testhrarr_proc_show(struct seq_file *m, void *v) {
  if (testhrarr_isRunning == 0) {
    seq_printf(m, "testhrarr proc: startup\n");
    testhrarr_startup();
  } else {
    seq_printf(m, "testhrarr proc: (is running, %d)\n", testhrarr_runcount);
  }
  return 0;
}

static int testhrarr_proc_open(struct inode *inode, struct  file *file) {
  return single_open(file, testhrarr_proc_show, NULL);
}

static const struct file_operations testhrarr_proc_fops = {
  .owner = THIS_MODULE,
  .open = testhrarr_proc_open,
  .read = seq_read,
  .llseek = seq_lseek,
  .release = single_release,
};


#if (HWDEBUG_STACK == 1)
static void sample_hbp_handler(struct perf_event *bp,
             struct perf_sample_data *data,
             struct pt_regs *regs)
{
  printk(KERN_INFO "%s value is changed\n", ksym_name);
  dump_stack();
  printk(KERN_INFO "Dump stack from sample_hbp_handler\n");
}
#endif

static int __init testhrarr_init(void)
{
  struct timespec tp_hr_res;
  #if (HWDEBUG_STACK == 1)
  struct perf_event_attr attr;
  #endif

  period_ms = 1000/HZ;
  hrtimer_get_res(CLOCK_MONOTONIC, &tp_hr_res);
  printk(KERN_INFO
    "Init testhrarr: %d ; HZ: %d ; 1/HZ (ms): %ld ; hrres: %lld.%.9ld\n",
               testhrarr_runcount,      HZ,        period_ms, (long long)tp_hr_res.tv_sec, tp_hr_res.tv_nsec );

  testhrarr_arr = (int*)kcalloc(ARRSIZE, sizeof(int), GFP_ATOMIC);
  testhrarr_arr_first = &testhrarr_arr[0];

  hrtimer_init(&my_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
  my_hrtimer.function = &testhrarr_timer_function;
  period_ns = period_ms*( (unsigned long)1E6L );
  ktime_period_ns = ktime_set(0,period_ns);

  printk(KERN_INFO
    " Addresses: _runcount 0x%p ; _arr 0x%p ; _arr[0] 0x%p (0x%p) ; _timer_function 0x%p ; my_hrtimer 0x%p; my_hrt.f 0x%p\n",
    &testhrarr_runcount, &testhrarr_arr, &(testhrarr_arr[0]), testhrarr_arr_first, &testhrarr_timer_function, &my_hrtimer, &my_hrtimer.function);


  proc_create("testhrarr_proc", 0, NULL, &testhrarr_proc_fops);


  #if (HWDEBUG_STACK == 1)
  hw_breakpoint_init(&attr);
  if (strcmp(ksym_name, "testhrarr_arr_first") == 0) {
    // just for testhrarr_arr_first - interpret the found symbol address
    // as int*, and dereference it to get the "real" address it points to
    attr.bp_addr = *((int*)kallsyms_lookup_name(ksym_name));
  } else {
    // the usual - address is kallsyms_lookup_name result
    attr.bp_addr = kallsyms_lookup_name(ksym_name);
  }
  attr.bp_len = HW_BREAKPOINT_LEN_1;
  attr.bp_type = HW_BREAKPOINT_W ; //| HW_BREAKPOINT_R;

  sample_hbp = register_wide_hw_breakpoint(&attr, (perf_overflow_handler_t)sample_hbp_handler);
  if (IS_ERR((void __force *)sample_hbp)) {
    int ret = PTR_ERR((void __force *)sample_hbp);
    printk(KERN_INFO "Breakpoint registration failed\n");
    return ret;
  }

  // explicit cast needed to show 64-bit bp_addr as 32-bit address
  // https://stackru.com/questions/11796909/how-to-resolve-cast-to-pointer-from-integer-of-different-size-warning-in-c-co/11797103#11797103
  printk(KERN_INFO "HW Breakpoint for %s write installed (0x%p)\n", ksym_name, (void*)(uintptr_t)attr.bp_addr);
  #endif

  return 0;
}

static void __exit testhrarr_exit(void)
{
  int ret_cancel = 0;
  kfree(testhrarr_arr);
  while( hrtimer_callback_running(&my_hrtimer) ) {
    ret_cancel++;
  }
  if (ret_cancel != 0) {
    printk(KERN_INFO " testhrarr Waited for hrtimer callback to finish (%d)\n", ret_cancel);
  }
  if (hrtimer_active(&my_hrtimer) != 0) {
    ret_cancel = hrtimer_cancel(&my_hrtimer);
    printk(KERN_INFO " testhrarr active hrtimer cancelled: %d (%d)\n", ret_cancel, testhrarr_runcount);
  }
  if (hrtimer_is_queued(&my_hrtimer) != 0) {
    ret_cancel = hrtimer_cancel(&my_hrtimer);
    printk(KERN_INFO " testhrarr queued hrtimer cancelled: %d (%d)\n", ret_cancel, testhrarr_runcount);
  }
  remove_proc_entry("testhrarr_proc", NULL);
  #if (HWDEBUG_STACK == 1)
  unregister_wide_hw_breakpoint(sample_hbp);
  printk(KERN_INFO "HW Breakpoint for %s write uninstalled\n", ksym_name);
  #endif
  printk(KERN_INFO "Exit testhrarr\n");
}

module_init(testhrarr_init);
module_exit(testhrarr_exit);

MODULE_LICENSE("GPL");

Вам нужна аппаратная поддержка для этого. Процессору нужно распознавать, когда записывается определенный адрес памяти, и вызывать некоторый код - обработчик прерываний или исключений. По своему опыту, я видел это на платформе PowerPC, но не на x86. Это называется аппаратной точкой наблюдения.

Теоретически, если вы работаете в эмуляторе, вы можете смоделировать это поведение, но я совершенно незнаком с существующими в данный момент эмуляторами.

РЕДАКТИРОВАТЬ: я выкопал немного больше, и кажется, что в Linux есть универсальный интерфейс hw breakpoint, и у x86 такой регистр. Это называется DR7. Посмотрите на функцию в 'include/linux/hw_breakpoint.h'. Похоже, что ptrace и / или perf используют эти интерфейсы. Удачи в отладке!

Другие вопросы по тегам