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