xref: /openbmc/qemu/tests/functional/reverse_debugging.py (revision 55f5bf716a65f67663d0769bcb8c017764b3e53a)
1*951ededfSThomas Huth# Reverse debugging test
2*951ededfSThomas Huth#
3*951ededfSThomas Huth# SPDX-License-Identifier: GPL-2.0-or-later
4*951ededfSThomas Huth#
5*951ededfSThomas Huth# Copyright (c) 2020 ISP RAS
6*951ededfSThomas Huth#
7*951ededfSThomas Huth# Author:
8*951ededfSThomas Huth#  Pavel Dovgalyuk <Pavel.Dovgalyuk@ispras.ru>
9*951ededfSThomas Huth#
10*951ededfSThomas Huth# This work is licensed under the terms of the GNU GPL, version 2 or
11*951ededfSThomas Huth# later.  See the COPYING file in the top-level directory.
12*951ededfSThomas Huthimport os
13*951ededfSThomas Huthimport logging
14*951ededfSThomas Huth
15*951ededfSThomas Huthfrom qemu_test import LinuxKernelTest, get_qemu_img
16*951ededfSThomas Huthfrom qemu_test.ports import Ports
17*951ededfSThomas Huth
18*951ededfSThomas Huth
19*951ededfSThomas Huthclass ReverseDebugging(LinuxKernelTest):
20*951ededfSThomas Huth    """
21*951ededfSThomas Huth    Test GDB reverse debugging commands: reverse step and reverse continue.
22*951ededfSThomas Huth    Recording saves the execution of some instructions and makes an initial
23*951ededfSThomas Huth    VM snapshot to allow reverse execution.
24*951ededfSThomas Huth    Replay saves the order of the first instructions and then checks that they
25*951ededfSThomas Huth    are executed backwards in the correct order.
26*951ededfSThomas Huth    After that the execution is replayed to the end, and reverse continue
27*951ededfSThomas Huth    command is checked by setting several breakpoints, and asserting
28*951ededfSThomas Huth    that the execution is stopped at the last of them.
29*951ededfSThomas Huth    """
30*951ededfSThomas Huth
31*951ededfSThomas Huth    timeout = 10
32*951ededfSThomas Huth    STEPS = 10
33*951ededfSThomas Huth    endian_is_le = True
34*951ededfSThomas Huth
35*951ededfSThomas Huth    def run_vm(self, record, shift, args, replay_path, image_path, port):
36*951ededfSThomas Huth        from avocado.utils import datadrainer
37*951ededfSThomas Huth
38*951ededfSThomas Huth        logger = logging.getLogger('replay')
39*951ededfSThomas Huth        vm = self.get_vm(name='record' if record else 'replay')
40*951ededfSThomas Huth        vm.set_console()
41*951ededfSThomas Huth        if record:
42*951ededfSThomas Huth            logger.info('recording the execution...')
43*951ededfSThomas Huth            mode = 'record'
44*951ededfSThomas Huth        else:
45*951ededfSThomas Huth            logger.info('replaying the execution...')
46*951ededfSThomas Huth            mode = 'replay'
47*951ededfSThomas Huth            vm.add_args('-gdb', 'tcp::%d' % port, '-S')
48*951ededfSThomas Huth        vm.add_args('-icount', 'shift=%s,rr=%s,rrfile=%s,rrsnapshot=init' %
49*951ededfSThomas Huth                    (shift, mode, replay_path),
50*951ededfSThomas Huth                    '-net', 'none')
51*951ededfSThomas Huth        vm.add_args('-drive', 'file=%s,if=none' % image_path)
52*951ededfSThomas Huth        if args:
53*951ededfSThomas Huth            vm.add_args(*args)
54*951ededfSThomas Huth        vm.launch()
55*951ededfSThomas Huth        console_drainer = datadrainer.LineLogger(vm.console_socket.fileno(),
56*951ededfSThomas Huth                                    logger=self.log.getChild('console'),
57*951ededfSThomas Huth                                    stop_check=(lambda : not vm.is_running()))
58*951ededfSThomas Huth        console_drainer.start()
59*951ededfSThomas Huth        return vm
60*951ededfSThomas Huth
61*951ededfSThomas Huth    @staticmethod
62*951ededfSThomas Huth    def get_reg_le(g, reg):
63*951ededfSThomas Huth        res = g.cmd(b'p%x' % reg)
64*951ededfSThomas Huth        num = 0
65*951ededfSThomas Huth        for i in range(len(res))[-2::-2]:
66*951ededfSThomas Huth            num = 0x100 * num + int(res[i:i + 2], 16)
67*951ededfSThomas Huth        return num
68*951ededfSThomas Huth
69*951ededfSThomas Huth    @staticmethod
70*951ededfSThomas Huth    def get_reg_be(g, reg):
71*951ededfSThomas Huth        res = g.cmd(b'p%x' % reg)
72*951ededfSThomas Huth        return int(res, 16)
73*951ededfSThomas Huth
74*951ededfSThomas Huth    def get_reg(self, g, reg):
75*951ededfSThomas Huth        # value may be encoded in BE or LE order
76*951ededfSThomas Huth        if self.endian_is_le:
77*951ededfSThomas Huth            return self.get_reg_le(g, reg)
78*951ededfSThomas Huth        else:
79*951ededfSThomas Huth            return self.get_reg_be(g, reg)
80*951ededfSThomas Huth
81*951ededfSThomas Huth    def get_pc(self, g):
82*951ededfSThomas Huth        return self.get_reg(g, self.REG_PC)
83*951ededfSThomas Huth
84*951ededfSThomas Huth    def check_pc(self, g, addr):
85*951ededfSThomas Huth        pc = self.get_pc(g)
86*951ededfSThomas Huth        if pc != addr:
87*951ededfSThomas Huth            self.fail('Invalid PC (read %x instead of %x)' % (pc, addr))
88*951ededfSThomas Huth
89*951ededfSThomas Huth    @staticmethod
90*951ededfSThomas Huth    def gdb_step(g):
91*951ededfSThomas Huth        g.cmd(b's', b'T05thread:01;')
92*951ededfSThomas Huth
93*951ededfSThomas Huth    @staticmethod
94*951ededfSThomas Huth    def gdb_bstep(g):
95*951ededfSThomas Huth        g.cmd(b'bs', b'T05thread:01;')
96*951ededfSThomas Huth
97*951ededfSThomas Huth    @staticmethod
98*951ededfSThomas Huth    def vm_get_icount(vm):
99*951ededfSThomas Huth        return vm.qmp('query-replay')['return']['icount']
100*951ededfSThomas Huth
101*951ededfSThomas Huth    def reverse_debugging(self, shift=7, args=None):
102*951ededfSThomas Huth        from avocado.utils import gdb
103*951ededfSThomas Huth        from avocado.utils import process
104*951ededfSThomas Huth
105*951ededfSThomas Huth        logger = logging.getLogger('replay')
106*951ededfSThomas Huth
107*951ededfSThomas Huth        # create qcow2 for snapshots
108*951ededfSThomas Huth        logger.info('creating qcow2 image for VM snapshots')
109*951ededfSThomas Huth        image_path = os.path.join(self.workdir, 'disk.qcow2')
110*951ededfSThomas Huth        qemu_img = get_qemu_img(self)
111*951ededfSThomas Huth        if qemu_img is None:
112*951ededfSThomas Huth            self.skipTest('Could not find "qemu-img", which is required to '
113*951ededfSThomas Huth                          'create the temporary qcow2 image')
114*951ededfSThomas Huth        cmd = '%s create -f qcow2 %s 128M' % (qemu_img, image_path)
115*951ededfSThomas Huth        process.run(cmd)
116*951ededfSThomas Huth
117*951ededfSThomas Huth        replay_path = os.path.join(self.workdir, 'replay.bin')
118*951ededfSThomas Huth
119*951ededfSThomas Huth        # record the log
120*951ededfSThomas Huth        vm = self.run_vm(True, shift, args, replay_path, image_path, -1)
121*951ededfSThomas Huth        while self.vm_get_icount(vm) <= self.STEPS:
122*951ededfSThomas Huth            pass
123*951ededfSThomas Huth        last_icount = self.vm_get_icount(vm)
124*951ededfSThomas Huth        vm.shutdown()
125*951ededfSThomas Huth
126*951ededfSThomas Huth        logger.info("recorded log with %s+ steps" % last_icount)
127*951ededfSThomas Huth
128*951ededfSThomas Huth        # replay and run debug commands
129*951ededfSThomas Huth        with Ports() as ports:
130*951ededfSThomas Huth            port = ports.find_free_port()
131*951ededfSThomas Huth            vm = self.run_vm(False, shift, args, replay_path, image_path, port)
132*951ededfSThomas Huth        logger.info('connecting to gdbstub')
133*951ededfSThomas Huth        g = gdb.GDBRemote('127.0.0.1', port, False, False)
134*951ededfSThomas Huth        g.connect()
135*951ededfSThomas Huth        r = g.cmd(b'qSupported')
136*951ededfSThomas Huth        if b'qXfer:features:read+' in r:
137*951ededfSThomas Huth            g.cmd(b'qXfer:features:read:target.xml:0,ffb')
138*951ededfSThomas Huth        if b'ReverseStep+' not in r:
139*951ededfSThomas Huth            self.fail('Reverse step is not supported by QEMU')
140*951ededfSThomas Huth        if b'ReverseContinue+' not in r:
141*951ededfSThomas Huth            self.fail('Reverse continue is not supported by QEMU')
142*951ededfSThomas Huth
143*951ededfSThomas Huth        logger.info('stepping forward')
144*951ededfSThomas Huth        steps = []
145*951ededfSThomas Huth        # record first instruction addresses
146*951ededfSThomas Huth        for _ in range(self.STEPS):
147*951ededfSThomas Huth            pc = self.get_pc(g)
148*951ededfSThomas Huth            logger.info('saving position %x' % pc)
149*951ededfSThomas Huth            steps.append(pc)
150*951ededfSThomas Huth            self.gdb_step(g)
151*951ededfSThomas Huth
152*951ededfSThomas Huth        # visit the recorded instruction in reverse order
153*951ededfSThomas Huth        logger.info('stepping backward')
154*951ededfSThomas Huth        for addr in steps[::-1]:
155*951ededfSThomas Huth            self.gdb_bstep(g)
156*951ededfSThomas Huth            self.check_pc(g, addr)
157*951ededfSThomas Huth            logger.info('found position %x' % addr)
158*951ededfSThomas Huth
159*951ededfSThomas Huth        # visit the recorded instruction in forward order
160*951ededfSThomas Huth        logger.info('stepping forward')
161*951ededfSThomas Huth        for addr in steps:
162*951ededfSThomas Huth            self.check_pc(g, addr)
163*951ededfSThomas Huth            self.gdb_step(g)
164*951ededfSThomas Huth            logger.info('found position %x' % addr)
165*951ededfSThomas Huth
166*951ededfSThomas Huth        # set breakpoints for the instructions just stepped over
167*951ededfSThomas Huth        logger.info('setting breakpoints')
168*951ededfSThomas Huth        for addr in steps:
169*951ededfSThomas Huth            # hardware breakpoint at addr with len=1
170*951ededfSThomas Huth            g.cmd(b'Z1,%x,1' % addr, b'OK')
171*951ededfSThomas Huth
172*951ededfSThomas Huth        # this may hit a breakpoint if first instructions are executed
173*951ededfSThomas Huth        # again
174*951ededfSThomas Huth        logger.info('continuing execution')
175*951ededfSThomas Huth        vm.qmp('replay-break', icount=last_icount - 1)
176*951ededfSThomas Huth        # continue - will return after pausing
177*951ededfSThomas Huth        # This could stop at the end and get a T02 return, or by
178*951ededfSThomas Huth        # re-executing one of the breakpoints and get a T05 return.
179*951ededfSThomas Huth        g.cmd(b'c')
180*951ededfSThomas Huth        if self.vm_get_icount(vm) == last_icount - 1:
181*951ededfSThomas Huth            logger.info('reached the end (icount %s)' % (last_icount - 1))
182*951ededfSThomas Huth        else:
183*951ededfSThomas Huth            logger.info('hit a breakpoint again at %x (icount %s)' %
184*951ededfSThomas Huth                        (self.get_pc(g), self.vm_get_icount(vm)))
185*951ededfSThomas Huth
186*951ededfSThomas Huth        logger.info('running reverse continue to reach %x' % steps[-1])
187*951ededfSThomas Huth        # reverse continue - will return after stopping at the breakpoint
188*951ededfSThomas Huth        g.cmd(b'bc', b'T05thread:01;')
189*951ededfSThomas Huth
190*951ededfSThomas Huth        # assume that none of the first instructions is executed again
191*951ededfSThomas Huth        # breaking the order of the breakpoints
192*951ededfSThomas Huth        self.check_pc(g, steps[-1])
193*951ededfSThomas Huth        logger.info('successfully reached %x' % steps[-1])
194*951ededfSThomas Huth
195*951ededfSThomas Huth        logger.info('exiting gdb and qemu')
196*951ededfSThomas Huth        vm.shutdown()
197