1*951ededfSThomas Huth# Reverse debugging test 2*951ededfSThomas Huth# 3*951ededfSThomas Huth# SPDX-License-Identifier: GPL-2.0-or-later 4*951ededfSThomas Huth# 5*951ededfSThomas Huth# Copyright (c) 2020 ISP RAS 6*951ededfSThomas Huth# 7*951ededfSThomas Huth# Author: 8*951ededfSThomas Huth# Pavel Dovgalyuk <Pavel.Dovgalyuk@ispras.ru> 9*951ededfSThomas Huth# 10*951ededfSThomas Huth# This work is licensed under the terms of the GNU GPL, version 2 or 11*951ededfSThomas Huth# later. See the COPYING file in the top-level directory. 12*951ededfSThomas Huthimport os 13*951ededfSThomas Huthimport logging 14*951ededfSThomas Huth 15*951ededfSThomas Huthfrom qemu_test import LinuxKernelTest, get_qemu_img 16*951ededfSThomas Huthfrom qemu_test.ports import Ports 17*951ededfSThomas Huth 18*951ededfSThomas Huth 19*951ededfSThomas Huthclass ReverseDebugging(LinuxKernelTest): 20*951ededfSThomas Huth """ 21*951ededfSThomas Huth Test GDB reverse debugging commands: reverse step and reverse continue. 22*951ededfSThomas Huth Recording saves the execution of some instructions and makes an initial 23*951ededfSThomas Huth VM snapshot to allow reverse execution. 24*951ededfSThomas Huth Replay saves the order of the first instructions and then checks that they 25*951ededfSThomas Huth are executed backwards in the correct order. 26*951ededfSThomas Huth After that the execution is replayed to the end, and reverse continue 27*951ededfSThomas Huth command is checked by setting several breakpoints, and asserting 28*951ededfSThomas Huth that the execution is stopped at the last of them. 29*951ededfSThomas Huth """ 30*951ededfSThomas Huth 31*951ededfSThomas Huth timeout = 10 32*951ededfSThomas Huth STEPS = 10 33*951ededfSThomas Huth endian_is_le = True 34*951ededfSThomas Huth 35*951ededfSThomas Huth def run_vm(self, record, shift, args, replay_path, image_path, port): 36*951ededfSThomas Huth from avocado.utils import datadrainer 37*951ededfSThomas Huth 38*951ededfSThomas Huth logger = logging.getLogger('replay') 39*951ededfSThomas Huth vm = self.get_vm(name='record' if record else 'replay') 40*951ededfSThomas Huth vm.set_console() 41*951ededfSThomas Huth if record: 42*951ededfSThomas Huth logger.info('recording the execution...') 43*951ededfSThomas Huth mode = 'record' 44*951ededfSThomas Huth else: 45*951ededfSThomas Huth logger.info('replaying the execution...') 46*951ededfSThomas Huth mode = 'replay' 47*951ededfSThomas Huth vm.add_args('-gdb', 'tcp::%d' % port, '-S') 48*951ededfSThomas Huth vm.add_args('-icount', 'shift=%s,rr=%s,rrfile=%s,rrsnapshot=init' % 49*951ededfSThomas Huth (shift, mode, replay_path), 50*951ededfSThomas Huth '-net', 'none') 51*951ededfSThomas Huth vm.add_args('-drive', 'file=%s,if=none' % image_path) 52*951ededfSThomas Huth if args: 53*951ededfSThomas Huth vm.add_args(*args) 54*951ededfSThomas Huth vm.launch() 55*951ededfSThomas Huth console_drainer = datadrainer.LineLogger(vm.console_socket.fileno(), 56*951ededfSThomas Huth logger=self.log.getChild('console'), 57*951ededfSThomas Huth stop_check=(lambda : not vm.is_running())) 58*951ededfSThomas Huth console_drainer.start() 59*951ededfSThomas Huth return vm 60*951ededfSThomas Huth 61*951ededfSThomas Huth @staticmethod 62*951ededfSThomas Huth def get_reg_le(g, reg): 63*951ededfSThomas Huth res = g.cmd(b'p%x' % reg) 64*951ededfSThomas Huth num = 0 65*951ededfSThomas Huth for i in range(len(res))[-2::-2]: 66*951ededfSThomas Huth num = 0x100 * num + int(res[i:i + 2], 16) 67*951ededfSThomas Huth return num 68*951ededfSThomas Huth 69*951ededfSThomas Huth @staticmethod 70*951ededfSThomas Huth def get_reg_be(g, reg): 71*951ededfSThomas Huth res = g.cmd(b'p%x' % reg) 72*951ededfSThomas Huth return int(res, 16) 73*951ededfSThomas Huth 74*951ededfSThomas Huth def get_reg(self, g, reg): 75*951ededfSThomas Huth # value may be encoded in BE or LE order 76*951ededfSThomas Huth if self.endian_is_le: 77*951ededfSThomas Huth return self.get_reg_le(g, reg) 78*951ededfSThomas Huth else: 79*951ededfSThomas Huth return self.get_reg_be(g, reg) 80*951ededfSThomas Huth 81*951ededfSThomas Huth def get_pc(self, g): 82*951ededfSThomas Huth return self.get_reg(g, self.REG_PC) 83*951ededfSThomas Huth 84*951ededfSThomas Huth def check_pc(self, g, addr): 85*951ededfSThomas Huth pc = self.get_pc(g) 86*951ededfSThomas Huth if pc != addr: 87*951ededfSThomas Huth self.fail('Invalid PC (read %x instead of %x)' % (pc, addr)) 88*951ededfSThomas Huth 89*951ededfSThomas Huth @staticmethod 90*951ededfSThomas Huth def gdb_step(g): 91*951ededfSThomas Huth g.cmd(b's', b'T05thread:01;') 92*951ededfSThomas Huth 93*951ededfSThomas Huth @staticmethod 94*951ededfSThomas Huth def gdb_bstep(g): 95*951ededfSThomas Huth g.cmd(b'bs', b'T05thread:01;') 96*951ededfSThomas Huth 97*951ededfSThomas Huth @staticmethod 98*951ededfSThomas Huth def vm_get_icount(vm): 99*951ededfSThomas Huth return vm.qmp('query-replay')['return']['icount'] 100*951ededfSThomas Huth 101*951ededfSThomas Huth def reverse_debugging(self, shift=7, args=None): 102*951ededfSThomas Huth from avocado.utils import gdb 103*951ededfSThomas Huth from avocado.utils import process 104*951ededfSThomas Huth 105*951ededfSThomas Huth logger = logging.getLogger('replay') 106*951ededfSThomas Huth 107*951ededfSThomas Huth # create qcow2 for snapshots 108*951ededfSThomas Huth logger.info('creating qcow2 image for VM snapshots') 109*951ededfSThomas Huth image_path = os.path.join(self.workdir, 'disk.qcow2') 110*951ededfSThomas Huth qemu_img = get_qemu_img(self) 111*951ededfSThomas Huth if qemu_img is None: 112*951ededfSThomas Huth self.skipTest('Could not find "qemu-img", which is required to ' 113*951ededfSThomas Huth 'create the temporary qcow2 image') 114*951ededfSThomas Huth cmd = '%s create -f qcow2 %s 128M' % (qemu_img, image_path) 115*951ededfSThomas Huth process.run(cmd) 116*951ededfSThomas Huth 117*951ededfSThomas Huth replay_path = os.path.join(self.workdir, 'replay.bin') 118*951ededfSThomas Huth 119*951ededfSThomas Huth # record the log 120*951ededfSThomas Huth vm = self.run_vm(True, shift, args, replay_path, image_path, -1) 121*951ededfSThomas Huth while self.vm_get_icount(vm) <= self.STEPS: 122*951ededfSThomas Huth pass 123*951ededfSThomas Huth last_icount = self.vm_get_icount(vm) 124*951ededfSThomas Huth vm.shutdown() 125*951ededfSThomas Huth 126*951ededfSThomas Huth logger.info("recorded log with %s+ steps" % last_icount) 127*951ededfSThomas Huth 128*951ededfSThomas Huth # replay and run debug commands 129*951ededfSThomas Huth with Ports() as ports: 130*951ededfSThomas Huth port = ports.find_free_port() 131*951ededfSThomas Huth vm = self.run_vm(False, shift, args, replay_path, image_path, port) 132*951ededfSThomas Huth logger.info('connecting to gdbstub') 133*951ededfSThomas Huth g = gdb.GDBRemote('127.0.0.1', port, False, False) 134*951ededfSThomas Huth g.connect() 135*951ededfSThomas Huth r = g.cmd(b'qSupported') 136*951ededfSThomas Huth if b'qXfer:features:read+' in r: 137*951ededfSThomas Huth g.cmd(b'qXfer:features:read:target.xml:0,ffb') 138*951ededfSThomas Huth if b'ReverseStep+' not in r: 139*951ededfSThomas Huth self.fail('Reverse step is not supported by QEMU') 140*951ededfSThomas Huth if b'ReverseContinue+' not in r: 141*951ededfSThomas Huth self.fail('Reverse continue is not supported by QEMU') 142*951ededfSThomas Huth 143*951ededfSThomas Huth logger.info('stepping forward') 144*951ededfSThomas Huth steps = [] 145*951ededfSThomas Huth # record first instruction addresses 146*951ededfSThomas Huth for _ in range(self.STEPS): 147*951ededfSThomas Huth pc = self.get_pc(g) 148*951ededfSThomas Huth logger.info('saving position %x' % pc) 149*951ededfSThomas Huth steps.append(pc) 150*951ededfSThomas Huth self.gdb_step(g) 151*951ededfSThomas Huth 152*951ededfSThomas Huth # visit the recorded instruction in reverse order 153*951ededfSThomas Huth logger.info('stepping backward') 154*951ededfSThomas Huth for addr in steps[::-1]: 155*951ededfSThomas Huth self.gdb_bstep(g) 156*951ededfSThomas Huth self.check_pc(g, addr) 157*951ededfSThomas Huth logger.info('found position %x' % addr) 158*951ededfSThomas Huth 159*951ededfSThomas Huth # visit the recorded instruction in forward order 160*951ededfSThomas Huth logger.info('stepping forward') 161*951ededfSThomas Huth for addr in steps: 162*951ededfSThomas Huth self.check_pc(g, addr) 163*951ededfSThomas Huth self.gdb_step(g) 164*951ededfSThomas Huth logger.info('found position %x' % addr) 165*951ededfSThomas Huth 166*951ededfSThomas Huth # set breakpoints for the instructions just stepped over 167*951ededfSThomas Huth logger.info('setting breakpoints') 168*951ededfSThomas Huth for addr in steps: 169*951ededfSThomas Huth # hardware breakpoint at addr with len=1 170*951ededfSThomas Huth g.cmd(b'Z1,%x,1' % addr, b'OK') 171*951ededfSThomas Huth 172*951ededfSThomas Huth # this may hit a breakpoint if first instructions are executed 173*951ededfSThomas Huth # again 174*951ededfSThomas Huth logger.info('continuing execution') 175*951ededfSThomas Huth vm.qmp('replay-break', icount=last_icount - 1) 176*951ededfSThomas Huth # continue - will return after pausing 177*951ededfSThomas Huth # This could stop at the end and get a T02 return, or by 178*951ededfSThomas Huth # re-executing one of the breakpoints and get a T05 return. 179*951ededfSThomas Huth g.cmd(b'c') 180*951ededfSThomas Huth if self.vm_get_icount(vm) == last_icount - 1: 181*951ededfSThomas Huth logger.info('reached the end (icount %s)' % (last_icount - 1)) 182*951ededfSThomas Huth else: 183*951ededfSThomas Huth logger.info('hit a breakpoint again at %x (icount %s)' % 184*951ededfSThomas Huth (self.get_pc(g), self.vm_get_icount(vm))) 185*951ededfSThomas Huth 186*951ededfSThomas Huth logger.info('running reverse continue to reach %x' % steps[-1]) 187*951ededfSThomas Huth # reverse continue - will return after stopping at the breakpoint 188*951ededfSThomas Huth g.cmd(b'bc', b'T05thread:01;') 189*951ededfSThomas Huth 190*951ededfSThomas Huth # assume that none of the first instructions is executed again 191*951ededfSThomas Huth # breaking the order of the breakpoints 192*951ededfSThomas Huth self.check_pc(g, steps[-1]) 193*951ededfSThomas Huth logger.info('successfully reached %x' % steps[-1]) 194*951ededfSThomas Huth 195*951ededfSThomas Huth logger.info('exiting gdb and qemu') 196*951ededfSThomas Huth vm.shutdown() 197