Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Bug indétectable via printf() / cout

148 réponses
Avatar
AG
Bonjour =E0 tous,

Je travaille dans une =E9quipe de personnes pour lesquels le d=E9bugger
n'est pas l'outil qui tombe sous le sens lorsqu'il s'agit de trouver
les bugs des outils qu'ils ont d=E9velopp=E9.

Afin de les sensibiliser, je recherche un exemple de bug difficile
(voir impossible) a d=E9tecter via des printf()/cout qui n'afficheraient
que les contenus des variables. (Je me rends bien compte qu'on doit
pouvoir tout d=E9bugger avec printf()/cout, mais il faut parfois
afficher les adresses des variable (en plus de leur valeur), voir
parfois la m=E9moire =E0 certains endroits.)

Je voudrais construire un exemple simple (notions de C++ pas trop
compliqu=E9es) et court (un seul fichier, 100 lignes max) pour qu'on
puisse l'=E9tudier en un quart d'heure, mais le d=E9bugger en un temps
presque infini sans d=E9bugger.

Il est possible que l'exemple, puisqu'il y a un bug, d=E9pende de la
plateforme mais c'est un peu in=E9vitable.

L'id=E9al serait que le plantage ait lieu bien apr=E8s le bug...=E7a rajout=
e
du piment. Bref, vous voyez l'id=E9e quoi...

J'avais plusieurs pistes d'exploitation de bug:

Piste 1:
Boucle for d=E9croissante sur un entier non sign=E9 : for(size_t i =3D N;
0<=3D i; i--)

Piste 2:
comparaison de double : double x; ... ; x =3D=3D 0.0

Piste 3:
retour de malloc() non test=E9

Piste 4:
caract=E8re de fin de ligne non test=E9 (\0)

Mais jusque l=E0, je crains qu'il ne soit trop facile de d=E9tecter le
bug

J'ai donc ensuite pens=E9 aux r=E9f=E9rences. Mais ce code est encore un pe=
u
trop voyant. Dans le main(), on peut facilement se demander pourquoi
f1 et f2 sont des r=E9f=E9rences, ce qui met directement la puce =E0
l'oreille. Peut =EAtre trouvez vous ce code bien trop compliqu=E9, ou
auriez vous en t=EAte un mani=E8re de "l'am=E9liorer" :-)

A bon entendeur salut.

AG.




#include <iostream>

using namespace std;

#define BUFFER_LENGTH 10

template<int N, class T>
class fifo_circular
{
private:
T * position;
size_t pos_offset;
T buffer[N];

public:
fifo_circular() { pos_offset =3D N-1; position =3D buffer + pos_offset;
for(int i=3D0;i<N;i++) buffer[i]=3DT(0);};

T step(T &input)
{
*position =3D input;

if(pos_offset>0) // ici il y aurait peut =EAtre moyen de glisser le
bug de la piste 1
pos_offset--;
else
pos_offset =3D N-1;

position =3D buffer + pos_offset;
return *position;
};

template<int M, class U> friend ostream & operator<<(ostream & o,
const fifo_circular<M,U> &f);
};

template<int N, class T>
fifo_circular<N,T> & Init(T & value)
{
fifo_circular<N,T> & f =3D fifo_circular<N,T>(); // h=E9 h=E9 h=E9

for(size_t i =3D 0; i < N; i++)
f.step(value);
return f;
}

template<int M, class U>
ostream & operator<<(ostream & o, const fifo_circular<M,U> &f)
{
for(size_t i =3D 0; i < M; i++)
o << f.buffer[i] << " ";
return o;
}

int main(int argc, char * argv[])
{
int a =3D 1;
fifo_circular<5,int> & f1 =3D Init<5,int>(a); // ici, c'est un peu trop
voyant. On se demande pourquoi f1 est d=E9clar=E9 en tant que
r=E9f=E9rence...
a =3D 2;
fifo_circular<5,int> & f2 =3D Init<5,int>(a);

cout << "fifo f1: " << f1 << "\n";
cout << "fifo f2: " << f2 << "\n";

return 0;
}

