OSDN Git Service

Merge tag 'for-upstream' of https://gitlab.com/bonzini/qemu into staging
[qmiga/qemu.git] / tests / avocado / reverse_debugging.py
1 # Reverse debugging test
2 #
3 # Copyright (c) 2020 ISP RAS
4 #
5 # Author:
6 #  Pavel Dovgalyuk <Pavel.Dovgalyuk@ispras.ru>
7 #
8 # This work is licensed under the terms of the GNU GPL, version 2 or
9 # later.  See the COPYING file in the top-level directory.
10 import os
11 import logging
12
13 from avocado import skipIf
14 from avocado_qemu import BUILD_DIR
15 from avocado.utils import gdb
16 from avocado.utils import process
17 from avocado.utils.network.ports import find_free_port
18 from avocado.utils.path import find_command
19 from boot_linux_console import LinuxKernelTest
20
21 class ReverseDebugging(LinuxKernelTest):
22     """
23     Test GDB reverse debugging commands: reverse step and reverse continue.
24     Recording saves the execution of some instructions and makes an initial
25     VM snapshot to allow reverse execution.
26     Replay saves the order of the first instructions and then checks that they
27     are executed backwards in the correct order.
28     After that the execution is replayed to the end, and reverse continue
29     command is checked by setting several breakpoints, and asserting
30     that the execution is stopped at the last of them.
31     """
32
33     timeout = 10
34     STEPS = 10
35     endian_is_le = True
36
37     def run_vm(self, record, shift, args, replay_path, image_path, port):
38         logger = logging.getLogger('replay')
39         vm = self.get_vm()
40         vm.set_console()
41         if record:
42             logger.info('recording the execution...')
43             mode = 'record'
44         else:
45             logger.info('replaying the execution...')
46             mode = 'replay'
47             vm.add_args('-gdb', 'tcp::%d' % port, '-S')
48         vm.add_args('-icount', 'shift=%s,rr=%s,rrfile=%s,rrsnapshot=init' %
49                     (shift, mode, replay_path),
50                     '-net', 'none')
51         vm.add_args('-drive', 'file=%s,if=none' % image_path)
52         if args:
53             vm.add_args(*args)
54         vm.launch()
55         return vm
56
57     @staticmethod
58     def get_reg_le(g, reg):
59         res = g.cmd(b'p%x' % reg)
60         num = 0
61         for i in range(len(res))[-2::-2]:
62             num = 0x100 * num + int(res[i:i + 2], 16)
63         return num
64
65     @staticmethod
66     def get_reg_be(g, reg):
67         res = g.cmd(b'p%x' % reg)
68         return int(res, 16)
69
70     def get_reg(self, g, reg):
71         # value may be encoded in BE or LE order
72         if self.endian_is_le:
73             return self.get_reg_le(g, reg)
74         else:
75             return self.get_reg_be(g, reg)
76
77     def get_pc(self, g):
78         return self.get_reg(g, self.REG_PC)
79
80     def check_pc(self, g, addr):
81         pc = self.get_pc(g)
82         if pc != addr:
83             self.fail('Invalid PC (read %x instead of %x)' % (pc, addr))
84
85     @staticmethod
86     def gdb_step(g):
87         g.cmd(b's', b'T05thread:01;')
88
89     @staticmethod
90     def gdb_bstep(g):
91         g.cmd(b'bs', b'T05thread:01;')
92
93     @staticmethod
94     def vm_get_icount(vm):
95         return vm.qmp('query-replay')['return']['icount']
96
97     def reverse_debugging(self, shift=7, args=None):
98         logger = logging.getLogger('replay')
99
100         # create qcow2 for snapshots
101         logger.info('creating qcow2 image for VM snapshots')
102         image_path = os.path.join(self.workdir, 'disk.qcow2')
103         qemu_img = os.path.join(BUILD_DIR, 'qemu-img')
104         if not os.path.exists(qemu_img):
105             qemu_img = find_command('qemu-img', False)
106         if qemu_img is False:
107             self.cancel('Could not find "qemu-img", which is required to '
108                         'create the temporary qcow2 image')
109         cmd = '%s create -f qcow2 %s 128M' % (qemu_img, image_path)
110         process.run(cmd)
111
112         replay_path = os.path.join(self.workdir, 'replay.bin')
113         port = find_free_port()
114
115         # record the log
116         vm = self.run_vm(True, shift, args, replay_path, image_path, port)
117         while self.vm_get_icount(vm) <= self.STEPS:
118             pass
119         last_icount = self.vm_get_icount(vm)
120         vm.shutdown()
121
122         logger.info("recorded log with %s+ steps" % last_icount)
123
124         # replay and run debug commands
125         vm = self.run_vm(False, shift, args, replay_path, image_path, port)
126         logger.info('connecting to gdbstub')
127         g = gdb.GDBRemote('127.0.0.1', port, False, False)
128         g.connect()
129         r = g.cmd(b'qSupported')
130         if b'qXfer:features:read+' in r:
131             g.cmd(b'qXfer:features:read:target.xml:0,ffb')
132         if b'ReverseStep+' not in r:
133             self.fail('Reverse step is not supported by QEMU')
134         if b'ReverseContinue+' not in r:
135             self.fail('Reverse continue is not supported by QEMU')
136
137         logger.info('stepping forward')
138         steps = []
139         # record first instruction addresses
140         for _ in range(self.STEPS):
141             pc = self.get_pc(g)
142             logger.info('saving position %x' % pc)
143             steps.append(pc)
144             self.gdb_step(g)
145
146         # visit the recorded instruction in reverse order
147         logger.info('stepping backward')
148         for addr in steps[::-1]:
149             self.gdb_bstep(g)
150             self.check_pc(g, addr)
151             logger.info('found position %x' % addr)
152
153         logger.info('seeking to the end (icount %s)' % (last_icount - 1))
154         vm.qmp('replay-break', icount=last_icount - 1)
155         # continue - will return after pausing
156         g.cmd(b'c', b'T02thread:01;')
157
158         logger.info('setting breakpoints')
159         for addr in steps:
160             # hardware breakpoint at addr with len=1
161             g.cmd(b'Z1,%x,1' % addr, b'OK')
162
163         logger.info('running reverse continue to reach %x' % steps[-1])
164         # reverse continue - will return after stopping at the breakpoint
165         g.cmd(b'bc', b'T05thread:01;')
166
167         # assume that none of the first instructions is executed again
168         # breaking the order of the breakpoints
169         self.check_pc(g, steps[-1])
170         logger.info('successfully reached %x' % steps[-1])
171
172         logger.info('exitting gdb and qemu')
173         vm.shutdown()
174
175 class ReverseDebugging_X86_64(ReverseDebugging):
176     """
177     :avocado: tags=accel:tcg
178     """
179
180     REG_PC = 0x10
181     REG_CS = 0x12
182     def get_pc(self, g):
183         return self.get_reg_le(g, self.REG_PC) \
184             + self.get_reg_le(g, self.REG_CS) * 0x10
185
186     # unidentified gitlab timeout problem
187     @skipIf(os.getenv('GITLAB_CI'), 'Running on GitLab')
188     def test_x86_64_pc(self):
189         """
190         :avocado: tags=arch:x86_64
191         :avocado: tags=machine:pc
192         """
193         # start with BIOS only
194         self.reverse_debugging()
195
196 class ReverseDebugging_AArch64(ReverseDebugging):
197     """
198     :avocado: tags=accel:tcg
199     """
200
201     REG_PC = 32
202
203     # unidentified gitlab timeout problem
204     @skipIf(os.getenv('GITLAB_CI'), 'Running on GitLab')
205     def test_aarch64_virt(self):
206         """
207         :avocado: tags=arch:aarch64
208         :avocado: tags=machine:virt
209         :avocado: tags=cpu:cortex-a53
210         """
211         kernel_url = ('https://archives.fedoraproject.org/pub/archive/fedora'
212                       '/linux/releases/29/Everything/aarch64/os/images/pxeboot'
213                       '/vmlinuz')
214         kernel_hash = '8c73e469fc6ea06a58dc83a628fc695b693b8493'
215         kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash)
216
217         self.reverse_debugging(
218             args=('-kernel', kernel_path))