Користуючись програмою kdbg (це інтерфейс над дебаггера gdb), я простежив логіку виконання програми і виявив, що в якийсь момент кілька protected-властивостей одного з об'єктів починають містити неприпустимі значення, внаслідок чого і спотворюється логіка. Ніяких сеттерів для цих властивостей в коді не було. Значення властивостей виставлялися з якихось умов в самому класі, причому діапазон можливих значень був сильно обмежений. А ті значення, які я спостерігав, дуже відрізнялися від можливих.
Існувала проблемма, що якийсь код писав дані не в ту область пам'яті. Зазвичай це відбувається при виході за кордон масиву.
На жаль, існуючі матеріали в інтернеті мало розповідають про те, як налагоджувати в gdb C ++ програми стосовно класів і властивості. Тому довелося розбиратися в неходженими.
У середовищі kdbg є можливість встановлювати watchpoint-и, але по факту вони в kdbg не працюють. Дебаг довелося робити в консольному gdb. так як в ньому watchpoint-и працюють як належить. Передбачається, що читач знайомий з основами дебага в gdb. вміє ставити брекпоінти, виконувати програму по кроках, і знає як переглядати значення змінних.
Отже, ми знаходимося в конструкторі. Нам буде допомагати той факт, що значення this в будь-якому конструкторі відомо, так як пам'ять для створюваного примірників класу завжди виділяється заздалегідь.
В "чистому" gdb треба дати команду:
> Print this.counter
Цими командами ми дізналися значення змінної і в разі kdbg правильне написання імені.
> print (This.counter)
Взагалі я помітив, що в "чистому" gdb необов'язково абсолютно правильно писати вирази в синтаксисі C / C ++. Наприклад, символи "->" можна замінювати точкою ".", А писати символи "*" для інформування про тип покажчика теж необов'язково. Це спрощує налагодження.
Властивість може являти собою покажчик на якийсь базовий тип або покажчик на інший об'єкт (буде розглянуто нижче). У цьому випадку дії ті ж самі! Просто, перебуваючи в конструкторі, треба додебажіть до того місця, коли покажчик проініціалізіруется. Після чого тикаємо на нього правою кнопкою мишки, вибираємо "Watch expression". В області спостережень з'явиться рядок:
Причому, цей рядок може бути розгорнута, і в ній можна побачити значення, на яке посилається даний покажчик.
Часто буває так, що проблеммная змінна знаходиться в властивості, яке представляє собою якийсь об'єкт. І вже в цьому об'єкті знаходиться проблеммное властивість.
В цьому випадку, треба крок за кроком додебажіться до ініціалізації об'єкта-властивості. Після чого можна "розвернути" дерево змінних до інтересуемой змінної, і точно так же по правій кнопці мишки вибирати "Watch expression". В області спостережень з'явиться рядок:
В "чистому" gdb для таких же дій можна дати команду:
> Print this.window.active
+-> (Bool *). Active 0x859f161
Далі передбачається, що налагодження йде в gdb, так як в kdbg watchpoint-и є, але вони не працюють.
Передбачається, що в класі STApp існує змінна pWindow типу "покажчик на об'єкт STWindow", а у цього об'єкта STWindow є bool -змінного bSleep. Значення цієї змінної в якийсь момент псується. Потрібно зловити цей момент і подивитися код, який псує дані.
Ставимо брекпоінт на першу інструкцію в конструкторі класу STApp. Запускаємо програму на виконання. Після зупинки покроково доходимо до моменту, в якому проініцалізірован об'єкт pWindow. Далі переконуємося, що ініціалізація пройшла нормально:
$ 1 = (STApp * const) 0xbffff284
(Gdb) print this.pWindow
$ 2 = (class STWindow *) 0x859ee10
(Gdb) print this.pWindow.bSleep
Судячи з вихідного коду, спочатку змінна bSleep повинна бути false. так що все в порядку.
(Gdb) print (This.pWindow.bSleep)
$ 4 = (bool *) 0x859f161
(Gdb) watch * ((int *) 0x859f161)
Hardware watchpoint 2: * ((int *) 0x859f161)
Перевіряємо, як додався цей watchpoint:
Num Type Disp Enb Address What
1 breakpoint keep y 0x08070fa8 in STApp :: STApp (int, char **)
breakpoint already hit 1 time
2 hw watchpoint keep y * ((int *) 0x859f161)
Бачимо два переривання програми: перше переривання - це звичайний breakpoint, який у нас спрацював. Друге переривання - це якраз створений нами апаратний watchpoint.
Коли спрацював watchpoint
Після установки watchpoint-а даємо команду continue. щоб програма продовжила своє виконання. І в якийсь момент gdb зупинить виконання програми:
Hardware watchpoint 2: * ((int *) 0x859f161)
Old value = 1409833472
New value = 1413336268
button :: compute (this = 0x859f160, deltaT = 0.100000001)
14 fLastPressed + = deltaT;
Подивимося, що за код поруч з цією 14-м рядком:
void button :: compute (float deltaT)
Ага, по всій видимості, якийсь об'єкт класу button розміщується десь поряд з потрібними bSleep. і при запам'ятовуванні значення fLastPressed. пише це значення туди, де лежить bSleep. Подивимося стек викликів:
# 0 button :: compute (this = 0x859f160, deltaT = 0.100000001)
at /St/Window/button.cpp:14
# 1 0xb644d5a3 in STBaseWindow :: compute (this = 0x859ee10, deltaT = 0.100000001)
at /St/Window/stWindowsSDL.cpp:423
# 2 0xb642d2b6 in STSceneGraph :: render_scene (this = 0xb1fda008)
at /St/Render/SceneGraph/stSceneGraph.cpp:412
# 3 0xb6437106 in STSceneGraphSlot :: render_scene (this = 0xb1fda008)
at /St/Render/SceneGraph/stSceneGraphSlot.cpp:138
# 4 0x08070b82 in STApp :: Idle (this = 0xbffff284)
at /St.2.0/Game/stApp.cpp:39
# 5 0xb644db65 in STBaseWindow :: MainLoop (this = 0x859ee10)
at /St/Window/stWindowsSDL.cpp:275
# 6 0xb63ff42c in App :: loop (this = 0xbffff284)
at /St/Application/app.cpp:49
# 7 0x08071c71 in STApp :: Run (this = 0xbffff284, argc = 1, argv = 0xbffff364)
at /St.2.0/Game/stApp.cpp:495
# 8 0x08073280 in main (argc = 1, argv = 0xbffff364)
at /St.2.0/Game/stMain.cpp:13
Заглянемо в код, який викликає метод button :: compute (). він судячи з інформації з стека викликів, розташований тут: /St/Window/stWindowsSDL.cpp:423. Виглядає цей код так:
for (int c = 0; c<25;c++ )
dEmulattionButton [c] .compute (deltaT);
dButtons [c] .compute (deltaT);
>
Ага, масиви об'єктів. І "магічна константа" 25. А яка розмірність у цих масивів? Знаходимо в заголовки /St/Window/stWindowsSDL.h такий код:
button dButtons [24];
button dEmulattionButton [24];
.
Знову "магічна константа", але на цей раз 24. Думаю, тут все зрозуміло - маємо класичний гавнокод з магічними константами і виходом за межі масиву.
Таким чином, ми з'ясували, в чому була проблема, і виправити її не складе труднощів.
У Linux, за дивною традицією, імена програм часто человеконечітаемие. Тому, звертаю увагу, що консольний дебагер називається gdb - G nu D eB ugger. А графічний KDE-інтерфейс до нього називається kdbg - K de D eB ugG er. Тобто, буква g на початку gdb позначає G nu, а в кінці kdbg означає суфікс G er. Не плутайте написання.
Всім удачі, і безглючная програм.