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

Я хотел бы иметь возможность установить контрольную точку в GDB и запустить ее к этой точке - и в процессе распечатать строки, которые она "перешла".

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

$ cat > test.c <<EOF
#include "stdio.h"

int count=0;

void doFunction(void) {
  // two steps forward
  count += 2;
  // one step back
  count--;
}

int main(void) {
  // some pointless init commands;
  count = 1;
  count += 2;
  count = 0;
  //main loop
  while(1) {
    doFunction();
    printf("%d\n", count);
  }
}
EOF

$ gcc -g -Wall test.c -o test.exe
$ chmod +x test.exe
$ gdb -se test.exe
...
Reading symbols from /path/to/test.exe...done.
(gdb) b main
Breakpoint 1 at 0x80483ec: file test.c, line 14.
(gdb) b doFunction
Breakpoint 2 at 0x80483c7: file test.c, line 7.

Чтобы начать сеанс, мне нужно запустить (r) программу, которая затем остановится на первой точке останова (main):

(gdb) r
Starting program: /path/to/test.exe 

Breakpoint 1, main () at test.c:14
14    count = 1;
(gdb) 

В этот момент я могу, например, нажать continue (c); и процесс будет проходить, не выводит ничего, и прерывается по запрошенной строке:

(gdb) c
Continuing.

Breakpoint 2, doFunction () at test.c:7
7     count += 2;
(gdb)

С другой стороны, вместо продолжения - я могу идти по строкам, используя шаг (s) или следующий (n); например:

14    count = 1;
(gdb) n
15    count += 2;
(gdb) s
16    count = 0;
(gdb) s
19      doFunction();
(gdb) s

Breakpoint 2, doFunction () at test.c:7
7     count += 2;
(gdb) s
9     count--;
(gdb) s
10  }
(gdb) s
main () at test.c:20
20      printf("%d\n", count);
(gdb) s
...
(gdb) s
_IO_vfprintf_internal (s=Cannot access memory at address 0xe5853361
) at vfprintf.c:210
210 vfprintf.c: No such file or directory.
    in vfprintf.c
(gdb) s
245 in vfprintf.c
(gdb) s
210 in vfprintf.c
(gdb) n
245 in vfprintf.c
...
(gdb) n
2006    in vfprintf.c
(gdb) n
__printf (format=0x80484f0 "%d\n") at printf.c:39
39  printf.c: No such file or directory.
    in printf.c
(gdb) n
main () at test.c:21
21    }
(gdb) n
19      doFunction();
(gdb) n

Breakpoint 2, doFunction () at test.c:7
7     count += 2;
(gdb) 

В любом случае, я знаю, что я могу сохранить Enter нажатой, и последняя введенная команда (шаг или следующий) будет повторяться (оставшийся бит более длинный сеанс во втором случае, чтобы показать, что "следующий" остается на том же уровне, шаги "шаг" внутри вызываемых функций). Однако, как видно, в зависимости от того, будет ли шаг или следующий запуск, это может занять некоторое время, пока не будет достигнут результат - и поэтому я не хочу сидеть в течение 10 минут, когда моя рука застряла на кнопке Enter:)

Итак, мой вопрос: могу ли я как-то проинструктировать gdb работать с "точкой останова 2" без дополнительного вмешательства пользователя - при распечатке строк, через которые он проходит, как будто нажата кнопка (или следующая)?

Ответ 1

Ну, это было нелегко, но я думаю, что я немного его понял:) Я прошел через кучу неудачных попыток (опубликовано здесь); соответствующий код ниже.

