Охота (память, связанный с 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 к ребенку.

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

Другие вопросы по тегам