This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[RFC][Python] New command 'fstep' using the GDB Python API


Hello,

Attached is a patch which implements a new command 'fstep' using the
GDB Python API.  The functionality is similar to the 'step' command,
but 'fstep' will bypass all intermediary functions and step into the
last function called on the source line. For example, we could have
the following in C++:

a = func1 (func2 (obj1 + obj2));

At the above line, 'step' command will step into the operator+
function, whereas 'fstep' will step into func1.

I do not have docs yet with the patch as I would first like to get the
basics of the patch sorted out. The implementation looks up for the
last 'call' instruction on the current source line, and hence is not
perfect. It will not work for cases where the last instruction cannot
be determined before executing some of the functions on the source
line. For example, if we have the following:

a = func1 (obj1) && func2 (obj2);

then, if func1 returns false, then func2 (which is the last function
on the source line) is not evaluated. However, this is not known
before func1 is executed. In such cases, the current implementation of
'fstep' steps to next source line of the current function.

The implementation at this point might also not yet be working as
desired for a few cases. For example, if we have a source line as
follows:

func1(); func2();

'fstep' will step into func2, but that might not be what is desired.

ChangeLog
2013-03-19  Siva Chandra Reddy  <sivachandra@google.com>

        Implementation of a new command 'fstep' using the GDB Python
        API
        * data-directory/Makefile.in: Add entry for the new file.
        * python/lib/gdb/command/fstep.py: Implementation of the new
        command.

        testsuite/
        * gdb.python/py-fstep.cc: Test casea for the new command.
        * gdb.python/py-fstep.exp: Tests for the new command.

Thanks,
Siva Chandra
diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
index d98ac77..e61c8d2 100644
--- a/gdb/data-directory/Makefile.in
+++ b/gdb/data-directory/Makefile.in
@@ -61,6 +61,7 @@ PYTHON_FILES = \
 	gdb/command/pretty_printers.py \
 	gdb/command/prompt.py \
 	gdb/command/explore.py \
+	gdb/command/fstep.py \
 	gdb/function/__init__.py \
 	gdb/function/strfns.py
 
