Example #1
0
    def __init__(self, coredump_path, build_output_path, processor, formatter):
        """
        @brief Initialise the specific information for a core: chipdata,
        debuginfo and the functions used in Interactive mode that use the
        specific information.
        The formatter is included because of the differences between the
        Automatic and Interactive modes.
        @param[in] self Pointer to the current object
        @param[in] coredump_path
        @param[in] build_output_path
        @param[in] processor
        @param[in] formatter
        """

        self.processor = processor
        if cu.global_options.live:
            if cu.global_options.kalcmd_object is not None:
                from ACAT.Core.LiveKalcmd import LiveKalcmd
                self.chipdata = LiveKalcmd(cu.global_options.kalcmd_object,
                                           self.processor)
            else:
                from ACAT.Core.LiveSpi import LiveSpi
                if self.processor == 0:
                    self.chipdata = LiveSpi(
                        cu.global_options.kal,
                        cu.global_options.spi_trans,
                        self.processor,
                        wait_for_proc_to_start=cu.global_options.
                        wait_for_proc_to_start)
                else:
                    self.chipdata = LiveSpi(cu.global_options.kal2,
                                            cu.global_options.spi_trans,
                                            self.processor,
                                            wait_for_proc_to_start=False)
        else:
            from ACAT.Core.Coredump import Coredump
            self.chipdata = Coredump(coredump_path, processor)

        if build_output_path == "":
            # Try to get the build path automatically. If this fails it will
            # throw an exception and we'll bail.
            build_output_path = get_build_output_path(self.chipdata)

            # search for the elf file name
            import glob
            elf_files = glob.glob(os.path.join(build_output_path, '*.elf'))
            # Filter out the "_external.elf" files generated by some _release
            # builds -- we want the corresponding internal one with full
            # symbols (which we assume must exist).
            elf_files = [
                elf_file for elf_file in elf_files
                if not elf_file.endswith("_external.elf")
            ]
            if len(elf_files) > 1:
                raise ct.Usage(
                    "ERROR: Multiple elf files in the build output, "
                    "don't know which to use.")
            # remove the .elf extension
            build_output_path = elf_files[0].replace(".elf", "")
            cu.global_options.build_output_path_p0 = build_output_path
            cu.global_options.build_output_path_p1 = build_output_path

        if processor == 0:
            if cu.global_options.kymera_p0 is None:
                self.debuginfo = DebugInformation(cu.global_options.ker)
                self.debuginfo.read_kymera_debuginfo(build_output_path)
                cu.global_options.kymera_p0 = self.debuginfo

                # If the two build are the same set the main build for the
                # other processor
                if cu.global_options.build_output_path_p0 == \
                        cu.global_options.build_output_path_p1:

                    cu.global_options.kymera_p1 = \
                        cu.global_options.kymera_p0
            else:
                self.debuginfo = cu.global_options.kymera_p0
        else:
            if cu.global_options.kymera_p1 is None:
                self.debuginfo = DebugInformation(cu.global_options.ker)
                self.debuginfo.read_kymera_debuginfo(build_output_path)
                cu.global_options.kymera_p1 = self.debuginfo

                # If the two build are the same set the main build for the
                # other processor
                if cu.global_options.build_output_path_p0 == \
                        cu.global_options.build_output_path_p1:

                    cu.global_options.kymera_p0 = \
                        cu.global_options.kymera_p1
            else:
                self.debuginfo = cu.global_options.kymera_p1

        # load the patches
        if cu.global_options.patch is not None:
            patches = load_boundle(cu.global_options.patch)
            self.debuginfo.update_patches(patches)

        # check if there are any bundles that needs reading
        if cu.global_options.bundle_paths is not None:
            # check if they were already read
            if cu.global_options.bundles is None:
                # if not than read all of them
                bundles_dictionary = {}
                for bundle_path in cu.global_options.bundle_paths:
                    bundles_dictionary.update(load_boundle(bundle_path))
                # and save it in global_options to avoid reading them multiple
                # times.
                cu.global_options.bundles = bundles_dictionary

            # set the bundle dictionary.
            self.debuginfo.update_bundles(cu.global_options.bundles)

        self.debuginfo.set_table(MappedTable(self.chipdata, self.debuginfo))

        # Set the debug info for the chipdata
        self.chipdata.set_debuginfo(self.debuginfo)

        self.formatter = formatter
        Functions.__init__(self, self.chipdata, self.debuginfo, self.formatter)

        self.available_analyses = {}
