code-coverage: modify intermediate layer algorithm to improve code coverage

- The intermediate layer uses traces from the plugin and DWARF data
from the binaries to match and create code coverage.
- The regulars expresions that group functions within the DWARF
data had been modified in order to capture more functions in the
binaries that correspond to source code.
- A refactoring including more classes were created to encapsulate
how the algorithm was implemented by the code.

Signed-off-by: Saul Romero <saul.romero@arm.com>
diff --git a/coverage-tool/coverage-reporting/cc_logger.py b/coverage-tool/coverage-reporting/cc_logger.py
new file mode 100644
index 0000000..67bf984
--- /dev/null
+++ b/coverage-tool/coverage-reporting/cc_logger.py
@@ -0,0 +1,28 @@
+import logging
+import time
+
+time_file = time.strftime("%Y%m%d-%H%M%S")
+# Create a global logger
+logger = logging.getLogger(__name__)
+logger.setLevel(logging.DEBUG)
+
+# Create handlers
+c_handler = logging.StreamHandler()
+f_handler = logging.FileHandler(f'cc_logger.log')
+c_handler.setLevel(logging.DEBUG)
+f_handler.setLevel(logging.DEBUG)
+
+# Create formatters and add it to handlers
+c_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %('
+                             'message)s')
+f_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %('
+                             'message)s')
+c_handler.setFormatter(c_format)
+f_handler.setFormatter(f_format)
+
+# Add handlers to the logger
+logger.addHandler(c_handler)
+logger.addHandler(f_handler)
+
+
+
diff --git a/coverage-tool/coverage-reporting/intermediate_layer.py b/coverage-tool/coverage-reporting/intermediate_layer.py
index 9ef6f81..5d25a7b 100644
--- a/coverage-tool/coverage-reporting/intermediate_layer.py
+++ b/coverage-tool/coverage-reporting/intermediate_layer.py
@@ -1,6 +1,6 @@
 # !/usr/bin/env python
 ###############################################################################
-# Copyright (c) 2020-2022, ARM Limited and Contributors. All rights reserved.
+# Copyright (c) 2020-2023, ARM Limited and Contributors. All rights reserved.
 #
 # SPDX-License-Identifier: BSD-3-Clause
 ###############################################################################
@@ -21,10 +21,15 @@
 import subprocess
 import json
 from argparse import RawTextHelpFormatter
-import logging
+import cc_logger
 import time
 from typing import Dict
 from typing import List
+from typing import Generator
+from typing import Union
+from typing import Tuple
+from functools import cached_property
+import logging
 
 __version__ = "7.0"
 
@@ -178,68 +183,6 @@
     return ranges
 
 
