OSDN Git Service

128d85bc0ed2d7dbf6cb6162e0a581255c6dfad7
[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 datadrainer
16 from avocado.utils import gdb
17 from avocado.utils import process
18 from avocado.utils.network.ports import find_free_port
19 from avocado.utils.path import find_command
20 from boot_linux_console import LinuxKernelTest
21
22 class ReverseDebugging(LinuxKernelTest):
23     """
24     Test GDB reverse debugging commands: reverse step and reverse continue.
25     Recording saves the execution of some instructions and makes an initial
26     VM snapshot to allow reverse execution.
27     Replay saves the order of the first instructions and then checks that they
28     are executed backwards in the correct order.
29     After that the execution is replayed to the end, and reverse continue
30     command is checked by setting several breakpoints, and asserting
31     that the execution is stopped at the last of them.
32     """
33
34     timeout = 10
35     STEPS = 10
36     endian_is_le = True
37
38     def run_vm(self, record, shift, args, replay_path, image_path, port):
39         logger = logging.getLogger('replay')
40         vm = self.get_vm()
41         vm.set_console()
42         if record:
43             logger.info('recording the execution...')
44             mode = 'record'
45         else:
46             logger.info('replaying the execution...')
47             mode = 'replay'
48             vm.add_args('-gdb', 'tcp::%d' % port, '-S')
49         vm.add_args('-icount', 'shift=%s,rr=%s,rrfile=%s,rrsnapshot=init' %
50                     (shift, mode, replay_path),
51                     '-net', 'none')
52         vm.add_args('-drive', 'file=%s,if=none' % image_path)
53         if args:
54             vm.add_args(*args)
55         vm.launch()
56         console_drainer = datadrainer.LineLogger(vm.console_socket.fileno(),
57                                     logger=self.log.getChild('console'),
58                                     stop_check=(lambda : not vm.is_running()))
59         console_drainer.start()
60         return vm
61
62     @staticmethod
63     def get_reg_le(g, reg):
64         res = g.cmd(b'p%x' % reg)
65         num = 0
66         for i in range(len(res))[-2::-2]:
67             num = 0x100 * num + int(res[i:i + 2], 16)
68         return num
69
70     @staticmethod
71     def get_reg_be(g, reg):
72         res = g.cmd(b'p%x' % reg)
73         return int(res, 16)
74
75     def get_reg(self, g, reg):
76         # value may be encoded in BE or LE order
77         if self.endian_is_le:
78             return self.get_reg_le(g, reg)
79         else:
80             return self.get_reg_be(g, reg)
81
82     def get_pc(self, g):
83         return self.get_reg(g, self.REG_PC)
84
85     def check_pc(self, g, addr):
86         pc = self.get_pc(g)
87         if pc != addr:
88             self.fail('Invalid PC (read %x instead of %x)' % (pc, addr))
89
90     @staticmethod
91     def gdb_step(g):
92         g.cmd(b's', b'T05thread:01;')
93
94     @staticmethod
95     def gdb_bstep(g):
96         g.cmd(b'bs', b'T05thread:01;')
97
98     @staticmethod
99     def vm_get_icount(vm):
100         return vm.qmp('query-replay')['return']['icount']
101
102     def reverse_debugging(self, shift=7, args=None):
103         logger = logging.getLogger('replay')
104
105         # create qcow2 for snapshots
106         logger.info('creating qcow2 image for VM snapshots')
107         image_path = os.path.join(self.workdir, 'disk.qcow2')
108         qemu_img = os.path.join(BUILD_DIR, 'qemu-img')
109         if not os.path.exists(qemu_img):
110             qemu_img = find_command('qemu-img', False)
111         if qemu_img is False:
112             self.cancel('Could not find "qemu-img", which is required to '
113                         'create the temporary qcow2 image')
114         cmd = '%s create -f qcow2 %s 128M' % (qemu_img, image_path)
115         process.run(cmd)
116
117         replay_path = os.path.join(self.workdir, 'replay.bin')
118         port = find_free_port()
119
120         # record the log
121         vm = self.run_vm(True, shift, args, replay_path, image_path, port)
122         while self.vm_get_icount(vm) <= self.STEPS:
123             pass
124         last_icount = self.vm_get_icount(vm)
125         vm.shutdown()
126
127         logger.info("recorded log with %s+ steps" % last_icount)
128
129         # replay and run debug commands
130         vm = self.run_vm(False, shift, args, replay_path, image_path, port)
131         logger.info('connecting to gdbstub')
132         g = gdb.GDBRemote('127.0.0.1', port, False, False)
133         g.connect()
134         r = g.cmd(b'qSupported')
135         if b'qXfer:features:read+' in r:
136             g.cmd(b'qXfer:features:read:target.xml:0,ffb')
137         if b'ReverseStep+' not in r:
138             self.fail('Reverse step is not supported by QEMU')
139         if b'ReverseContinue+' not in r:
140             self.fail('Reverse continue is not supported by QEMU')
141
142         logger.info('stepping forward')
143         steps = []
144         # record first instruction addresses
145         for _ in range(self.STEPS):
146             pc = self.get_pc(g)
147             logger.info('saving position %x' % pc)
148             steps.append(pc)
149             self.gdb_step(g)
150
151         # visit the recorded instruction in reverse order
152         logger.info('stepping backward')
153         for addr in steps[::-1]:
154             self.gdb_bstep(g)
155             self.check_pc(g, addr)
156             logger.info('found position %x' % addr)
157
158         # visit the recorded instruction in forward order
159         logger.info('stepping forward')
160         for addr in steps:
161             self.check_pc(g, addr)
162             self.gdb_step(g)
163             logger.info('found position %x' % addr)
164
165         # set breakpoints for the instructions just stepped over
166         logger.info('setting breakpoints')
167         for addr in steps:
168             # hardware breakpoint at addr with len=1
169             g.cmd(b'Z1,%x,1' % addr, b'OK')
170
171         # this may hit a breakpoint if first instructions are executed
172         # again
173         logger.info('continuing execution')
174         vm.qmp('replay-break', icount=last_icount - 1)
175         # continue - will return after pausing
176         # This could stop at the end and get a T02 return, or by
177         # re-executing one of the breakpoints and get a T05 return.
178         g.cmd(b'c')
179         if self.vm_get_icount(vm) == last_icount - 1:
180             logger.info('reached the end (icount %s)' % (last_icount - 1))
181         else:
182             logger.info('hit a breakpoint again at %x (icount %s)' %
183                         (self.get_pc(g), self.vm_get_icount(vm)))
184
185         logger.info('running reverse continue to reach %x' % steps[-1])
186         # reverse continue - will return after stopping at the breakpoint
187         g.cmd(b'bc', b'T05thread:01;')
188
189         # assume that none of the first instructions is executed again
190         # breaking the order of the breakpoints
191         self.check_pc(g, steps[-1])
192         logger.info('successfully reached %x' % steps[-1])
193
194         logger.info('exitting gdb and qemu')
195         vm.shutdown()
196
197 class ReverseDebugging_X86_64(ReverseDebugging):
198     """
199     :avocado: tags=accel:tcg
200     """
201
202     REG_PC = 0x10
203     REG_CS = 0x12
204     def get_pc(self, g):
205         return self.get_reg_le(g, self.REG_PC) \
206             + self.get_reg_le(g, self.REG_CS) * 0x10
207
208     # unidentified gitlab timeout problem
209     @skipIf(os.getenv('GITLAB_CI'), 'Running on GitLab')
210     def test_x86_64_pc(self):
211         """
212         :avocado: tags=arch:x86_64
213         :avocado: tags=machine:pc
214         """
215         # start with BIOS only
216         self.reverse_debugging()
217
218 class ReverseDebugging_AArch64(ReverseDebugging):
219     """
220     :avocado: tags=accel:tcg
221     """
222
223     REG_PC = 32
224
225     # unidentified gitlab timeout problem
226     @skipIf(os.getenv('GITLAB_CI'), 'Running on GitLab')
227     def test_aarch64_virt(self):
228         """
229         :avocado: tags=arch:aarch64
230         :avocado: tags=machine:virt
231         :avocado: tags=cpu:cortex-a53
232         """
233         kernel_url = ('https://archives.fedoraproject.org/pub/archive/fedora'
234                       '/linux/releases/29/Everything/aarch64/os/images/pxeboot'
235                       '/vmlinuz')
236         kernel_hash = '8c73e469fc6ea06a58dc83a628fc695b693b8493'
237         kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash)
238
239         self.reverse_debugging(
240             args=('-kernel', kernel_path))
241
242 class ReverseDebugging_ppc64(ReverseDebugging):
243     """
244     :avocado: tags=accel:tcg
245     """
246
247     REG_PC = 0x40
248
249     # unidentified gitlab timeout problem
250     @skipIf(os.getenv('GITLAB_CI'), 'Running on GitLab')
251     def test_ppc64_pseries(self):
252         """
253         :avocado: tags=arch:ppc64
254         :avocado: tags=machine:pseries
255         """
256         # SLOF branches back to its entry point, which causes this test
257         # to take the 'hit a breakpoint again' path. That's not a problem,
258         # just slightly different than the other machines.
259         self.endian_is_le = False
260         self.reverse_debugging()
261
262     @skipIf(os.getenv('GITLAB_CI'), 'Running on GitLab')
263     def test_ppc64_powernv(self):
264         """
265         :avocado: tags=arch:ppc64
266         :avocado: tags=machine:powernv
267         """
268         self.endian_is_le = False
269         self.reverse_debugging()