Example #2
0
    def get_reg_func(self, identifier):
        """
        @brief Like Analysis.get_reg_strict(), except it's not strict (!)
        'identifier' can be a register name, or address. If it's a name, we
        attempt to find the closest match in our list of registers.
        If more than one match is found, an AmbiguousSymbol exception is
        raised.

        Returns a DataSym.
        @param[in] self Pointer to the current object
        @param[in] identifier
        """

        reg = None  # Will be a ConstSym.

        # If the user supplied an address, and it smells like a register,
        # attempt to look it up.
        if isinstance(identifier, (int, long)):
            if Arch.get_dm_region(identifier) == "MMR":
                # Look for constants that have a value of the supplied address.
                # Inherently risky, since any random constant could have a value
                # that looks like a register address.
                # Since we only do this to set the name, it should be ok.
                possible_regs = [
                    item[0] for item in self.debuginfo.constants.items()
                    if item[1] == identifier
                ]
                if possible_regs:
                    reg_name = " or ".join(possible_regs)
                    reg = ct.ConstSym(reg_name, identifier)
        else:
            # Look up the supplied name in our list of constants.
            # If the name is found, reg.value is actually going to be the address
            # of the register.

            # get_constant might throw an AmbiguousSymbol exception here; in this
            # case we want to catch it, and weed out any matches that aren't
            # register names.
            try:
                if 'regfile' in identifier:
                    return self.chipdata.get_reg_strict(identifier)

                return self.chipdata.get_reg_strict('regfile_' + identifier)
            except KeyError:
                pass
            except BaseException:
                # This shoud be on UnknownRegister but is too hard to import
                pass

            try:
                reg = self.debuginfo.get_constant(identifier)
            except ct.AmbiguousSymbol as ambs:
                # We helpfully store the ambiguous matches in the exception
                # args
                ambiguous_matches = ambs.args[1]

                actual_ambiguous_matches = []
                for match in ambiguous_matches:
                    amconst = self.debuginfo.get_constant_strict(
                        match["name"], match["elf_id"])
                    if Arch.get_dm_region(amconst.value, False) == "MMR":
                        actual_ambiguous_matches.append(match)

                if not actual_ambiguous_matches:
                    # We actually ended up finding no real registers
                    reg = None
                else:
                    # If all the matches are aliases for each other, we can
                    # return that value. if they're different,
                    # admit our mistake.
                    val = self.debuginfo.get_constant_strict(
                        actual_ambiguous_matches[0]["name"],
                        actual_ambiguous_matches[0]["elf_id"])
                    # The first is always the same with the first.
                    success = True
                    # Skip the first which is used to check against.
                    for match in actual_ambiguous_matches[1:]:
                        try:
                            if val != self.debuginfo.get_constant_strict(
                                    match["name"], match["elf_id"]):
                                success = False
                                break
                        # Todo remevoe this if B-242063 is corrected.
                        except BaseException:
                            success = False
                            break

                    if success:
                        # We actually got only one register match - work with
                        # it.
                        reg = self.debuginfo.get_constant_strict(
                            actual_ambiguous_matches[0]["name"])
                    else:
                        apology = "Multiple potential matches found " + \
                            "for register name '" + identifier + "': "
                        raise ct.AmbiguousSymbol(apology,
                                                 actual_ambiguous_matches)
            try:
                if reg and (Arch.get_dm_region(reg.value) != "MMR"):
                    # Reg.value wasn't the address of a memory-mapped register; it
                    # was probably a random symbolic constant. Oh well.
                    return None
            except Arch.NotDmRegion:
                if reg.value == 0xfffffff:
                    # For Crescendo, it has been noticed that the register are
                    # being treated as constants with the value 0xfffffff.
                    # Furthermore, must strip the C and asm specific symbols
                    # for get_reg_strict().
                    try:
                        if '$_' in reg.name:
                            reg_name = reg.name[2:]
                            return self.chipdata.get_reg_strict(reg_name)
                        elif '$' in reg.name:
                            reg_name = reg.name[1:]
                            return self.chipdata.get_reg_strict(reg_name)
                    except BaseException:
                        return self.chipdata.get_reg_strict(reg.name)

        if reg is None:
            return None

        # If we get here, we found something.
        # There's a small chance we've got an unrelated constant, if its
        # value looks sufficiently like the address of a memory-mapped
        # register (e.g. $codec.stream_decode.FAST_AVERAGE_SHIFT_CONST).
        # Can't do much about that.

        # Look up the register contents.
        try:
            regcontents = self.chipdata.get_data(reg.value)
            fullreg = ct.DataSym(reg.name, reg.value, regcontents)
        except KeyError:
            # Reg.value wasn't the address of a register, for some reason.
            fullreg = None

        return fullreg
Example #3
0
    def get_var_func(self, identifier, elf_id=None, datalen=None):
        """
        @brief Like Analysis.get_var_strict(), except it's not strict (!)
        'identifier' can be a variable name, or address. If it's a name, we
        attempt to find the closest match in our list of variables.
        If more than one match is found, an AmbiguousSymbol exception is
        raised.

        In this case the user can also provide a data length; if it is set,
        we return a slice of data, 'datalen' addressable units long starting at
        the address specified by 'identifier'.
        Returns a Variable
        @param[in] self Pointer to the current object
        @param[in] identifier Could be name or address
        @param[in] elf_id The bundle elf id if the variable is in a
                            downloadable capability.
        @param[in] datalen If the identifier is an address the data length is
                            specified by this input.
        """
        # For Crescendo, data can only be fetched as words. Since it is
        # octet-addressed, the addresses must be divisible with the number of
        # addresses per word (32 bit words - 4 octets, therefore addresses must
        # be divisble with 4).
        if isinstance(identifier, (int, long)):
            identifier = cu.get_correct_addr(identifier, Arch.addr_per_word)

        # Same as above. The lengths are measured in addressable units.
        if datalen is not None:
            datalen = cu.convert_byte_len_word(datalen, Arch.addr_per_word)
        # The following is necessary since we can't rely on variable
        # sizes. If a (say) register address was passed in here we will likely
        # match a variable entry for $flash.data24.__Limit.
        if isinstance(identifier, (int, long)) and \
                Arch.get_dm_region(identifier) == "MMR":
            return None

        # First, look up the variable in the debug information.
        # Even if the user supplied an address rather than a name, it's nice
        # if we can tell them which variable it might be part of.

        # Might throw an AmbiguousSymbol exception here; can't get that with
        # an address but can with a variable name.
        var = None
        try:
            var = self.debuginfo.get_var(identifier, elf_id)
        except ct.AmbiguousSymbol as amb:
            # Filter out matches of struct/array members, where their parent is
            # also in the list of matches.
            matches = amb.args[1]
            quarantine_list = []
            for match in matches:
                try:
                    mvar = self.debuginfo.get_var_strict(
                        match["name"], match["elf_id"])
                    if mvar.parent is not None and mvar.parent.name in matches:
                        # This is a struct/array member
                        quarantine_list.append(match)
                    else:
                        possible_parent = mvar
                except ValueError:
                    # Out of memory can be seen for asm memory reagions. Ignore
                    # them.
                    quarantine_list.append(match)

            # If the number of things in the quarantine list is EXACTLY ONE MORE
            # than the number of things in the matches list, then we probably
            # have found a single variable and all its members.
            if len(matches) == len(quarantine_list) + 1:
                var = possible_parent
            else:
                # Give up
                raise ct.AmbiguousSymbol(amb.args[0], amb.args[1])

        if var is None:
            return None

        # Don't necessarily want to modify the actual variable entry below*,
        # so maybe create a copy here.
        # * Why? Well var is just a reference to the original variable in the
        # debuginfo class - we ought not to change it frivolously, since it
        # could break some other analysis.
        # In this case, we don't want to permanently munge the name
        # just because we're doing a slice this time.
        ret_var = var
        if datalen:
            if isinstance(identifier, (int, long)):
                if var.address == identifier and var.size <= datalen:
                    ret_var = copy.deepcopy(var)
                    ret_var.name = "User-defined slice, part of: " + \
                        var.name + " ???"
                    # We want to get a slice of data, not just the variable
                    # entry.
                    ret_var.size = datalen
                    # If the identifier is a variable name, don't include any
                    # members we might have already inspected.
                    ret_var.members = None
                else:
                    ret_var = ct.Variable("???", identifier, datalen)
        else:
            # Mitigation: we can't rely on 'var' actually containing the
            # supplied address, due to the lack of size information (see
            # KerDebugInfo.py). So work around it here.
            if isinstance(identifier, (int, long)) and \
                    identifier >= (var.address + Arch.addr_per_word * var.size):
                # Just return the value at the given address.
                ret_var = ct.Variable(var.name + " ???", identifier, 1)

        # Now get the data value(s) from chipdata. Look in DM first, only
        # try const if we run out of options.
        try:
            ret_var.value = self.chipdata.get_data(ret_var.address,
                                                   ret_var.size)
        except ct.InvalidDmLength as oor:
            # Address was valid, but size was not.
            # oor.args[1] contains the last valid address in the supplied
            # range.
            valid_size = (oor.max_length - ret_var.address) + 1
            ret_var.value = self.chipdata.get_data(ret_var.address, valid_size)
            ret_var.size = valid_size
        except ct.InvalidDmAddress:
            # The address wasn't valid. Could be that this variable is
            # actually in dm const.
            try:
                ret_var.value = self.debuginfo.get_dm_const(
                    ret_var.address, ret_var.size)
            except ct.InvalidDmConstLength as oor:
                # Address was valid, but size was not.
                valid_size = oor.max_length - ret_var.address
                ret_var.value = self.debuginfo.get_dm_const(
                    ret_var.address, valid_size)
            except ct.InvalidDmConstAddress:
                # Ok we really are stuck. Return variable with a value of None.
                ret_var.value = self.debuginfo.get_kymera_debuginfo(
                ).debug_strings[ret_var.address]
                return ret_var

        # Need a way to work out whether we've already inspected this
        # variable, so we can avoid doing it more than once.
        # An inspection *should* result in a non-empty type_name string.
        # Also, don't inspect the slices. It would be bad.
        ret_var.members = []
        var_elf_id = self.debuginfo.table.get_elf_id_from_address(
            ret_var.address)
        if not var_elf_id:
            var_elf_id = self.debuginfo.get_kymera_debuginfo().elf_id

        self.debuginfo.inspect_var(ret_var, var_elf_id)
        return ret_var