-def list_of_functions_for_binary(elf_name: str) -> Dict[str, Dict[str, any]]:
-    """
-    Get an array of the functions in the elf file
-
-    :param elf_name: Elf binary file name
-    :return: An array of function address start, function address end,
-            function dwarf signature (sources) indexed by function name
-    """
-    _functions = {}
-    command = "%s -t %s | awk 'NR>4' | sed /^$/d" % (OBJDUMP, elf_name)
-    symbols_output = os_command(command)
-    rex = r'([0-9a-fA-F]+) (.{7}) ([^ ]+)[ \t]([0-9a-fA-F]+) (.*)'
-    symbols = symbols_output.split('\n')[:-1]
-    for sym in symbols:
-        try:
-            symbol_details = re.findall(rex, sym)
-            symbol_details = symbol_details[0]
-            if 'F' not in symbol_details[1]:
-                continue
-            function_name = symbol_details[4]
-            # We don't want the .hidden for hidden functions
-            if function_name.startswith('.hidden '):
-                function_name = function_name[len('.hidden '):]
-            if function_name not in _functions:
-                _functions[function_name] = {'start': symbol_details[0],
-                                             'end': symbol_details[3],
-                                             'sources': False}
-            else:
-                logger.warning("'{}' duplicated in '{}'".format(
-                    function_name,
-                    elf_name))
-        except Exception as ex:
-            logger.error("@Listing functions at file {}: {}".format(
-                elf_name,
-                ex))
-    return _functions
-
-
-def apply_functions_exclude(elf_config, functions):
-    """
-    Remove excluded functions from the list of functions
-
-    :param elf_config: Config for elf binary file
-    :param functions: Array of functions in the binary elf file
-    :return: Tuple with included and excluded functions
-    """
-    if 'exclude_functions' not in elf_config:
-        return functions, []
-    incl = {}
-    excl = {}
-    for fname in functions:
-        exclude = False
-        for rex in elf_config['exclude_functions']:
-            if re.match(rex, fname):
-                exclude = True
-                excl[fname] = functions[fname]
-                break
-        if not exclude:
-            incl[fname] = functions[fname]
-    return incl, excl
-
-
 def remove_workspace(path, workspace):
     """
     Get the relative path to a given workspace
@@ -268,8 +211,11 @@
                 continue
             if cols[1] == "function":
                 fln[cols[0]] = int(cols[2])
-            elif cols[1] == "label" and cols[0] == "func":
-                fln[cols[-1]] = int(cols[2])
+            elif cols[1] == "label":
+                if cols[0] == "func":
+                    fln[cols[-1]] = int(cols[2])
+                elif cols[0] + ":" == cols[-1]:
+                    fln[cols[0]] = int(cols[2])
     except BaseException:
         logger.warning("Warning: Can't get all function line numbers from %s" %
                        source_file)
@@ -277,7 +223,6 @@
         logger.warning(f"Warning: Unknown error '{ex}' when executing command "
                        f"'{command}'")
         return {}
-
     return fln
 
 
@@ -299,8 +244,9 @@
         if not FUNCTION_LINES_ENABLED:
             return 0
         if filename not in self.filenames:
-            newp = os.path.join(self.workspace, filename)
-            self.filenames[filename] = get_function_line_numbers(newp)
+            source_file = os.path.join(self.workspace, filename)
+            # Get all functions with their lines in the source file
+            self.filenames[filename] = get_function_line_numbers(source_file)
         return 0 if function_name not in self.filenames[filename] else \
             self.filenames[filename][function_name]
 
@@ -310,52 +256,130 @@
     dwarf signature in order to produce logical information to be matched with
     traces and produce a code coverage report"""
 
-    def __init__(self, dump: str, function_list: Dict[str, Dict[str, any]],
-                 _workspace: str, _remove_workspace: bool,
-                 function_line_numbers: FunctionLineNumbers):
+    def __init__(self, dump: str, _workspace: str, _remove_workspace: bool,
+                 local_workspace: str):
         """
         Initialisation of the instance to parse binary files.
 
         :param dump: Binary dump (string) containing assembly code and source
         code metadata, i.e. source code location and line number.
-        :param function_list: Dictionary of functions defined in the binary
-        dump.
-        :param _workspace: Workspace (folder) where the source files were built.
-        :param _remove_workspace: Boolean to indicate if the building of
-        source files is local (false) or in a CI (true).
-        :param function_line_numbers: Object instance to get a function line
-        number within a source code file.
+        :param _workspace: Workspace (folder) where the source files were
+        built from.
+        :param _remove_workspace: Boolean to indicate if the build of
+        source files was local (false) or from a CI (true).
+        :param local_workspace: Path to the local workspace where the source
+        files reside
         """
         self.dump = dump
-        self.function_list = function_list
+        self.no_source_functions = self.get_no_source_functions()
         self.workspace = _workspace
         self.remove_workspace = _remove_workspace
