Est-ce que l'adresse 0000000C est une adresse spéciale?

32

Parfois, lorsque vous programmez, les choses se cassent. Vous avez commis une erreur et votre programme essaie de lire à partir d'une mauvaise adresse.

Une chose qui me frappe souvent est que ces exceptions sont:

Access violation at address 012D37BC in module 'myprog.exe'. Read of address 0000000C.

Maintenant, je vois beaucoup de journaux d'erreurs et ce qui me frappe est le: 0000000C. Est-ce une adresse "spéciale"? Je vois d'autres violations d'accès avec de mauvaises lectures mais les adresses semblent juste aléatoires, mais celle-ci revient souvent dans des situations totalement différentes.

Pieter B
la source
1
Je l' ai aussi remarqué que 0000000Cest façon plus fréquente que 00000008, mais aucune des réponses semblent adresse du tout: /
Mooing Canard
2
Peut-être System.Runtime.CompilerServices.RuntimeHelpers.OffsetToStringDataest 12=0x0Cune raison pour laquelle ce décalage est plus fréquent.
Mark Hurd
1
@ MarkHurd C'est effrayant. Pensez-vous vraiment qu'il y a tellement d'applications non gérées qui, à dessein, lisent / écrivent des chaînes .NET que cela constituerait une source majeure de violations d'accès?
Luaan

Réponses:

57

00000000est une adresse spéciale (le pointeur nul). 0000000Cest exactement ce que vous obtenez lorsque vous ajoutez un décalage de 12 au pointeur nul, probablement parce que quelqu'un a essayé d'obtenir le zmembre d'une structure comme celle ci-dessous par le biais d'un pointeur qui était en réalité nul.

struct Foo {
    int w, x, y; // or anything else that takes 12 bytes including padding
    // such as: uint64_t w; char x;
    // or: void *w; char padding[8];
    // all assuming an ordinary 32 bit x86 system
    int z;
}