Example #4
0
    def get_data(self, address, length=0, ignore_overflow=False):
        """
        @brief Returns the contents of one or more addresses.
        This allows you to grab a chunk of memory, e.g. get_data(0x600, 50).
        Addresses can be from any valid DM region (DM1, PM RAM, mapped NVMEM,
        memory-mapped registers, etc.)

        Note:
         The length supplied is measured in addressable units.

         get_data(addr) will return a single value;
         get_data(addr, 1) will return a list with a single member.
         get_data(addr, 10) will return a list with ten members or a list with
         three members (when the memory is octet addressed, 32bit words).

        If the address is out of range, a KeyError exception will be raised.
        If the address is valid but the length is not (i.e. address+length
        is not a valid address) an OutOfRange exception will be raised.

        Note that reading PM RAM via DM is not supported (since not all chips
        map PM into DM). Use get_data_pm() instead.
        @param[in] self Pointer to the current object
        @param[in] address
        @param[in] length = 0
        @param[in] ignore_overflow Ignore if the read goes out from the memory
            region and append zeros. This is useful if an union is at the end
            of the memory region.
        """
        # We helpfully accept address as either a hex string or an integer
        try:
            # Try to convert address from a hex string to an integer
            address = int(address, 16)
        except TypeError:
            # Probably means address was already an integer
            pass
        address = cu.get_correct_addr(address, Arch.addr_per_word)

        if length == 0:
            mem_region = Arch.get_dm_region(address, False)
        else:
            # kalaccess will wrap the memory if the read goes out of boundary.
            # So to ignore overflow just get the memory region from the
            # beginning.
            if ignore_overflow:
                mem_region = Arch.get_dm_region(address)
            else:
                mem_region = Arch.get_dm_region(
                    address +
                    cu.convert_byte_len_word(length, Arch.addr_per_word) -
                    Arch.addr_per_word)
        try:
            if ('DM' in mem_region) or ('SLT' in mem_region) or (
                    'MMR' in mem_region) or ('NVMEM' in mem_region) or (
                        'PMRAM' in mem_region) or ('MCU' in mem_region):
                if length == 0:
                    return self.dm[address:address + Arch.addr_per_word][0]

                return tuple([
                    int(x) for x in self.
                    dm[address:address +
                       cu.convert_byte_len_word(length, Arch.addr_per_word)]
                ])
            else:
                if length == 0:
                    raise KeyError(
                        "Key " + cu.hex(address + Arch.addr_per_word) +
                        " is not a valid DM address",
                        address + Arch.addr_per_word)

                raise ct.OutOfRange(
                    "Key " + cu.hex(address + cu.convert_byte_len_word(
                        length, Arch.addr_per_word) - Arch.addr_per_word) +
                    " is not a valid DM address", address +
                    cu.convert_byte_len_word(length, Arch.addr_per_word) -
                    Arch.addr_per_word)
        except Exception as exception:
            if "Transport failure (Unable to read)" in exception:
                sys.stderr.write(str(exception))
                raise ct.ChipNotPowered

            # Will also spit exceptions to log file, if set.
            sys.stderr.write(traceback.format_exc())

            raise exception