В основном проблема "следующего/шага до точки останова" заключается в том, как определить, является ли вы "on" точкой останова или нет, если отладчик остановлен (на шаге). Заметьте, что я использую GDB 7.2-1ubuntu11 (текущий для Ubuntu 11.04). Итак, это было так:

  • Впервые я нашел о "Переменные удобства" , и если учесть, что есть счетчики программ и такие доступные, должна быть какая-то удобная переменная GDB, дает статус "точки останова" и может использоваться непосредственно в GDB script. Просматривая индекс ссылки GDB, я просто не могу найти таких переменных (мои попытки находятся в nub.gdb)
  • При отсутствии такой внутренней переменной "статус точки останова" - единственное, что осталось сделать - это захватить вывод командной строки ( "stdout" ) GDB (в ответ на команды) в виде строки и проанализировать ее ( ищет "точку останова" )
  • Затем я узнал о Python API для GDB и команду gdb.execute("CMDSTR", toString=True), которая, по-видимому, именно то, что необходимо для захвата вывод: "По умолчанию любой вывод, созданный командой, отправляется на стандартный вывод gdb. Если параметр to_string имеет значение True, то вывод будет собран gdb.execute и возвращен как строка [ 1]"!
    • Итак, сначала я попытался создать script (pygdb-nub.py, gdbwrap), который будет использовать gdb.execute в рекомендуемом порядке; не удалось - из-за этого:
    • Тогда я подумал, что буду использовать программу python script to subprocess.Popen GDB, заменяя ее stdin и stdout; и затем продолжить контроль GDB (pygdb-sub.py) - это тоже не сработало... (видимо, потому что я не перенаправлял stdin/вне права)
    • Затем я подумал, что буду использовать сценарии python для вызова из GDB (через source), который будет внутренне fork в pty, когда gdb.execute должен быть вызван, чтобы захватить его вывод (pygdb-fork.gdb, pygdb-fork.py)... Это почти сработало - поскольку возвращаются строки; однако GDB замечает, что что-то не так: "[tcsetpgrp failed in terminal_inferior: Operation not allowed]", и последующие строки возврата, похоже, не меняются.

И наконец, подход, который работал: временно перенаправляет вывод GDB из gdb.execute в файл журнала в ОЗУ (Linux: /dev/shm); а затем, читая его, разглаживая его и печатая его из python - python также обрабатывает простой цикл while, который до достижения точки останова.

Ирония заключается в том, что большинство этих ошибок, вызвавших это решение путем перенаправления лог файла, на самом деле недавно зафиксированы в SVN; что они будут распространяться на дистрибутивы в ближайшем будущем, и можно будет напрямую использовать gdb.execute("CMDSTR", toString=True):/Тем не менее, поскольку я не могу рисковать строить GDB из источника прямо сейчас (и, возможно, столкнуться с возможными новыми несовместимыми), это хорошо достаточно для меня также:)

 

Вот соответствующие файлы (частично также в pygdb-fork.gdb, pygdb-fork.py):

pygdb-logg.gdb:

# gdb script: pygdb-logg.gdb
# easier interface for pygdb-logg.py stuff
# from within gdb: (gdb) source -v pygdb-logg.gdb
# from cdmline: gdb -x pygdb-logg.gdb -se test.exe

# first, "include" the python file:
source -v pygdb-logg.py

# define shorthand for nextUntilBreakpoint():
define nub
  python nextUntilBreakpoint()
end

# set up breakpoints for test.exe:
b main
b doFunction

# go to main breakpoint
run

pygdb-logg.py:

# gdb will 'recognize' this as python
#  upon 'source pygdb-logg.py'
# however, from gdb functions still have
#  to be called like:
#  (gdb) python print logExecCapture("bt")

import sys
import gdb
import os

def logExecCapture(instr):
  # /dev/shm - save file in RAM
  ltxname="/dev/shm/c.log"

  gdb.execute("set logging file "+ltxname) # lpfname
  gdb.execute("set logging redirect on")
  gdb.execute("set logging overwrite on")
  gdb.execute("set logging on")
  gdb.execute(instr)
  gdb.execute("set logging off")

  replyContents = open(ltxname, 'r').read() # read entire file
  return replyContents

# next until breakpoint
def nextUntilBreakpoint():
  isInBreakpoint = -1;
  # as long as we don't find "Breakpoint" in report:
  while isInBreakpoint == -1:
    REP=logExecCapture("n")
    isInBreakpoint = REP.find("Breakpoint")
    print "LOOP:: ", isInBreakpoint, "\n", REP

 

