web-dev-qa-db-fra.com

Comment localiser la violation d'accès "à l'adresse 00000000"

Je sais comment créer un fichier .map pour rechercher les erreurs de violation d'accès lorsque le message d'erreur comprend une adresse réelle.

Mais que se passe-t-il si le message d'erreur indique

Access violation at address 00000000. Read of address 00000000.

Où dois-je commencer à chercher la cause de ce problème ...?

29
ObiWanKenobi

Une violation d'accès à n'importe où près de l'adresse '00000000' indique un accès pointeur nul. Vous utilisez quelque chose avant qu'il ne soit créé, très probablement, ou après qu'il ait été FreeAndNil () 'd.

Souvent, cela est dû à l'accès à un composant au mauvais endroit lors de la création du formulaire, ou à ce que votre formulaire principal essaie d'accéder à quelque chose dans un module de données qui n'a pas encore été créé.

MadExcept facilite le suivi de ces choses et est gratuit pour une utilisation non commerciale. (En fait, une licence d'utilisation commerciale est également assez peu coûteuse et vaut bien l'argent.)

29
Ken White

La réponse acceptée ne raconte pas toute l'histoire.

Oui, chaque fois que vous voyez des zéros, un pointeur NULL est impliqué. C'est parce que NULL est par définition zéro. Donc, appeler zéro NULL peut ne pas dire grand-chose.

Ce qui est intéressant dans le message que vous obtenez est le fait que NULL est mentionné deux fois . En fait, le message que vous signalez ressemble un peu aux messages que les systèmes d'exploitation de marque Windows affichent à l'utilisateur.

Le message indique que l'adresse NULL a tenté de lire NULL. Alors qu'est-ce que cela signifie? Plus précisément, comment une adresse se lit-elle?

Nous pensons généralement aux instructions à une adresse de lecture et d'écriture de mémoire à certaines adresses. Savoir cela nous permet d'analyser le message d'erreur. Le message essaie d'articuler que l'instruction à adresse NULL a essayé de lire NULL.

Bien sûr, il n'y a aucune instruction à l'adresse NULL, c'est pourquoi nous considérons NULL comme spécial dans notre code. Mais chaque instruction peut être considérée comme commençant par la tentative de se lire. Si le registre EIP du CPU est à l'adresse NULL, alors le CPU tentera de lire le opcode pour une instruction à partir de l'adresse 0x00000000 (NULL) . Cette tentative de lecture de NULL échouera et générera le message que vous avez reçu.

Dans le débogueur, notez que EIP est égal à 0x00000000 lorsque vous recevez ce message. Cela confirme la description que je vous ai donnée.

La question devient alors "pourquoi mon programme essaie-t-il d'exécuter l'adresse NULL". Il y a trois possibilités qui me viennent à l'esprit:

  • Vous avez tenté de faire un appel de fonction via un pointeur de fonction que vous avez déclaré, affecté à NULL, jamais initialisé autrement, et vous déréférencez.
  • De même, vous pouvez appeler une méthode C++ "abstraite" qui a une entrée NULL dans la table vtable de l'objet. Celles-ci sont créées dans votre code avec la syntaxe virtual function_name()=0.
  • Dans votre code, un tampon de pile a été débordé lors de l'écriture de zéros. Les zéros ont été écrits au-delà de la fin du tampon de pile, sur l'adresse de retour préservée. Lorsque la fonction exécute ultérieurement son instruction ret, la valeur 0x00000000 (NULL) est chargée à partir de l'emplacement mémoire écrasé. Ce type d'erreur, le débordement de pile, est l'éponyme de notre forum.

Puisque vous mentionnez que vous appelez une bibliothèque tierce, je soulignerai que la bibliothèque peut s'attendre à ce que vous fournissiez un pointeur de fonction nonNULL en entrée à une API. Ces fonctions sont parfois appelées fonctions de "rappel".

Vous devrez utiliser le débogueur pour affiner davantage la cause de votre problème, mais les possibilités ci-dessus devraient vous aider à résoudre l'énigme.

33
Heath Hunnicutt

Vous commencez à regarder près de ce code que vous savez avez exécuté, et vous arrêtez de chercher lorsque vous atteignez le code que vous savez n'avez pas exécuté.