Example #5
0
    def get_pool(self, pool):
        """Gets a specific memory pool, split into blocks.

        Note:
            'pool' is the unique pool ID for a pool; if you want a pool in
            DM2 (e.g. L_Mem2Pool2) you'll need to know what that is (e.g.
            pool=14).

        Args:
            pool

        Returns:
            A nonstandard Variable containing individual blocks of the
            pool.
        """
        # Look up the debug information, unless we already have.
        if self._do_debuginfo_lookup:
            self._lookup_debuginfo()
            self._do_debuginfo_lookup = False

        # Get the pool control data.
        # Blocks all have a header, so work out the actual size of each block.
        block_size = self.pooldata[pool].get_member(
            'blockSizeWords').value + self.block_overhead_words

        # get total number of blocks
        blocks_total = self._get_blockcount(pool)

        if pool <= 2:
            poolname = "L_memPool{0:d}".format(pool)
        else:
            poolname = "L_mem2Pool{0:d}".format(pool % 3)

        pool_var = self.chipdata.get_var_strict(poolname)
        if pool_var.parent is not None and Arch.kal_arch == 4:
            pool_var = self.chipdata.get_var_strict(str(pool_var.parent.name))

        # Because we don't want to modify the real pool variable use deepcopy
        # for the values
        fake_pool_var = ct.Variable(name=pool_var.name,
                                    address=pool_var.address,
                                    size=pool_var.size,
                                    value=copy.deepcopy(pool_var.value),
                                    var_type=pool_var.type,
                                    debuginfo=None,
                                    members=[],
                                    parent=None)

        fake_pool_var.array_len = blocks_total

        # Change the Type field to reflect the fact that this fake var is a
        # list of blocks
        fake_pool_var.type_name = fake_pool_var.type_name + \
            '[' + str(block_size) + ']'

        for block_id in range(blocks_total):
            start_addr = fake_pool_var.value + block_size * \
                block_id * Arch.addr_per_word  # contiguous
            end_size = block_size * Arch.addr_per_word
            # Use deep copy to avoid changing the original values.
            block_var = ct.Variable(
                name=fake_pool_var.name + '[~' + str(block_id) + '~]',
                address=start_addr,
                size=block_size,
                value=self.chipdata.get_data(start_addr, end_size),
                var_type=None,
                debuginfo=None,
                members=None,
                parent=fake_pool_var)
            block_var.type_name = fake_pool_var.type_name
            block_var.pool = pool
            fake_pool_var.members.append(block_var)

        # If this is a live chip a subsequent analysis should read new values
        # from the chip. So set the flag so that debuginfo is looked up.
        if self.chipdata.is_volatile():
            self._do_debuginfo_lookup = True

        return fake_pool_var
Example #6
0
def parse_args(parameters):  # pylint: disable=too-many-branches,too-many-statements
    """
    @brief Function which parses the input parameters of ACAT.
    @param[in] parameters Input parameters.
    """
    import ACAT.Core.CoreTypes as ct
    # Assign the command-line options
    try:
        options, args = getopt.getopt(
            parameters, "b:c:w:k:l:t:hiImdp:s:a:j:q",
            ["help", "elf_check", "patch="]
        )
    except getopt.GetoptError as err:
        raise ct.Usage("ERROR: " + builtins.str(err))

    for option, value in options:
        if option in ("-h", "--help"):
            print(HELP_STRING)
            sys.exit()
        elif option == "-b":
            global_options.build_output_path_p0 = os.path.normcase(value)
            if global_options.build_output_path_p1 == "":
                # in case the dualcore image is using a single build output
                global_options.build_output_path_p1 = global_options.build_output_path_p0
        elif option == "-c":
            global_options.coredump_path = os.path.normcase(value)
            # Increase the cache validity internal because all the data is
            # static. 10 minutes will be probably enough to hold the data
            # for the whole automatic run.
            global_options.cache_validity_interval = 600.0
        elif option == "-w":
            global_options.html_path = os.path.normcase(value)
        elif option == "-k":
            if global_options.pythontools_path == "":
                kalimbalab_path = os.path.abspath(value)
                global_options.pythontools_path = kalimbalab_path + \
                    os.path.normcase("/pythontools")
            else:
                raise ct.Usage("'-k' option conflicting '-t' option")
        elif option == "-t":
            if global_options.pythontools_path == "":
                global_options.pythontools_path = os.path.abspath(value)
            else:
                raise ct.Usage("'-k' option conflicting '-t' option")
        elif option == "-i":
            global_options.interactive = True
        elif option == "-I":
            global_options.interactive = True
            global_options.use_ipython = True
        elif option == "-m":
            global_options.build_mismatch_allowed = True
        elif option == "-p":
            global_options.processor = int(value)
        elif option == "-s":
            global_options.live = True
            global_options.spi_trans = value
        elif option == "-d":
            if global_options.kalcmd_object is not None:
                raise ct.Usage("Kalcmd does not support dual core")
            else:
                global_options.is_dualcore = True
        elif option == "-l":
            if global_options.kalcmd_object is not None:
                raise ct.Usage("Kalcmd does not support dual core")
            else:
                global_options.is_dualcore = True
                global_options.build_output_path_p1 = os.path.normcase(value)
        elif option == "-a":
            # Internal option not advertised to users: -a <sim_object>
            # Kalcmd2 object used to connect to a simulation without
            # enabling debug mode.
            if global_options.is_dualcore:
                raise ct.Usage("Kalcmd does not support dual core")
            else:
                global_options.kalcmd_object = value
                global_options.live = True
        elif option == '-j':
            if global_options.bundle_paths is None:
                global_options.bundle_paths = []
            global_options.bundle_paths.append(value)
        elif option == '-q':
            # Thi flag is only used when ACAT is launched from QDME. When this
            # flag is set ACAT will wait until the chip is booted.
            global_options.wait_for_proc_to_start = True
        elif option == "--elf_check":
            global_options.dependency_check = True
        elif option == "--patch":
            global_options.patch = value
        else:
            raise ct.Usage("ERROR: Unhandled option")

    if args:
        raise ct.Usage("ERROR: Unknown option " + builtins.str(args))

    # Output error if we're missing anything essential.
    if (not global_options.live) and (global_options.coredump_path == ""):
        raise ct.Usage("ERROR: Path to coredump file not supplied")
    if not os.path.exists(global_options.pythontools_path):
        raise ct.Usage(
            "ERROR: Path to Pythontools %s invalid or not supplied" %
            global_options.pythontools_path)

    _import_pythontools()
