__memcpy_sse2_unaligned - что это значит в деталях?

Во время работы над моим компилятором я получил эту ошибку:

Program received signal SIGSEGV, Segmentation fault.
__memcpy_sse2_unaligned () at ../sysdeps/x86_64/multiarch/memcpy-sse2-unaligned.S:33

Как мне получить информацию о том, что пошло не так? Я знаю, из строки backtrace это строка memcpy, которая ее вызывает, но как я могу понять, как выровнено память? И как я знаю, как он должен быть выровнен?

Проект представляет собой компилятор с back-end LLVM с использованием среды выполнения Zend/PHP с сборщиком мусора OCaml, поэтому есть много вещей, которые могут пойти не так.

Я подозреваю, что эта строка является частью проблемы:

zend_string *str = (zend_string *)caml_alloc(ZEND_MM_ALIGNED_SIZE(_STR_HEADER_SIZE + len + 1), 0);

где caml_alloc были pemalloc в исходном коде Zend.

Segfault происходит при выполнении 10'000 конкатенаций строк. Это результат valgrind:

==7501== Invalid read of size 8
==7501==    at 0x4C2F790: [email protected]@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7501==    by 0x4D7E58: subsetphp_concat_function (bindings.c:160)
==7501==    by 0x4D7F52: foo (llvm_test.s:21)
==7501==    by 0x4D7FA9: main (llvm_test.s:60)
==7501==  Address 0x61db938 is 2,660,600 bytes inside a block of size 3,936,288 free'd
==7501==    at 0x4C2BDEC: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7501==    by 0x4C2627: do_compaction (in /home/olle/kod/subsetphp/test)
==7501==    by 0x4C2735: caml_compact_heap (in /home/olle/kod/subsetphp/test)
==7501==    by 0x4D08DF: caml_major_collection_slice (in /home/olle/kod/subsetphp/test)
==7501==    by 0x4D2DCF: caml_minor_collection (in /home/olle/kod/subsetphp/test)
==7501==    by 0x4D2FBC: caml_check_urgent_gc (in /home/olle/kod/subsetphp/test)
==7501==    by 0x4D7C45: subsetphp_string_alloc (bindings.c:90)
==7501==    by 0x4D7CEE: subsetphp_string_init (bindings.c:122)
==7501==    by 0x4D7DEA: subsetphp_concat_function (bindings.c:149)
==7501==    by 0x4D7F52: foo (llvm_test.s:21)
==7501==    by 0x4D7FA9: main (llvm_test.s:60)

Любые советы оценены.

Изменить:

extern value subsetphp_concat_function(value v1, value v2) 
{

  CAMLparam2(v1, v2);

  zend_string *str1 = Zend_string_val(v1);
  zend_string *str2 = Zend_string_val(v2);
  size_t str1_len = str1->len;
  size_t str2_len = str2->len;
  size_t result_len = str1_len + str2_len;

  value result = subsetphp_string_init("", result_len, 1);
  zend_string *zend_result = Zend_string_val(result);

  if (str1_len > SIZE_MAX - str2_len) {
    zend_error_noreturn(E_ERROR, "String size overflow");
  }

  memcpy(zend_result->val, str1->val, str1_len);  // This is line 160
  memcpy(zend_result->val + str1_len, str2->val, str2_len);
  zend_result->len = result_len;
  zend_result->val[result_len] = '\0';

  CAMLreturn(result);
}

Изменить 2:

Так как valgrind дает мне эту строку

Address 0x61db938 is 2,660,600 bytes inside a block of size 3,936,288 free'd

Я предполагаю, что я пытаюсь скопировать что-то, что уже было освобождено, что означает, что я не говорю GC OCaml правильно, когда что-то больше не ссылается.

Ответ 1

Эти ошибки говорят вам, что во время memcpy происходит что-то плохое, возможно, что-то вроде нулевого указателя или ошибки в размерах.

Не утруждайтесь __memcpy_sse2_unaligned, это деталь реализации memcpy. memcpy имеет множество различных реализаций, оптимизированных для разных случаев, и динамически рассылает их наиболее эффективным с учетом контекста. Кажется, что это используется, когда доступны инструкции sse2, а указатели не привязаны к границам 16 байтов (инструкции sse2 не могут загружать значения без выравнивания), что, вероятно, выполняется путем копирования одного байта за раз до достижения 16-байтовой границы, а затем переключения на быстрый путь.

Что касается специфических деталей OCaml gc, связанных с LLVM, вы должны быть достаточно осторожны с тем, как вы обрабатываете указатели кучи. Поскольку вы не знаете, используете ли вы механизм gcroot или новые точки состояния, я предположим, что вы используете gcroot.

Так как OCaml gc - это движущийся коллекционер (перемещающийся от небольшой кучи к большой куче и перемещающийся во время уплотнения), каждое выделение может потенциально привести к недействительности указателя. Это означает, что обычно небезопасно факторировать доступ к полям для выделенных значений кучи. Например, это небезопасно:

v = field(0, x) r = function_call(...) w = field(0, v)

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

v = field(0, x) r = function_call(...) v' = field(0, x) w = field(0, v')

Кстати, я даже не уверен, что механизм gcroot может корректно обрабатывать перемещение gc (что llvm не оптимизирует вещи, которые он должен делать t).

Итак, что обычно означает, что не рекомендуется использовать gcroot с OCaml GC. Новый способ лучше подходит для такого рода GC, но вам все равно нужно быть осторожным, чтобы не обращаться к указателю на вызовы функций или распределения.

Таким образом, ваша ошибка может быть связана с такой проблемой: указатель был действителен в какой-то момент, затем значение было перемещено во время уплотнения, что привело к тому, что некоторая страница gc не использовалась, поэтому была освобождена.