]>
git.proxmox.com Git - mirror_qemu.git/blob - tests/acceptance/reverse_debugging.py
1 # Reverse debugging test
3 # Copyright (c) 2020 ISP RAS
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.
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
):
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.
36 def run_vm(self
, record
, shift
, args
, replay_path
, image_path
):
37 logger
= logging
.getLogger('replay')
41 logger
.info('recording the execution...')
44 logger
.info('replaying the execution...')
46 vm
.add_args('-s', '-S')
47 vm
.add_args('-icount', 'shift=%s,rr=%s,rrfile=%s,rrsnapshot=init' %
48 (shift
, mode
, replay_path
),
50 vm
.add_args('-drive', 'file=%s,if=none' % image_path
)
57 def get_reg_le(g
, reg
):
58 res
= g
.cmd(b
'p%x' % reg
)
60 for i
in range(len(res
))[-2::-2]:
61 num
= 0x100 * num
+ int(res
[i
:i
+ 2], 16)
65 def get_reg_be(g
, reg
):
66 res
= g
.cmd(b
'p%x' % reg
)
69 def get_reg(self
, g
, reg
):
70 # value may be encoded in BE or LE order
72 return self
.get_reg_le(g
, reg
)
74 return self
.get_reg_be(g
, reg
)
77 return self
.get_reg(g
, self
.REG_PC
)
79 def check_pc(self
, g
, addr
):
82 self
.fail('Invalid PC (read %x instead of %x)' % (pc
, addr
))
86 g
.cmd(b
's', b
'T05thread:01;')
90 g
.cmd(b
'bs', b
'T05thread:01;')
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
)
111 replay_path
= os
.path
.join(self
.workdir
, 'replay.bin')
114 vm
= self
.run_vm(True, shift
, args
, replay_path
, image_path
)
115 while self
.vm_get_icount(vm
) <= self
.STEPS
:
117 last_icount
= self
.vm_get_icount(vm
)
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)
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')
137 # record first instruction addresses
138 for _
in range(self
.STEPS
):
140 logger
.info('saving position %x' % pc
)
144 # visit the recorded instruction in reverse order
145 logger
.info('stepping backward')
146 for addr
in steps
[::-1]:
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')
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')
173 class ReverseDebugging_X86_64(ReverseDebugging
):
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
):
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'
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'))