LINUX.ORG.RU

Изменение поведения memcpy в glibc привело к странным ошибкам

 ,


0

2

Изменение поведения функции memcpy() в реализации glibc для x86_64 (для ia-32 ничего не изменилось) привело к странным ошибкам во многих программах. Например, искажению звука при проигрывании.

Проблема в том, что memcpy для перекрывающихся участков памяти теперь работает некорректно (как в общем-то и должно быть согласно документации). Но, как выяснилось, авторы многих проектов документацию не читали.

Несмотря на появление в обсуждении Линуса Торвальдса, у которого также появились проблемы со звуком в некоторых программах на его компьютере, ошибка была закрыта по причине «not a bug». В сообщении #38 Линус предлагает способ обхода этой проблемы.

>>> Подробности

имхо в базовых библиотеках надо быть консервативней.

Добавили неприятный баг. К тому же если автор не собирает программу на x86_64, то и лечить этот баг он не будет.

И профит весьма сомнителен. Ускорили функцию, только пользоваться ею теперь нельзя. Используте мол другую...

AVL2 ★★★★★ ()
Ответ на: комментарий от anonizmus

Речь не о том чтобы мигрировать с предыдущей версии а о новых закупках. Они уже будут RHEL 6, а не предыдущей версии. Те кто используют RHEL 5.x конечно сразу не перейдут, у них поддержка за эту версию проплачена, а не за новую. Но речь не об том кто когда перейдёт, а о том что кто будет покупать сейчас. И закупки эти будут не версий на основанных на ядрах 2.4 или древних 2.6

Deleted ()
Ответ на: комментарий от sergej

А почему она не выдает segfault?

а почему она должна его выдавать? Просто портит данные при копировании.

Ну это больше вопрос идеологии программирования. Я обычно придерживаюсь максимы «fail fast, fail clean». Мне кажется, что получить свалившуюся программу с указанием точного адреса и фрейм стека лучше, чем иметь программу «заикающуюся» при некоторых неясных случаях. Понятно, что также при этом должна иметься возможность прикрыть эту ошибку костылями до тех пор, пока она не будет исправлена.

Можно было бы наметить примерно такой путь исправления ошибок.

1. По умолчанию запускаем программу с версией memcpy, которая падает при вызывании некорректных параметров.

2. Когда программа падает - регистрируем ошибку и запускаем программу с костыльной версией memcpy. Дожидаемся исправления ошибки. Переходим к шагу 1.

sign ()
Ответ на: комментарий от aim1159

> он был, если я правильно понимаю, не бажным до glibc 2.12

Он всегда был бажным, потому что в описании функции memcpy запрет на перекрывающуюся память был с давних пор, а скорее всего, всегда. Вон Eddy_Em привёл цитату из старого man'a: http://www.linux.org.ru/jump-message.jsp?msgid=5545961&cid=5546197

И тут _ничего_ не поменялось.

anonymous_incognito ★★★★★ ()
Ответ на: комментарий от sign

> Ну это больше вопрос идеологии программирования. Я обычно придерживаюсь максимы «fail fast, fail clean»

Остаётся всё же неясным вопрос, почему бы разработчики glibc должны писать лишний код понижать скорость работы memcpy ради тех, кто не удосужился узнать, что ему необходима memmove? И самое главное, сколько ещё в glibc функций, которых кто-то использует неправильно за пределами документированного поведения?

anonymous_incognito ★★★★★ ()
Ответ на: комментарий от anonymous_incognito

> Вроде SSE3 уже давным-давным поддерживается AMD.

Даю маячок, SSSE3 != SSE3.

andreyu ★★★★★ ()
Ответ на: комментарий от ZuBB

Любопытно. Линус ещё и подкалывает: «Вы действительно собираетесь выпустить Fedora-14, зная, что не работает Adobe flash?»

anonymous_incognito ★★★★★ ()

Тема новости напомнила мне чем-то похожую проблему из совершенно другой области.

В SQL, если порядок возвращаемых данных не указан, предполагается, что данные могут возвращаться в произвольном порядке.

Но проблема в том, что обычно они возвращаются все-таки в каком-то определенном порядке, например, соответствующем использованным индексам.

При тестировании такие места, когда порядок возвращаемых данных важен, но не задан, могут оставаться невыявленными, а потом ошибка может проявиться в самый неподходящий момент.

Например, при параллельном выполнении запроса или при изменении плана выполнения запроса.

sign ()

