Как распечатать каждую выполненную строку в GDB автоматически, пока не будет достигнута определенная точка останова? - PullRequest
26 голосов
/ 04 августа 2011

Я хотел бы иметь возможность установить точку останова в 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) 

На данный момент - я могу, например, нажать продолжить (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 , и последняя введенная команда (шаг или следующая) будет повторяться ( во втором случае оставил сеанс немного длиннее, чтобы показать, что «next» остается на том же уровне, «step» входит внутрь функций, вызываемых ). Однако, как видно, в зависимости от того, выполняется ли шаг или следующий шаг, может потребоваться некоторое время, пока не будет достигнут результат - и поэтому я не хочу сидеть 10 минут, держа руку на кнопке «Ввод» :)

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

Ответы [ 4 ]

20 голосов
/ 06 августа 2011

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

По сути, проблема в «следующем / шаге до точки останова» заключается в том, как определить, «находитесь ли вы» на точке останова или нет, если отладчик остановлен (на шаге). Обратите внимание, что я использую GDB 7.2-1ubuntu11 (актуально для Ubuntu 11.04). Итак, все прошло так:

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

И, наконец, подход, который сработал, заключается в следующем: временное перенаправление вывода 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 загружает скрипт Python pygdb-logg.py, устанавливает псевдоним nub для nextUntilBreakpoint и инициализирует сеанс - все остальное обрабатывается скриптом Python. А вот пример сеанса - по отношению к источнику теста в 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, для чего мне это нужно :) РЕДАКТИРОВАТЬ: версияavr-gdb в Ubuntu 11.04 в настоящее время имеет 6.4, которая не распознает команду python: ()

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

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

5 голосов
/ 09 августа 2015

Как это сделать в 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
3 голосов
/ 20 октября 2014

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

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]
1 голос
/ 07 августа 2011

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

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

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

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

GDB TUI Screenshot

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

Ура!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...