Наблюдать за изменением (адресом памяти) в ядре 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; благодаря этим я нашел:
- отладка - аппаратные точки останова ядра Linux - переполнение стека
- Аппаратная точка останова (или точка наблюдения) - Архивы ядра Linux
... с помощью которого я мог бы развить пример, который я публикую здесь, 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 используют эти интерфейсы. Удачи в отладке!