¿Cómo imprimir cada línea ejecutada en GDB automáticamente hasta que se alcance un punto de interrupción determinado?

16 minutos de lectura

avatar de usuario
sdaau

Me gustaría poder establecer un punto de interrupción en GDB y hacer que se ejecute hasta ese punto, y en el proceso, imprimir las líneas que ha “atravesado”.

Aquí hay un ejemplo, basado en este archivo simple con un main y una función, y dos puntos de corte para cada uno:

$ 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.

Para iniciar la sesión, necesito ejecutar (r) el programa, que luego se detendrá en el primer punto de interrupción (main):

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

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

En este punto, puedo, por ejemplo, presionar continuar (c); y el proceso se ejecutará, sin generar nada, y se interrumpirá en la línea solicitada:

(gdb) c
Continuing.

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

Por otro lado, en lugar de continuar, puedo ir línea por línea, ya sea usando el paso (s) o siguiente (n); por ejemplo:

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) 

De todos modos, soy consciente de que puedo mantener Ingresar presionado, y el último comando ingresado (paso o siguiente) se repetirá (dejó una sesión un poco más larga en el segundo caso, para mostrar que ‘siguiente’ permanece en el mismo nivel, ‘paso’ pasos dentro de las funciones que se están llamando). Sin embargo, como se puede ver, dependiendo de si se ejecuta el paso o el siguiente, puede tomar un tiempo hasta que se alcance un resultado, por lo que no quiero sentarme durante 10 minutos con la mano atascada en el botón Intro 🙂

Entonces, mi pregunta es: ¿puedo instruir de alguna manera gdb para ejecutar hasta el ‘punto de interrupción 2’ sin más intervención del usuario, mientras imprime las líneas por las que pasa, como si se presionara el paso (o el siguiente)?

  • Esta respuesta a una pregunta similar (pero probablemente no duplicada) podría ser de alguna ayuda, pero no estoy seguro de cómo (o si) la secuencia de comandos podría modificarse para lidiar bien con un punto de interrupción: stackoverflow.com/questions/5812411/ gdb-automático-siguiente/…

    – Michael Burr

    04/08/2011 a las 19:31

  • Muchas gracias por eso @Michael Burr. Supongo que la única diferencia entre eso y esta pregunta es la condición para detener (aquí punto de interrupción, allí segfault). !

    – sdaau

    4 de agosto de 2011 a las 19:36

  • Si encuentra una manera de que el script tenga algún tipo de inteligencia sobre cuándo debe detenerse, publique un comentario aquí. Creo que sería una técnica generalmente útil.

    – Michael Burr

    4 de agosto de 2011 a las 20:08

avatar de usuario
sdaau

Bueno, esto no fue fácil, pero creo que lo entendí un poco 🙂 Pasé por un montón de intentos fallidos (publicado aquí); el código relevante está debajo.

Básicamente, el problema en un “siguiente/paso hasta el punto de interrupción” es cómo determinar si está “en” un punto de interrupción o no, si el depurador se detiene (en un paso). Tenga en cuenta que también uso GDB 7.2-1ubuntu11 (actual para Ubuntu 11.04). Entonces, fue así:

  • Primero descubrí sobre Variables de conveniencia, y pensamiento: dado que hay contadores de programas disponibles, debe haber alguna variable de conveniencia de GDB que proporcione el estado de “punto de interrupción” y se pueda usar directamente en un script de GDB. Después de mirar a través Índice de referencia del BGF por un tiempo, sin embargo, simplemente no puedo encontrar tales variables (mis intentos están en nub.gdb)
  • A falta de una variable interna de “estado de punto de interrupción” de este tipo, lo único que queda por hacer es capturar la salida de la línea de comandos (‘stdout’) de GDB (en respuesta a los comandos) como una cadena y analizarla (buscando ” punto de interrupción”)
  • Entonces, me enteré de API de Python al BGF, y el gdb.execute("CMDSTR", toString=True) comando, que aparentemente es exactamente lo que se necesita para capturar la salida: “De manera predeterminada, cualquier salida producida por el comando se envía a la salida estándar de gdb. Si el parámetro to_string es True, gdb.exete recopilará la salida y la devolverá como una cadena.[1]“!