Example #7
0
    def set_data(self, address, value):
        """
        @brief Sets the contents of one or more addresses.
        This allows you to write a list of values to a chunk of memory. Addresses
        can only be from any valid DM RAM or memory mapped register region.
        e.g. set_data(0x3e5d, [1 2 3])

        Note:
          set_data(address, [val]) writes address with a single value val
          set_data(address, [val1 ... valn]) writes the list of values to memory
            starting from address

        This function should only be implemented for live chips. And should not
        be called if the chip is not live.

        If the address is out of range, a KeyError exception will be raised.
        If the address is valid but the length is not (i.e. address+length
        is not a valid address) an OutOfRange exception will be raised.
        @param[in] self Pointer to the current object
        @param[in] address
        @param[in] value
        """
        # We helpfully accept address as either a hex string or an integer
        try:
            # Try to convert address from a hex string to an integer
            address = int(address, 16)
        except TypeError:
            # Probably means address was already an integer
            pass

        address = cu.get_correct_addr(address, Arch.addr_per_word)

        length = len(value)
        if length == 1:
            mem_region = Arch.get_dm_region(address)
        else:
            mem_region = Arch.get_dm_region(
                address +
                cu.convert_byte_len_word(length, Arch.addr_per_word) -
                Arch.addr_per_word)

        try:
            if ('DM' in mem_region) or ('MMR' in mem_region):
                self.dm[address] = value
            else:
                if length == 1:
                    raise KeyError(
                        "Key " + cu.hex(Arch.addr_per_word + address) +
                        " is not in a DM or PM region",
                        address + Arch.addr_per_word)
                else:
                    raise ct.OutOfRange(
                        "Key " + cu.hex(address + cu.convert_byte_len_word(
                            length, Arch.addr_per_word) - Arch.addr_per_word) +
                        " is not in a DM or PM region", address +
                        cu.convert_byte_len_word(length, Arch.addr_per_word) -
                        Arch.addr_per_word)
        except Exception as exception:
            if "Transport failure (Unable to read)" in exception:
                sys.stderr.write(str(exception))
                raise ct.ChipNotPowered
            else:
                raise exception
Example #8
0
    def run(self):
        """
        @brief This task calculates the total wake time using the
        NUM_RUN_CLKS hardware register. It reads the register value and
        re-reads it after the given time; it calculates the cycle count
        (MCPS = millions of cycles per second) using the following formula:
                delta * 2^register_shift
         MCPS = ------------------------
                         10^6

        The upper bound on MCPS is the CPU speed in MHz. During idle periods, the
        CPU will be in 'shallow sleep' (not clocked), during which time the
        NUM_RUN_CLKS register will not count up.

        If the CPU's speed is known, a wake-time percentage is also
        derived.

        @param[in] self Pointer to the current object
        """
        overflow_value = int(math.pow(2, self.register_size))
        total_overflow = 0

        # wait until we the measure event is set
        self._measure_event.wait()

        # read the initial value of the run clks and the start time.
        start_time = import_time.time()
        if Arch.addr_per_word != 4:
            start_clk = self.chipdata.get_reg_strict("$NUM_RUN_CLKS_MS").value
        else:
            start_clk = self.chipdata.get_reg_strict("$NUM_RUN_CLKS").value
        prev_clk = start_clk
        cur_clk = start_clk

        while self._measure_event.is_set():
            import_time.sleep(1)
            if Arch.addr_per_word != 4:
                cur_clk = self.chipdata.get_reg_strict(
                    "$NUM_RUN_CLKS_MS").value
            else:
                cur_clk = self.chipdata.get_reg_strict("$NUM_RUN_CLKS").value
            if cur_clk < prev_clk:
                self.formatter.output("Overflow detected")
                total_overflow += overflow_value
            prev_clk = cur_clk

        # read end time
        end_time = import_time.time()
        delta_time = (end_time - start_time)
        # print "delta_time (should be close to the given value)= ", delta_time

        # calculate the MCPS usage
        delta_clk = cur_clk + total_overflow - start_clk
        # print "delta_clk = ",  delta_clk

        mcps_usage = (float(delta_clk) * math.pow(2, self.register_shift)) / (
            delta_time * math.pow(10, 6))
        try:
            chip_cpu_speed_mhz = self.chipdata.get_var_strict(
                "$profiler.cpu_speed_mhz").value
            if chip_cpu_speed_mhz == 0:
                # invalid value. Raise exception to handle the error and
                # print error message.
                raise ct.DebugInfoNoVariable()
        except ct.DebugInfoNoVariable:
            # On CSRA681xx this is static and comes from Curator (see
            # SERVICE_ADVICE for CCP_FACTS_ABOUT_SUBSYSTEM IE).
            # On QCC3/5xxx it's more dynamic (and there's currently
            # no arrangement to communicate that to ACAT).
            self.formatter.output(
                "Can't determine current CPU speed from chip.\n"
                "CPU speed is an assumption!")
            chip_cpu_speed_mhz = Arch.chip_cpu_speed_mhz

        mcps_percent = (mcps_usage * 100) / chip_cpu_speed_mhz
        self.formatter.output(
            "Total MCPS used = %3.3f (CPU active for ~%6.3f%% @%dMHz)" %
            (mcps_usage, mcps_percent, chip_cpu_speed_mhz))

        self._mcps_usage = mcps_usage
        self._mcps_percent = mcps_percent
        self._chip_cpu_speed_mhz = chip_cpu_speed_mhz