10 réponses

Avatar
Senhon
"James Kanze" a écrit dans le message de groupe de
discussion :

On Dec 10, 9:03 am, ""
wrote:

Je me dis que tes macros pourraient me servir, soit directemetn, soit
comme base d'inspiration. Pourrais-tu les rendre disponibles, ou
penses-tu qu'elles te sont trop spécifiques ?



Plus intéressant serait de savoir où il a appris de faire des
macros VS, parce que moi, je suis prêt à investir un peu de
temps et d'effort pour les apprendre (vue que je risque de
travailler sous Windows pour pas mal du temps encore).



J'ai appris seul.
J'ai commencer déjà avec la série des VisualC. c'était des macro simplistes.
Puis lorsque Visual Studio est arrivé il a fallut tout refaire.
Et c'est un bien (même si cela ne plait pas à première vue), car les
possibilités offertes par VS2008 dépassent de loin ce qui était permis
auparavant.


Et ce qui m'a le plus servi c'est l'enregistrement de macro, et la doc, ...
plus quelques arrages de cheveux ...
Avatar
ld
On 10 déc, 16:20, Marc Boyer wrote:
Le 09-12-2009, ld a écrit :

>>   Et puis les débogueurs que je connaissais ne montraient pas d'hi storique.
>> Souvent, la question c'est "pourquoi cette var vaut ça ?", et tu rem ontes
>> le temps. En affichant les variables en console, la console se souvien t.

>>   De plus, en ce qui concerne les fuites mémoires,valgrindest
>> bien plus efficace que de suivre pas à pas le débogueur.

> C'est surement vrai pour les cas "d'ecole". Perso, j'ai rapidement
> atteint les limites de valgrind, des que ton programme manipule qques
> 100MB de data allouee dynamiquement, il devient beaucoup trop lent (x
> 1000) et trop gourmand (pas de resultat du tout sur une machine avec 4
> GB). Je m'en sers surtout pour debugger les tests unitaires, mais en
> conditions reelles, j'utilise d'autres techniques plus efficace...

   Quelles techniques ?



