## 3 Concepts and Methods
### Subgraph Association and Seed Mapping  
Existing fuzzers cannot directly utilize the callgraph of the target program to guide the fuzzing process. However, coverage-guided grey-box fuzzers typically drive the fuzzing process by guiding seed selection. In general, when a seed is mutated to generate testcases, the resulting execution traces are highly likely to be the same as or closely related to the original seed. By dividing the closely related callgraph subgraphs of the program into the same subtask, the testcases generated by mutating seeds belonging to a specific subtask are highly likely to also belong to that subtask. Based on the key observations above, this article proposes a method to map the callgraph-based task division to the division of the seed corpus to introduce task division into the ensemble fuzzing framework. The global seed corpus was divided into subcorpora, each corresponding to a subtask, by considering the portion of each seed's execution trace that belongs to the sub-callgraph of the corresponding subtask. Subsequently, each subcorpus was assigned to a different fuzzer to guide focused fuzzing on different spaces of the target program. Specifically, after obtaining the callgraph with subtask division information, all seeds in the global seed corpus were used as the input to execute the tested program sequentially, and for each seed, the total weight of functions belonging to each subtask along its execution trace was tallied up. Then, the seed was assigned to the subset of the global seed corpus corresponding to the subtask with the highest weight sum. Taking one seed from the global seed corpus as an example, Figure 6 illustrates the method of determining which specific subtask a given seed belongs to. Firstly, the seed was used as the input to execute the target program, and the execution trace was marked on the callgraph. In this specific example, the execution trace corresponding to this seed is \"Main\"\(\rightarrow\)\"Func 2\"\(\rightarrow\)\"Func 8\", as shown in the red path on the callgraph. Next, the execution trace corresponding to this seed was extracted and each function in the trace was labeled with the subtask ID it belongs to and its weight. Specifically, the first function in the execution trace was \"Main\", which belongs to Subtask 1 with a weight of 3; the second and third functions in the trace were \"Func 2\" and \"Func 8\", which belong to Subtask 2 with weights of 5 and 3, respectively. After that, the total weight of each subtask corresponding to the execution path corresponding to this seed was counted. For this specific seed, the weight for Subtask 1 was just the weight of the \"Main\" function, which is 3; the weight for Subtask 2 was the sum of the weights of \"Func 2\" and \"Func 8\", which is 8. The trace did not pass through any functions belonging to Subtask 3 or Subtask 4, so its weights for these two subtasks are both 0. Finally, the seed was assigned to the subtask with the highest total weight, which in this example is Subtask 2 with a weight of 8. When using the method mentioned above to divide the global seed corpus, there is a problem that the subsets of the global seed corpus corresponding to some of the subtasks can be empty. In this scenario, if a fuzzer is assigned to a subcorpus that does not contain any seeds, the fuzzing process cannot even be initiated. Therefore, our approach must ensure that each subcorpus has a sufficient number of seeds to fit the minimum requirement. Specifically, when dividing the seed corpus, a two-dimensional array is used to record the weights and the top k seeds for each subtask (the value of k is dynamic; when the total number of seeds is less than 100, k takes the value of the total number of seeds divided by 10 and rounded down, otherwise k takes the value of 10). After all of the seeds in the global seed corpus have been assigned, these seeds will be additionally copied to the corresponding subset of the global seed corpus.