А у меня рецепт от Линуса не сработал

$ LD_PRELOAD=mymemcpy.so /opt/google/chrome/google-chrome
ERROR: ld.so: object 'mymemcpy.so' from LD_PRELOAD cannot be preloaded: ignored.
ERROR: ld.so: object 'mymemcpy.so' from LD_PRELOAD cannot be preloaded: ignored.
ERROR: ld.so: object 'mymemcpy.so' from LD_PRELOAD cannot be preloaded: ignored.
ERROR: ld.so: object 'mymemcpy.so' from LD_PRELOAD cannot be preloaded: ignored.
ERROR: ld.so: object 'mymemcpy.so' from LD_PRELOAD cannot be preloaded: ignored.
ERROR: ld.so: object 'mymemcpy.so' from LD_PRELOAD cannot be preloaded: ignored.

кто-то может подсказать в чём может быть проблема?

rudchenkos ()
Ответ на: комментарий от rudchenkos

> подозреваю что SELinux... а ну-ка я его отключу на минутку

нет, не SELinux

rudchenkos ()
Ответ на: комментарий от anonymous_incognito

Остаётся всё же неясным вопрос, почему бы разработчики glibc должны писать лишний код понижать скорость работы memcpy ради тех, кто не удосужился узнать, что ему необходима memmove?

Я не понял про какой лишний код идет речь? Про код проверки на пересечение диапазонов?

Да, согласен, в общем случае это проблема. «Защитное» программирование без меры может привести к тому, что основное время будет затрачиваться на выполнение проверок. К тому же можно легко изменить сложность алгоритмов. Например, если мы попытаем при каждой операции двоичного поиска сначала проверять, что массив упорядочен, то мы получим большие проблемы в виде квадратичной сложности алгоритмов.

Но в данном конкретном случае, именно для этой функции, мне кажется проверка была бы не лишней. Неужели так сложно проверить пересечение двух диапзонов? Причем наверняка это можно сделать с какими-нибудь новомодными инструкциями без выполнения операций jump (чтобы не было проблем с предсказанием переходов).

sign ()
Ответ на: комментарий от devl547

> полный путь к либе указывай.

ага, спасибо! я уже тоже прочитал это в обсуждении самого бага (точнее NOABUG'а :) )

rudchenkos ()
Ответ на: комментарий от rudchenkos

chromium использует suid helper для песочницы,
LD_PRELOAD может и не пройти для него.

Sylvia ★★★★★ ()
Ответ на: комментарий от AVL2

>Добавили неприятный баг.

Какой баг?! Это не баг, это правильная работа функции описанная, в черт знает каком количестве документов.

linuxfan ()
Ответ на: комментарий от anonymous_incognito

>Остаётся всё же неясным вопрос, почему бы разработчики glibc должны писать лишний код понижать скорость работы memcpy ради тех, кто не удосужился узнать, что ему необходима memmove?

А что, glibc рисует? можт файлики создает? управляет роботами, расчеты проводит? Ни хрена. Все это делают прикладные программы, которые используют glibc.

А это значит, что любые изменения, ломающие тонны софта, должны быть хорошо обоснованы, долдны быть средства диагностики, отработанные рецепты исправления софта. А не так, что молча обновились и готово, сломато...

А тут что? Линус пишет, что его тупая похаканая версия работает быстрее в 6 случаях из 10. То есть, никакого смысла ломать совместимость вообще не было...

И самое главное, сколько ещё в glibc функций, которых кто-то использует неправильно за пределами документированного поведения?

Так вот, добавляйте ассерты, дебаги. Пишите валгринды. Чтоб весь дмесаг был забит вопляи о кривых руках. Только спасибо скажут. А нафиг ломать-то?

AVL2 ★★★★★ ()
Ответ на: комментарий от sign

> Я не понял про какой лишний код идет речь? Про код проверки на пересечение диапазонов?

Угу.

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

Несложно каждый раз проверять входные условия на допустимость, но так на таком низком уровне как memcpy обычно не делают.

Причем наверняка это можно сделать с какими-нибудь новомодными инструкциями без выполнения операций jump (чтобы не было проблем с предсказанием переходов).

Тогда уж лучше не мудрить, а использовать memcpy в том тривиальном виде, что предложил Линус. Оно даже как бы не быстрее оптимизированного варианта реально работает.

anonymous_incognito ★★★★★ ()
Ответ на: комментарий от AVL2

> Добавили неприятный баг.

Никаких багов не добавили. Просто привели поведение согласно документации. Вон торвальдсу это решение не понравилось, так он врапер для memcpy() написал. И судя по его тестам его врамер шустрее оптимизированного разработчиками glibc.

К тому же если автор не собирает программу на x86_64, то и лечить этот баг он не будет.


Ну и хрен с ним.

andreyu ★★★★★ ()
Ответ на: комментарий от AVL2

разработчики glibc должны писать по спекам,
если в функции заявлено что она работает так, то она так и должна работать,
те, кто используют вариант другой работы - сами делают себе проблемы, в частности с портируемостью кода на другие варианты libc

Sylvia ★★★★★ ()
Ответ на: комментарий от linuxfan

Это glibc. Это либа. В либах далеко не всегда бывают функции, которые гарантированно работают так, как надо.

AVL2 ★★★★★ ()
Ответ на: комментарий от rudchenkos

> А у меня рецепт от Линуса не сработал

Читайте дальше - там многие предлагают использовать полный путь к либе. Да и Торвальдс сожалеет, что вбил руками, а не скопировал решение.

andreyu ★★★★★ ()
BUGS
     In this implementation memcpy() is implemented using bcopy(3), and
     therefore the buffers may overlap.  On other systems, copying overlapping
     buffers may produce surprises.  A simpler solution is to not use
     memcpy().

OpenBSD 4.8

AptGet ★★★ ()
Ответ на: комментарий от AVL2

>Это glibc. Это либа.

Вот именно, что это glibc, а не какая-нибудь сраная libphonon.so. glibc работает четко, заботится об обратной совместимости, производительности и вообще достойна высших похвал.

А у конечного хомячка всегда есть выбор: не использовать говнософт или не использовать glibc с поддержкой SSE4.x.

linuxfan ()
Ответ на: комментарий от AptGet

спасибо за цитату,
т.е БСДшники намеренно пошли на создание баги )

