qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

reverse_debugging.py (7211B)


      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 
     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
     20 
     21 class 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 
     37     def run_vm(self, record, shift, args, replay_path, image_path, port):
     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'
     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),
     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')
    113         port = find_free_port()
    114 
    115         # record the log
    116         vm = self.run_vm(True, shift, args, replay_path, image_path, port)
    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
    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)
    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 
    175 class 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 
    192 class 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))