Quelle est la méthode standard la plus simple pour produire un Segfault en C?

Je pense que la question dit tout. Un exemple couvrant la plupart des normes de C89 à C11 serait utile. Je pensais que de celui-ci, mais je suppose que c’est juste un comportement indéfini:

#include  int main( int argc, char* argv[] ) { const char *s = NULL; printf( "%c\n", s[0] ); return 0; } 

MODIFIER:

Comme certains votes ont demandé des éclaircissements: je voulais avoir un programme avec une erreur de programmation habituelle (le plus simple que je pouvais imaginer était un défaut de segmentation), qui est garanti (par défaut) d’abandonner. Ceci est un peu différent de la question de segfault minimale, qui ne se soucient pas de cette assurance.

Un défaut de segmentation est un comportement défini par l’implémentation . La norme ne définit pas comment l’implémentation doit gérer un comportement indéfini et, en fait, l’implémentation peut optimiser les comportements indéfinis tout en restant conforme. Pour être clair, le comportement défini par l’implémentation est un comportement qui n’est pas spécifié par la norme, mais l’implémentation doit documenter. Un comportement non défini est un code non portable ou erroné et dont le comportement est imprévisible et ne peut donc pas être utilisé.

Si nous regardons le projet de norme C.33.3.3.3 comportement indéfini qui figure dans la section Termes, définitions et symboles du paragraphe 1, il est dit:

comportement, lors de l’utilisation d’une structure de programme non portable ou erronée ou de données erronées, pour lesquels la présente Norme internationale n’impose aucune exigence

et au paragraphe 2 dit:

NOTE Le comportement indéfini possible peut aller de l’ignorance complète de la situation à des résultats imprévisibles, au comportement lors de la traduction ou à l’exécution du programme d’une manière documentée caractéristique de l’environnement (avec ou sans message de diagnostic), à la fin d’une traduction émission d’un message de diagnostic).

Si, par contre, vous voulez simplement une méthode définie dans la norme qui causera une erreur de segmentation sur la plupart des systèmes Unix , alors raise(SIGSEGV) devrait atteindre cet objective. Bien que SIGSEGV soit à proprement parler défini comme suit:

SIGSEGV un access invalide au stockage

et §7.14 Gestion du signal dit:

Une mise en œuvre n’a pas besoin de générer l’un de ces signaux, sauf en cas d’appels explicites à la fonction raise . Des signaux supplémentaires et des pointeurs vers des fonctions non déclarables, avec des définitions de macros commençant respectivement par les lettres SIG et une lettre majuscule ou avec SIG_ et une lettre majuscule, peuvent également être spécifiés par l’implémentation. L’ensemble complet des signaux, leur sémantique et leur traitement par défaut sont définis par l’implémentation ; tous les numéros de signal doivent être positifs.

raise() peut être utilisé pour générer un segfault:

 raise(SIGSEGV); 

La norme ne mentionne que les comportements indéfinis. Il ne sait rien de la segmentation de la mémoire. Notez également que le code qui génère l’erreur n’est pas conforme à la norme. Votre code ne peut pas invoquer un comportement indéfini et être conforme à la norme en même temps.

Néanmoins, le moyen le plus court de produire une erreur de segmentation sur des architectures générant de tels défauts serait:

 int main() { *(int*)0 = 0; } 

Pourquoi est-ce sûr de produire un segfault? Parce que l’access à l’adresse mémoire 0 est toujours intercepté par le système; il ne peut jamais être un access valide (du moins pas par le code de l’espace utilisateur).

Notez bien sûr que toutes les architectures ne fonctionnent pas de la même manière. Sur certains d’entre eux, ce qui précède ne pouvait pas du tout tomber en panne, mais plutôt produire d’autres types d’erreurs. Ou la déclaration pourrait être parfaitement correcte, même, et l’emplacement de la mémoire 0 est accessible très bien. C’est l’une des raisons pour lesquelles la norme ne définit pas réellement ce qui se passe.