diff --git a/gdb/python/lib/gdb/command/fstep.py b/gdb/python/lib/gdb/command/fstep.py
new file mode 100644
index 0000000..07d9dbc
--- /dev/null
+++ b/gdb/python/lib/gdb/command/fstep.py
@@ -0,0 +1,121 @@
+# GDB 'fstep' command.
+# Copyright (C) 2013 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Implementation of the GDB 'fstep' command using the GDB Python API."""
+
+import gdb
+
+
+CALL_MNEMONIC_MAP = {
+    'i386:x86-64' : 'callq',
+    'i386' : 'call'
+}
+
+
+class FStepCommand(gdb.Command):
+    """Steps in to the last function called on the current line of execution.
+
+If there are no function calls on the current line of execution, then it
+behaves like the "step" command.  If there are function calls, but if the last
+function called can only be determined at run time, then this command steps to
+the next source line.
+    """
+
+    class StopEventHolder(object):
+        def __init__(self):
+            self._stop_event = None
+
+        def get_event(self):
+            return self._stop_event
+
+        def set_event(self, event):
+            self._stop_event = event
+
+
+    def __init__(self):
+        super(FStepCommand, self).__init__(
+            name='fstep', command_class=gdb.COMMAND_RUNNING)
+
+    def invoke(self, arg_str, from_tty):
+        top_frame = gdb.selected_frame()
+        if not top_frame:
+            raise gdb.GdbError('"fstep" can only be invoked when a frame is'
+                               'selected.')
+        start_thread = gdb.selected_thread()
+        frame_arch = top_frame.architecture()
+        arch_name = frame_arch.name()
+        call_mnemonic = CALL_MNEMONIC_MAP.get(arch_name)
+        if not call_mnemonic:
+            raise gdb.GdbError('"fstep" not available for the current '
+                               'frame target.')
+
+        start_pc = top_frame.pc()
+        sal = gdb.find_pc_line(start_pc)
+        end_pc = sal.last
+        disasm = frame_arch.disassemble(start_pc, end_pc)
+
+        last_call_addr = None
+        for insn in reversed(disasm):
+            if call_mnemonic in insn['asm']:
+                last_call_addr = insn['addr']
+                break
+
+        if last_call_addr:
+            stop_event = FStepCommand.StopEventHolder()
+            callback = lambda event: stop_event.set_event(event)
+            gdb.events.stop.connect(callback)
+            nexti_string = None
+            current_pc = start_pc
+            try:
+                while True:
+                    if current_pc == last_call_addr:
+                        # If the control reaches the last call instruction,
+                        # then fstep is successfully executed.
+                        gdb.events.stop.disconnect(callback)
+                        gdb.execute('step')
+                        return
+                    nexti_string = gdb.execute('nexti', to_string=True)
+                    current_frame = gdb.selected_frame()
+                    current_thread = gdb.selected_thread()
+                    if (current_frame == top_frame 
+                        and current_thread.num == start_thread.num):
+                        current_pc = top_frame.pc()
+                        if current_pc > end_pc or current_pc < start_pc:
+                            # If the execution goes beyond the line, then just
+                            # exit.
+                            break
+                        if type(stop_event.get_event()) != gdb.StopEvent:
+                            # If the execution stops for some other reason,
+                            # then again, exit.
+                            break
+                    else:
+                        # An exception could be thrown in the intermediate
+                        # function calls, in which case we just print the
+                        # exception info and exit.  Or, the execution could
+                        # have stopped in an other thread or function, in
+                        # which case again we just print the output and exit.
+                        break
+            finally:
+                if nexti_string:
+                    print nexti_string
+                gdb.events.stop.disconnect(callback)
+        else:
+            # If there is no call instruction on the current source line,
+            # behave like the 'step' command.
+            gdb.execute('step')
+
+
+FStepCommand()
diff --git a/gdb/testsuite/gdb.python/py-fstep.cc b/gdb/testsuite/gdb.python/py-fstep.cc
new file mode 100644
index 0000000..9ece747
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-fstep.cc
@@ -0,0 +1,143 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2013 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see  <http://www.gnu.org/licenses/>.
+*/
+
+#include <signal.h>
+
+namespace exp
+{
+
+class A
+{
+ public:
+  A () { }
+  A (const A& a);
+  int x, y;
+  void setx (int val);
+  void setx (const A a);
+  int getx (void);
+
+  A operator+ (const A a);
+  void resetx (void) { x = 0; }
+};
+
+void
+A::setx (int val)
+{
+  x = val;
+}
+
+int
+A::getx (void)
+{
+  return x;
+}
+
+void
+A::setx (const A a)
+{
+  x = a.x;
+  y = a.y;  
+}
+
+A
+A::operator+ (const A a)
+{
+  A new_a;
+
+  new_a.x = x + a.x;      /* Break in operator  */
+  new_a.y = y + a.y;
+
+  return a;
+}
+
+A::A (const A &a)
+{
+  x = a.x;
+  y = a.x * a.x;
+}
+
+}
+
+int global_int = 10;
+
+static int
+func (int a, int b)
+{
+  return a + b;
+}
+
+namespace setx 
+{
+
+void
+setx (exp::A a, exp::A b)
+{
+  a.x = 20;
+  b.x = 20;
+}
+
+}
+
+int
+segv (int a)
+{
+  raise (SIGSEGV);
+  return 0;
+}
+
+exp::A a_obj;
+
+int
+main (void)
+{
+  exp::A b_obj;
+  int a = 0, c;
+  bool b;
+
+  a_obj.setx (a = 10);              /* Call type 1  */
+
+  setx::setx (a_obj, b_obj);        /* Call type 2  */
+
+  b_obj.setx (a_obj + b_obj);       /* Call type 3  */
+
+  a_obj.resetx ();                  /* Call type 4  */
+
+  a_obj.setx (func (a, 10));        /* Call type 5  */
+
+  b_obj.setx (a_obj + b_obj);       /* Call type 6.  Essentially same as call
+                                       type 3.  However, the test should 'fstep'
+                                       here with a breakpoint set in the
+                                       operator function.  */
+
+  b_obj.setx (a_obj.getx ());       /* Call type 7.  Essentially Same as call
+                                       type 5.  However, the test should 'fstep'
+                                       here with a breakpoint set on an
+                                       intermediate instruction.  */
+
+  a_obj.setx (c = func (a, 10));    /* Call type 8.  Same as call type 5, but
+                                       the test should 'fstep' after setting a
+                                       watchpoint for c.  */
+
+  a = 1;
+  b = func (a, -1) && func (a, 1);  /* Call type 9  */
+
+  a_obj.setx (segv (a));            /* Call type 10.  Similar to call type 5,
+                                       but segv will raise SIGSEGV.  */
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.python/py-fstep.exp b/gdb/testsuite/gdb.python/py-fstep.exp
new file mode 100644
index 0000000..fa9f0c1
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-fstep.exp
@@ -0,0 +1,74 @@
+# Copyright (C) 2013 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the GDB testsuite.  It tests the mechanism
+# exposing values to Python.
+
+if { [skip_cplus_tests] } { continue }
+
+standard_testfile py-fstep.cc
+
+if {[prepare_for_testing $testfile.exp $testfile $srcfile {debug c++}]} {
+    return -1
+}
+
+# Skip all tests if Python scripting is not enabled.
+if { [skip_python_tests] } { continue }
+
+if ![runto_main] {
+   return -1
+}
+
+gdb_breakpoint [gdb_get_line_number "Call type 1"]
+gdb_continue_to_breakpoint "Call type 1" ".*Call type 1.*"
+gdb_test "fstep" ".*A::setx.*" "Test call type 1"
+
+gdb_breakpoint [gdb_get_line_number "Call type 2"]
+gdb_continue_to_breakpoint "Call type 2" ".*Call type 2.*"
+gdb_test "fstep" ".*setx::setx.*" "Test call type 2"
+
+gdb_breakpoint [gdb_get_line_number "Call type 3"]
+gdb_continue_to_breakpoint "Call type 3" ".*Call type 3.*"
+gdb_test "fstep" ".*A::setx.*" "Test call type 3"
+
+gdb_breakpoint [gdb_get_line_number "Call type 4"]
+gdb_continue_to_breakpoint "Call type 4" ".*Call type 4.*"
+gdb_test "fstep" ".*A::resetx.*" "Test call type 4"
+
+gdb_breakpoint [gdb_get_line_number "Call type 5"]
+gdb_continue_to_breakpoint "Call type 5" ".*Call type 5.*"
+gdb_test "fstep" ".*A::setx.*" "Test call type 5"
+
+gdb_breakpoint [gdb_get_line_number "Call type 6"]
+gdb_continue_to_breakpoint "Call type 6" ".*Call type 6.*"
+gdb_breakpoint [gdb_get_line_number "Break in operator"]
+gdb_test "fstep" ".*Break in operator.*" "Test call type 6"
+
+gdb_breakpoint [gdb_get_line_number "Call type 7"]
+gdb_continue_to_breakpoint "Call type 7" ".*Call type 7.*"
+gdb_py_test_silent_cmd "python l = gdb.selected_frame().architecture().disassemble(gdb.selected_frame().pc())\[0\]\['length'\]" "get insn length" 1
+gdb_py_test_silent_cmd "python gdb.execute('break *(%d)' % (gdb.selected_frame().pc() + l))" "Set breakpoint at intermediate instruction" 1
+gdb_test "fstep" "Breakpoint .*" "Test call type 7"
+
+gdb_breakpoint [gdb_get_line_number "Call type 8"]
+gdb_continue_to_breakpoint "Call type 8" ".*Call type 8.*"
+gdb_py_test_silent_cmd "watch c" "set watchpoint for c" 1
+gdb_test "fstep" ".*watchpoint .*Call type 8.*" "Test call type 8"
+
+gdb_breakpoint [gdb_get_line_number "Call type 9"]
+gdb_continue_to_breakpoint "Call type 9" ".*Call type 9.*"
+gdb_test "fstep" ".*Call type 10.*" "Test call type 9"
+
+gdb_test "fstep" ".*SIGSEGV.*" "Test call type 10"

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]