Я использую perf для профилирования сервера узла. Проблема в том, что после того, как я останавливаю сервер, perf фиксирует переполнение буфера и не записывает никаких данных.
perf record -e cycles:u -g -- npm run start
[ perf record: Woken up 96 times to write data ]
*** buffer overflow detected ***: terminated
Я попытался использовать memcheck valgrind для поиска переполнения буфера, но он ничего не сообщает, что означает, что это может быть переполнение статического буфера. Есть ли способ для perf сообщить, где происходит переполнение буфера?
Результат выполнения perf под gdb с точкой останова на __stack_chk_fail
Thread 1 "perf" received signal SIGABRT, Aborted.
__pthread_kill_implementation (threadid=<optimized out>, signo=signo@entry=6, no_tid=no_tid@entry=0)
at ./nptl/pthread_kill.c:44
44 ./nptl/pthread_kill.c: No such file or directory.
(gdb) bt
#0 __pthread_kill_implementation (threadid=<optimized out>, signo=signo@entry=6, no_tid=no_tid@entry=0)
at ./nptl/pthread_kill.c:44
#1 0x00007ffff72d4d2f in __pthread_kill_internal (signo=6, threadid=<optimized out>) at ./nptl/pthread_kill.c:78
#2 0x00007ffff7285ef2 in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#3 0x00007ffff7270472 in __GI_abort () at ./stdlib/abort.c:79
#4 0x00007ffff72c92d0 in __libc_message (action=action@entry=do_abort,
fmt=fmt@entry=0x7ffff73e3210 "*** %s ***: terminated\n") at ../sysdeps/posix/libc_fatal.c:155
#5 0x00007ffff7361e82 in __GI___fortify_fail (msg=msg@entry=0x7ffff73e31b6 "buffer overflow detected")
at ./debug/fortify_fail.c:26
#6 0x00007ffff7360990 in __GI___chk_fail () at ./debug/chk_fail.c:28
#7 0x00005555557e7ddd in memcpy (__len=40, __src=0x555556a28b38, __dest=0x7fffffff843c)
at /usr/include/x86_64-linux-gnu/bits/string_fortified.h:29
#8 write_buildid (fd=0x7fffffff8590, misc=<optimized out>, pid=-1, bid=0x555556a28b38,
name_len=<optimized out>, name=0x555556a28c0c "/opt/pylon/lib/libpylonbase-6.1.1.so") at util/build-id.c:312
#9 machine__write_buildid_table (machine=machine@entry=0x555555d9bef0, fd=fd@entry=0x7fffffff8590)
at util/build-id.c:361
#10 0x00005555557e865e in perf_session__write_buildid_table (session=session@entry=0x555555d9bd00,
fd=fd@entry=0x7fffffff8590) at util/build-id.c:374
#11 0x000055555581c4b9 in write_build_id (ff=ff@entry=0x7fffffff8590, evlist=evlist@entry=0x555555d96d60)
at util/header.c:320
#12 0x0000555555824fa3 in do_write_feat (evlist=0x555555d96d60, p=<synthetic pointer>, type=2, ff=0x7fffffff8590)
at util/header.c:3224
#13 perf_header__adds_write (fd=3, evlist=0x555555d96d60, header=<optimized out>) at util/header.c:3269
#14 perf_session__write_header (session=<optimized out>, evlist=0x555555d96d60, fd=3, at_exit=at_exit@entry=true)
at util/header.c:3353
#15 0x0000555555760777 in record__finish_output (rec=0x555555b9bb40 <record>) at builtin-record.c:1236
#16 0x0000555555763560 in __cmd_record (rec=0x555555b9bb40 <record>, argv=<optimized out>, argc=<optimized out>)
at builtin-record.c:2026
#17 cmd_record (argc=<optimized out>, argv=<optimized out>) at builtin-record.c:2835
#18 0x00005555557dc8a3 in run_builtin (p=p@entry=0x555555ba6cb8 <commands+216>, argc=argc@entry=8,
argv=argv@entry=0x7fffffffdb90) at perf.c:312
#19 0x000055555574af48 in handle_internal_command (argv=0x7fffffffdb90, argc=8) at perf.c:364
#20 run_argv (argv=<synthetic pointer>, argcp=<synthetic pointer>) at perf.c:408
#21 main (argc=8, argv=0x7fffffffdb90) at perf.c:538
Что интересно, я получаю ошибку переполнения буфера только тогда, когда я включаю конкретную надстройку C++, которая загружает пару дополнительных общих библиотек. Если я запускаю сервер без этой надстройки C++, то perf не сообщает о переполнении буфера.
В моей системе (Arch GNU/Linux с gcc 12.2.1, glibc 2.37-2) я получаю *** stack smashing detected ***: terminated
из тестовой программы (godbolt.org/z/3v69o98or). Запуск на Godbolt дает то же самое сообщение. Таким образом, это то же самое форматирование, но другое сообщение. Возможно, просто более старая версия glibc. Так что, вероятно, ошибка производительности.
Можете ли вы запустить perf record
под GDB с точкой останова на __stack_chk_fail
? (Тогда bt
ака обратная трассировка должна работать, чтобы увидеть, по крайней мере, непосредственного вызывающего абонента. Может быть, не дальше по стеку вызовов, если переполнение буфера прошло мимо канарейки к адресу возврата!) Какую perf
версию вы используете? Вы пытались воспроизвести это на более новой версии ядра/perf?
Я запускаю это на Debian с версией ядра 5.10.0-20-amd64 с версией perf 5.10.158. Я попробую запустить perf под gdb и посмотреть, смогу ли я получить стек вызовов
Отладочная сборка самого perf
, вероятно, позволит вам увидеть, частью какой функции является 0x00005555557c27bd
. (Или отладочные символы для имеющейся у вас сборки, если Debian поставляет отладочные символы в отдельном пакете, который вы можете установить, или что-то в этом роде.) Если вы не хотите отлаживать perf
самостоятельно, вы должны отправить отчет об ошибке разработчикам perf; им, вероятно, понадобится минимальный воспроизводимый пример кода, который вы профилируете, если вы не можете указать им правильное направление с помощью обратной трассировки с рабочими символами отладки и некоторого расследования GDB вызывающей стороны __GI___chk_fail
. Адреса кажутся действительными для PIE
Достаточно забавно, что я загрузил и построил perf с символами отладки, чтобы сделать именно это, и теперь он работает... Это тот же точный номер версии, но, может быть, есть разница в коммите? Это может быть проблема и с пакетом Debian. Или, что более вероятно, я построил его без опции защиты стека, поэтому переполнение буфера все еще происходит, но это не фатально.
По умолчанию Debian GCC, вероятно, включает -fstack-protector-strong
. Другая возможность заключается в том, что это была ошибка в perf
, которая с тех пор была обнаружена и исправлена, если вы загрузили последнюю версию. В таком случае отлично, не нужно ни о чем сообщать вышестоящим, просто наслаждайтесь использованием более нового, лучшего, менее глючного perf
. Или, если это была просто удача другого макета, это может повториться в других случаях, но вы будете готовы к отладке сейчас.
Восстановил производительность с помощью -fstack-protector-strong
и по-прежнему никаких проблем. Я просмотрел журналы сборки Debian и подтвердил, что они используют -fstack-protector-strong
наряду со многими другими параметрами. Это кажется странным пограничным случаем и, вероятно, даже не стоит отчета об ошибке. Спасибо за вашу помощь!
Вы собираете тот же исходный пакет, из которого была собрана сбойная версия, или более новую версию? Если вы собираете из того же исходного кода, что и исходный двоичный пакет, странно, что он не падает; -g
не влияет на генерацию кода, только на метаданные. Я думаю, что даже статические буферы должны оказаться в одних и тех же позициях относительно друг друга, а также, конечно, пространство стека, если вы использовали те же параметры оптимизации.
Это та же версия. Я действительно непреднамеренно воспроизвел проблему. Perf имеет ряд «функций», которые доступны только в том случае, если у вас установлены определенные библиотеки. Я установил некоторые библиотеки, и теперь perf снова падает! Теперь, когда у меня есть символы отладки, я отправлю отчет об ошибке.
Это ошибка в перформансе. Perf записывает только первые 20 символов идентификатора сборки с выделенным стеком массивом из 20 символов. У одной из загружаемых библиотек был идентификатор сборки, длина которого превышала 20 символов. Ошибка в ядре означала, что размер будет записан как более 20 символов, и когда мы попытались скопировать идентификатор сборки, мы разбили стек.
perf
может говорить о буферах сэмплов, написанных ядром. «Переполнение» может означать, что у ядра закончились буферы, предоставленные пространством пользователя, и ему пришлось отбросить данные, в отличие от уязвимости безопасности, связанной с переполнением буфера стека. Или, если это именно то сообщение, котороеgcc -fstack-protector-strong
использует, когда обнаруживает, что канарейка стека была перезаписана, то, вероятно, это так.