Je ne me suis pas etendu parce que ce sont celles que COS permet,
comme tracer tous (ou un sous-ensemble) des messages emis dans/pendant
un certain contexte, ou tracer tous les messages recus par un objet
particulier dans/pendant un certain contexte, etc... Bref tout ce qui
est lie a la delegation et/ou au MOP. Il suffit de quelques lignes
pour tracer toutes les allocation/deallocation d'objet d'un programme
(meme s'il contient des modules deja compile).

>> En prenant aux 10% des francais les plus riches 12% de leurs revenus,
>> on pourrait doubler les revenus des 10% les plus pauvres.
>>http://www.inegalites.fr/spip.php?article1&id_mot0

> Il serait surtout tant de decrire la richesse d'un pays par deciles et
> plus "en moyenne". La moyenne masque les inegalites croissantes avec
> les 10% "les plus riches" qui arrivent a faire augmenter la moyenne
> nationale malgre un appauvrissement des 50% "les plus pauvres"...

  On s'en fout des inégalités, c'est le PIB qui compte :-(



Le PIB, c'est pas la richesse d'un pays? Et tu le compte avant ou
apres avoir comptabilise les pertes seches genre prix de transfert?

a+, ld.
Avatar
ld
On 10 déc, 16:14, Marc Boyer wrote:
Le 10-12-2009, ld a écrit :

> On 10 déc, 12:49, Marc Boyer wrote :
>> Le 09-12-2009, James Kanze a écrit :
>>  Après avoir vainement essayé de trouver quelque chose qu'un
>> envionnement permettrait de faire, et pas l'autre, on arrive
>> à quelque chose de plus connu: la courbe d'apprentissage,
>> qui trace la productivité vs l'investissement.

> Quand tu parles de cette courbe, tu penses aux Unixes, a Linux ou a
> Posix? Parce que l'ecriture de script _portable_ utilisant ses outils
> est loin d'etre simple. Perso je me suis limite a Posix + gmake.

  Tu évoques un autre problème: la portabilité et/ou compatibilit é.
Win* s'est clairement assis sur le sujet, donc, on ne peut pas
lui reprocher de ne pas tenir ses promesses.



Avec Windows, les promesses sont a la hauteur des attentes: proche du
zero absolu.

  Quand je parlais d'investissement, c'était une question
humaine, pas logicielle, ce n'était pas le fait qu'un
script écrit en 1997 sur SunOS fonctionne en 2009 sur Linux,
mais plutôt que tu retrouves très vite tes habitudes, et que
tu vas assez vite être à même de corriger le truc.



Je parlais aussi de question humaine des que l'on partage son code.
Typiquement, tu ecris un joli programme/script en quelques heures et
le lendemain (pas le millenaire suivant!) tu as quelqu'un qui t'envoie
les messages d'erreur rapportes par son systeme/compilateur/shell/
etc... Et la tu vas sur les pages web d'OpenPosix (par exemple) et tu
decouvres que la moitie des options/facilites que tu utilises ne sont
pas standard et qu'il faut les emuler, ce qui demande encore plus de
code dont il faut verifier la portabilite.

Si tu veux un exemple concret d'un telle perte de temps, tu peux
regarder la macro mkpath ligne 73 dans le gmakefile

http://cos.cvs.sourceforge.net/viewvc/cos/CosBase/include/cos/prologue?revi sion=1.14&view=markup

qui emule abspath pour gmake 3.80 alors qu'a la ligne 86 on voit sa
definition pour gmake 3.81 (et plus). Le probleme, c'est que dans mon
institut, c'est encore le 3.80 qui est disponible partout...

>>    Et même si les Unix-like offre de plus en plus d'outils
>> à accès facile, les outils fort investissement/forte prod
>> restent la base du système, et très accessibles.

> Mais pas toujours portable.

  Oui, mais à mon sens, c'est un autre problème.



Cela depend de l'ouverture de ton code.

> Il y a souvent des compromis a faire comme
> bash, gmake, gawk etc... ce qui revient peut-etre a dire que GNU est
> entrain de propager son "standard". En 17 ans de Linux, je me suis
> souvent senti "perdu" dans les options des memes outils sur SunOs ou
> HPUX par exemple. Certe la philosophie reste la meme, mais ta courbe
> n'est pas si "droite" que ca.

  Non, mais si on te demande de faire la même chose sur Win98 ou
Windows 7, la variation risque d'être encore plus grande, non ?



Aucun idee, je n'ai jamais programme sous Windows, tout au plus
quelques macros Excel et ca m'a suffit ;-) Je suis passe de DOS/turboC/
ZortechC++ a Linux/gcc/g++ directement en 1992-93 et recemment a
MacOsX (avec des Linux et un Windows dans des machines virtuelles).

a+, ld.
Avatar
Jean-Marc Desperrier
James Kanze wrote:
> gdb se scripte assez bien, mais je ne sache pas qu'on puisse y
> définir des fonctions.


Selon la doc, tu peux, mais je ne connais pas jusqu'où.



Jusque très loin :
http://blog.mozilla.com/tglek/2009/07/01/python-gdb-logging-file-io/

[...]I wanted a non-painful way to figure out what’s causing bonus file
IO [...] I grabbed python gdb, and with some tips on syscalls from gdb
old-timers managed to produce a report to assign blame for open()ing
files to relevant [...] functions. [...]

Je vous conseille aussi ce que Tara raconte sur l'analyse statique de
code avec Dehydra/Treehydra. Vous avez le droit d'attendre que ce soit
disponible dans des package avec un gcc stable.
Avatar
Jean-Marc Desperrier
Alain Ketterlin wrote:
Le top, c'est les "watchpoints" de gdb (et d'autres debuggers
certainement). Ca te permet de surveiller un morceau de mémoire (y
compris si tu n'as plus de variable qui y pointe). Et si tu reste
raisonnable, c'est fait en hard.



On le fait aussi avec VC, mais la dernière fois que j'ai voulu j'ai eu
du mal à retrouver comment on faisait pour qu'il utilise vraiment les BP
hardware et non une simulation en soft.

Pour l'exemple, tu fais deux tableaux sur la pile, et une écriture qui
déborde du premier dans le deuxième, qui est sous surveillance.

On peut sûrement faire cela avec des printf.



Le cas similaire, qui est lui à peu près impossible à retrouver avec
juste un printf, c'est le code qui garde un pointeur sur un bloc qu'il a
déjà libéré, et qui ensuite écrit dedans au lieu de lire. Et le code qui
plante est celui qui a récupéré l'espace mémoire de ce buffer quand il
va le lire ensuite. Tout cela sans débordement, il ne s'agit donc pas
juste de vérifier les index par rapport à la taille.

Quelque minutes pour trouver avec un BP hardware, mais sinon une horreur
lorsque les deux bouts de code sont bien éloignés l'un de l'autre avec
pas mal d'autres choses au milieu (bon ya une technique, printf de tous
les malloc et de tous les free mais quelquefois il faut en plus la pile
d'appel pour s'y retrouver ce qui n'est pas forcément simple).
Avatar
loic.actarus.joly
On 10 déc, 13:47, "Senhon" wrote:

Dans ma dernière version, le fichier ".vsmacro" fait 607 ko ( je ne sai s pas
comment c'est composé, car j'en ai surement pas tapé autant), en comp araison
le fichier d'exemple fourni avec VS2008 ("Samples.vsmacros") fait  700k o.
Je ne compte pas les patrons de fichiers (qui compte peanuts en
comparaison).
Les macros, me sont légèrement personnalisées, mais pourraient êt re
facilement généralisables.

Mais ce qui m'épate c'est que cela ne te dispensera pas de fournir un e ffort
d'apprentissage, surtout si tu dois les modifier.



Oui, mais ça me donnera au moins une idée de ce qu'il peut être
intéressante de mettre en place comme raccourcis.

As-tu essayé, comme je le disais auparavant, d'utiliser la fonction
d'enregistrement de macro, c'a débroussaille grandement ...



En effet, c'est ainsi que j'ai commencé la plutpart de mes macros.
Avatar
loic.actarus.joly
On 10 déc, 15:54, James Kanze wrote:

Plus intéressant serait de savoir où il a appris de faire des
macros VS, parce que moi, je suis prêt à investir un peu de
temps et d'effort pour les apprendre (vue que je risque de
travailler sous Windows pour pas mal du temps encore).



Déjà, d'après ce que j'ai vu, il y a les macros, et les vraies
extensions (d'ailleurs, en parlant d'extension,
http://blogs.msdn.com/jaredpar/archive/2009/09/09/vim-emulator-editor-exten sion-released.aspx
pourrait t'intéresser, bien qu'il demande VS 2010 qui n'est encore
qu'en beta, je crois qu'ils ont changé pas mal de choses pour les
extensions dans cette version).

Pour apprendre les macros, il y a deux aspects :
- Le langage, là c'est du visual basic, c'est pas très beau, mais ça
s'apprend vite. Je n'ai pas contre jamais essayé de voir si on avait
ou pas accès à toutes l'API .NET directement (monter des boîtes de
dialogue...). Peut-être faut-il faire une extension pour ça ? En tout
cas on peut facilement appeler un exécutable externe, ou une DLL .NET.
- Le modèle objet de visual studio. Le point d'entrée est DTE, après,
tout est assez bien documenté dans l'aide en ligne. Ce qui manque peut
être c'est de la doc du genre "Si vous voulez faire ce genre de chose,
voici les 3/4 points d'entrée importants au milieu de la myriade
d'objet que nous exposons".
Avatar
Senhon
a écrit dans le message de groupe de
discussion :