-        self.function_definition = None
-        self.function_line_numbers = function_line_numbers
+        self.local_workspace = local_workspace
+        self.function_line_numbers = FunctionLineNumbers(self.local_workspace)
+
+    def get_no_source_functions(self) -> Dict[int, Dict]:
+        """Find in the dwarf dump all the functions with no source code i.e.:
+        function_name():
+               start_hex_address opcode
+               ....
+               end_hex_address opcode
+
+        :returns: Dictionary of functions indexed by start address function's
+        location
+        """
+        # The functions dict is [start_dec_address]={function name, function
+        # end address in decimal}
+        _functions = {}
+        groups = re.findall(r"(.+?)\(\):\n\s+([a-f0-9]+):."
+                            r"+?\n(\s+([a-f0-9]+):.+?\n)*", self.dump)
+        for group in groups:
+            function_name, start_hex_address, _, end_hex_address = group
+            if not end_hex_address:
+                end_hex_address = start_hex_address
+            _functions[int(start_hex_address, 16)] = {'name': function_name,
+                                                      'end_address': int(
+                                                          end_hex_address, 16)}
+        return _functions
+
+    class SourceCodeBlock(object):
+        """Class used to represent a source code block of information within
+        a function block in a binary dump file.
+        The source code block contains the following components:
+        - Source code file that contains the source code corresponding
+        to the assembly code.
+        - Line number within the source code file corresponding to the source
+        code.
+        - Assembly code block.
+        """
+
+        def __init__(self, source_code_block_dump):
+            """
+            Create an instance of a source code block within a function block.
+
+            :param source_code_block: Tuple of 3 elements that contains the
+            components of a source code block.
+            """
+            self.source_file, self.line_number, self.asm_code \
+                = source_code_block_dump
+
+        @staticmethod
+        def get(dwarf_data: str) -> Generator['BinaryParser.SourceCodeBlock',
+                                              None, None]:
+            source_block_groups = re.findall(r"(?s)(/[a-zA-Z_0-9][^\n]+?):"
+                                             r"([0-9]+)(?: [^\n]+)?\n(.+?)"
+                                             r"\n(?=/[a-zA-Z_0-9][^\n]+?"
+                                             r":[0-9]+[^\n]+?\n|\n$)",
+                                             dwarf_data)
+            for source_block_group in source_block_groups:
+                if len(source_block_group) != 3:
+                    logger.warning(f"Source code incomplete:"
+                                   f"{source_block_group}")
+                    continue
+                source_block_dump = list(source_block_group)
+                source_block_dump[-1] += "\n\n"  # For parsing assembly lines
+                yield BinaryParser.SourceCodeBlock(source_block_dump)
+
+        def __str__(self):
+            return f"'{self.source_file}:{self.line_number}'"
 
     class FunctionBlock(object):
         """Class used to parse and obtain a function block from the
-        binary dump file that corresponds to a function declaration within
-        the binary assembly code.
+        binary dump file that corresponds to a function declaration in the
+        source code file and a block of assembly code mixed with corresponding
+        source code lines, i.e. dwarf information.
         The function block has the following components:
-        - Function start address in memory (hexadecimal).
-        - Function name.
-        - Function code.
+        - Function name at source code.
+        - DWARF data.
+        - Function declaration's line number at source code.
+        This comes from dump blocks like these:
+        0000000000000230 <_setup>:
+        read_el(): <---- Function name at source code
+        /home/user/aarch64/setup.c:238 <------ Source file and line number
+        230:	d53e1100 	mrs	x0, scr_el3   <----- Assembly lines belonging to
+        the source code
+        no_setup():
+        /home/user/no_setup.c:618
+        234:	b2760000 	orr	x0, x0, #0x400
         """
 
         def __init__(self, function_group: List[str]):
             """
             Create an instance of a function block within a binary dump.
 
-            :param function_group: List containing the function start
-            address, name and code in the function block.
+            :param function_group: List containing the function name and
+            dwarf data of the block.
             """
-            self.start, self.name, self.code = function_group
-            self.source_file = None
+            self.name, self.dwarf = function_group
+            # Now obtain the function's source file
+            m = re.search(r"(/.+?):([0-9]+)(?: [^\n]+)?\n", self.dwarf)
+            self.source_file = m.groups()[0].strip() \
+                if m and len(m.groups()) == 2 else None
+            # Computed later
             self.function_line_number = None
 
         @staticmethod
-        def get(dump: str):
+        def get(dump: str) -> Generator['BinaryParser.FunctionBlock', None,
+                                        None]:
             """
             Static method generator to extract a function block from the binary
             dump.
@@ -365,43 +389,23 @@
             :return: A FunctionBlock object that is a logical representation
             of a function declaration within the binary dump.
             """
-            function_groups = re.findall(
-                r"(?s)([0-9a-fA-F]+) <([a-zA-Z0-9_]+)>:\n(.+?)(?=[A-Fa-f0-9]* "
-                r"<[a-zA-Z0-9_]+>:)", dump, re.DOTALL | re.MULTILINE)
+            function_groups = re.findall(r"(?s)([a-zA-Z0-9_]+?)\(\):"
+                                         r"\n(/.+?:[0-9]+?.+?)\n"
+                                         r"(?=[a-zA-Z0-9_]+?\(\):\n|\n\n$)",
+                                         dump)
             for group in function_groups:
-                if len(group) != 3:
+                if len(group) != 2:
                     continue
                 function_group = list(group)