Sylvia ★★★★★ ()
Ответ на: комментарий от Sylvia

>разработчики glibc должны писать по спекам, если в функции заявлено что она работает так, то она так и должна работать,

Ура, мы оптимизировали либу! Теперь она работает более капризно, но зато медленно.

те, кто используют вариант другой работы - сами делают себе проблемы, в частности с портируемостью кода на другие варианты libc

большой вопрос - memcpy есть везде, а вот гнутые заменители с проверками ой ли...

AVL2 ★★★★★ ()
Ответ на: комментарий от AptGet
DESCRIPTION
       The memcpy() function copies n bytes from memory area src to memory area dest.  The memory areas should not overlap.  Use memmove(3) if the memory areas do overlap.
CONFORMING TO
       SVr4, 4.3BSD, C89, C99, POSIX.1-2001.

Толерантность к быдлокоду порождает бомбы замедленного действия.

linuxfan ()
Ответ на: комментарий от linuxfan

>заботится об обратной совместимости

хм, даже не знаю, как это интерпретировать.

AVL2 ★★★★★ ()
Ответ на: комментарий от Sylvia

>спасибо за цитату, т.е БСДшники намеренно пошли на создание баги )

Да, производительностью в OpenBSD и не пахнет, зато позаботились о нечитателях манов.

AptGet ★★★ ()
Ответ на: комментарий от linuxfan

> Толерантность к быдлокоду порождает бомбы замедленного действия.

А я согласен.

AptGet ★★★ ()

кстати так, к слову...

ICC обычно переопределяет memcpy() на внутреннюю встроенную быструю функцию

Sylvia ★★★★★ ()
Ответ на: комментарий от AVL2

>хм, даже не знаю, как это интерпретировать.

Подумай о том, что даже на новых 64-битных системах можно без проблем запустить старый 32-битный нативный Neverwinter.

linuxfan ()

Несмотря на то, что Линус довольно тонко там троллит, при этом успевая быстроенько накатать workaround, да ещё и показав что «оптимизация» не очень-то что-либо ускорила, его позиция всё-таки не очень понятна.

Типа: зачем всё менять, если это что-то ломает у тех, кто не мледует указаниям и рекомедациям? Да и вообще, все рекомедации фигня, раз при компиляции и работе некорректного кода никаких ошибок не выдаётся.

Именно из-за этого в ядре так много gcc-мов, и (наверное) прочих грязноватых хаков?

octy ★★ ()
Ответ на: комментарий от linuxfan

откуда данные ? ) gcc как раз линкует с libc версией