On 10 déc, 15:54, James Kanze wrote:

Plus intéressant serait de savoir où il a appris de faire des
macros VS, parce que moi, je suis prêt à investir un peu de
temps et d'effort pour les apprendre (vue que je risque de
travailler sous Windows pour pas mal du temps encore).




Pour apprendre les macros, il y a deux aspects :
- Le langage, là c'est du visual basic, c'est pas très beau, mais ça
s'apprend vite. Je n'ai pas contre jamais essayé de voir si on avait
ou pas accès à toutes l'API .NET directement (monter des boîtes de
dialogue...)



Oui, j'y songe aussi, mais il faut trouver la disponibilité.

. Peut-être faut-il faire une extension pour ça ? En tout
cas on peut facilement appeler un exécutable externe, ou une DLL .NET.
- Le modèle objet de visual studio. Le point d'entrée est DTE, après,
tout est assez bien documenté dans l'aide en ligne.



Au point ou tu en es je ne pourrais rien t'apporter de plus.
Avatar
BOUCNIAUX Benjamin
Bonjour à tous,

Je trouve ce fil interressant à lire, et je me permets d'y apporter ma
petite contribution.

Le Mon, 30 Nov 2009 13:27:49 +0000, Marc Boyer a écrit :

Le 30-11-2009, AG a écrit :
Je travaille dans une équipe de personnes pour lesquels le débugger
n'est pas l'outil qui tombe sous le sens lorsqu'il s'agit de trouver
les bugs des outils qu'ils ont développé.



