web-dev-qa-db-fra.com

__memcpy_sse2_unaligned - qu'est-ce que cela signifie en détail?

En travaillant sur mon compilateur, j'ai eu cette erreur:

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

Comment obtenir des détails sur ce qui n'a pas fonctionné ici? Je sais par la trace que c'est une ligne memcpy qui la provoque, mais comment puis-je voir comment la mémoire est alignée? Et comment savoir comment doit-il être aligné?

Le projet est un compilateur avec un back-end LLVM utilisant le runtime Zend/PHP avec le garbage collector OCaml, donc il y a beaucoup de choses qui peuvent mal tourner.

Je soupçonne que cette ligne fait partie du problème:

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

caml_alloc étaient pemalloc dans le code source de Zend.

Le défaut de segmentation se produit lors de la concaténation de 10 000 chaînes. Voici la sortie de valgrind:

==7501== Invalid read of size 8
==7501==    at 0x4C2F790: memcpy@@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)

Tous les conseils appréciés.

Modifier:

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);
}

Edit 2:

Depuis que valgrind me donne cette ligne

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

Je suppose que j'essaie de copier quelque chose qui a déjà été libéré, ce qui signifie que je ne dis pas correctement au GC OCaml quand quelque chose n'est plus référencé.

16
Olle Härstedt

Cette erreur vous indique que quelque chose de mauvais se produit pendant la mémcpy, probablement quelque chose comme un pointeur nul ou une erreur dans les tailles.

Ne vous embêtez pas avec __memcpy_sse2_unaligned, C'est un détail d'implémentation de memcpy. memcpy a beaucoup d'implémentations différentes optimisées pour les différents cas et envoyées dynamiquement à la plus efficace compte tenu du contexte. Celui-ci semble être utilisé lorsque les instructions sse2 sont disponibles et que les pointeurs ne sont pas alloués aux limites de 16 octets (les instructions sse2 ne peuvent pas charger les valeurs non alignées), ce qui est probablement fait en copiant un octet à la fois jusqu'à ce qu'une limite de 16 octets soit atteinte, puis en basculant vers le chemin rapide.

En ce qui concerne les détails spécifiques à OCaml gc liés à LLVM, vous devez faire très attention à la façon dont vous gérez les pointeurs de tas. Comme vous ne dites pas si vous utilisez le mécanisme gcroot ou les nouveaux points d'état, je suppose que vous utilisez gcroot.

Étant donné que l'OCaml gc est un collecteur en mouvement (déplacement d'un segment mineur vers un segment principal et déplacement pendant le compactage), chaque allocation peut potentiellement invalider un pointeur. Cela signifie qu'il est généralement dangereux de factoriser l'accès aux champs aux valeurs allouées au tas. Par exemple, ce n'est pas sûr:

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

l'appel de fonction pourrait effectuer certaines allocations qui pourraient déclencher un compactage.

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

Soit dit en passant, je ne suis même pas certain que le mécanisme gcroot puisse gérer correctement le déplacement de gc (que llvm n'optimise pas les choses qu'il ne devrait pas).

Donc, cela signifie généralement que ce n'est pas une bonne idée d'utiliser gcroot avec le GC d'OCaml. La nouvelle méthode est meilleure pour ce type de GC, mais vous devez toujours faire attention à ne pas accéder au pointeur entre les appels de fonction ou les allocations.

Donc, votre erreur peut être liée à ce type de problème: le pointeur était valide à un moment donné, puis une valeur a été déplacée pendant le compactage, ce qui a entraîné l'inutilisation d'une page gc, donc la libération.

15
Pierre Chambart