blob: f24287cd0a0c8cd21a456a1f86c17492756e888d [file] [log] [blame]
Pavel Dovgalyukbe52eca2020-10-03 20:14:06 +03001# 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.
10import os
11import logging
12
Alex Bennée9b45cc92023-12-01 09:36:28 +000013from avocado import skipUnless
Pavel Dovgalyukbe52eca2020-10-03 20:14:06 +030014from avocado_qemu import BUILD_DIR
Nicholas Piggincd43f002023-11-16 21:53:52 +100015from avocado.utils import datadrainer
Pavel Dovgalyukbe52eca2020-10-03 20:14:06 +030016from avocado.utils import gdb
17from avocado.utils import process
Alex Bennée0395b192020-10-21 17:31:36 +010018from avocado.utils.network.ports import find_free_port
Pavel Dovgalyukbe52eca2020-10-03 20:14:06 +030019from avocado.utils.path import find_command
20from boot_linux_console import LinuxKernelTest
21
22class 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
Alex Bennée0395b192020-10-21 17:31:36 +010038 def run_vm(self, record, shift, args, replay_path, image_path, port):
Pavel Dovgalyukbe52eca2020-10-03 20:14:06 +030039 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'
Alex Bennée0395b192020-10-21 17:31:36 +010048 vm.add_args('-gdb', 'tcp::%d' % port, '-S')
Pavel Dovgalyukbe52eca2020-10-03 20:14:06 +030049 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()
Nicholas Piggincd43f002023-11-16 21:53:52 +100056 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()
Pavel Dovgalyukbe52eca2020-10-03 20:14:06 +030060 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')
Alex Bennée0395b192020-10-21 17:31:36 +0100118 port = find_free_port()
Pavel Dovgalyukbe52eca2020-10-03 20:14:06 +0300119
120 # record the log
Alex Bennée0395b192020-10-21 17:31:36 +0100121 vm = self.run_vm(True, shift, args, replay_path, image_path, port)
Pavel Dovgalyukbe52eca2020-10-03 20:14:06 +0300122 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
Alex Bennée0395b192020-10-21 17:31:36 +0100130 vm = self.run_vm(False, shift, args, replay_path, image_path, port)
Pavel Dovgalyukbe52eca2020-10-03 20:14:06 +0300131 logger.info('connecting to gdbstub')
Alex Bennée0395b192020-10-21 17:31:36 +0100132 g = gdb.GDBRemote('127.0.0.1', port, False, False)
Pavel Dovgalyukbe52eca2020-10-03 20:14:06 +0300133 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
Nicholas Piggin76e9c1d2023-08-08 14:20:00 +1000158 # 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)
Pavel Dovgalyukbe52eca2020-10-03 20:14:06 +0300164
Nicholas Piggin76e9c1d2023-08-08 14:20:00 +1000165 # set breakpoints for the instructions just stepped over
Pavel Dovgalyukbe52eca2020-10-03 20:14:06 +0300166 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
Nicholas Piggin76e9c1d2023-08-08 14:20:00 +1000171 # 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
Pavel Dovgalyukbe52eca2020-10-03 20:14:06 +0300185 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
Manos Pitsidianakis35a5a332024-02-20 10:52:09 +0200194 logger.info('exiting gdb and qemu')
Pavel Dovgalyukbe52eca2020-10-03 20:14:06 +0300195 vm.shutdown()
196
197class ReverseDebugging_X86_64(ReverseDebugging):
Fabiano Rosas9bb9a3f2023-02-13 17:29:18 -0300198 """
199 :avocado: tags=accel:tcg
200 """
201
Pavel Dovgalyukbe52eca2020-10-03 20:14:06 +0300202 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
Thomas Huthea6a33e2023-11-21 11:08:42 +0100208 # unidentified gitlab timeout problem
Alex Bennée9b45cc92023-12-01 09:36:28 +0000209 @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab')
Pavel Dovgalyukbe52eca2020-10-03 20:14:06 +0300210 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
218class ReverseDebugging_AArch64(ReverseDebugging):
Fabiano Rosas9bb9a3f2023-02-13 17:29:18 -0300219 """
220 :avocado: tags=accel:tcg
221 """
222
Pavel Dovgalyukbe52eca2020-10-03 20:14:06 +0300223 REG_PC = 32
224
Thomas Huthea6a33e2023-11-21 11:08:42 +0100225 # unidentified gitlab timeout problem
Alex Bennée9b45cc92023-12-01 09:36:28 +0000226 @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab')
Pavel Dovgalyukbe52eca2020-10-03 20:14:06 +0300227 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(
Wainer dos Santos Moschettad377ba42021-04-30 10:34:10 -0300240 args=('-kernel', kernel_path))
Nicholas Piggin761a13b2023-08-08 14:20:01 +1000241
242class ReverseDebugging_ppc64(ReverseDebugging):
243 """
244 :avocado: tags=accel:tcg
245 """
246
247 REG_PC = 0x40
248
Thomas Huthea6a33e2023-11-21 11:08:42 +0100249 # unidentified gitlab timeout problem
Alex Bennée9b45cc92023-12-01 09:36:28 +0000250 @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab')
Nicholas Piggin761a13b2023-08-08 14:20:01 +1000251 def test_ppc64_pseries(self):
252 """
253 :avocado: tags=arch:ppc64
254 :avocado: tags=machine:pseries
Alex Bennée5d25fcb2023-12-01 09:36:33 +0000255 :avocado: tags=flaky
Nicholas Piggin761a13b2023-08-08 14:20:01 +1000256 """
257 # SLOF branches back to its entry point, which causes this test
258 # to take the 'hit a breakpoint again' path. That's not a problem,
259 # just slightly different than the other machines.
260 self.endian_is_le = False
261 self.reverse_debugging()
262
Thomas Huthea6a33e2023-11-21 11:08:42 +0100263 # See https://gitlab.com/qemu-project/qemu/-/issues/1992
Alex Bennée9b45cc92023-12-01 09:36:28 +0000264 @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab')
Nicholas Piggin761a13b2023-08-08 14:20:01 +1000265 def test_ppc64_powernv(self):
266 """
267 :avocado: tags=arch:ppc64
268 :avocado: tags=machine:powernv
Alex Bennée5d25fcb2023-12-01 09:36:33 +0000269 :avocado: tags=flaky
Nicholas Piggin761a13b2023-08-08 14:20:01 +1000270 """
271 self.endian_is_le = False
272 self.reverse_debugging()