-                function_group[-1] += "\n"
+                function_group[-1] += "\n\n"  # For parsing source code blocks
                 yield BinaryParser.FunctionBlock(function_group)
 
-    class SourceCodeBlock(object):
-        """Class used to represent a source code block of information within
-        a function block in a binary dump file.
-        The source code block contains the following components:
-        - Optional function name where the source code/assembly code is defined.
-        - Source code file that contains the source code corresponding
-        to the assembly code.
-        - Line number within the source code file corresponding to the source
-        code.
-        - Assembly code block.
-        """
+        @property
+        def values(self):
+            return self.name, self.source_file, self.function_line_number
 
-        def __init__(self, source_code_block):
-            """
-            Create an instance of a source code block within a function block.
-
-            :param source_code_block: Tuple of 4 elements that contains the
-            components of a source code block.
-            """
-            self.function_name, self.source_file, self.line, self.asm_code \
-                = source_code_block
-
-        def get_assembly_line(self):
-            """Getter to return and AssemblyLine instance that corresponds to
-             a logical representation of an assembly code line contained
-             within a source code block (assembly code block)"""
-            return BinaryParser.AssemblyLine.get(self)
+        def __str__(self):
+            return f"'{self.name}:{self.function_line_number}'"
 
     class AssemblyLine(object):
         """Class used to represent an assembly code line within an
@@ -421,67 +425,41 @@
             """
             self.hex_line_number, self.opcode = line
             self.dec_address = int(self.hex_line_number, 16)
+            self.times_executed = 0
 
         @staticmethod
-        def get(source_code_block):
+        def get(asm_code: str) -> Generator['BinaryParser.AssemblyLine',
+                                            None, None]:
             """
             Static method generator to extract an assembly code line from a
             assembly code block.
 
-            :param source_code_block: Object that contains the assembly code
-            within the source code block.
+            :param asm_code: Lines of assembly code within the dump
             :return: AssemblyLine object.
             """
-            lines = re.findall(
-                r"^[\s]+([a-fA-F0-9]+):\t(.+?)\n",
-                source_code_block.asm_code, re.DOTALL | re.MULTILINE)
+            lines = re.findall(r"^\s+([a-fA-F0-9]+):\t(.+?)\n", asm_code,
+                               re.DOTALL | re.MULTILINE)
             for line in lines:
                 if len(line) != 2:
+                    logger.warning(f"Assembly code incomplete: {line}")
                     continue
                 yield BinaryParser.AssemblyLine(line)
 
-    class FunctionDefinition(object):
-        """
-        Class used to handle a function definition i.e. function name, source
-        code filename and line number where is declared.
-        """
+    @staticmethod
+    def get_asm_line(source_code_block: 'BinaryParser.SourceCodeBlock',
+                     traces_stats) -> \
+            Generator['BinaryParser.AssemblyLine', None, None]:
+        """Generator method to obtain all assembly line codes within a source
+        code line """
+        traces_stats = traces_stats
+        for asm_line in BinaryParser.AssemblyLine.get(
+                source_code_block.asm_code):
+            asm_line.times_executed = traces_stats.get(asm_line.dec_address,
+                                                       [0])[0]
+            yield asm_line
 
-        def __init__(self, function_name):
-            """
-            Create an instance representing a function definition within a
-            function code block.
-
-            :param function_name: Initial function name
-            """
-            self.function_line_number = None
-            self.function_name = function_name
-            self.source_file: str = None
-
-        def update_sources(self, source_files, function_line_numbers):
-            """
-            Method to update source files dictionary
-
-            :param source_files: Dictionary that contains the representation
-            of the intermediate layer.
-
-            :param function_line_numbers: Object that obtains the start line
-            number for a function definition inside it source file.
-            :return:Nothing
-            """
-            source_files.setdefault(self.source_file, {"functions": {},
-                                                       "lines": {}})
-            if self.function_name not in \
-                    source_files[self.source_file]["functions"]:
-                self.function_line_number = \
-                    function_line_numbers.get_line_number(
-                        self.source_file,
-                        self.function_name)
-                source_files[self.source_file]["functions"][
-                    self.function_name] = {"covered": False,
-                                           "line_number":
-                                               self.function_line_number}
-
-    def get_source_code_block(self, function_block: FunctionBlock):
+    def get_source_code_block(self, function_block: FunctionBlock) -> \
+            Generator['BinaryParser.SourceCodeBlock', None, None]:
         """
         Generator method to obtain all the source code blocks within a
         function block.
@@ -490,61 +468,103 @@
         the source code blocks.
         :return: A SourceCodeBlock object.
         """
