Отладка приложений в FreeBasic (c помощью GDB)

gdb.png

Многие программисты на начальном этапе и не только, не представляют или не хотят представлять, как может отладчик помочь в устранении ошибок в своей программе. А ведь данный инструмент значительно экономит время и нервы. Конечно если ваша программа состоит из 10 строк, то тут пожалуй отладчик не нужен, ошибка видна невооруженным глазом, но что делать когда у вас более 1000 строк? Или хуже того, эти же 1000 строк разбиты на несколько модулей? Как в таком случае быстро отловить вылет в программе или некорректное выполнение? Конечно вы можете по исходнику раставить кучу строк-меток с незамысловатым содержимым:

? "работа 1 процедуры"

или

? "переменная A ="; A

И в консоль будет выводится то, что успеет до вылета программы, но сколько нужно будет установить таких меток, пока результат будет достигнут?

После этого необходимо изучить то что вывелось и найти то злосчастное место.

Давайте я попробую показать, как все это легко достигается с помощью отладчика.

Следует уточнить что методы , описанные ниже , да и вообще использование любого отладчика не годится для игр, использующих DirectX, OpenGl и другие библиотеки , основанные на этих двух или родственные им. Для игр единственный верный вариант отладки это вести логирование в файл и реже в консольный вывод.

--------------------------------------------------------------------------------------------------------

Немного об отладчике:
Сам по себе отладчик - это обычная программа, которая може внедриться в другую (отлаживаемую) программу. При этом программа становится для отладчика "послушной", а он в свою очередь способен не только иметь полную информацию о каждом ходе выполнения своей жертвы, но и менять любые данные принадлежащие и доступные жертве. Более того отлаживаемая программа может быть вообще не ваша и не иметь исходных текстов. Зная ассемблер, вы поймете устройство отлаживаемой программы и сможете изменить ее устройство по своему усмотрению. Именно поэтому для любителей исследовать чужой код, отладчик первый нужный инструмент.

Первое что нужно знать для того, чтобы производить отладку, это как компилировать приложение. Здесь все просто. Достаточно при компилировании указать ключик -g (пример: fbc -g -s gui file.bas) и можно использовать любой из предназначенных отладчиков для FreeBasic (а их три: встроенный в редактор FbEdit, FBDebugger , Insight (оболочка над GDB) или без оболочки просто GDB).

Начнем с кроссплатформенного решения , а именно с использования консольного отладчика GDB. Кстати данный отладчик до сих пор поддерживается разработчиком(ами). Последнее обновление было в ноябре 2012 года. Этот отладчик успешно используют для нескольких языков программирования и в их числе FreeBasic. Я надеюсь, что у вас в переменных средах системы установлен путь до отладчика GDB. Если нет, для интересующихся я расскажу:

Нажимаем меню ПУСК
Далее свойства МОЙ КОМПЬЮТЕР.
Далее ДОПОЛНИТЕЛЬНЫЕ ПАРАМЕТРЫ СИСТЕМЫ
Далее ПЕРЕМЕННЫЕ СРЕДЫ

peremennie_sredi.png

Далее в имени вашего профиля находим переменную path . Если ее нет , создаем. Если она есть, выделяем и щелкаем кнопку Изменить

peremennie_sredi2.png

Далее НЕ удаляя оттуда ни единой буквы, ставим точку с запятой и записываем туда путь до вашего отладчика (у всех этот путь разный). Так если у вас компилятор (файл fbc.exe) находится в папке:

C:\Freebasic\Compilier

То путь к отладчику должен быть:

C:\Freebasic\Compilier\bin\win32

Далее нажимаем кнопку ОК. Все.

Более того я вам советую прописать таким же образом путь до компилятора, до библиотек freebasic и заголовочных файлов. В итоге у вас должно получиться примерно так:

C:\Freebasic\Compilier;C:\Freebasic\Compilier\lib;C:\Freebasic\Compilier\inc;C:\Freebasic\Compilier\bin\win32

Если вам не хочется устанавливать путь до отладчика и компилятора , тогда при использовании в командной строке или в BAT файле придется указывать полный путь до них.

Давайте напишем небольшую программу с заведомой ошибкой, которая ведет к вылету программы:

Function GetPointer(i As Integer) As Integer Ptr
    Dim ipReturn As Integer Ptr
    If i = 1 Then
        ipReturn = New Integer
        *ipReturn = 100
    Endif   
    Return ipReturn
End Function

Dim pInt As Integer Ptr

pInt = GetPointer(1)
Print *pInt
pInt = GetPointer(3)
Print *pInt
Sleep

Как я и говорил, в маленьких программах разглядеть ошибку не составит труда. Тем более здесь идет консольный вывод, который скажет нам что, после четвертой строки снизу какая-то неполадка, поскольку вторая строка снизу уже не выводится в консоль. А теперь уберем вывод в консоль и напишем код без него:

Function GetPointer(i As Integer) As Integer Ptr
    Dim ipReturn As Integer Ptr
    If i = 1 Then
        ipReturn = New Integer
        *ipReturn = 100
    Endif   
    Return ipReturn
End Function

Dim pInt As Integer Ptr
Dim iValue As Integer

pInt = GetPointer(1)
iValue = *pInt
pInt = GetPointer(3)
iValue = *pInt
Sleep

Здесь после запуска и вылета программы не так очевидно, из-за того что нет никакого визуального вывода. Давайте сохраним исходный файл как file.bas и скомпилируем программу так:

fbc -g -s console file.bas

Далее скормим программу нашему кроссплатформенному другу:

gdb file.exe

И у нас в консоли высветится что-то типа этого:

GNU gdb (GDB) 7.2
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 Or later <http://gnu.org/licenses/gpl.html>
This Is free software: you are free To change And redistribute it.
There Is NO WARRANTY, To the extent permitted by law.  Type "show copying"
And "show warranty" For details.
This GDB was configured As "mingw32".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from D:\file.exe...done.
(gdb)

То есть наш отладчик готов к работе. Теперь набрав RUN или сокращенно r и следом enter мы запускаем отладку. И что же мы видим:

Starting program: D:\file.exe
[New Thread 544.0x45c]

Program received signal SIGSEGV, Segmentation fault.
0x004013c0 in main (__FB_ARGC__=<Error reading variable>,
    __FB_ARGV__=<Error reading variable>) at file.bas:16
16      iValue = *pInt
(gdb)

И хоть тут на английском, но вполне четко описана ошибка:

Program received signal SIGSEGV, Segmentation fault.

более того даже указывается в какой процедуре, какого файла и в какой именно строке это произошло:

 0x004013c0 in main (__FB_ARGC__=<Error reading variable>,
    __FB_ARGV__=<Error reading variable>) at file.bas:16
16 iValue = *pInt

Мы с помощью отладчика легко нашли ошибку в программе. А теперь удалите из исходника последние три строчки, сохраните, заново скомпилируйте и попробуйте отладить. По идее должно высветиться примерно следующее:

Starting program: D:\file.exe
[New Thread 600.0x9cc]

Program exited normally.

То есть программа выполнена нормально. После необходимых действий с отладчиком, настанет время завершить его. Для этого наживаем q (сокращ. от слова QUIT) и соглашаемся с предупреждением о выходе клавишей y (сокращ. от слова YES).

Будет еще удобнее, если мы компилирование и запуск с отладчиком будем делать из BAT файла. Для нашей конкретной программы можно написать примерно следующее:

fbc -g -s console file.bas
If EXIST file.exe gdb file.exe
pause

Здесь достаточно будет выполнить этот BAT файл.

Для любого файла BAS , можно применить такой батник:

fbc -g -s console %~n1.bas
If EXIST %~n1.exe gdb %~n1.exe
pause

Но тогда запуск делать либо из командной строки:

BATFILE file.bas

Либо просто перетащив файл .bas на иконку файла .bat.

Очень часто при отладке требуется остановиться на какой-нибудь строке или говоря другими словами поставить брекпоинт , кто-то называет точками остановки. Строка в данном случае может содержать несколько команд (например: a = 5: b=6: c= 7). Отладчиком данная строка может выполнится за один ход или за три, в зависимости от нужной команды. Самый первый брекпоинт надо ставить перед командой r. Остальные можно во время отладки. Брекпоинты ставятся командой b (сокращ. от слова BREAK). Чтобы поставить брекпоинт на второй строке, нужно ввести