Example #9
0
    def get_pool(self, pool):
        """
        @brief Get a specific memory pool, split into blocks.
        Returns a nonstandard Variable containing individual blocks of the
        pool.
        Note: 'pool' is the unique pool ID for a pool; if you want a pool in
        DM2 (e.g. L_Mem2Pool2) you'll need to know what that is (e.g. pool=14)
        @param[in] self Pointer to the current object
        @param[in] pool
        """
        # Look up the debug information, unless we already have.
        if self._do_debuginfo_lookup:
            self._lookup_debuginfo()
            self._do_debuginfo_lookup = False

        # Get the pool control data.
        # Blocks all have a header, so work out the actual size of each block.
        block_size = self.pooldata[pool].get_member(
            'blockSizeWords').value + self.block_overhead_words

        # Get initial value of numBlocksFree, i.e. total blocks in the pool
        blocks_total = self.debuginfo.get_dm_const(
            self.pooldata[pool].get_member('numBlocksFree').address, 0)
        # What the free ptr was initialised to: first block in the pool;
        # dualcore - first free block
        initial_free_ptr = self.p_pool[pool]

        # Get the pool contents (could be slow on a live chip).
        # This relies on a slightly complicated (but safe) trick: the pool variable
        # (e.g. 'L_MemPool5') has address <initial_free_ptr>, so 'get' that.
        pool_var = self.chipdata.get_var_strict(initial_free_ptr)
        if pool_var.parent is not None and Arch.kal_arch == 4:
            pool_var = self.chipdata.get_var_strict(str(pool_var.parent.name))

        # Because we don't want to modify the real pool variable use deepcopy
        # for the values
        fake_pool_var = ct.Variable(name=pool_var.name,
                                    address=pool_var.address,
                                    size=pool_var.size,
                                    value=copy.deepcopy(pool_var.value),
                                    var_type=pool_var.type,
                                    debuginfo=None,
                                    members=[],
                                    parent=None)
        fake_pool_var.array_len = blocks_total

        # Change the Type field to reflect the fact that this fake var is a
        # list of blocks
        fake_pool_var.type_name = fake_pool_var.type_name + \
            '[' + str(block_size) + ']'

        for block_id in range(blocks_total):
            start_addr = fake_pool_var.address + block_size * \
                block_id * Arch.addr_per_word  # contiguous
            # Use deep copy to avoid changing the original values.
            block_var = ct.Variable(
                name=fake_pool_var.name + '[~' + str(block_id) + '~]',
                address=start_addr,
                size=block_size,
                value=fake_pool_var.value[
                    (start_addr - fake_pool_var.address) /
                    Arch.addr_per_word:((start_addr - fake_pool_var.address) /
                                        Arch.addr_per_word + block_size)],
                var_type=None,
                debuginfo=None,
                members=None,
                parent=fake_pool_var)
            block_var.type_name = fake_pool_var.type_name
            block_var.pool = pool
            fake_pool_var.members.append(block_var)

        # If this is a live chip a subsequent analysis should read new values
        # from the chip. So set the flag so that debuginfo is looked up.
        if self.chipdata.is_volatile():
            self._do_debuginfo_lookup = True

        return fake_pool_var
Example #10
0
    def _alloc_blocks(self, heap_address, heap_size):

        magic_val = 0xabcd01
        alloc_info = []
        debug_info = []

        address = heap_address
        heap_data = self.chipdata.get_data(heap_address, heap_size)
        total = 0
        index = -Arch.addr_per_word
        # calculate the offset of the magic field
        testblock = self.chipdata.cast(address, 'mem_node')
        magic_var = testblock.get_member('u').get_member('magic')
        testblock_address = magic_var.address
        magic_offset = testblock_address - address

        # Search through the entire heap block, looking for allocated blocks
        # based on the presence of the magic word
        while True:
            try:
                index = index + Arch.addr_per_word * \
                    (heap_data[index / Arch.addr_per_word + 1:].index(magic_val) + 1)
                address = heap_address + index - magic_offset
                testblock = self.chipdata.cast(address, 'mem_node')
                magic = testblock.get_member('u').get_member('magic').value
                if magic != magic_val:
                    raise ct.AnalysisError(
                        "Magic word not found at expected offset.")

                length = testblock.get_member('length').value
                if (length > 0) and \
                        (address + length < heap_address + heap_size):
                    alloc_info.append(
                        "Allocated block size : {0:>4d} at address: {1:0>6x}".
                        format(length, address))
                    index = index + length
                    total = total + length
                    if self.pmalloc_debug_enabled:
                        file_address = testblock.get_member('file').value
                        line = testblock.get_member('line').value
                        if line == 0:
                            # 'file' is actually the value of rlink when we called malloc.
                            # We can look that up to work out roughly where the allocation
                            # took place.
                            owner = self.debuginfo.get_source_info(
                                file_address)
                            owner_hint = owner.src_file + \
                                ', near line ' + str(owner.line_number)
                            debug_info.append(
                                "Ptr: {0:0>6x} size: {1:>3d} allocated by: {2:s}"
                                .format(address, length, owner_hint))
                        else:
                            filename = self.debuginfo.read_const_string(
                                file_address)
                            debug_info.append(
                                "Ptr: {0:0>6x} size: {1:>3d} allocated by: {2:s}, line {3:d}"
                                .format(address, length, filename, line))
            except ValueError:
                break

        alloc_info.append("Total heap allocation : {0:>d}".format(total))

        return alloc_info, debug_info