Mais est-ce grave ? J'avoue ne quasiment jamais me servir
de débugger. Je travaille surtout en précondition/invariant (assert) +
tests unitaires, et éventuellement valgrind quand je soupsonne une
corruption mémoire.

Et une fois le bug identifié, à grand coups de cerr
+ assert.

D'autant que souvent, le bug n'apparait pas en mode débug ;-)

Marc Boyer



Pour ma part, je ne pense pas qu'une technique soit mieux qu'une autre.
Le tout, c'est d'avoir la capacité à trouver son bug, quelque soit la
manière.
Partant de ce postulat, pourquoi ne pas simplement utiliser l'équivalent
de ces différentes manières?

Je m'explique : quand je développe, je base toutes mes gestions d'erreur
sur des exceptions, y compris les controles appliqués (valeurs acceptées,
pointeurs non NULL, retours de syscalls corrects, etc.).
Pour chaque exception levée/reçue, j'ajoute une trace (via une macro,
désactivable donc) qui résulte en une stack trace complète dans les logs.
Ca fait un peu java, mais c'est très clair.
Au bout de la stack trace (cad au niveau le plus haut de la gestion
d'erreur, qui n'est pas forcément main() ), j'affiche le message d'erreur
qui est remonté ; une manière élégante de voir d'où est venu le bug, et
en plus d'où venaient les appels à cette fonctionnalité.
Et cela permet également d'implémenter des préconditions/invariants.

Personnellement, je trouve l'assert un peu brutal, et le printf trop
limitatif :)

Pourquoi complètement stopper un programme lorsque quelque chose ne se
passe pas bien? Autant, un assert est levé dans une partie du code de bas
niveau appelé depuis une procédure de plus haut niveau sans importance,
et qui ne serait pas forcément "critique" d'un point de vue fonctionnel.
Pourquoi ne pas laisser le choix en fonction de l'implémentation faite ?
Cela permet aussi de choisir l'équivalent à assert, à savoir "je stoppe à
la moindre erreur".