Sylvia ★★★★★ ()
Ответ на: комментарий от Sylvia

>откуда данные ? )

Из первых рук. Во-первых, это особо выделялось в changelog к какому-то релизу gcc версий. Во-вторых, gcc -S -O2.

linuxfan ()
Ответ на: комментарий от linuxfan

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

Sylvia ★★★★★ ()
Ответ на: комментарий от linuxfan

Это как раз понятно.

на этом фоне непонятно такое безответственное поведение.

AVL2 ★★★★★ ()
Ответ на: комментарий от octy

>Типа: зачем всё менять, если это что-то ломает у тех, кто не мледует указаниям и рекомедациям?

потому что их много. И их код нужен.

Да и вообще, все рекомедации фигня, раз при компиляции и работе некорректного кода никаких ошибок не выдаётся.

от glibc ждут сверхстабильной работы. Иначе все рухнет, что и происходит.

AVL2 ★★★★★ ()
Ответ на: комментарий от Sylvia

>он может делать инлайн, если это есть в заголовочных файлах

В смысле, если где-то есть #define memcpy __builtin_memcpy?

linuxfan ()
Ответ на: комментарий от Sylvia

вот еще к слову


http://software.intel.com/en-us/articles/memcpy-performance/

The source and destination regions are assumed not to overlap, but no checking is performed

прояснила по gcc, источник тот же

32-bit gcc is aggressive about in-lining memcpy() into optimized code (-O2/-O3). Unless the compiler has sufficient information to know of a contrary preference, 'rep movsl' will appear in the .o or .s code output.
This aggressive in-lining behavior may be removed by the gcc option -fno-builtin-memcpy By itself, this option is inadvisable, as the separately compiled memcpy() usually provided in glibc is ineffective

НО (!)

x86-64 gcc memcpy()

As x86-64 supports no non-SSE2 platforms, builtin-memcpy is not present.


Sylvia ★★★★★ ()
Ответ на: комментарий от sign

>>> А почему она не выдает segfault?

а почему она должна его выдавать? Просто портит данные при копировании.

Ну это больше вопрос идеологии программирования.

Просто это не сегфолт по определению: оба участка памяти принадлежат программе, оба, скорее всего, доступны и на чтение, и на запись.

guitarist ★★ ()
Ответ на: комментарий от Sylvia
$ cat 1.c
#include <string.h>
#include <stddef.h>

int main()
{
	return (int)(ptrdiff_t)memcpy(NULL, NULL + 10, 10);
}
$ gcc -S 1.c && cat 1.s
// skipped
	movl	$10, %edx
	movl	$10, %esi
	movl	$0, %edi
	call	memcpy
//skipped

Но

$ gcc -S -O2 1.c && cat 1.s
	movq	10, %rax
	movq	%rax, 0
	movzwl	18, %eax
	movw	%ax, 8
	xorl	%eax, %eax
	ret

А вот если добавить argc, и делать memcoy(dst, src, argc), то получится вызов memcpy из glibc. Итого баг на 64-битной архитектуре проявляется не всегда

linuxfan ()
Ответ на: комментарий от linuxfan

Подтверждаю.

$ gcc -O2 memcpytest.c -o memcpytest
$ nm memcpytest | grep memcpy
00000000 F memcpytest.c

$ gcc memcpytest.c -o memcpytest
$ nm memcpytest | grep memcpy
         U memcpy
00000000 F memcpytest.c

$ gcc -v
Reading specs from /usr/lib/gcc-lib/i386-unknown-openbsd4.8/4.2.1/specs
Target: i386-unknown-openbsd4.8
Configured with: OpenBSD/i386 system compiler
Thread model: posix
gcc version 4.2.1 20070719
AptGet ★★★ ()
Ответ на: комментарий от AptGet

тем не менее
:/bin$ nm -D awk |grep memcpy
U memcpy
:/bin$ nm -D bash |grep memcpy
U memcpy
:/bin$ nm -D gzip |grep memcpy
U memcpy
:/bin$ nm -D cat |grep memcpy
U memcpy


и если бы gcc столь добросовестно заменял memcpy на встроенную версию, то навряд ли бы поднималась эта тема где обсуждается не gcc, а glibc memcpy, или вы думаете что флеш компилят без -O2 ? )

Sylvia ★★★★★ ()

>как выяснилось, авторы многих проектов документацию не читали.

но хотя бы осуждают?

registrant ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.