-        # When not present the block function name applies
-        self.function_definition = BinaryParser.FunctionDefinition(
-            function_block.name)
-        pattern = r'(?s)(^[a-zA-Z0-9_]+)?(?:\(\):\n)?(^{0}.+?):([0-9]+)[' \
-                  r'^\n]*\n(.+?)(?={0}.+?:[0-9]+.+\n|^[a-zA-Z0-9_]+\(' \
-                  r'\):\n)'.format(self.workspace)
-        source_code_blocks = re.findall(pattern,
-                                        "{}\n{}/:000".format(
-                                            function_block.code,
-                                            self.workspace),
-                                        re.DOTALL |
-                                        re.MULTILINE)
-        for block in source_code_blocks:
-            if len(block) != 4:
-                continue
-            source_code_block = BinaryParser.SourceCodeBlock(block)
-            if source_code_block.function_name:
-                # Usually in the first iteration function name is not empty
-                # and is the function's name block
-                self.function_definition.function_name = \
-                    source_code_block.function_name
-            self.function_definition.source_file = source_code_block.source_file
+        for source_code_block in BinaryParser.SourceCodeBlock.get(
+                function_block.dwarf):
             if self.remove_workspace:
-                self.function_definition.source_file = remove_workspace(
+                source_code_block.source_file = remove_workspace(
                     source_code_block.source_file, self.workspace)
             yield source_code_block
 
-    def get_function_block(self):
+    def get_function_block(self) -> Generator['BinaryParser.FunctionBlock',
+                                              None, None]:
         """Generator method to obtain all the function blocks contained in
         the binary dump file.
         """
         for function_block in BinaryParser.FunctionBlock.get(self.dump):
-            # Find out if the function block has C source code filename in
-            # the function block code
-            signature_group = re.findall(
-                r"(?s){}\(\):\n(/.+?):[0-9]+.*(?:\r*\n\n|\n$)".format(
-                    function_block.name), function_block.code,
-                re.DOTALL | re.MULTILINE)
-            if not signature_group:
-                continue  # Function does not have dwarf signature (sources)
-            if function_block.name not in self.function_list:
-                print("Warning:Function '{}' not found in function list!!!".
-                      format(function_block.name))
-                continue  # Function not found in function list
-            source_code_file = signature_group[0]
-            function_block.source_file = source_code_file
+            if function_block.source_file is None:
+                logger.warning(f"Source file not found for function "
+                               f"{function_block.name}, will not be covered")
+                continue
             if self.remove_workspace:
                 function_block.source_file = remove_workspace(
-                    source_code_file, self.workspace)
+                    function_block.source_file, self.workspace)
             function_block.function_line_number = \
                 self.function_line_numbers.get_line_number(
                     function_block.source_file, function_block.name)
             yield function_block
 
 