Ajoutez à cela une sauvegarde des données d'entrée lors de la gestion
d'erreur, et une compilation optimisée avec les symboles de debug dans
des fichiers séparés.
Lors d'un problème en production, il suffit généralement de regarder la
stacktrace pour voir ce qui s'est produit, et comment corriger le
problème.
J'accorde le fait qu'il sera toujours nécessaire d'arriver à reproduire
le bug avec un test unitaire, mais ce n'est pas toujours le cas (si une
frame de niveau supérieur est corrompue, il se peut que le programme
"lache" lors d'appels un peu plus bas, ou après plusieurs retours ...
Sans compter que quand une stack est foirée, printf ne fait pas toujours
son boulot comme il faut.
Un exemple de bug difficile à trouver avec un printf serait par exemple
l'oubli d'un return dans le code (oui ok, il existe Wall/Werr, mais je
vois assez souvent ce genre d'erreur chez certains développeurs :) ).
Autrement, l'idée du programme freezé me vient aussi à l'esprit. strace
est bien, mais ne vaut pas un bon gdb dans la plupart des cas.

Le débugueur, quoiqu'en disent certains, est toujours un outil plus
qu'utile. En plus d'afficher de la variable, il permet d'ajouter du
watchpoint, de suivre le fil d'exécution, de dumper des portions de
mémoire, et modifier des données, le tout sans modifier le programme (ce
qui n'est pas le cas si on implémente du cin dans le programme pour
modifier des valeurs).
La seule condition est d'avoir les symboles de debug, et ce n'est pas ce
qui empeche de livrer une application optimisée et stripée. Et pour une
analyse post mortem, c'est le recours qui fournira le plus
d'informations, puisque la reproduction du bug n'est pas nécessaire: il
suffit d'analyser le core. Cela fournit un peu plus de détail, à mon
sens, que les derniers printf/cout qui ont été faits par le programme.
Sans compter la possibilité de générer l'image d'un process à un instant
T pour y revenir à posteriori.

Autre outil, le memory profiler. Je me force à l'utiliser à chaque fois
que je sors un nouveau programme ou un nouveau test unitaire. Ca permet
toujours une vérification supplémentaire, pas très couteuse.

Ensuite, je suis d'accord qu'au départ, la production d'un bug dépend
beaucoup de la qualité du développement.
Sans parler de la lisibilité du code, le fait de se servir de son cerveau
à chaque ligne de code pondue aide énormément, plutot que tester à
tatillon au fur et à mesure de l'avancement du dit bout de code.
Pour commencer, une bonne analyse et une bonne conception de
l'application limitent beaucoup les problèmes et facilitent les tests des
composants.
Ensuite, il faut toujours stresser ses applications/modules avant de les
livrer. L'appli qui marche avec les données attendues, ce n'est pas bien,
c'est la moindre des choses. L'application qui peut prendre n'importe
quoi en entrée sans se gaufrer et en gérant les erreurs, c'est mieux :)

Bref, je pense qu'il n'existe pas de solution meilleure qu'une autre, le
tout est d'en utiliser le maximum, en choisissant comment on gère les
problèmes "d'en haut" de l'application. Meme les meilleurs programmeurs
ne sont jamais à l'abri d'un oubli.

Ce que je remarque en me relisant, c'est que pour du petit bug, la trace
peut suffir, mais je ne pense pas dans tous les cas. Pour du "bon" bug
(SIGSEGV/SIGPIPE, problèmes de synchros entre threads, freeze, etc.), le
débugueur apporte un confort inégalé, et permet de plus de s'attacher au
process incriminé pour une analyse en live (faites l'équivalent d'un
x/100x $sp avec des printf, sur un process en cours).

Et je rejoins Fabien sur VC et sa gestion de projet complètement moisie
qui fait perdre un temps fou, surtout quand il s'agit d'une petite
tache :)

Cordialement,

Benjamin BOUCNIAUX.
Avatar
Fabien LE LEZ
On Fri, 25 Dec 2009 17:20:55 -0600, BOUCNIAUX Benjamin
:

quand je développe, je base toutes mes gestions d'erreur
sur des exceptions



Attention, quand tu lances une exception, pas mal de destructeurs sont
appelés. Par conséquent, lancer une exception présuppose que le
programme est dans un état assez stable pour ce faire.
Si tu t'aperçois qu'un de tes objets est dans un état incohérent, il
vaut mieux arrêter tout de suite le programme, sans appeler le
destructeur de cet objet.