В принципе, pygdb-logg.gdb загружает pygdb-logg.py python script, устанавливает псевдоним nub для nextUntilBreakpoint и инициализирует сеанс - все остальное обрабатывается python script. И вот пример сеанса - в отношении источника теста в OP:

$ gdb -x pygdb-logg.gdb -se test.exe
...
Reading symbols from /path/to/test.exe...done.
Breakpoint 1 at 0x80483ec: file test.c, line 14.
Breakpoint 2 at 0x80483c7: file test.c, line 7.

Breakpoint 1, main () at test.c:14
14    count = 1;
(gdb) nub
LOOP::  -1
15    count += 2;

LOOP::  -1
16    count = 0;

LOOP::  -1
19      doFunction();

LOOP::  1

Breakpoint 2, doFunction () at test.c:7
7     count += 2;

(gdb) nub
LOOP::  -1
9     count--;

LOOP::  -1
10  }

LOOP::  -1
main () at test.c:20
20      printf("%d\n", count);

1
LOOP::  -1
21    }

LOOP::  -1
19      doFunction();

LOOP::  1

Breakpoint 2, doFunction () at test.c:7
7     count += 2;

(gdb)

... так же, как я этого хотел: P Просто не знаю, насколько это надежно (и можно ли использовать его в avr-gdb, для чего мне это нужно:) EDIT: версия avr -gdb в Ubuntu 11.04 в настоящее время является 6.4, что не распознает команду python:()

 

Хорошо, надеюсь, это поможет кому-то,
Ура!

 

Вот некоторые ссылки:

Ответ 2

Основываясь на ссылке в ответе @sdaau (http://www.mail-archive.com/[email protected]/msg00031.html), я создал свой собственный script, чтобы просто продолжать отправлять ' и чтение вывода gdb постоянно, при печати вывода в текстовый файл и терминал, конечно, мой script может быть изменен, чтобы соответствовать любым другим нуждам, однако я надеюсь, что сделанная вами модификация должна соответствовать большинству потребностей людей.

http://www.codeground.net/coding/gdb-step-into-all-lines-to-get-full-application-flow/

wget http://www.codeground.net/downloads/gdbwalkthrough.c
gcc gdbwalkthrough.c -o gdbwalkthrough
./gdbwalkthrough <application full path> [application arguments]

Ответ 3

Как это сделать в gdb, используя командный файл. Измените аргумент файла и количество циклов по мере необходимости.

gdb -x run.gdb

run.gdb:

set pagination off
set logging file gdb.log
set logging on
set $i = 0
file main
break main
break WriteData
# sadly, commands not getting executed on reaching breakpoint 2
commands 2
  set $i=1000
  print "commands 2 : %d",$i
end
run
while ( $i < 1000 )
  step
  # next
  # continue
  set $i = $i + 1
end

Ответ 4

В качестве нового ответа, поскольку предыдущий уже запутан:) В принципе, если целью является наблюдение за выполнением строк кода источника (и/или сборки), поскольку программа работает, поскольку мотивация часто для меня при поиске в "автоматическую распечатку" - тогда, в принципе, очень быстрый способ - использовать режим GDB TUI; Я цитирую:

c - поведение gdb: оптимизированное значение - переполнение стека # 1354762

Используйте режим TUI GDB. Моя копия GDB позволяет это, когда я набираю минус и Enter. Затем введите C-x 2 (удерживая нажатой клавишу Control и нажмите X, отпустите оба и затем нажмите 2). Это поместит его в разделительный источник и демонстрационный дисплей. Затем используйте stepi и nexti для перемещения одной машинной команды за раз. Используйте C-x o для переключения между окнами TUI.

Фокус в том, что даже если вы нажмете continue - этот источник времени будет показан и указан в TUI; и после выполнения программы:

GDB TUI Screenshot

... и это для меня позволяет избежать многих ситуаций, когда я должен был бы script точки останова в "контексте авто-шага" (хотя есть еще такие ситуации). Документы о TUI: TUI - Отладка с помощью GDB

Ура!