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 = {}
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
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
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
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
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()
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
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
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
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
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)
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)
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
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)