+class CoverageHandler(object):
+    """ Class used to handle source files coverage linked with their functions
+    and line code coverage from function blocks obtained from DWARF data and
+    trace code coverage from CC plugin"""
+
+    def __init__(self):
+        self._source_files = {}
+
+    def add_function_coverage(self, function_data:
+                              Union[BinaryParser.FunctionBlock,
+                                    Tuple[str, str, int]]):
+        """ Add a function coverage block and a source file coverage block,
+        if not already created and link them"""
+        # Unpack function data either as an FunctionBlock object property or a
+        # tuple
+        name, source_file, function_line_number = function_data.values if \
+            isinstance(function_data, BinaryParser.FunctionBlock) else \
+            function_data
+
+        # Add source file coverage block it if not already there
+        self._source_files.setdefault(source_file,
+                                      {"functions": {}, "lines": {}})
+        # Add a function coverage block (if not existent) from a function
+        # block using the function block name as key and link it to the source
+        # file coverage block
+        self._source_files[source_file]["functions"].setdefault(
+            name, {"covered": False, "line_number": function_line_number})
+
+    def add_line_coverage(self, source_code_block:
+                          BinaryParser.SourceCodeBlock):
+        """ Add a line coverage block and a source file coverage block,
+        if not already created and link them"""
+        # Add source file coverage block it if not already there
+        self._source_files.setdefault(source_code_block.source_file,
+                                      {"functions": {}, "lines": {}})
+        # Add a line coverage block (if not existent) from a source block
+        # using the source code line number as key and link it to the source
+        # file coverage block
+        self._source_files[source_code_block.source_file]["lines"].setdefault(
+            source_code_block.line_number, {"covered": False, "elf_index": {}})
+
+    def add_asm_line(self, source_code_block: BinaryParser.SourceCodeBlock,
+                     asm_line: BinaryParser.AssemblyLine, elf_index: int):
+        """Add an assembly line from the DWARF data linked to a source code
+        line"""
+        self._source_files[source_code_block.source_file]["lines"][
+            source_code_block.line_number]["elf_index"].setdefault(
+            elf_index, {})
+        self._source_files[source_code_block.source_file]["lines"][
+            source_code_block.line_number]["elf_index"][
+            elf_index].setdefault(asm_line.dec_address,
+                                  (asm_line.opcode, asm_line.times_executed))
+
+    def set_line_coverage(self, source_code_block:
+                          BinaryParser.SourceCodeBlock, value: bool):
+        self._source_files[source_code_block.source_file]["lines"][
+            source_code_block.line_number]["covered"] = value
+
+    def set_function_coverage(self, function_block:
+                              Union[BinaryParser.FunctionBlock,
+                                    Tuple[str, str]], value: bool):
+        name, source_file = (function_block.name, function_block.source_file)\
+            if isinstance(function_block, BinaryParser.FunctionBlock) else \
+            function_block
+        self._source_files[source_file]["functions"][name]["covered"] = value
+
+    @property
+    def source_files(self):
+        return self._source_files
+
+
 class IntermediateCodeCoverage(object):
     """Class used to process the trace data along with the dwarf
     signature files to produce an intermediate layer in json with
@@ -570,7 +590,7 @@
         # 'elf_index'; {elf index}=>{assembly address}=>(opcode,
         # times executed),
         # 'functions': {function name}=>is covered(boolean)}
-        self.source_files_coverage = {}
+        self.coverage = CoverageHandler()
         self.functions = []
         # Unique set of elf list of files
         self.elf_map = {}
@@ -584,7 +604,6 @@
         This method writes the intermediate json file output linking
         the trace data and c source and assembly code.
         """
-        self.source_files_coverage = {}
         self.asm_lines = {}
         # Initialize for unknown elf files
         self.elf_custom = ELF_MAP["custom_offset"]
@@ -594,24 +613,14 @@
         for elf in self.elfs:
             # Gather information
             elf_name = elf['name']
-            # Trace data
+            # Obtain trace data
             self.traces_stats = load_stats_from_traces(elf['traces'])
-            functions_list = list_of_functions_for_binary(elf_name)
-            (functions_list, excluded_functions) = apply_functions_exclude(
-                elf, functions_list)
             # Produce code coverage
-            self.process_binary(elf_name, functions_list)
-            sources_config = self.config['parameters']['sources']
-            # Now check code coverage in the functions with no dwarf signature
-            # (sources)
-            nf = {f: functions_list[f] for f in
-                  functions_list if not
-                  functions_list[f]["sources"]}
-            self.process_fn_no_sources(nf)
+            self._process_binary(elf_name)
             # Write to the intermediate json file
