Про Mac OS X и совместимость (и ленивый линкер)

По случаю выхода XCode5 взялся я проверять, а что у этой штуки с backward compatibility, не сломалось ли чего, будет ли работать на Mac OS 10.5, к примеру...

И, надо сказать, результаты меня расстроили (помимо необъяснимой проблемы с VMWare

Для начала я наткнулся на широко известную проблему с ___bzero, на которую все кто мог уже наступили:

dyld: lazy symbol binding failed: Symbol not found: ___bzero
  Referenced from: ....
  Expected in: /usr/lib/libSystem.B.dylib

Лечение нашлось быстро: -mmacosx-version-min=10.5 эту проблему лечит.

Дальше стало хуже:

posix_memalign(), если попросить у нее мегабайтиков 100 (меньше не пробовал), на 10.5 не справляется. Причем, сайт developer.apple.com полагает, что эта функция появилась только в 10.6. Удивительное рядом - она вызывается, правда результат ее вызова неудовлетворительный (в коде поведение одинаковое что при ейной ошибке, что при исполнении без ошибок но и отсутствии аллокации, в детали не вдавался).

Сделал K (меньше N) экспериментов, убедился что на 10.5 malloc (у меня!) возвращает всегда 16-byte aligned, временно сделал затычку (ну и написал в TODO, что надо бы анонимный mmap() в это место).

Варез начал запускаться и местами работать. А местами - падать (не при запуске, в процессе). Потому что нашлось сразу две новые фишки:

  • __ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l (или, если человеческим языком, то std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)). Каковой __ostream_insert на самом деле используется в std::string, а std::string - в std::runtime_error.
  • У этой самой макоси - ленивая линковка, поэтому падает оно не при запуске, со словами "символ не нашелся", а на рантайме (weak linking).

Конкретную проблему я порешал, запретив использование RawSpeed на 10.5 (и posix_memalign() и __ostream::insert - обе проблемы были в RawSpeed), но осадок остался.

Дивлюсь я на небо, та думку гадаю:

  1. (деструктивная часть): "Программа запускается и как-то работает" в этой вашей Макоси не означает ничего. Мы просто могли не дойти до места, где потребуется (и не найдется) нужный символ. Тестирование обычным способом становится веселым.
  2. (конструктивная): а как бы проверить, что все символы, которые у моей софтины (и всех используемых библиотек вообще-то) в действительности присутствуют? Погуглил все известные слова, не нашел.

Comments

Так может прогнать под каким code coverage tool "под нагрузкой" и посмотреть, получилось ли близкое к 100%. Вопрос, конечно, как подсунуть такую нагрузку, чтобы код залез во все дырки всех if()/else()/else()/else(). Такого достичь сложно, но можно.
И, к сожалению, не знаком с маковыми компиляторами и тулзами, которые доступны.

Некоторое (изрядное) количество else{} - это ж обработка всяких исключительных ситуаций (битый файл, нехватка памяти, невозможность записать на RO-диск, ну там мало ли).

Ну и второе - это гуй. Я вообще не понимаю, как его нормально тестировать (знаю, какие-то средства есть, но тем не менее).

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

Почему бы и нет? При этом, создав пакет таких тестов Вы получите отличный "набор" для прогона тулзы перед запуском в паблик.

Я вот уже 25 лет обхожусь без TDD - и еще столько же обойдусь. И хотя бывают всякие смешные ошибки - я не понимаю, как я покрыл бы их тестами (если бы я додумался бы *это* проверять - я бы и без тестов проверил бы).

Ну вот из свежего: текстура 496x496 выводится в дырку 512x512. Чтобы это тестировать - надо целую инфраструктуру замутить: рендерить в фреймбуфер (специально подготовленное изображение, у которого value считается из координат) и проверять что все оказалось на своих местах. А глазами - если знать что ошибка может быть - просто сразу видно, без инфраструктуры. Ну и какой смысл?

ненене, никакой tdd, не.
Я бы, я бы попробовал бы как бы.
Сделать набор равов(?), обработка которых вцелом заставляет пройти код по максимально возможным закоулкам, причём в двух вариантах
1) просто равы
2) "битые" равы (ну, чтобы отлоивить вские косяки в не шибко важных (вероятно?) местах, типа "парсинг экзифа")

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

"Глазами" столько всего, боюсь, не отследить.

LibRaw так и тестируется, по набору raw.

Добавить туда еще статистику - можно, но это минимальный кусок rawdigger. Остальное - скорее таки гуй, чем не он (ну там размещение сетки на картинке мишени - дохера кода, а подсчет статистики - недохера).

Под Unixами так же. По умолчанию ELF использует lazy bindings, выключается переменной окружения LD_BIND_NOW. Как выключить для mach-O, не знаю.

Solaris придумал symbol versioning, чтобы можно было определять наличие требуемых символов без их разрешения. GNU творчески развил, каждый символ получает версию. Часто авторы библиотек добавляют новые символы в существующие версии, убивая полезность проверки.

http://www.fortran-2000.com/ArnaudRecipes/sharedlib.html

Тут пишут, что DYLD_BIND_AT_LAUNCH.

> убедился что на 10.5 malloc (у меня!) возвращает всегда 16-byte aligned

на x86-64 malloc() всегда должен возвращать 128-bit aligned -- требуется для long double.

есть что-то похожее:

For example, suppose in Xcode you set the deployment target (minimum required version) to OS X v10.2 and the target SDK (maximum allowed version) to OS X v10.3. During compilation, the compiler would weakly link any interfaces that were introduced in OS X version 10.3 while strongly linking earlier interfaces. This would allow your application to continue running on OS X version 10.2 but still take advantage of newer features when they are available.

Ну вот насколько я понял, этот самый deployment target выражается в -mmin-macosx-version=

Кроме того, я заглянул в потроха тамошнему STL-ю (из которого вырос тот iostream, который). Да, там что-то пытаются запрятать через #ifdef __TARGETING_4_0_DYLIB но даже если этот дефайн явно подефайнить, зависимость от std::__ostream_insert остается.