Example #11
0
    def cast(self,
             addr,
             type_def,
             deref_type=False,
             section="DM",
             elf_id=None):
        """Casts an address to a variable.

        This method takes the address of some (malloc'd) memory and
        'casts' it to be a variable of type 'type_def'.

        Type can be either a type_def name, or a type ID. However, if the
        processor is patched and the type ID is given, providing `elf_id`
        is also mandatory.

        Often (especially in automatic analysis), we know the address of
        the memory but the typeId of its _pointer_. If 'type_def' is an ID
        and 'deref_type' is set to True, we will look up the type
        pointed-to, rather than the actual typeId supplied. (This isn't
        necessary if the type_def name is supplied directly; in that case
        we must assume that the address contains something of that
        type_def.).

        Args:
            addr
            type_def
            deref_type (bool, optional)
            section (str, optional)
            elf_id (int, optional)

        Returns:
            a Variable.
        """
        is_patched = len(self.debuginfo.debug_infos[None]) > 2
        if (is_patched and isinstance(type_def, numbers.Integral)
                and elf_id is None):
            # Type Definition ID may refer to a different structure when a
            # processor is patched.
            logger.error(
                "When `type_def` is given as an integer and there is a "
                "patch, `elf_id` is mandatory. Please provide one.")
            return

        # Look up type info, even if type is already a typeId (since it
        # could be a pointer, or a typedef, or 'volatile', etc.)
        (varname, typeid, ptr_typeid, _, _, varsize, type_elf_id,
         has_union) = self.debuginfo.get_type_info(type_def, elf_id)

        if typeid is None:
            raise ValueError("Type " + str(type_def) + " not found!")

        if deref_type:
            # All the stuff we just got is valid for the pointer, not the
            # pointed-to type. So dereference it.
            if ptr_typeid:
                (varname, typeid, ptr_typeid, _, _, varsize, type_elf_id,
                 has_union) = self.debuginfo.get_type_info(ptr_typeid, elf_id)
            else:
                raise ValueError("Type pointed to by " + str(type_def) +
                                 " not found!")

        # First check if flag 'PM' is present. This means special case that
        # casting is required for PM region
        if section == 'PM':
            pm_region = Arch.get_pm_region(addr)
            if pm_region != 'PMRAM':
                raise ValueError(
                    "Address " + hex(addr) +
                    " does not point to the PMRAM region - can't cast it!")
            var = ct.Variable(name=varname,
                              address=addr,
                              size=varsize,
                              value=None,
                              var_type=typeid)  # Our juicy payload
            var.value = self.get_data_pm(addr, varsize)

        # If PM flag is not given check that the memory is in DM RAM. If
        # it points into const (or the debug region) then there should
        # already be a variable at that address which can tell us
        # everything we need to know.
        # Do this now so that we can sanity-check the size of the variable
        # with the size we extracted from typeinfo.
        else:
            dm_region = Arch.get_dm_region(addr)
            if 'NVMEM' in dm_region or dm_region == 'DEBUG':
                constvar = self.get_var_strict(addr)
                if constvar.size != varsize:
                    raise ValueError(
                        "Address " + hex(addr) +
                        " points to NVMEM/DEBUG region - can't cast it!")
                return constvar
            elif dm_region == 'BAC':
                raise ValueError("Address " + hex(addr) +
                                 " points to the BAC region - can't cast it!")
            elif (dm_region not in ('DM1RAM', 'DM2RAM', 'DMSRAM')
                  and Arch.get_pm_region(addr) == 'PMROM'):
                raise ValueError("Address " + hex(addr) +
                                 " is a function pointer - can't cast it!")

            var = ct.Variable(varname, addr, varsize, None, typeid)
            # Ignore memory overflow if the structure has a unin.
            # Some parts of the union might fit in the memory.
            var.value = self.get_data(addr, varsize, ignore_overflow=has_union)

        # Don't add this (presumably malloc'd) variable to our store of
        # variables; it might become invalid if talking to a live chip.
        return self.debuginfo.inspect_var(var, type_elf_id)
Example #12
0
    def get_reg_strict(self, identifier):
        """Get the value of a register.

        Provided for the convenience of all Analysis modules, this method
        looks up the value of memory-mapped and processor registers.

        'identifier' can be a register name (which must be EXACT), or
        address.

        In order to make sure they are unique, the names processor registers
        must be preceded by 'REGFILE_', e.g. 'REGFILE_PC'.

        Args:
            identifier

        Returns:
            a DataSym containing the register details plus its value.
        """

        reg = None  # Will be a ConstSym.
        # If the caller supplied an address, and it smells like a register,
        # attempt to look it up.
        if isinstance(identifier, numbers.Integral):
            if Arch.get_dm_region(identifier) == "MMR":
                # Look for constants that have a value of the supplied
                # address.  Inherently risky, since any random constant
                # could have a value that looks like a register address.
                # Since we only do this to set the name, it should be ok
                # even in strict mode.
                constant_variables = self.debuginfo.get_constants_variables()
                possible_regs = [
                    item[0] for item in list(constant_variables.items())
                    if item[1] == identifier
                ]
                if possible_regs:
                    reg_name = " or ".join(possible_regs)
                    reg = ct.ConstSym(reg_name, identifier)
        elif re.search('regfile_', identifier.lower()) is not None:
            # Must be a processor register? Usually these are listed in
            # constants, so we could look up the address in the same way
            # as for memory-mapped registers below. But on some chips
            # (e.g. Gordon) they're not (in fact, they may not even be
            # mapped into DM at all), so we need to get them from chipdata
            # by name.
            val = self.get_proc_reg(identifier)
            return ct.DataSym(identifier, None, val)
        else:
            # Look up the supplied name in our list of constants.
            # If the name is found, reg.value is actually going to be the
            # address of the register.
            # get_constant_strict might throw an exception here; make no
            # effort to intercept it because we are being strict!
            if '?int64_lo' not in identifier and Arch.addr_per_word == 4:
                identifier = identifier + '?int64_lo'
                return self.get_reg_strict(identifier)
            else:
                reg = self.debuginfo.get_constant_strict(identifier)

        if reg is None or Arch.get_dm_region(reg.value) != "MMR":
            # The supplied identifier wasn't a unique name or valid address.
            if isinstance(identifier, int):
                errid = hex(identifier)
            else:
                errid = identifier
            raise ValueError("Identifier " + errid +
                             " is not valid in strict mode!")

        # Look up register contents.
        # This should be safe, since we know reg.value is valid (right??)
        regcontents = self.get_data(reg.value)
        return ct.DataSym(reg.name, reg.value, regcontents)
