target/cris: Prefer fast cpu_env() over slower CPU QOM cast macro
[qemu/ar7.git] / tests / avocado / reverse_debugging.py
blob92855a02a549994699eccb4dff47c7882a550c62
1 # Reverse debugging test
3 # Copyright (c) 2020 ISP RAS
5 # Author:
6 # Pavel Dovgalyuk <Pavel.Dovgalyuk@ispras.ru>
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
13 from avocado import skipUnless
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
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 """
34 timeout = 10
35 STEPS = 10
36 endian_is_le = True
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
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
70 @staticmethod
71 def get_reg_be(g, reg):
72 res = g.cmd(b'p%x' % reg)
73 return int(res, 16)
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)
82 def get_pc(self, g):
83 return self.get_reg(g, self.REG_PC)
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))
90 @staticmethod
91 def gdb_step(g):
92 g.cmd(b's', b'T05thread:01;')
94 @staticmethod
95 def gdb_bstep(g):
96 g.cmd(b'bs', b'T05thread:01;')
98 @staticmethod
99 def vm_get_icount(vm):
100 return vm.qmp('query-replay')['return']['icount']
102 def reverse_debugging(self, shift=7, args=None):
103 logger = logging.getLogger('replay')
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)
117 replay_path = os.path.join(self.workdir, 'replay.bin')
118 port = find_free_port()
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()
127 logger.info("recorded log with %s+ steps" % last_icount)
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')
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)
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)
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)
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')
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)))
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;')
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])
194 logger.info('exiting gdb and qemu')
195 vm.shutdown()
197 class ReverseDebugging_X86_64(ReverseDebugging):
199 :avocado: tags=accel:tcg
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
208 # unidentified gitlab timeout problem
209 @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab')
211 def test_x86_64_pc(self):
213 :avocado: tags=arch:x86_64
214 :avocado: tags=machine:pc
216 # start with BIOS only
217 self.reverse_debugging()
219 class ReverseDebugging_AArch64(ReverseDebugging):
221 :avocado: tags=accel:tcg
224 REG_PC = 32
226 # unidentified gitlab timeout problem
227 @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab')
229 def test_aarch64_virt(self):
231 :avocado: tags=arch:aarch64
232 :avocado: tags=machine:virt
233 :avocado: tags=cpu:cortex-a53
235 kernel_url = ('https://archives.fedoraproject.org/pub/archive/fedora'
236 '/linux/releases/29/Everything/aarch64/os/images/pxeboot'
237 '/vmlinuz')
238 kernel_hash = '8c73e469fc6ea06a58dc83a628fc695b693b8493'
239 kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash)
241 self.reverse_debugging(
242 args=('-kernel', kernel_path))
244 class ReverseDebugging_ppc64(ReverseDebugging):
246 :avocado: tags=accel:tcg
249 REG_PC = 0x40
251 # unidentified gitlab timeout problem
252 @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab')
254 def test_ppc64_pseries(self):
256 :avocado: tags=arch:ppc64
257 :avocado: tags=machine:pseries
258 :avocado: tags=flaky
260 # SLOF branches back to its entry point, which causes this test
261 # to take the 'hit a breakpoint again' path. That's not a problem,
262 # just slightly different than the other machines.
263 self.endian_is_le = False
264 self.reverse_debugging()
266 # See https://gitlab.com/qemu-project/qemu/-/issues/1992
267 @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab')
269 def test_ppc64_powernv(self):
271 :avocado: tags=arch:ppc64
272 :avocado: tags=machine:powernv
273 :avocado: tags=flaky
275 self.endian_is_le = False
276 self.reverse_debugging()