cachepc-qemu

Fork of AMDESE/qemu with changes for cachepc side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-qemu
Log | Files | Refs | Submodules | LICENSE | sfeed.txt

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.
     10import os
     11import logging
     12
     13from avocado import skipIf
     14from avocado_qemu import BUILD_DIR
     15from avocado.utils import gdb
     16from avocado.utils import process
     17from avocado.utils.network.ports import find_free_port
     18from avocado.utils.path import find_command
     19from boot_linux_console import LinuxKernelTest
     20
     21class 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
    175class 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
    192class 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))