Y finalmente, el enfoque que funcionó es: redirigir temporalmente la salida de GDB desde un gdb.execute a un archivo de registro en RAM (Linux: /dev/shm); y luego volver a leerlo, analizarlo e imprimirlo desde python; python también maneja un bucle while simple que avanza hasta que se alcanza un punto de interrupción.

La ironía es que la mayoría de estos errores, que causaron esta solución al redirigir el archivo de registro, en realidad se corrigieron recientemente en SVN; lo que significa que se propagarán a las distribuciones en un futuro cercano, y uno podrá usar gdb.execute("CMDSTR", toString=True) directamente :/ Sin embargo, como no puedo arriesgarme a construir GDB desde la fuente en este momento (y posiblemente encontrarme con posibles nuevas incompatibilidades), esto también es lo suficientemente bueno para mí 🙂

Aquí están los archivos relevantes (parcialmente también en pygdb-fork.gdb,pygdb-fork.py):

pygdb-logg.gdb es:

# 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 es:

# 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

Básicamente, pygdb-logg.gdb carga el pygdb-logg.py secuencia de comandos de python, configura el alias nub por nextUntilBreakpointe inicializa la sesión; todo lo demás lo maneja el script de python. Y aquí hay una sesión de muestra, con respecto a la fuente de prueba en 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)

… tal como lo quería: P Simplemente no sé qué tan confiable es (y si será posible usarlo en avr-gdbque es para lo que necesito esto 🙂 EDITAR: la versión de avr-gdb en Ubuntu 11.04 es actualmente 6.4, que no reconoce el comando python 🙁)

Bueno, espero que esto ayude a alguien,
¡Salud!

Aquí algunas referencias:

  • ¿Pusiste esto en un repositorio? ¿Se reemplaza por algún otro código?

    – Janus Troelsen

    13/09/2015 a las 20:31

  • Quiero agregar, encontré hay otra manera simple de hacer lo mismosin un script de python.

    – Hola angel

    17/07/2016 a las 19:32

avatar de usuario
invitado

¿Qué hay de hacerlo así en gdb, usando un archivo de comando? Cambie el argumento del archivo y el recuento de bucles según sea necesario.

gdb -x run.gdb

ejecutar.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

Basado en el enlace en la respuesta de @sdaau (http://www.mail-archive.com/gdb@gnu.org/msg00031.html), creé mi propia secuencia de comandos para simplemente seguir enviando ‘s’ y leyendo la salida de gdb continuamente, mientras imprimía la salida en un archivo de texto y terminal, por supuesto, mi secuencia de comandos puede modificarse para adaptarse a las necesidades de cualquier otra persona, sin embargo, espero que el La modificación que hice debería adaptarse a las necesidades de la mayoría de las personas.

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]

avatar de usuario
sdaau

Como una nueva respuesta, dado que la anterior ya está acaparada 🙂 Básicamente, si el punto es observar la ejecución de las líneas de código fuente (y / o ensamblador) mientras el programa se ejecuta, ya que la motivación es a menudo para mí cuando busco “impresión automática” — entonces, básicamente, una forma muy rápida es usar el modo GDB TUI; cito:

c – comportamiento de gdb: valor optimizado

Utilice el modo TUI de GDB. Mi copia de GDB lo habilita cuando escribo el signo menos y Enter. Luego escriba Cx 2 (es decir, mantenga presionado Control y presione X, suelte ambos y luego presione 2). Eso lo pondrá en fuente dividida y pantalla de desmontaje. Luego use stepi y nexti para mover una instrucción de máquina a la vez. Use Cx o para cambiar entre las ventanas TUI.