la source
29
Ou peut-être parce que certaines petites valeurs intégrales ont été déréférencées à tort comme s'il s'agissait d'un pointeur. Les petites valeurs sont beaucoup plus courantes que les énormes, ce qui a tendance à produire des adresses illégales telles que 0X0000000C plutôt que, par exemple, 0x43FCC893.
Kilian Foth
3
J'ai posé cette question parce que 0000000C revient si souvent comparé à d'autres adresses. Pourquoi le décalage 12 est-il plus commun que le décalage 4, 8 ou 16?
Pieter B
5
Après une enquête plus approfondie, cette réponse est tout à fait correcte. Dans mon source, la propriété "tag" des classes est largement utilisée (que je sois obligée de la gérer ou non). La propriété tag de mon cas fait partie d'une classe de base de bas niveau et a toujours été créée à ce décalage.
Pieter B
1
Excellent point. Peut-être que le pointeur null a été couvert, mais le pointeur null ++ est juste une adresse normale (et dans ce cas invalide), il échoue donc uniquement lors de son accès.
Neil
8
@ Leushenko Oui, la protection de la mémoire fonctionne généralement sur des pages entières, et même s'il était possible d'attraper seulement 0, il est préférable de protéger également les adresses suivantes, car elles risquent d'être utilisées si l'arithmétique de pointeur avec un pointeur null se produit (comme dans Cas de l'OP).
11

Sous Windows, il est interdit de déréférencer la totalité de la première et de la dernière page , c'est-à-dire le premier ou le dernier 64 Ko de la mémoire de processus (les plages 0x00000000vers 0x0000ffffet 0xffff0000vers 0xffffffffdans une application 32 bits).

Cela consiste à intercepter le comportement non défini de déréférencement d'un pointeur ou d'un index null dans un tableau null. La taille de la page étant de 64 Ko, Windows doit donc empêcher toute plage valide de la première ou de la dernière page.

Cela ne protège pas contre les pointeurs non initialisés qui pourraient avoir n'importe quelle valeur (y compris les adresses valides).

monstre à cliquet
la source
7
Windows ne peut pas vraiment faire ça. Le tableau de pages est une structure définie et requise par x86, et les petites pages sont fixées à 4 Ko. Il est gravé dans la pierre (plus précisément dans le silicium). Le 64Ko est probablement pour plus de commodité.
ElderBug
12
Dans ce cas, je préférerais écrire 64 Ko au lieu de 65 Ko, car la taille de la puissance de deux est pertinente.
CodesInChaos
4
La plage de 64 Ko est un reste de la version Aplha de NT. Et ce n'est pas la taille de la page, mais la granularité de l'allocation. blogs.msdn.com/b/oldnewthing/archive/2003/10/08/55239.aspx
shf301
3
@CodesInChaos: Bien que les lettres majuscules "M", "G" et "T" soient ambiguës, je ne vois aucune raison de déconseiller l'utilisation de "k" pour 10 ^ 3 et de "K" pour 2 ^ 10.
Supercat
2
@MooingDuck Oui, c'est pour cette raison que j'ai précisé les petites pages. La plupart des processeurs x64 prennent également en charge les pages 1GiB. Autant que je sache, Windows a toujours des pages de 4 Ko, sauf s’il est alloué avec des API spéciales.
ElderBug
2

Quant à savoir pourquoi 0x0Csemble plus commun que 0x08(est-ce vraiment? Je ne sais pas; et dans quels types d’applications?), Cela pourrait avoir à voir avec les pointeurs de table de méthodes virtuelles. C'est vraiment plus un commentaire (devinette massive :), mais c'est un peu plus grand, alors allez-y ... Si vous avez une classe avec des méthodes virtuelles, ses propres champs vont être déplacés 0x04. Par exemple, une classe qui hérite d'une autre classe virtuelle peut avoir une structure de mémoire comme celle-ci:

0x00 - VMT pointer for parent
0x04 - Field 1 in parent
0x08 - VMT pointer for child
0x0C - Field 1 in child

Est-ce un scénario commun, ou même proche? Je ne suis pas sûr. Cependant, notez que dans une application 64 bits, cela pourrait être déplacé de manière encore plus intéressante vers la 0x0Cvaleur:

0x00 - VMT parent
0x08 - Field 1 parent
0x0C - VMT child
0x14 - Field 2 child

Il y a donc beaucoup de cas où les applications peuvent avoir un chevauchement important des décalages null-pointeur. Il peut s'agir du premier champ d'une classe enfant ou de son pointeur de table de méthode virtuelle - nécessaire chaque fois que vous appelez une méthode virtuelle sur une instance. Ainsi, si vous appelez une méthode virtuelle sur un nullpointeur, vous obtiendrez une violation d'accès sur sa VMT offset. La prévalence de cette valeur particulière pourrait alors avoir quelque chose à voir avec une API commune fournissant une classe ayant un modèle d'héritage similaire, ou plus vraisemblablement, une interface particulière (tout à fait possible pour certaines classes d'applications, telles que les jeux DirectX). Il serait peut-être possible de suivre une cause commune simple comme celle-ci, mais j'ai tendance à me débarrasser des applications qui effectuent une déréférencement nulle assez rapidement, alors ...

Luaan
la source
1
Si vous parcourez les commentaires, vous pouvez réduire considérablement les suppositions.
Déduplicateur
@DuDuplicator Eh bien, je trouve l'idée que les chaînes .NET gérées sont utilisées dans du code non sécurisé avec des opérations de pointeur manuel est effrayante, et l'idée que cela serait la cause principale de violations d'accès, encore plus. "Ouais, c'est totalement sûr pour la mémoire, ne vous inquiétez pas, nous avons utilisé C #. Nous venons de modifier manuellement la mémoire à partir de C ++, mais c'est sûr en C #."
Luaan