-        data = {"source_files": self.source_files_coverage,
+        data = {"source_files": self.coverage.source_files,
                 "configuration": {
-                    "sources": sources_config,
+                    "sources": self.config['parameters']['sources'],
                     "metadata": "" if 'metadata' not in
                                       self.config['parameters'] else
                     self.config['parameters']['metadata'],
@@ -631,114 +640,119 @@
                 ELF_MAP["custom_offset"] += 1
         return self.elf_map[elf_name]
 
-    def process_binary(self, elf_filename: str, function_list):
+    def _process_binary(self, elf_filename: str) -> BinaryParser:
         """
         Process an elf file i.e. match the source code and asm lines against
         trace files (coverage).
 
         :param elf_filename: Elf binary file name
-        :param function_list: List of functions in the elf file i.e.
-                                [(address start, address end, function name)]
         """
         command = "%s -Sl %s | tee %s" % (OBJDUMP, elf_filename,
                                           elf_filename.replace(".elf", ".dump"))
         dump = os_command(command, show_command=True)
-        dump += "\n0 <null>:"  # For pattern matching the last function
+        # with open(elf_filename.replace(".elf", ".dump"), "r") as f:
+        #    dump = f.read()
+        dump += "\n\n"  # For pattern matching the last function
+        logger.info(f"Parsing assembly file {elf_filename}")
         elf_name = os.path.splitext(os.path.basename(elf_filename))[0]
-        function_line_numbers = FunctionLineNumbers(self.local_workspace)
         elf_index = self.get_elf_index(elf_name)
-        # Pointer to files dictionary
-        source_files = self.source_files_coverage
-        parser = BinaryParser(dump, function_list, self.workspace,
-                              self.remove_workspace, function_line_numbers)
+        parser = BinaryParser(dump, self.workspace, self.remove_workspace,
+                              self.local_workspace)
+        total_number_functions = 0
+        functions_covered = 0
         for function_block in parser.get_function_block():
-            function_list[function_block.name]["sources"] = True
-            source_files.setdefault(function_block.source_file,
-                                    {"functions": {},
-                                     "lines": {}})
-            source_files[function_block.source_file]["functions"][
-                function_block.name] = {"covered": False,
-                                        "line_number":
-                                            function_block.function_line_number}
-            is_function_block_covered = False
-            source_code_block: BinaryParser.SourceCodeBlock
+            total_number_functions += 1
+            # Function contains source code
+            self.coverage.add_function_coverage(function_block)
+            is_function_covered = False
             for source_code_block in parser.get_source_code_block(
                     function_block):
-                if parser.function_definition.function_name in function_list:
-                    function_list[parser.function_definition.function_name][
-                        "sources"] = True
-                parser.function_definition.update_sources(source_files,
-                                                          function_line_numbers)
-                source_file_ln = \
-                    source_files[parser.function_definition.source_file][
-                        "lines"].setdefault(source_code_block.line,
-                                            {"covered": False, "elf_index": {}})
-                for asm_block in source_code_block.get_assembly_line():
-                    times_executed = 0 if \
-                        asm_block.dec_address not in self.traces_stats else \
-                        self.traces_stats[asm_block.dec_address][0]
-                    if times_executed > 0:
-                        is_function_block_covered = True
-                        source_file_ln["covered"] = True
-                        source_files[parser.function_definition.source_file][
-                            "functions"][
-                            parser.function_definition.function_name][
-                            "covered"] = True
-                    source_file_ln.setdefault("elf_index", {'elf_index': {}})
-                    if elf_index not in source_file_ln["elf_index"]:
-                        source_file_ln["elf_index"][elf_index] = {}
-                    if asm_block.dec_address not in \
-                            source_file_ln["elf_index"][elf_index]:
-                        source_file_ln["elf_index"][elf_index][
-                            asm_block.dec_address] = (
-                            asm_block.opcode, times_executed)
-            source_files[function_block.source_file]["functions"][
-                function_block.name]["covered"] |= is_function_block_covered
+                self.coverage.add_line_coverage(source_code_block)
+                is_line_covered = False
+                for asm_line in parser.get_asm_line(source_code_block,
+                                                    self.traces_stats):
+                    # Here it is checked the line coverage
+                    is_line_covered = asm_line.times_executed > 0 or \
+                                      is_line_covered
+                    self.coverage.add_asm_line(source_code_block, asm_line,
+                                               elf_index)
+                logger.debug(f"Source file {source_code_block} is "
+                             f"{'' if is_line_covered else 'not '}covered")
+                if is_line_covered:
+                    self.coverage.set_line_coverage(source_code_block, True)
+                    is_function_covered = True
+            logger.debug(f"\tFunction '{function_block.name}' at '"
+                         f"{function_block.source_file} is "
+                         f"{'' if is_function_covered else 'not '}covered")
+            if is_function_covered:
+                self.coverage.set_function_coverage(function_block, True)
+                functions_covered += 1
+        logger.info(f"Total functions: {total_number_functions}, Functions "
+                    f"covered:{functions_covered}")
+        # Now check code coverage in the functions with no dwarf signature
+        self._process_fn_no_sources(parser)
+        return parser
 
-    def process_fn_no_sources(self, function_list):
+    def _process_fn_no_sources(self, parser: BinaryParser):
         """
         Checks function coverage for functions with no dwarf signature i.e
          sources.
 
-        :param function_list: Dictionary of functions to be checked
+        :param parser: Binary parser that contains objects needed
+        to check function line numbers including the dictionary of functions
+        to be checked i.e [start_dec_address]={'name', 'end_address'}
         """
         if not FUNCTION_LINES_ENABLED:
             return  # No source code at the workspace
-        address_seq = sorted(self.traces_stats.keys())
-        for function_name in function_list:
-            # Just check if the start address is in the trace logs
-            covered = function_list[function_name]["start"] in address_seq
-            # Find the source file
-            files = os_command(("grep --include *.c --include *.s -nrw '{}' {}"
-                                "| cut -d: -f1").format(function_name,
-                                                        self.local_workspace))
-            unique_files = set(files.split())
-            sources = []
-            line_number = 0
+        traces_addresses = sorted(self.traces_stats.keys())
+        traces_address_pointer = 0
+        _functions = parser.no_source_functions
+        functions_addresses = sorted(_functions.keys())
+        address_size = 4
+        for start_address in functions_addresses:
+            function_covered = False
+            function_name = _functions[start_address]['name']
+            # Get all files in the source code where the function is defined
+            source_files = os_command("grep --include '*.c' --include '*.s' "
+                                      "--include '*.S' -nrw '{}' {}"
+                                      "| cut -d: -f1".
+                                      format(function_name,
+                                             self.local_workspace))
+            unique_files = set(source_files.split())
+            sources_found = []
             for source_file in unique_files:
-                d = get_function_line_numbers(source_file)
-                if function_name in d:
-                    line_number = d[function_name]
-                    sources.append(source_file)
-            if len(sources) > 1:
-                logger.warning("'{}' declared in {} files:{}".format(
-                    function_name, len(sources),
-                    ", ".join(sources)))
-            elif len(sources) == 1:
-                source_file = remove_workspace(sources[0],
-                                               self.local_workspace)
-                if source_file not in self.source_files_coverage:
-                    self.source_files_coverage[source_file] = {"functions": {},
-                                                               "lines": {}}
-                if function_name not in \
-                        self.source_files_coverage[source_file]["functions"] \
-                        or covered:
-                    self.source_files_coverage[source_file]["functions"][
-                        function_name] = {"covered": covered,
-                                          "line_number": line_number}
+                line_number = parser.function_line_numbers.get_line_number(
+                    source_file, function_name)
+                if line_number > 0:
+                    sources_found.append((source_file, line_number))
+            if len(sources_found) == 0:
+                logger.debug(f"'{function_name}' not found in sources")
+            elif len(sources_found) > 1:
+                logger.warning(f"'{function_name}' declared in "
+                               f"{len(sources_found)} files")
             else:
-                logger.warning("Function '{}' not found in sources.".format(
-                    function_name))
+                source_file_found, function_line_number = sources_found[0]
+                function_source_file = remove_workspace(source_file_found,
+                                                        self.local_workspace)
+                self.coverage.add_function_coverage((function_name,
+                                                     function_source_file,
+                                                     function_line_number))
+                for in_function_address in \
+                        range(start_address,
+                              _functions[start_address]['end_address']
+                              + address_size, address_size):
+                    if in_function_address in traces_addresses[
+                                              traces_address_pointer:]:
+                        function_covered = True
+                        traces_address_pointer = traces_addresses.index(
+                            in_function_address) + 1
+                        break
+                logger.info(f"Added non-sources function '{function_name}' "
+                            f"with coverage: {function_covered}")
+                if function_covered:
+                    self.coverage.set_function_coverage((function_name,
+                                                         function_source_file),
+                                                        function_covered)
 
 
 json_conf_help = """
@@ -818,6 +832,7 @@
     except Exception as ex:
         print("Error at opening and processing JSON: {}".format(ex))
         return
+    print(json.dumps(config, indent=4))
     # Setting toolchain binary tools variables
     OBJDUMP = config['parameters']['objdump']
     READELF = config['parameters']['readelf']
@@ -841,10 +856,7 @@
 
 
 if __name__ == '__main__':
-    logging.basicConfig(filename='intermediate_layer.log', level=logging.DEBUG,
-                        format=('%(asctime)s %(levelname)s %(name)s '
-                                '%(message)s'))
-    logger = logging.getLogger(__name__)
+    logger = cc_logger.logger
     start_time = time.time()
     main()
     elapsed_time = time.time() - start_time