Before processing a CFGJob.
        Right now each block is traced at most once. If it is traced more than once, we will mark it as "should_skip"
        before tracing it.
        An AngrForwardAnalysisSkipJob exception is raised in order to skip analyzing the job.

        :param CFGJob job: The CFG job object.
        :param dict _locals: A bunch of local variables that will be kept around when handling this job and its
                        corresponding successors.
        :return: None

def _pre_job_handling(self, job):  # pylint:disable=arguments-differ
        """
        Before processing a CFGJob.
        Right now each block is traced at most once. If it is traced more than once, we will mark it as "should_skip"
        before tracing it.
        An AngrForwardAnalysisSkipJob exception is raised in order to skip analyzing the job.

        :param CFGJob job: The CFG job object.
        :param dict _locals: A bunch of local variables that will be kept around when handling this job and its
                        corresponding successors.
        :return: None
        """

        # Extract initial info the CFGJob
        job.call_stack_suffix = job.get_call_stack_suffix()
        job.current_function = self.kb.functions.function(job.func_addr, create=True,
                                                          syscall=job.jumpkind.startswith("Ijk_Sys"))
        src_block_id = job.src_block_id
        src_exit_stmt_idx = job.src_exit_stmt_idx
        src_ins_addr = job.src_ins_addr
        addr = job.addr

        # Log this address
        if l.level == logging.DEBUG:
            self._analyzed_addrs.add(addr)

        if addr == job.func_addr:
            # Store the input state of this function
            self._function_input_states[job.func_addr] = job.state

        # deregister this job
        self._deregister_analysis_job(job.func_addr, job)

        # Generate a unique key for this job
        block_id = job.block_id

        # Should we skip tracing this block?
        should_skip = False
        if self._traced_addrs[job.call_stack_suffix][addr] >= self._max_iterations:
            should_skip = True
        elif self._is_call_jumpkind(job.jumpkind) and \
             self._call_depth is not None and \
             len(job.call_stack) > self._call_depth and \
             (self._call_tracing_filter is None or self._call_tracing_filter(job.state, job.jumpkind)):
            should_skip = True

        # SimInspect breakpoints support
        job.state._inspect('cfg_handle_job', BP_BEFORE)

        # Get a SimSuccessors out of current job
        sim_successors, exception_info, _ = self._get_simsuccessors(addr, job, current_function_addr=job.func_addr)

        # determine the depth of this basic block
        if self._max_steps is None:
            # it's unnecessary to track depth when we are not limiting max_steps
            depth = None
        else:
            if src_block_id is None:
                # oh this is the very first basic block on this path
                depth = 0
            else:
                src_cfgnode = self._nodes[src_block_id]
                depth = src_cfgnode.depth + 1
                # the depth will not be updated later on even if this block has a greater depth on another path.
                # consequently, the `max_steps` limit is not veyr precise - I didn't see a need to make it precise
                # though.

        if block_id not in self._nodes:
            # Create the CFGNode object
            cfg_node = self._create_cfgnode(sim_successors, job.call_stack, job.func_addr, block_id=block_id,
                                            depth=depth
                                            )

            self._nodes[block_id] = cfg_node
            self._nodes_by_addr[cfg_node.addr].append(cfg_node)

        else:
            # each block_id should only correspond to one CFGNode
            # use the existing CFGNode object
            # note that since we reuse existing CFGNodes, we may miss the following cases:
            #
            # mov eax, label_0       mov ebx, label_1
            #     jmp xxx                 jmp xxx
            #         |                      |
            #         |                      |
            #         *----------------------*
            #                     |
            #                 call eax
            #
            # Since the basic block "call eax" will only be traced once, either label_0 or label_1 will be missed in
            # this case. Indirect jump resolution might be able to get it, but that's another story. Ideally,
            # "call eax" should be traced twice with *different* sim_successors keys, which requires block ID being flow
            # sensitive, but it is way too expensive.

            cfg_node = self._nodes[block_id]

        # Increment tracing count for this block
        self._traced_addrs[job.call_stack_suffix][addr] += 1

        if self._keep_state:
            # TODO: if we are reusing an existing CFGNode, we will be overwriting the original input state here. we
            # TODO: should save them all, which, unfortunately, requires some redesigning :-(
            cfg_node.input_state = sim_successors.initial_state

        # See if this job cancels another FakeRet
        # This should be done regardless of whether this job should be skipped or not, otherwise edges will go missing
        # in the CFG or function transiton graphs.
        if job.jumpkind == 'Ijk_FakeRet' or \
                (job.jumpkind == 'Ijk_Ret' and block_id in self._pending_jobs):
            # The fake ret is confirmed (since we are returning from the function it calls). Create an edge for it
            # in the graph.

            if block_id in self._pending_jobs:
                the_job = self._pending_jobs.pop(block_id)  # type: PendingJob
                self._deregister_analysis_job(the_job.caller_func_addr, the_job)
            else:
                the_job = job

            self._graph_add_edge(the_job.src_block_id, block_id,
                                 jumpkind='Ijk_FakeRet',
                                 stmt_idx=the_job.src_exit_stmt_idx,
                                 ins_addr=src_ins_addr
                                 )
            self._update_function_transition_graph(the_job.src_block_id, block_id,
                                                   jumpkind='Ijk_FakeRet',
                                                   ins_addr=src_ins_addr,
                                                   stmt_idx=the_job.src_exit_stmt_idx,
                                                   confirmed=True
                                                   )

        if sim_successors is None or should_skip:
            # We cannot retrieve the block, or we should skip the analysis of this node
            # But we create the edge anyway. If the sim_successors does not exist, it will be an edge from the previous
            # node to a PathTerminator
            self._graph_add_edge(src_block_id, block_id,
                                 jumpkind=job.jumpkind,
                                 stmt_idx=src_exit_stmt_idx,
                                 ins_addr=src_ins_addr
                                 )
            self._update_function_transition_graph(src_block_id, block_id,
                                                   jumpkind=job.jumpkind,
                                                   ins_addr=src_ins_addr,
                                                   stmt_idx=src_exit_stmt_idx)

            # We are good. Raise the exception and leave
            raise AngrSkipJobNotice()

        self._update_thumb_addrs(sim_successors, job.state)

        # We store the function hints first. Function hints will be checked at the end of the analysis to avoid
        # any duplication with existing jumping targets
        if self._enable_function_hints:
            if sim_successors.sort == 'IRSB' and sim_successors.all_successors:
                function_hints = self._search_for_function_hints(sim_successors.all_successors[0])
                for f in function_hints:
                    self._pending_function_hints.add(f)

        self._graph_add_edge(src_block_id, block_id, jumpkind=job.jumpkind, stmt_idx=src_exit_stmt_idx,
                             ins_addr=src_ins_addr)
        self._update_function_transition_graph(src_block_id, block_id, jumpkind=job.jumpkind,
                                               ins_addr=src_ins_addr, stmt_idx=src_exit_stmt_idx)

        if block_id in self._pending_edges:
            # there are some edges waiting to be created. do it here.
            for src_key, dst_key, data in self._pending_edges[block_id]:
                self._graph_add_edge(src_key, dst_key, **data)
            del self._pending_edges[block_id]

        block_info = {reg: sim_successors.initial_state.registers.load(reg) for reg in self.project.arch.persistent_regs}
        self._block_artifacts[addr] = block_info

        job.cfg_node = cfg_node
        job.sim_successors = sim_successors
        job.exception_info = exception_info

        # For debugging purposes!
        job.successor_status = {}