Ce que vous recherchez est probablement un endroit où votre programme appelle une fonction via un pointeur de fonction, mais ce pointeur est nul.

Il est également possible que vous ayez une corruption de pile. Vous avez peut-être remplacé l'adresse de retour d'une fonction par zéro, et l'exception se produit à la fin de la fonction. Vérifiez les débordements de tampon possibles et si vous appelez des fonctions DLL, assurez-vous d'avoir utilisé la convention d'appel et le nombre de paramètres appropriés.

Ce n'est pas un cas ordinaire d'utilisation d'un pointeur nul, comme une référence d'objet non affectée ou PChar. Dans ces cas, vous aurez une valeur "à l'adresse x" non nulle. Étant donné que l'instruction s'est produite à l'adresse zéro, vous savez que le pointeur d'instructions du CPU ne pointait vers aucune instruction valide. C'est pourquoi le débogueur ne peut pas vous montrer quelle ligne de code a provoqué le problème - il n'y a = aucune ligne de code. Vous devez le trouver en trouvant le code qui mène à l'endroit où le CPU a sauté à l'adresse non valide.

La pile d'appels pourrait encore être intacte, ce qui devrait au moins vous rapprocher de votre objectif. Si vous avez une corruption de pile, cependant, vous ne pourrez peut-être pas faire confiance à la pile d'appels.

7
Rob Kennedy

Si vous obtenez "Violation d'accès à l'adresse 00000000", vous appelez un pointeur de fonction qui n'a pas été affecté - éventuellement un gestionnaire d'événements ou une fonction de rappel.

par exemple

type
TTest = class(TForm);
protected
  procedure DoCustomEvent;
public
  property OnCustomEvent : TNotifyEvent read FOnCustomEvent  write FOnCustomEvent;
end;

procedure TTest.DoCustomEvent;
begin
  FOnCustomEvent(Self);  
end;

Au lieu de

procedure TTest.DoCustomEvent;
begin
  if Assigned(FOnCustomEvent) then // need to check event handler is assigned!
    FOnCustomEvent(Self);  
end;

Si l'erreur se trouve dans un composant tiers et que vous pouvez suivre le code incriminé, utilisez un gestionnaire d'événements vide pour empêcher l'AV.

3
Gerry Coll

Quand je suis tombé sur ce problème, je commence généralement à regarder les endroits où je FreeAndNil () ou tout simplement xxx: = NIL; les variables et le code après cela.

Quand rien d'autre n'a aidé, j'ai ajouté une fonction Log () pour sortir des messages de divers endroits suspects pendant l'exécution, puis j'ai regardé plus tard ce journal pour suivre où dans le code la violation d'accès vient.

Il existe bien sûr de nombreuses solutions plus élégantes pour détecter ces violations, mais si vous ne les avez pas à votre disposition, la méthode d'essai et d'erreur à l'ancienne fonctionne très bien.

2
K.Sandell

Je seconderai madExcept et des outils similaires, comme Eurekalog, mais je pense que vous pouvez également faire un bon chemin avec FastMM. Avec le débogage complet activé, cela devrait vous donner quelques indices sur ce qui ne va pas.

Quoi qu'il en soit, même si Delphi utilise FastMM par défaut, cela vaut la peine d'obtenir le FastMM complet pour son contrôle supplémentaire sur la journalisation.

1
Vegar

Utilisez MadExcept. Ou JclDebug.

1
Warren P

C'est probablement parce que vous accédez directement ou indirectement à un appel de bibliothèque qui accède à un pointeur NULL. Dans ce cas particulier, il semble que vous ayez sauté sur une adresse NULL, qui est un peu plus poilue.

D'après mon expérience, le moyen le plus simple de les retrouver est de l'exécuter avec un débogueur et de vider une trace de pile.

Alternativement, vous pouvez le faire "à la main" et ajouter beaucoup de journalisation jusqu'à ce que vous puissiez localiser exactement dans quelle fonction (et éventuellement LOC) cette violation s'est produite.

Jetez un œil à Stack Tracer , ce qui pourrait vous aider à améliorer votre débogage.

1
0xfe