Example #13
0
    def _alloc_blocks(self, heap_address, heap_size):
        """
        @brief Reads and checks the allocated blocks.
        @param[in] self Pointer to the current object
        @param[in] heap_address The heap start address
        @param[in] heap_size The heap size.
        @param[out] Returns two lists. One for the ... The other for ...
        """
        magic_val = 0xabcd01
        alloc_info = []
        debug_info = []

        address = heap_address
        heap_data = self.chipdata.get_data(heap_address, heap_size)
        total = 0
        index = -Arch.addr_per_word
        # calculate the offset of the magic field
        testblock = self.chipdata.cast(address, 'mem_node')
        testblock_magic = testblock.get_member('u').get_member('magic')
        testblock_address = testblock_magic.address
        magic_offset = testblock_address - address

        # Search through the entire heap block, looking for allocated blocks
        # based on the presence of the magic word
        while True:
            try:
                index = index + Arch.addr_per_word * \
                    (heap_data[index / Arch.addr_per_word +
                               1:].index(magic_val) + 1)
                address = heap_address + index - magic_offset
                testblock = self.chipdata.cast(address, 'mem_node')
                magic = testblock.get_member('u').get_member('magic').value
                if magic != magic_val:
                    raise ct.AnalysisError(
                        "Magic word not found at expected offset.")

                length = testblock.get_member('length').value
                if (length > 0) and (address + length <
                                     heap_address + heap_size):
                    alloc_info.append(
                        "Allocated block size : {0:>4d} at address: {1:0>6x}".
                        format(length, address))
                    index = index + length
                    total = total + length
                    if self.pmalloc_debug_enabled:
                        file_address = testblock.get_member('file').value
                        line = testblock.get_member('line').value
                        if line == 0:
                            # 'file' is actually the value of rlink when we called malloc.
                            # We can look that up to work out roughly where the allocation
                            # took place.
                            try:
                                owner = self.debuginfo.get_source_info(
                                    file_address)
                                owner_hint = (owner.src_file + ', near line ' +
                                              str(owner.line_number))
                            except ct.BundleMissingError:
                                owner_hint = ("No source information." +
                                              "Bundle is missing.")
                            debug_info.append(
                                "Ptr: {0:0>6x} size: {1:>3d} allocated by: "
                                "{2:s}".format(address, length, owner_hint))
                        else:
                            try:
                                filename = self.debuginfo.read_const_string(
                                    file_address)
                            except KeyError:
                                filename = "Filename cannot be read!"
                            except ct.BundleMissingError:
                                filename = ("Filename cannot be read! " +
                                            "Bundle is missing.")
                            debug_info.append(
                                "Ptr: {0:0>6x} size: {1:>3d} allocated by: "
                                "{2:s}, line {3:d}".format(
                                    address, length, filename, line))
            except ValueError:
                break

        alloc_info.append("Total heap allocation : {0:>d}".format(total))

        return alloc_info, debug_info
Example #14
0
    def cast(self,
             addr,
             type_def,
             deref_type=False,
             section="DM",
             elf_id=None):
        """
        @brief This method takes the address of some (malloc'd) memory and 'casts'
        it to be a variable of type 'type_def'.
        Type can be either a type_def name, or a typeId (if you already happen
        to know that).
        Often (especially in automatic analysis), we know the address of the
        memory but the typeId of its _pointer_. If 'type_def' is an ID and
        'deref_type' is set to True, we will look up the type pointed-to,
        rather than the actual typeId supplied. (This isn't necessary if the
        type_def name is supplied directly; in that case we must assume that the
        address contains something of that type_def.)

        Returns a Variable.
        @param[in] self Pointer to the current object
        @param[in] addr
        @param[in] type_def
        @param[in] deref_type = False
        @param[in] section = 'DM'
        @param[in] elf_id = None
        @param[out] variable
        """
        # Look up type info, even if type is already a typeId (since it could be a pointer,
        # or a typedef, or 'volatile', etc.)
        (varname, typeid, ptr_typeid, _, _, varsize, type_elf_id, has_union) = \
            self.debuginfo.get_type_info(type_def, elf_id)

        if typeid is None:
            raise ValueError("Type " + str(type_def) + " not found!")

        if deref_type:
            # All the stuff we just got is valid for the pointer, not the
            # pointed-to type. So dereference it.
            if ptr_typeid:
                (varname, typeid, ptr_typeid, _, _, varsize, type_elf_id, has_union) = \
                    self.debuginfo.get_type_info(ptr_typeid, elf_id)
            else:
                raise ValueError("Type pointed to by " + str(type_def) +
                                 " not found!")

        # First check if flag 'PM' is present. This means special case that
        # casting is required for PM region
        if section == 'PM':
            pm_region = Arch.get_pm_region(addr)
            if pm_region != 'PMRAM':
                raise ValueError(
                    "Address " + cu.hex(addr) +
                    " does not point to the PMRAM region - can't cast it!")
            var = ct.Variable(name=varname,
                              address=addr,
                              size=varsize,
                              value=None,
                              var_type=typeid)  # Our juicy payload
            var.value = self.get_data_pm(addr, varsize)

        # If PM flag is not given check that the memory is in DM RAM. If it points into const
        # (or the debug region) then there should already be a variable at that
        # address which can tell us everything we need to know.
        # Do this now so that we can sanity-check the size of the variable with
        # the size we extracted from typeinfo.
        else:
            dm_region = Arch.get_dm_region(addr)
            if 'NVMEM' in dm_region or dm_region == 'DEBUG':
                constvar = self.get_var_strict(addr)
                if constvar.size != varsize:
                    raise ValueError(
                        "Address " + cu.hex(addr) +
                        " points to NVMEM/DEBUG region - can't cast it!")
                return constvar
            elif dm_region == 'BAC':
                raise ValueError("Address " + cu.hex(addr) +
                                 " points to the BAC region - can't cast it!")
            elif dm_region != 'DM1RAM' and \
                dm_region != 'DM2RAM' and \
                Arch.get_pm_region(addr) == 'PMROM':
                raise ValueError("Address " + cu.hex(addr) +
                                 " is a function pointer - can't cast it!")

            var = ct.Variable(varname, addr, varsize, None, typeid)
            # Ignore memory overflow if the structure has a unin.
            # Some parts of the union might fit in the memory.
            var.value = self.get_data(addr, varsize, ignore_overflow=has_union)

        # Don't add this (presumably malloc'd) variable to our store of variables;
        # it might become invalid if talking to a live chip.
        return self.debuginfo.inspect_var(var, type_elf_id)