b 2

И дальше клавишу r для запуска. Мы остановились на второй строке выполнения и можем теперь построчно выполнять инструкции программы, каждый раз останавливаясь на следующей. Или говоря языком программистов , делать трассировку отлаживаемого файла. Делается это командой s (сокращ. от слова STEP) или n (сокращ. от слова NEXT) в зависимости от задачи. При следующих нажатиях ENTER будет пошаговое выполнение программы, если трассировка делается с помощью s . Или тоже пошаговое, но не заходя в процедуры , если трассировка выполняется с помощью n . А если указать после s или n через пробел определенное число, то трассировка будет идти уже не по одной строке , а по указанному вами числу выполняемых строк, (то есть с пропусками , а значит быстрее). Так же чтобы получить при каждом ходе трассировки одну выполняемую машинную инструкцию (то есть разделить строку типа a = 5: b=6: c= 7 на три выполняемые инструкции), можно применять команды:si (сокращ. от выраж: Stepi ) или ni (сокращ. от выраж: Nexti)

Брекпоинтов можно установить множество и совсем не обязательно останавливаться на каждой строке программы. Можно только исключительно на брекпоинтах. Для этого надо наставить нужное кол-во брекпоинтов и переходить по ним с помощью с (сокращ. от слова CONTINUE)

На самом деле брекпоинты бывают не только жесткозаданные, какие мы использовали выше, но остановки на доступе к памяти (чтение \запись) и условными, то есть остановки в программе по определенным условиям. Более того, GDB позволяет установить любое число точек останова в одном и том же месте вашей программы, когда точки останова являются условными.

Для удаления брекпоинтов нужно ввести Clear с номером строки, а просто для временного отключения \ включения: disable \ enable с номером строки. Можно установить брекпоинт, используя имя функции, например:

b GETPOINTER

Для вывода всех установленных брекпоинтов можно использовать команду:

info b

Конечно просто раставлять брекпоинты, бывает мало, нужно просматривать значения переменных в определенные моменты выполнения времени. Тут все просто: наш привычный бейсиковский оператор print или сокращенно p и следом через пробел имя переменной. Так же никто не мешает просматривать значения указателей, пример:

p *PINT

Для просмотра всех ячеек массива нужно только указать команду p и имя массива. Пример:

p IARRAY

Если требуется просмотреть какую-то одну ячейку, то тут надо использовать синтаксис языка СИ , то есть использовать квадратные скобочки. Пример:

p IARRAY[6]

Команда disp – добавляет выражение в список выражений, значения которых отображаются каждый раз при остановке программы (это чтобы не чередовать s, p, s, p).
Есть еще одна замечательная команда watch для установки слежения за переменной. Она будет останавливать программу, когда значение в переменной изменится. Использование простое:

watch IVALUE

И конечно небольшой пример условного брекпоинта:

b 5 If IARRAY[7]=7

В этом примере будет остановка на 5 строке , только при условии , что 7 ячейка массива будет равна 7.

Но и этого бывает мало... Программисту зачастую требуется изменять значения переменных на лету. И тут GDB на высоте. Делать присвоение значений можно как командой set , так и знакомой командой print Примеры:

set IARRAY[3]=55

или

p IARRAY[3]=55

При том, во втором случае значение 55 еще выводится в консоль.

Нужно помнить, что при отладке FreeBasic программ, названия переменных, функций нужно вводить в верхнем регистре, независимо от того как вы их называли в программе!

В реале отладчик GDB очень мощный, и то что описано в этой статье - лишь малые крохи по сравнению с тем, что предлагает этот замечательный отладчик. Больше скажу, на изучение всех тонкостей отладчика уйдет масса времени и для большинства задач этого на самом деле не требуется. Хотя для продвинутых программистов конечно потребуется просматривать стек, определять последовательность вызовов процедур, присоединяться к уже запущенной программе и пр. Все это конечно же есть в GDB. При желании вы всегда можете выполнить команду HELP в отладчике и получить полную по нему информацию (правда на английском). В другой статье (или может быть в статьях) я коснусь отладки с помощью отладчиков, имеющих GUI интерфейс.