OSDN Git Service

net: Provide MemReentrancyGuard * to qemu_new_nic()
[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     def test_x86_64_pc(self):
209         """
210         :avocado: tags=arch:x86_64
211         :avocado: tags=machine:pc
212         """
213         # start with BIOS only
214         self.reverse_debugging()
215
216 class ReverseDebugging_AArch64(ReverseDebugging):
217     """
218     :avocado: tags=accel:tcg
219     """
220
221     REG_PC = 32
222
223     def test_aarch64_virt(self):
224         """
225         :avocado: tags=arch:aarch64
226         :avocado: tags=machine:virt
227         :avocado: tags=cpu:cortex-a53
228         """
229         kernel_url = ('https://archives.fedoraproject.org/pub/archive/fedora'
230                       '/linux/releases/29/Everything/aarch64/os/images/pxeboot'
231                       '/vmlinuz')
232         kernel_hash = '8c73e469fc6ea06a58dc83a628fc695b693b8493'
233         kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash)
234
235         self.reverse_debugging(
236             args=('-kernel', kernel_path))
237
238 class ReverseDebugging_ppc64(ReverseDebugging):
239     """
240     :avocado: tags=accel:tcg
241     """
242
243     REG_PC = 0x40
244
245     def test_ppc64_pseries(self):
246         """
247         :avocado: tags=arch:ppc64
248         :avocado: tags=machine:pseries
249         """
250         # SLOF branches back to its entry point, which causes this test
251         # to take the 'hit a breakpoint again' path. That's not a problem,
252         # just slightly different than the other machines.
253         self.endian_is_le = False
254         self.reverse_debugging()
255
256     def test_ppc64_powernv(self):
257         """
258         :avocado: tags=arch:ppc64
259         :avocado: tags=machine:powernv
260         """
261         self.endian_is_le = False
262         self.reverse_debugging()