Pavel Dovgalyuk | be52eca | 2020-10-03 20:14:06 +0300 | [diff] [blame] | 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. |
| 10 | import os |
| 11 | import logging |
| 12 | |
Alex Bennée | 9b45cc9 | 2023-12-01 09:36:28 +0000 | [diff] [blame] | 13 | from avocado import skipUnless |
Pavel Dovgalyuk | be52eca | 2020-10-03 20:14:06 +0300 | [diff] [blame] | 14 | from avocado_qemu import BUILD_DIR |
Nicholas Piggin | cd43f00 | 2023-11-16 21:53:52 +1000 | [diff] [blame] | 15 | from avocado.utils import datadrainer |
Pavel Dovgalyuk | be52eca | 2020-10-03 20:14:06 +0300 | [diff] [blame] | 16 | from avocado.utils import gdb |
| 17 | from avocado.utils import process |
Alex Bennée | 0395b19 | 2020-10-21 17:31:36 +0100 | [diff] [blame] | 18 | from avocado.utils.network.ports import find_free_port |
Pavel Dovgalyuk | be52eca | 2020-10-03 20:14:06 +0300 | [diff] [blame] | 19 | from avocado.utils.path import find_command |
| 20 | from boot_linux_console import LinuxKernelTest |
| 21 | |
| 22 | class 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ée | 0395b19 | 2020-10-21 17:31:36 +0100 | [diff] [blame] | 38 | def run_vm(self, record, shift, args, replay_path, image_path, port): |
Pavel Dovgalyuk | be52eca | 2020-10-03 20:14:06 +0300 | [diff] [blame] | 39 | 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ée | 0395b19 | 2020-10-21 17:31:36 +0100 | [diff] [blame] | 48 | vm.add_args('-gdb', 'tcp::%d' % port, '-S') |
Pavel Dovgalyuk | be52eca | 2020-10-03 20:14:06 +0300 | [diff] [blame] | 49 | 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 Piggin | cd43f00 | 2023-11-16 21:53:52 +1000 | [diff] [blame] | 56 | 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 Dovgalyuk | be52eca | 2020-10-03 20:14:06 +0300 | [diff] [blame] | 60 | 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ée | 0395b19 | 2020-10-21 17:31:36 +0100 | [diff] [blame] | 118 | port = find_free_port() |
Pavel Dovgalyuk | be52eca | 2020-10-03 20:14:06 +0300 | [diff] [blame] | 119 | |
| 120 | # record the log |
Alex Bennée | 0395b19 | 2020-10-21 17:31:36 +0100 | [diff] [blame] | 121 | vm = self.run_vm(True, shift, args, replay_path, image_path, port) |
Pavel Dovgalyuk | be52eca | 2020-10-03 20:14:06 +0300 | [diff] [blame] | 122 | 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ée | 0395b19 | 2020-10-21 17:31:36 +0100 | [diff] [blame] | 130 | vm = self.run_vm(False, shift, args, replay_path, image_path, port) |
Pavel Dovgalyuk | be52eca | 2020-10-03 20:14:06 +0300 | [diff] [blame] | 131 | logger.info('connecting to gdbstub') |
Alex Bennée | 0395b19 | 2020-10-21 17:31:36 +0100 | [diff] [blame] | 132 | g = gdb.GDBRemote('127.0.0.1', port, False, False) |
Pavel Dovgalyuk | be52eca | 2020-10-03 20:14:06 +0300 | [diff] [blame] | 133 | 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 Piggin | 76e9c1d | 2023-08-08 14:20:00 +1000 | [diff] [blame] | 158 | # 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 Dovgalyuk | be52eca | 2020-10-03 20:14:06 +0300 | [diff] [blame] | 164 | |
Nicholas Piggin | 76e9c1d | 2023-08-08 14:20:00 +1000 | [diff] [blame] | 165 | # set breakpoints for the instructions just stepped over |
Pavel Dovgalyuk | be52eca | 2020-10-03 20:14:06 +0300 | [diff] [blame] | 166 | 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 Piggin | 76e9c1d | 2023-08-08 14:20:00 +1000 | [diff] [blame] | 171 | # 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 Dovgalyuk | be52eca | 2020-10-03 20:14:06 +0300 | [diff] [blame] | 185 | 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 Pitsidianakis | 35a5a33 | 2024-02-20 10:52:09 +0200 | [diff] [blame] | 194 | logger.info('exiting gdb and qemu') |
Pavel Dovgalyuk | be52eca | 2020-10-03 20:14:06 +0300 | [diff] [blame] | 195 | vm.shutdown() |
| 196 | |
| 197 | class ReverseDebugging_X86_64(ReverseDebugging): |
Fabiano Rosas | 9bb9a3f | 2023-02-13 17:29:18 -0300 | [diff] [blame] | 198 | """ |
| 199 | :avocado: tags=accel:tcg |
| 200 | """ |
| 201 | |
Pavel Dovgalyuk | be52eca | 2020-10-03 20:14:06 +0300 | [diff] [blame] | 202 | 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 Huth | ea6a33e | 2023-11-21 11:08:42 +0100 | [diff] [blame] | 208 | # unidentified gitlab timeout problem |
Alex Bennée | 9b45cc9 | 2023-12-01 09:36:28 +0000 | [diff] [blame] | 209 | @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab') |
Pavel Dovgalyuk | be52eca | 2020-10-03 20:14:06 +0300 | [diff] [blame] | 210 | 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 | |
| 218 | class ReverseDebugging_AArch64(ReverseDebugging): |
Fabiano Rosas | 9bb9a3f | 2023-02-13 17:29:18 -0300 | [diff] [blame] | 219 | """ |
| 220 | :avocado: tags=accel:tcg |
| 221 | """ |
| 222 | |
Pavel Dovgalyuk | be52eca | 2020-10-03 20:14:06 +0300 | [diff] [blame] | 223 | REG_PC = 32 |
| 224 | |
Thomas Huth | ea6a33e | 2023-11-21 11:08:42 +0100 | [diff] [blame] | 225 | # unidentified gitlab timeout problem |
Alex Bennée | 9b45cc9 | 2023-12-01 09:36:28 +0000 | [diff] [blame] | 226 | @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab') |
Pavel Dovgalyuk | be52eca | 2020-10-03 20:14:06 +0300 | [diff] [blame] | 227 | 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 Moschetta | d377ba4 | 2021-04-30 10:34:10 -0300 | [diff] [blame] | 240 | args=('-kernel', kernel_path)) |
Nicholas Piggin | 761a13b | 2023-08-08 14:20:01 +1000 | [diff] [blame] | 241 | |
| 242 | class ReverseDebugging_ppc64(ReverseDebugging): |
| 243 | """ |
| 244 | :avocado: tags=accel:tcg |
| 245 | """ |
| 246 | |
| 247 | REG_PC = 0x40 |
| 248 | |
Thomas Huth | ea6a33e | 2023-11-21 11:08:42 +0100 | [diff] [blame] | 249 | # unidentified gitlab timeout problem |
Alex Bennée | 9b45cc9 | 2023-12-01 09:36:28 +0000 | [diff] [blame] | 250 | @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab') |
Nicholas Piggin | 761a13b | 2023-08-08 14:20:01 +1000 | [diff] [blame] | 251 | def test_ppc64_pseries(self): |
| 252 | """ |
| 253 | :avocado: tags=arch:ppc64 |
| 254 | :avocado: tags=machine:pseries |
Alex Bennée | 5d25fcb | 2023-12-01 09:36:33 +0000 | [diff] [blame] | 255 | :avocado: tags=flaky |
Nicholas Piggin | 761a13b | 2023-08-08 14:20:01 +1000 | [diff] [blame] | 256 | """ |
| 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 Huth | ea6a33e | 2023-11-21 11:08:42 +0100 | [diff] [blame] | 263 | # See https://gitlab.com/qemu-project/qemu/-/issues/1992 |
Alex Bennée | 9b45cc9 | 2023-12-01 09:36:28 +0000 | [diff] [blame] | 264 | @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab') |
Nicholas Piggin | 761a13b | 2023-08-08 14:20:01 +1000 | [diff] [blame] | 265 | def test_ppc64_powernv(self): |
| 266 | """ |
| 267 | :avocado: tags=arch:ppc64 |
| 268 | :avocado: tags=machine:powernv |
Alex Bennée | 5d25fcb | 2023-12-01 09:36:33 +0000 | [diff] [blame] | 269 | :avocado: tags=flaky |
Nicholas Piggin | 761a13b | 2023-08-08 14:20:01 +1000 | [diff] [blame] | 270 | """ |
| 271 | self.endian_is_le = False |
| 272 | self.reverse_debugging() |