Охота (память, связанный с GC), исчезновение гейзенбаг без ASLR
ОС: Linux/Debian/Sid/x86_64 (и Linux/Debian/Testing/x86_64); моя система GCC, используемая для компиляции, - это 6.1.1 (и 5.3 с Debian / Testing). Gnu libc составляет 2,22; Ядро Linux - 4.5; GDB - это система 7.10 или моя собственная, собранная из источника FSF, 7.11
Я ищу (почти две недели) гейзенбаг, связанный с сборкой памяти и мусора, в экспериментальной ветви GCC MELT (MELT грубо говорит на языке, подобном Lisp, для настройки компилятора GCC; диалект MELT переводится на C++ с использованием MELT сам), который вы могли бы получить с
svn co -r236207 svn://gcc.gnu.org/svn/gcc/branches/melt-branch gcc-melt
затем (как для каждого варианта или ветви GCC) построить его во внешнем дереве, например
mkdir _ObjMelt
cd _ObjMelt
../gcc-melt/configure --disable-bootstrap --enable-checks=gc \
--enable-plugins --disable-multilib --enable-languages=c,c++,lto
(вы можете передать другие варианты ../gcc-melt/configure
например, CXXFLAGS='-g3 -O0 -DMELT_HAVE_RUNTIME_DEBUG=1'
если ты хотел; Вы можете удалить --enable-checks=gc
опция)
и конечно make
(или же make -j4
); эта сборка может занять более получаса (и, вероятно, потерпит неудачу с ASLR, см. ниже)
MELT имеет сборщик мусора для копирования поколений (и я подозреваю, что ошибка является ключевым в нем) и использует много метапрограммирования (в частности, большая часть кодов сканирования и пересылки для копирующего GC генерируется MELT).
( valgrind
здесь не поможет: мы реализуем копирующий GC, а сам GCC - даже без MELT- утечка памяти)
MELT загружается. Обычная процедура сборки восстанавливает вдвое испускаемый код C++ из исходного кода MELT. Обычный способ - создать код на C++ make
чтобы получить общий объект, и dlopen
этот общий объект, и снова.
Без ASLR сборка всегда завершается успешно (и она выполняет значительный тест: начальную загрузку MELT и анализ времени выполнения MELT с помощью компиляции, расширенной MELT). И я мог бы даже восстановить код времени выполнения с make upgrade-warmelt
,
Но с включенным ASLR сборка завершается неудачно, сбой всегда происходит одинаково (обратите внимание, что cc1plus
тает один):
cc1plus: note: MELT got fatal failure from ../../gcc-melt/gcc/melt-runtime.h:900
cc1plus: fatal error: corrupted memory heap with null magic discriminant
in 0x2bab6a8; GC#11
compilation terminated.
MELT BUILD SCRIPT FAILURE:
melt-build-script.tpl:382/307-melt-build-script.tpl:459/382 failed
with arguments @meltbuild-stage2/warmelt-normatch.args
Я отключаю ASLR, например, с exec setarch $(uname -m) -R /bin/bash
; ну и конечно же при беге gdb
ASLR отключен по умолчанию (если я не сделаю set disable-randomization 0
как команда GDB).
Мой коллега Франк Ведрин предложил мне использовать средства обратного исполнения gdb
; в принципе, это должно быть так же просто, как установить точку останова в моем ГХ (и в fatal_error
& melt_fatal_info
называется melt_fatal_error
макрос...), достичь GC#11
состояние, сделать record
для последующего выполнения в обратном порядке, запустите ошибочный случай (с помощью set disable-randomization 0
отключить ASLR) до "сбоя", затем reverse-cont
до точки останова в GC, и использовать watch
с умом. К сожалению, это вызывает широко известную ошибку GDB ( Sourceware # 19365, Ubuntu # 1573786, Redhat # 1136403,...) - что недавние снимки GDB похожи gdb-7.11.50.20160514
не правильно-
(Теперь я испытываю желание избежать этой ошибки GDB, возможно, имея свой собственный memset
& memcpy
подпрограммы с #pragma GCC optimize ("-Og")
до них; но это выглядит слишком далеко)
Для чего это стоит, сообщение о сбое дается следующим кодом (около строки 900 моего melt-runtime.h
):
static inline int
melt_magic_discr (melt_ptr_t p)
{
if (!p)
return 0;
#if MELT_HAVE_DEBUG > 0 || MELT_HAVE_RUNTIME_DEBUG > 0
if (MELT_UNLIKELY(!p->u_discr))
{
/* This should never happen, we are asking the discriminant of a
not yet filled, since cleared, memory zone. */
melt_fatal_error
("corrupted memory heap with null discriminant in %p; GC#%ld",
(void*) p, melt_nb_garbcoll);
}
#endif /*MELT_HAVE_DEBUG or MELT_HAVE_RUNTIME_DEBUG */
gcc_assert (p->u_discr != NULL);
return p->u_discr->meltobj_magic;
}
Я предполагаю, что ошибка может быть трудной ошибкой GC вокруг пересылки "дискриминанта" (своего рода поля "тип" или "класс" или "метаданные" в каждом значении MELT) в редком случае, когда этот дискриминант все еще в молодом поколении... Добавление некоторого кода, чтобы избежать того, что действительно сделало ошибку, случается позже, но я совсем не уверен.
Любые подсказки или советы по отладке heisenbug, относящиеся к фактическим виртуальным адресам (следовательно, разумным для ASLR!), Приветствуются.
Я даже добавил код инициализации, чтобы иметь возможность mmap
или же sbrk
несколько бесполезных мегабайт, надеясь "воспроизвести" случайный адрес, заданный mmap
(вызывается calloc
используется MELT и его GC). Это еще не помогло!
1 ответ
Подход, который я использовал в своем сборщике мусора Smalltalk, состоит в том, чтобы скопировать кучу перед каждым сборщиком мусора и выполнить сборку мусора в копии, а затем повторить отладку в случае сбоя копии. Это относительно тривиально, если система, как и моя, разработана на высокоуровневом языке; Копирование кучи - это просто копирование графа объектов, составляющих симуляцию виртуальной машины (а в симуляции куча находится в одном большом байтовом массиве).
Применение этого метода в вашем контексте, вероятно, будет значительно более сложным, но не должно быть невозможным. Позвольте мне набросать это здесь...
Я назову процесс, который вы пытаетесь отладить, "хозяином" и теми, которые клонированы, чтобы попробовать GC для него, детьми.
Перед GC в мастере, сделайте разветвление и попросите ребенка выполнить GC, запустив проверку утечки в дочернем устройстве и выйдя со статусом выхода, отражающим, успешно или нет GC. Затем мастер продолжает свой собственный GC, если ребенок преуспел. В противном случае это циклы, порождающие детей, которые повторяют неудачный GC. Затем вы отлаживаете ребенка.
Ребенок должен быть запущен в двух штатах. Первоначальный запуск в каждом ГХ просто запускает ГХ и завершает работу со статусом успеха. Последующие вилки, которые мы теперь знаем, потерпят неудачу, могут перейти в состояние ожидания, чтобы вы могли присоединить gdb к ребенку.
Я называю это "отладкой лемминга", поскольку можно прыгать через скалу столько клонов, сколько нужно, до тех пор, пока сбой не будет отлажен. Дайте мне знать, если у вас получится.