Un programme correct ne produit pas de erreur de segmentation. Et vous ne pouvez pas décrire le comportement déterministe d’un programme incorrect.

Un “défaut de segmentation” est une chose qu’un processeur x86 fait. Vous l’obtenez en essayant de référencer la mémoire de manière incorrecte. Il peut également faire référence à une situation où l’access à la mémoire provoque une erreur de page (par exemple, essayer d’accéder à la mémoire non chargée dans les tables de pages) et le système d’exploitation décide que vous n’avez pas le droit de demander cette mémoire. Pour déclencher ces conditions, vous devez programmer directement pour votre système d’exploitation et votre matériel. Ce n’est rien qui est spécifié par le langage C.

Si nous supposons que nous n’élevons pas un signal d’appel, il est probable que le défaut de segmentation provienne d’un comportement non défini. Le comportement indéfini est indéfini et un compilateur est libre de refuser de traduire, de sorte qu’aucune réponse avec indéfini ne peut échouer sur toutes les implémentations. De plus, un programme qui invoque un comportement indéfini est un programme erroné.

Mais celui-ci est le plus court, je peux obtenir cette erreur sur mon système:

 main(){main();} 

(Je comstack avec gcc et -std=c89 -O0 ).

Et au fait, ce programme appelle-t-il vraiment un bevahior indéfini?

Sur certaines plates-formes, un programme C conforme aux normes peut échouer avec une erreur de segmentation s’il demande trop de ressources au système. Par exemple, allouer un object volumineux avec malloc peut sembler aboutir, mais plus tard, lorsque l’on accède à l’object, il se bloquera.

Notez qu’un tel programme n’est pas ssortingctement conforme; les programmes qui répondent à cette définition doivent respecter chacune des limites minimales de mise en œuvre.

Un programme C conforme aux normes ne peut pas générer une erreur de segmentation, car les seuls autres moyens sont les comportements non définis.

Le signal SIGSEGV peut être déclenché explicitement, mais il n’y a pas de symbole SIGSEGV dans la bibliothèque C standard.

(Dans cette réponse, “conforme à la norme” signifie: “Utilise uniquement les fonctionnalités décrites dans certaines versions de la norme ISO C, en évitant les comportements non spécifiés, définis par l’implémentation ou indéfinis, mais pas nécessairement limités à l’implémentation.”)

La plupart des réponses à cette question portent sur le point essentiel, à savoir: La norme C n’inclut pas le concept de faute de segmentation. (Depuis C99, il inclut le numéro de signal SIGSEGV , mais il ne définit aucune circonstance où ce signal est délivré, autre que raise(SIGSEGV) , qui, comme indiqué dans d’autres réponses, ne compte pas.)

Par conséquent, il n’y a pas de programme “ssortingctement conforme” (c’est-à-dire un programme qui utilise uniquement des constructions dont le comportement est entièrement défini par le standard C, seul) et qui est garanti pour provoquer une erreur de segmentation.

Les erreurs de segmentation sont définies par une norme différente, POSIX . Ce programme est garanti pour provoquer soit une erreur de segmentation, soit une “erreur de bus” équivalente sur le plan fonctionnel ( SIGBUS ), sur tout système entièrement conforme à POSIX.1-2008, y compris les options Protection de la mémoire et Temps réel avancé. sysconf , posix_memalign et mprotect réussissent. Ma lecture de C99 est que ce programme a un comportement défini par l’implémentation (pas indéfini!) En tenant compte uniquement de ce standard, et qu’il est donc conforme mais pas ssortingctement conforme .

 #define _XOPEN_SOURCE 700 #include  #include  #include  #include  #include  #include  int main(void) { size_t pagesize = sysconf(_SC_PAGESIZE); if (pagesize == (size_t)-1) { fprintf(stderr, "sysconf: %s\n", strerror(errno)); return 1; } void *page; int err = posix_memalign(&page, pagesize, pagesize); if (err || !page) { fprintf(stderr, "posix_memalign: %s\n", strerror(err)); return 1; } if (mprotect(page, pagesize, PROT_NONE)) { fprintf(stderr, "mprotect: %s\n", strerror(errno)); return 1; } *(long *)page = 0xDEADBEEF; return 0; } 