El truco aquí es que, incluso si aciertas continue – esta fuente de tiempo se mostrará e indicará en la TUI; y seguido mientras se ejecuta el programa:

Captura de pantalla de TUI de GDB

… y esto para mí evita muchas situaciones en las que tendría que escribir los puntos de interrupción en un “contexto de pasos automáticos” (aunque todavía hay tales situaciones). Documentos sobre TUI: TUI – Depuración con GDB

¡Salud!

avatar de usuario
Simón Sobisch

La respuesta actualmente aceptada incluye una gran cantidad de archivos io y solo se detiene en los puntos de interrupción, pero se ignoran los puntos de observación, las señales y posiblemente incluso el final del programa.

Usando la API de Python, esto se puede manejar muy bien:

  • definir un comando de usuario (con un argumento adicional para decir qué tan rápido es el paso automático)
  • opcional: defina un parámetro para el valor predeterminado (ambas variantes a continuación)
  • hacer el ciclo while dentro de python, manejar la interrupción de teclado “esperada” de CTRL-C
  • registrar un stop controlador de eventos que verifica el motivo de la detención y almacena el tipo de paso allí
  • ajuste el bucle while para detener una parada “no simple” (punto de interrupción/punto de observación/señal/…)

El siguiente código se puede colocar en un gdb-auto-step.py que se puede activar con source gdb-auto-step.py cuando lo desee (o inclúyalo en el archivo .gdbinit para que esté siempre disponible):

import gdb
import time

class CmdAutoStep (gdb.Command):
    """Auto-Step through the code until something happens or manually interrupted.
An argument says how fast auto stepping is done (1-19, default 5)."""
    def __init__(self):
        print('Registering command auto-step')
        super(CmdAutoStep, self).__init__("auto-step", gdb.COMMAND_RUNNING)
        gdb.events.stop.connect(stop_handler_auto_step)
    def invoke(self, argument, from_tty):
        # sanity check - are we even active, prevents a spurious "no registers" exception
        try:
            gdb.newest_frame()
        except gdb.error:
            raise gdb.GdbError("The program is not being run.")

        # calculate sleep time
        if argument:
            if not argument.isdigit():
                raise gdb.GdbError("argument must be a digit, not " + argument)
            number = int(argument)
            if number == 0 or number > 19:
                raise gdb.GdbError("argument must be a digit between 1 and 19")   
        sleep_time = 3.0 / (1.4 ** number)

        # activate GDB scrolling, otherwise we'd auto-step only one page
        pagination = gdb.parameter("pagination")
        if pagination:
            gdb.execute("set pagination off", False, False)

        # recognize the kind of stop via stop_handler_auto_step 
        global last_stop_was_simple
        last_stop_was_simple = True

        # actual auto-stepping
        try:
            while last_stop_was_simple:
                gdb.execute("step")
                time.sleep(sleep_time)
        # we just quit the loop as requested
        # pass keyboard and user errors unchanged
        except (KeyboardInterrupt, gdb.GdbError):
            raise
        # that exception is unexpected, but we never know...
        except Exception:
            traceback.print_exc()
        # never leave without cleanup...
        finally:
            if pagination:
                gdb.execute("set pagination on", False, False)

def stop_handler_auto_step(event):
    # check the type of stop, the following is the common one after step/next,
    # a more complex one would be a subclass (for example breakpoint or signal)
    global last_stop_was_simple
    last_stop_was_simple = type(event) is gdb.StopEvent

CmdAutoStep()

Para especificar el valor predeterminado con un parámetro (también conocido como “la forma gdb”), agregue otro parámetro y utilícelo (también viene con 0 = ilimitado)

