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.network.ports import find_free_port
18 from avocado.utils.path import find_command
19 from boot_linux_console import LinuxKernelTest
21 class ReverseDebugging(LinuxKernelTest):
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.
37 def run_vm(self, record, shift, args, replay_path, image_path, port):
38 logger = logging.getLogger('replay')
42 logger.info('recording the execution...')
45 logger.info('replaying the execution...')
47 vm.add_args('-gdb', 'tcp::%d' % port, '-S')
48 vm.add_args('-icount', 'shift=%s,rr=%s,rrfile=%s,rrsnapshot=init' %
49 (shift, mode, replay_path),
51 vm.add_args('-drive', 'file=%s,if=none' % image_path)
58 def get_reg_le(g, reg):
59 res = g.cmd(b'p%x' % reg)
61 for i in range(len(res))[-2::-2]:
62 num = 0x100 * num + int(res[i:i + 2], 16)
66 def get_reg_be(g, reg):
67 res = g.cmd(b'p%x' % reg)
70 def get_reg(self, g, reg):
71 # value may be encoded in BE or LE order
73 return self.get_reg_le(g, reg)
75 return self.get_reg_be(g, reg)
78 return self.get_reg(g, self.REG_PC)
80 def check_pc(self, g, addr):
83 self.fail('Invalid PC (read %x instead of %x)' % (pc, addr))
87 g.cmd(b's', b'T05thread:01;')
91 g.cmd(b'bs', b'T05thread:01;')
94 def vm_get_icount(vm):
95 return vm.qmp('query-replay')['return']['icount']
97 def reverse_debugging(self, shift=7, args=None):
98 logger = logging.getLogger('replay')
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)
112 replay_path = os.path.join(self.workdir, 'replay.bin')
113 port = find_free_port()
116 vm = self.run_vm(True, shift, args, replay_path, image_path, port)
117 while self.vm_get_icount(vm) <= self.STEPS:
119 last_icount = self.vm_get_icount(vm)
122 logger.info("recorded log with %s+ steps" % last_icount)
124 # replay and run debug commands
125 vm = self.run_vm(False, shift, args, replay_path, image_path, port)
126 logger.info('connecting to gdbstub')
127 g = gdb.GDBRemote('127.0.0.1', port, False, False)
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')
137 logger.info('stepping forward')
139 # record first instruction addresses
140 for _ in range(self.STEPS):
142 logger.info('saving position %x' % pc)
146 # visit the recorded instruction in reverse order
147 logger.info('stepping backward')
148 for addr in steps[::-1]:
150 self.check_pc(g, addr)
151 logger.info('found position %x' % addr)
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;')
158 logger.info('setting breakpoints')
160 # hardware breakpoint at addr with len=1
161 g.cmd(b'Z1,%x,1' % addr, b'OK')
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;')
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])
172 logger.info('exitting gdb and qemu')
175 class ReverseDebugging_X86_64(ReverseDebugging):
177 :avocado: tags=accel:tcg
183 return self.get_reg_le(g, self.REG_PC) \
184 + self.get_reg_le(g, self.REG_CS) * 0x10
186 # unidentified gitlab timeout problem
187 @skipIf(os.getenv('GITLAB_CI'), 'Running on GitLab')
188 def test_x86_64_pc(self):
190 :avocado: tags=arch:x86_64
191 :avocado: tags=machine:pc
193 # start with BIOS only
194 self.reverse_debugging()
196 class ReverseDebugging_AArch64(ReverseDebugging):
198 :avocado: tags=accel:tcg
203 # unidentified gitlab timeout problem
204 @skipIf(os.getenv('GITLAB_CI'), 'Running on GitLab')
205 def test_aarch64_virt(self):
207 :avocado: tags=arch:aarch64
208 :avocado: tags=machine:virt
209 :avocado: tags=cpu:cortex-a53
211 kernel_url = ('https://archives.fedoraproject.org/pub/archive/fedora'
212 '/linux/releases/29/Everything/aarch64/os/images/pxeboot'
214 kernel_hash = '8c73e469fc6ea06a58dc83a628fc695b693b8493'
215 kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash)
217 self.reverse_debugging(
218 args=('-kernel', kernel_path))