Кратко что в этой статье: плагин dashboard, настройка вывода, подсветки, работа с несколькими консолями. Пропуск файлов, функций, захода в стандартную библиотеку. Сокращенный вывод istringstream, istream. Доступ к переменным вне зоны видимости.
Если вам тоскливо видеть пустую строку, И хотелось бы себя почувствовать пилотом космического корабля, то можно установить плагин. На первое время он может помочь войти в работу с GDB.
Плагин Dashboard
скачать https://github.com/cyrus-and/gdb-dashboard
Демо:
Установка происходит практически в один клик
cd
wget -P ~ https://github.com/cyrus-and/gdb-dashboard/raw/master/.gdbinit
Мы просто поместили файл с настройками GDB (.gdbinit) в домашнюю папку. Дальше вы можете его поднастроить под себя.
Настройка отображаемой информации
включить, отключить dashboard
dashboard -enabled on/off
Что бы показать только исходный код, стек, переменные
dashboard -layout source stack variables
включить сортировку переменных по имени
dashboard variables -style sort 1
Выводить переменные в столбик
dashboard variables -style compact 0
Показать/скрыть окно с регистрами.
dashboard registers
Задать размер высоты для окна с исходным кодом.
dashboard source -style height 30
справка тут:
>>>help dashboard
Также используйте клавишу Tab, чтобы GDB отображал возможные варианты дополнения кода.
>>> три стрелочки вбок это обозначает ввод в gdb. Кстати, их если захочется можно поменять на что то свое.
по каждому выводимому пункту можно получить дополнительную справку. Например: help dashboard variables -style
Вывод на разные консоли
в консоли набирайте: tty
Получим ответ в виде строки
/dev/pts/1
в GDB:
dashboard -output /dev/pts/1 (Весь интерфейс dashboard находится в другой консоли)
dashboard source -output /dev/pts/2 (Окно с исходным кодом переместили в соседнюю консоль.)
Подсветка синтаксиса.
Что бы посмотреть какая тема установлена:
dashboard -style syntax_highlighting
посмотреть список доступных тем.
Набираем в GDB: (можно прямо целиком скопипастить в gdb)
python
from pygments.styles import *
for style in get_all_styles():
print(style)
end
Для того что бы их интерактивно можно было выбирать темы. Используем код ниже, в цикле он будет показывать возможные варианты подсветки кода. Мы перебираем варианты, нажимая Enter.
python
from pygments.styles import *
for style in get_all_styles():
command = 'dashboard -style syntax_highlighting {!r}'.format(style)
gdb.execute(command)
print(command)
if input('Use this style? (y/N) ') == 'y':
break
end
Подсветку можно менять на свой вкус.
Происходит она по той же логике что и подсветка в консоли линукс
https://habr.com/ru/companies/first/articles/672464/
dashboard -style style_selected_1 '38;05;15;48;05;67'
Он покажет на голубом фоне белым цветом текст.
Там хитрая комбинация чисел в которую даже не стоит и вникать.
Проще зайти на сайт, выбрать нужный цвет и скопировать последовательность чисел. Кстати с помощью этих чисел, вы можете раскрашивать вывод cout в консоли.
std::cout << "\033[01;38;05;222m Привет мир!" << std::endl;
http://terminal-color-builder.mudasobwa.ru/
Прочие настройки можно глянуть на wiki dashboard
Настройки GDB
Это то что включил dashboard в ~/.gdbinit
set history save
Эта команда включает сохранение истории команд, введенных в GDB. Это позволяет вам просматривать и повторно использовать предыдущие команды.
Мне удобно что бы у каждого проекта была своя история. Поэтому я в каждом проекте создаю скрытый файл .gdb где сохраняю и историю команд и точки останова и другие полезные файлы для данного проекта, скрипты и прочее.
set verbose off
Эта команда отключает подробный (verbose) вывод GDB. Это означает, что GDB будет выводить только необходимую информацию, а не подробные сообщения.
set print pretty on
Эта команда включает форматированный (pretty) вывод структур данных и массивов в GDB. Обычно вывод делается максимально компактным, в одну строчку. Теперь вывод может быть на несколько страниц зато сразу видно где что. При желании даже такой дикий вывод можно сократить.
set print array off
Эта команда отключает отображение массивов в "развернутом" виде по умолчанию. Вместо этого GDB будет отображать массивы в более компактном формате. Т.е. вместо того что бы все вывести в столбик он выдаст в строку.
set print array-indexes on
Эта команда включает отображение индексов элементов массивов в выводе GDB. Это помогает легче ориентироваться в структуре массивов.
set python print-stack full
Эта команда устанавливает, что при возникновении исключений в Python-скриптах, GDB будет выводить полную трассировку стека вызовов. Это может быть полезно при отладке сценариев, написанных на Python и используемых в GDB.
Пропуск файлов при отладке
Что бы при отладке мы не заходили в стандартную библиотеку.
Добавим в наш ~/.gdbinit следующие строки
skip -gfi /usr/local/include/c++/15.0.0/*
skip -gfi /usr/local/include/c++/15.0.0/bits/*
skip -gfi /home/idis/gcc/x86_64-pc-linux-gnu/libstdc++-v3/include/*
skip -gfi /home/idis/gcc/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/*
skip -gfi /home/idis/gcc/x86_64-pc-linux-gnu/libstdc++-v3/libsupc++/*
skip -gfi /home/idis/gcc/libstdc++-v3/libsupc++/*
skip -gfi /home/idis/gcc/libstdc++-v3/include/*
skip -gfi /home/idis/gcc/libstdc++-v3/include/bits/*
skip enable
Теперь когда я набираю команду step (s) т.е. шаг с заходом, теперь я не буду входить в стандартную библиотеку. Кроме случаев когда было брошено исключение, была установлена точка останова.
Само собой если у вас большой проект, вы можете игнорировать свои файлы. Например вам не надо заходить в файл, там где вы парсите свои данные.
Чтобы добавить файл в список игнорируемых, вы можете воспользоваться функцией быстрого добавления в skip.
Если вы уже вошли в этот файл и больше не хотите в него возвращаться, просто наберите skip file, но если в этом файле будет происходить ловля исключения, или точка останова, то мы все таки в этот файл будем заходить, в других случаях нет. Можно так же не заходя в файл сразу прописать путь к файлу.
info skip - покажет все не отслеживаемые файлы.
skip delete 3-5 - удалит из не отслеживаемых файлов файлы под номерами 3, 4, 5. Теперь мы будем отлаживаться в прежнем режиме, т.е. теперь отладчик будет заходить в эти файлы.
Кстати пропускать можно не только файлы но и функции!. skip function
Там же можно задавать различные условия когда мы хотим пропускать функцию Подробнее тут
Отображение содержимого потока.
#include <iostream>
#include <sstream>
using namespace std;
int main ()
{
string str = "hello";
std::istringstream strm (str);
std::istream &input(strm);
while (strm)
cout <<(char) input.get();
}
давайте скомпилируем такую программу g++ -ggdb3 -O0 main.cpp
-ggdb3 создает более полную отладочную информацию
-O0 отключаем оптимизацию. Т.е. не даем g++ изменять наш код.
Тут у нас два потока istream и istringstream istream является базовым классом для istringstream. Поэтому то что прокатит для istringstream не прокати для istream. То что делает наша программа можно кратко записать так
string -> istringstream -> istream -> cout
Наша сейчас задача понять, как получать данные из наших структур. Потому что то же встретит вас и когда вы захотите посмотреть свой класс.
Зачем нам это? для того что бы посмотреть как можно получить нужную информацию в gdb.
допустим, мы хотим посмотреть что у нас в потоке strm.
запускаем gdb:
gdb ./a.out
>>>start
>>>next
>>>print str (сокращенно можно ввести p str)
получаем на выходе такое
Не обычный вывод, не правда ли? Он нам выкатил вообще все что он знает об str. Но хотелось бы видеть поменьше всего.
>>> p str.c_str()
Все бы хорошо но адрес мне не нужен.
>>> printf "%s\n", str.c_str()
Тут мы используем printf указывая что печататься будет строка (%s)
Немножко заморочно такое писать? Мы дальше облегчим себе работу ведь это была все лишь строка. Давайте глянем как нам получить содержимое потока.
>>> next
>>> print strm
вывод у нас более чем на 2 страницы. И по первости можно этого испугаться.
Если вы видите то что на картинке ниже, значит у вас стоит такая настройка.
>>> set print pretty off
то вывод будет такой:
Час от часу не легче... я думал вывод string это перебор, а тут такое....
тут все просто, вначале обязательно включите или добавьте в свои настройки gdb ~/.gdbinit
>>> set print pretty on
теперь вывод у нас будет такой:
Уже хоть что то... хотя бы сразу видно "hello"
Но как же нам сократить вывод до той части что нам нужна? И не видеть ничего лишнего. А нам тут целую структуру объекта выдали.
Ищем поле которое нас интересует.
Итак у нас есть строка и два указателя. Один указывает на начало, а второй на текущее местоположение. Может конечно и плохо что он выдал столько информации, зато мы получили знание, что istringstream хранит содержимого потока. Хотя чего удивляться, мы же его скопировали.
_M_in_beg = 0x7fffffffdc68 "hello", (указатель на начало)
_M_in_cur = 0x7fffffffdc68 "hello", (указатель на текущую позицию)
_M_p = 0x7fffffffdc68 "hello" ( Строка )
что бы сделать вывод отдельного поля, надо посмотреть во что вложено наше поле. Если я хочу вывести значение поля _M_p то вводим такую команду.
>>> print strm._M_stringbuf._M_string._M_dataplus._M_p
Опять вывод слишком большой, тип, адрес, значение. А что если мне не нужно отображение типа. Хорошо добавляем (char*) в начало
>>> print (char*) strm._M_stringbuf._M_string._M_dataplus._M_p
Что бы оставить вывод только строки, без адреса (чуть выше мы уже встречались с такой записью printf "%s\n"):
>>>printf "%s\n", strm._M_stringbuf._M_string._M_dataplus._M_p
На протяжении всей программы это значение меняться не будет. Так что оно нам не особо интересно, но где то в голове держим, что все это добро храниться в памяти.
Файл с настройками для проекта
Я уже говорил что в каждом проекте полезно создать свою папку .gdb в которой будут лежать наши точки останова, история команд которая была введена для текущего проекта, макросы, сокращения, скрипты и прочее.
В следующей статье я подробно на этом остановлюсь.
Итак у нас есть длинная команда которую мы не хотим вводить каждый раз.
можно создать для него макрос. В gdb набираем:
>>> define print_strm
>printf "%s\n", strm._M_stringbuf._M_string._M_dataplus._M_p
>end
Теперь введя в gdb print_strm мы получим содержимое strm
Итак давайте в нашем проекте создадим папку .gdb и поместим в него файл print_variable.gdb запишем наш макрос, который будет показывать куда показывает сейчас указатель.
Так же помним, что у istringstream базовый класc это istream. И то куда указывает указатель istream туда же указывает указатель istringstream.
define print_strm
printf "%s\n", strm._M_stringbuf._M_in_cur
end
Потом мы создадим в ~/.gdbinit скрипт который будет проверять если в нашем проекте папка .gdb если есть, то будет считывать наши настройки, если нет то создаст эту папку и в нее будет сохранять историю команд, точек останова и прочее.
Для того что бы наш скрипт заработал, надо его запустить.
>>> source ./.gdb/print_variable.gdb
Все, теперь мы можем короткой командой print_strm смотреть содержимое strm.
Красота.
Не забывайте, что вы можете из gdb выполнять консольные команды.
>>> shell top (выйти q)
>>> shell nvim ./.gdb/print_variable.gdb
>>> shell g++ -ggdb3 -O0 main.cpp (для запуска сценария она не нужна, я привожу ее как пример)
после компиляции ничего особого делать не надо, просто начните отлаживаться заново start / run
ps start останавливается в начале программы, run когда встречает точку останова.
istream
А что же с istream и input?
>>> print input
Тот момент когда думаешь что хуже быть не может... и вот тебе на.
Попробуем пошагать что бы сместился указатель может что то измениться?
Нет.. Все стоит намертво.
Давайте начнем печатать в gdb
>>> print input. далее жмем таб
Говорим да... покажи все.
о знакомые лица... peek(), get()
>>> print (char) input.peek()
О а что так можно было ? Выходит что можно, но не всегда.
С этого и надо было начать !!! Зачем нас мучал?
работаем с vector
давайте создадим вектор и попробуем посмотреть содержимое его.
std::vector <int> vector_int {2,3,4,5,6};
std::vector <string> vector_str {"hello", "world", "some"};
да ну как же так... вообще нет ни одного значения. И что теперь писать "войну и мир" для вектора и всех типов а так же для set, map и тд...
Скажем дружно... Да кому это нужно?
Для того что бы у нас был хороший красивый вывод надо подключить Pretty-print. делается это очень просто.
В начало!!!! нашего файла. Еще раз говорю, в начало нашего файла ~/.gdbinit мы пишем такой код:
python
import sys
sys.path.insert(0, '/usr/share/gcc/python/')
from libstdcxx.v6.printers import register_libstdcxx_printers
register_libstdcxx_printers (None)
end
обрати внимание на эту строку /usr/share/gcc/python/ у вас она может быть другого вида, может быть вместо gcc, gcc-8 или еще что. Ориентир папка libstdcxx внутри папки python.
теперь запускаем наш gdb смотрим... красота ничего лишнего.
А как же istringstream там тоже работает? Да.
И map работает? Да, все теперь отображается красиво. И не надо много и нудно писать различные скрипты.
Я ранее показал как с ними работать, для того, что бы вы свои данные могли выводить красиво.
disable pretty-printer - отключить красивый вывод. Будет прежний вывод.
enable pretty-printer - включить красивый вывод
Фокус покус. (Вне зоны видимости)
Теперь давайте я вам покажу еще один фокус.
Допустим у меня есть два файла. Файл_1 содержит функцию foo в которой была объявлена какая то переменная, которую мы передали в функцию boo работа которой происходит в другом файле. И мы хотим из boo увидеть что происходит в foo. Дайте ближе к практике.
в main.cpp у нас есть функция LoadJSON суть ее в том, что бы мы могли работать со строкой, как с потоком. Так как Load (std::istream &) работает только с потоком.
А теперь я хочу из функции Load посмотреть что у меня происходит с strm.
более того я могу посмотреть что с strm происходит гораздо из более глубокого вложения. Как? очень просто:
'Имя_файла'::'Имя_функции'::Переменная
обрати внимание на кавычки (это те кавычке что на букве э). Имя функции берем в кавычки если оно содержит < :: >
printf "%s\n", 'main.cpp'::LoadJSON::strm._M_stringbuf._M_in_cur
Как видим мы ушли глубоко далеко от функции LoadJSON но несмотря на это можем смотреть что там происходит.