class ParameterAutoStep (gdb.Parameter):
    """auto-step default speed (0-19, default 5)"""
    def __init__(self):
        self.set_doc = """Set speed for "auto-step", internally used to calculate sleep time between "step"s.
set "auto-step 0" causes there to be no sleeping."""
        self.show_doc = "Speed value for auto-step."
        super(ParameterAutoStep, self).__init__("auto-step", gdb.COMMAND_RUNNING, gdb.PARAM_UINTEGER)
        self.value = 5
        self.backup = self.value

    def get_set_string (self):
        try:
            self.value = int(ParameterAutoStep.validate(self.value))
        except gdb.GdbError:
            self.value = int (self.backup)
            raise
        self.backup = self.value
        return ""

    @staticmethod
    def validate (argument):
        """validation for auto-step speed"""
        try:
            speed = int(argument)
            if speed < 0 or speed > 19:
                raise ValueError()
        except (TypeError, ValueError):
            raise gdb.GdbError("speed-argument must be an integer between 1 and 19, or 0")
        return speed

class CmdAutoStep (gdb.Command):
    """Auto-Step through the code until something happens or manually interrupted.
An argument says how fast auto stepping is done (see parameter "auto-step")."""
    def __init__(self):
        print('Registering command and parameter auto-step')
        super(CmdAutoStep, self).__init__("auto-step", gdb.COMMAND_RUNNING)
        self.defaultSpeed = ParameterAutoStep()
        gdb.events.stop.connect(stop_handler_auto_step)

    def invoke(self, argument, from_tty):
        # sanity check - are we even active, prevents a spurious "no registers" exception
        try:
            gdb.newest_frame()
        except gdb.error:
            raise gdb.GdbError("The program is not being run.")

        # calculate sleep time
        if argument:
            number = ParameterAutoStep.validate(argument) # raises an error if not valid
        else:
            number = self.defaultSpeed.value
        if number:
            sleep_time = 3.0 / (1.4 ** number)
        else:
            sleep_time = 0

        # activate GDB scrolling, otherwise we'd auto-step only one page
        pagination = gdb.parameter("pagination")
        if pagination:
            gdb.execute("set pagination off", False, False)

        # recognize the kind of stop via stop_handler_auto_step 
        global last_stop_was_simple
        last_stop_was_simple = True

        # actual auto-stepping
        try:
            while last_stop_was_simple:
                gdb.execute("step")
                time.sleep(sleep_time)
        # we just quit the loop as requested
        # pass keyboard and user errors unchanged
        except (KeyboardInterrupt, gdb.GdbError):
            raise
        # that exception is unexpected, but we never know...
        except Exception:
            traceback.print_exc()
        # never leave without cleanup...
        finally:
            if pagination:
                gdb.execute("set pagination on", False, False)

def stop_handler_auto_step(event):
    # check the type of stop, the following is the common one after step/next,
    # a more complex one would be a subclass (for example breakpoint or signal)
    global last_stop_was_simple
    last_stop_was_simple = type(event) is gdb.StopEvent

CmdAutoStep()

avatar de usuario
viaceslavos

En realidad, tengo un repositorio de Github con una extensión Python-GDB, que hace exactamente lo mismo que has descrito, pero con algo más de funcionalidad.

Puedes simplemente clonar el repositorio:

git clone https://github.com/Viaceslavus/gdb-debug-until.git

y alimente el script de python a GDB con el siguiente comando dentro de GDB:

source gdb-debug-until/debug_until.py

(Cambie la ruta del script de Python si es necesario)

Y ahora puede usar el siguiente comando para ejecutar cada línea de su código hasta un punto de interrupción:

debug-until somefile.c:100 --args="" --end="somefile.c:200"

“somefile.c:100” aquí es un punto de interrupción inicial, y “somefile.c:200” es el punto de interrupción final.
“–args” especifica un conjunto de argumentos para su programa (puede omitirlo si no hay argumentos).

Con esta extensión, también puede ejecutar varias veces el código (con la opción ‘-r’) e incluso especificar algunos eventos que deben manejarse durante la depuración. Para más información ver:
https://github.com/Viaceslavus/gdb-debug-hasta

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad