Skip to content

Commit

Permalink
CR fixes:
Browse files Browse the repository at this point in the history
- place all coverage-related code in its own sub-directory
- allow easy extension with other coverage formats
- add some documentation
  • Loading branch information
assafcarlsbad committed May 30, 2020
1 parent 8197c33 commit b8d168f
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 20 deletions.
Empty file added qiling/coverage/__init__.py
Empty file.
35 changes: 35 additions & 0 deletions qiling/coverage/coverage.py
@@ -0,0 +1,35 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
# Built on top of Unicorn emulator (www.unicorn-engine.org)

from abc import ABC, abstractmethod

class QlCoverage(ABC):
"""
An abstract base class for concrete code coverage collectors.
To add support for a new coverage format, just derive from this class and implement
all the methods marked with the @abstractmethod decorator.
"""

def __init__(self):
super().__init__()

@property
@staticmethod
@abstractmethod
def FORMAT_NAME():
raise NotImplementedError

@abstractmethod
def activate(self):
pass

@abstractmethod
def deactivate(self):
pass

@abstractmethod
def dump_coverage(self, coverage_file):
pass

1 change: 1 addition & 0 deletions qiling/coverage/formats/__init__.py
@@ -0,0 +1 @@
__all__ = ["drcov"]
32 changes: 16 additions & 16 deletions qiling/coverage.py → qiling/coverage/formats/drcov.py
Expand Up @@ -5,7 +5,7 @@

from ctypes import Structure
from ctypes import c_uint32, c_uint16
from contextlib import contextmanager
from ..coverage import QlCoverage

# Adapted from https://www.ayrx.me/drcov-file-format
class bb_entry(Structure):
Expand All @@ -15,11 +15,22 @@ class bb_entry(Structure):
("mod_id", c_uint16)
]

class QlCoverage():
def __init__(self, ql, drcov_version = 2, drcov_flavor = "drcov"):
class QlDrCoverage(QlCoverage):
"""
Collects emulated code coverage and formats it in accordance with the DynamoRIO based
tool drcov: https://dynamorio.org/dynamorio_docs/page_drcov.html
The resulting output file can later be imported by coverage visualization tools such
as Lighthouse: https://github.com/gaasedelen/lighthouse
"""

FORMAT_NAME = "drcov"

def __init__(self, ql):
super().__init__()
self.ql = ql
self.drcov_version = drcov_version
self.drcov_flavor = drcov_flavor
self.drcov_version = 2
self.drcov_flavor = 'drcov'
self.basic_blocks = []
self.bb_callback = None

Expand Down Expand Up @@ -48,14 +59,3 @@ def dump_coverage(self, coverage_file):
cov.write(f"BB Table: {len(self.basic_blocks)} bbs\n".encode())
for bb in self.basic_blocks:
cov.write(bytes(bb))

@contextmanager
def collect_coverage(ql, coverage_file):
cov = QlCoverage(ql)
cov.activate()
try:
yield
finally:
cov.deactivate()
if coverage_file:
cov.dump_coverage(coverage_file)
39 changes: 39 additions & 0 deletions qiling/coverage/utils.py
@@ -0,0 +1,39 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
# Built on top of Unicorn emulator (www.unicorn-engine.org)

from .coverage import QlCoverage
from .formats import *
from contextlib import contextmanager

class CoverageFactory():
def __init__(self):
self.coverage_collectors = {subcls.FORMAT_NAME:subcls for subcls in QlCoverage.__subclasses__()}

@property
def formats(self):
return self.coverage_collectors.keys()

def get_coverage_collector(self, ql, name):
return self.coverage_collectors[name](ql)

factory = CoverageFactory()

@contextmanager
def collect_coverage(ql, name, coverage_file):
"""
Context manager for emulating a given piece of code with coverage collection turned on.
Example:
with collect_coverage(ql, 'drcov', 'output.cov'):
ql.run(...)
"""

cov = factory.get_coverage_collector(ql, name)
cov.activate()
try:
yield
finally:
cov.deactivate()
if coverage_file:
cov.dump_coverage(coverage_file)
9 changes: 5 additions & 4 deletions qltool
Expand Up @@ -8,7 +8,7 @@ import argparse, os, string, sys, ast
from binascii import unhexlify
from qiling import *
from qiling import __version__ as ql_version
from qiling.coverage import collect_coverage
from qiling.coverage import utils as cov_utils


def parse_args(parser, commands):
Expand Down Expand Up @@ -168,8 +168,9 @@ if __name__ == '__main__':
help='Stop running while encounter any error (only use it with debug mode)')
comm_parser.add_argument('-m','--multithread', action='store_true', default=False, dest='multithread', help='Run in multithread mode')
comm_parser.add_argument('--timeout', required=False, help='Emulation timeout')
comm_parser.add_argument('-c', '--coverage', required=False, default=None, dest='coverage', help='Code coverage file name')

comm_parser.add_argument('-c', '--coverage-file', required=False, default=None, dest='coverage_file', help='Code coverage file name')
comm_parser.add_argument('--coverage-format', required=False, default='drcov', dest='coverage_format',
choices=cov_utils.factory.formats, help='Code coverage file format')
options = parser.parse_args()

# var check
Expand Down Expand Up @@ -260,7 +261,7 @@ if __name__ == '__main__':
timeout = int(options.timeout)

# ql run
with collect_coverage(ql, options.coverage):
with cov_utils.collect_coverage(ql, options.coverage_format, options.coverage_file):
ql.run(timeout=timeout)
exit(ql.os.exit_code)

Expand Down

0 comments on commit b8d168f

Please sign in to comment.