Il est difficile de définir une méthode de segmentation des erreurs sur un programme sur des plates-formes non définies. Un défaut de segmentation est un terme vague qui n’est pas défini pour toutes les plates-formes (par exemple, les petits ordinateurs simples).

Considérant uniquement les systèmes d’exploitation qui prennent en charge les processus , les processus peuvent recevoir une notification indiquant qu’une erreur de segmentation s’est produite.

De plus, en limitant les systèmes d’exploitation aux systèmes d’exploitation Unix, une méthode fiable pour qu’un processus reçoive un signal SIGSEGV est kill(getpid(),SIGSEGV)

Comme c’est le cas dans la plupart des problèmes multi-plateformes, chaque plate-forme peut (généralement) avoir une définition différente de la segmentation.

Mais pour être pratique, les OS actuels de mac, lin et win

 *(int*)0 = 0; 

De plus, ce n’est pas un mauvais comportement de provoquer un segfault. Certaines implémentations de assert() provoquent un signal SIGSEGV qui pourrait produire un fichier core. Très utile lorsque vous avez besoin d’autopsie.

Ce qui est pire que de provoquer un segfault, c’est de le cacher:

 try { anyfunc(); } catch (...) { printf("?\n"); } 

ce qui cache l’origine d’une erreur et tout ce que vous avez à faire est:

 ? 

.

  main; 

C’est tout.

Vraiment.

En main cela définit le main comme une variable . En C, les variables et les fonctions sont des symboles – des pointeurs en mémoire, de sorte que le compilateur ne les distingue pas et que ce code ne génère pas d’erreur.

Cependant, le problème réside dans la façon dont le système exécute les exécutables. En résumé, la norme C exige que tous les exécutables C intègrent un point d’entrée de préparation d’environnement, ce qui revient essentiellement à «appeler le main ».

Dans ce cas particulier, cependant, main est une variable, elle est donc placée dans une section de mémoire non exécutable appelée .bss , destinée aux variables (par opposition à .text pour le code). Essayer d’exécuter du code dans .bss viole sa segmentation spécifique, le système lance donc une erreur de segmentation.

Pour illustrer, voici (en partie) un objdump du fichier résultant:

 # (unimportant) Disassembly of section .text: 0000000000001020 <_start>: 1020: f3 0f 1e fa endbr64 1024: 31 ed xor %ebp,%ebp 1026: 49 89 d1 mov %rdx,%r9 1029: 5e pop %rsi 102a: 48 89 e2 mov %rsp,%rdx 102d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp 1031: 50 push %rax 1032: 54 push %rsp 1033: 4c 8d 05 56 01 00 00 lea 0x156(%rip),%r8 # 1190 <__libc_csu_fini> 103a: 48 8d 0d df 00 00 00 lea 0xdf(%rip),%rcx # 1120 <__libc_csu_init> # This is where the program should call main 1041: 48 8d 3d e4 2f 00 00 lea 0x2fe4(%rip),%rdi # 402c 
1048: ff 15 92 2f 00 00 callq *0x2f92(%rip) # 3fe0 <__libc_start_main@GLIBC_2.2.5> 104e: f4 hlt 104f: 90 nop # (nice things we still don't care about) Disassembly of section .data: 0000000000004018 <__data_start>: ... 0000000000004020 <__dso_handle>: 4020: 20 40 00 and %al,0x0(%rax) 4023: 00 00 add %al,(%rax) 4025: 00 00 add %al,(%rax) ... Disassembly of section .bss: 0000000000004028 <__bss_start>: 4028: 00 00 add %al,(%rax) ... # main is in .bss (variables) instead of .text (code) 000000000000402c
: 402c: 00 00 add %al,(%rax) ... # aaand that's it!

PS: Cela ne fonctionnera pas si vous comstackz un exécutable plat. Au lieu de cela, vous provoquerez un comportement indéfini.