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