| // Copyright (c) 2019 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "source/fuzz/fuzzer.h" |
| |
| #include <cassert> |
| #include <memory> |
| #include <numeric> |
| #include <sstream> |
| |
| #include "source/fuzz/fact_manager/fact_manager.h" |
| #include "source/fuzz/fuzzer_context.h" |
| #include "source/fuzz/fuzzer_pass_add_access_chains.h" |
| #include "source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h" |
| #include "source/fuzz/fuzzer_pass_add_composite_extract.h" |
| #include "source/fuzz/fuzzer_pass_add_composite_inserts.h" |
| #include "source/fuzz/fuzzer_pass_add_composite_types.h" |
| #include "source/fuzz/fuzzer_pass_add_copy_memory.h" |
| #include "source/fuzz/fuzzer_pass_add_dead_blocks.h" |
| #include "source/fuzz/fuzzer_pass_add_dead_breaks.h" |
| #include "source/fuzz/fuzzer_pass_add_dead_continues.h" |
| #include "source/fuzz/fuzzer_pass_add_equation_instructions.h" |
| #include "source/fuzz/fuzzer_pass_add_function_calls.h" |
| #include "source/fuzz/fuzzer_pass_add_global_variables.h" |
| #include "source/fuzz/fuzzer_pass_add_image_sample_unused_components.h" |
| #include "source/fuzz/fuzzer_pass_add_loads.h" |
| #include "source/fuzz/fuzzer_pass_add_local_variables.h" |
| #include "source/fuzz/fuzzer_pass_add_loop_preheaders.h" |
| #include "source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h" |
| #include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h" |
| #include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h" |
| #include "source/fuzz/fuzzer_pass_add_parameters.h" |
| #include "source/fuzz/fuzzer_pass_add_relaxed_decorations.h" |
| #include "source/fuzz/fuzzer_pass_add_stores.h" |
| #include "source/fuzz/fuzzer_pass_add_synonyms.h" |
| #include "source/fuzz/fuzzer_pass_add_vector_shuffle_instructions.h" |
| #include "source/fuzz/fuzzer_pass_adjust_branch_weights.h" |
| #include "source/fuzz/fuzzer_pass_adjust_function_controls.h" |
| #include "source/fuzz/fuzzer_pass_adjust_loop_controls.h" |
| #include "source/fuzz/fuzzer_pass_adjust_memory_operands_masks.h" |
| #include "source/fuzz/fuzzer_pass_adjust_selection_controls.h" |
| #include "source/fuzz/fuzzer_pass_apply_id_synonyms.h" |
| #include "source/fuzz/fuzzer_pass_construct_composites.h" |
| #include "source/fuzz/fuzzer_pass_copy_objects.h" |
| #include "source/fuzz/fuzzer_pass_donate_modules.h" |
| #include "source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h" |
| #include "source/fuzz/fuzzer_pass_expand_vector_reductions.h" |
| #include "source/fuzz/fuzzer_pass_flatten_conditional_branches.h" |
| #include "source/fuzz/fuzzer_pass_inline_functions.h" |
| #include "source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h" |
| #include "source/fuzz/fuzzer_pass_interchange_zero_like_constants.h" |
| #include "source/fuzz/fuzzer_pass_invert_comparison_operators.h" |
| #include "source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h" |
| #include "source/fuzz/fuzzer_pass_merge_blocks.h" |
| #include "source/fuzz/fuzzer_pass_merge_function_returns.h" |
| #include "source/fuzz/fuzzer_pass_mutate_pointers.h" |
| #include "source/fuzz/fuzzer_pass_obfuscate_constants.h" |
| #include "source/fuzz/fuzzer_pass_outline_functions.h" |
| #include "source/fuzz/fuzzer_pass_permute_blocks.h" |
| #include "source/fuzz/fuzzer_pass_permute_function_parameters.h" |
| #include "source/fuzz/fuzzer_pass_permute_instructions.h" |
| #include "source/fuzz/fuzzer_pass_permute_phi_operands.h" |
| #include "source/fuzz/fuzzer_pass_propagate_instructions_down.h" |
| #include "source/fuzz/fuzzer_pass_propagate_instructions_up.h" |
| #include "source/fuzz/fuzzer_pass_push_ids_through_variables.h" |
| #include "source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h" |
| #include "source/fuzz/fuzzer_pass_replace_branches_from_dead_blocks_with_exits.h" |
| #include "source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h" |
| #include "source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h" |
| #include "source/fuzz/fuzzer_pass_replace_irrelevant_ids.h" |
| #include "source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h" |
| #include "source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h" |
| #include "source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h" |
| #include "source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h" |
| #include "source/fuzz/fuzzer_pass_replace_parameter_with_global.h" |
| #include "source/fuzz/fuzzer_pass_replace_params_with_struct.h" |
| #include "source/fuzz/fuzzer_pass_split_blocks.h" |
| #include "source/fuzz/fuzzer_pass_swap_commutable_operands.h" |
| #include "source/fuzz/fuzzer_pass_swap_conditional_branch_operands.h" |
| #include "source/fuzz/fuzzer_pass_toggle_access_chain_instruction.h" |
| #include "source/fuzz/fuzzer_pass_wrap_regions_in_selections.h" |
| #include "source/fuzz/pass_management/repeated_pass_manager.h" |
| #include "source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h" |
| #include "source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h" |
| #include "source/fuzz/pass_management/repeated_pass_manager_simple.h" |
| #include "source/fuzz/pass_management/repeated_pass_recommender_standard.h" |
| #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" |
| #include "source/fuzz/transformation_context.h" |
| #include "source/opt/build_module.h" |
| #include "source/spirv_fuzzer_options.h" |
| #include "source/util/make_unique.h" |
| |
| namespace spvtools { |
| namespace fuzz { |
| |
| namespace { |
| const uint32_t kIdBoundGap = 100; |
| |
| } // namespace |
| |
| Fuzzer::Fuzzer(spv_target_env target_env, MessageConsumer consumer, |
| const std::vector<uint32_t>& binary_in, |
| const protobufs::FactSequence& initial_facts, |
| const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers, |
| std::unique_ptr<RandomGenerator> random_generator, |
| bool enable_all_passes, |
| RepeatedPassStrategy repeated_pass_strategy, |
| bool validate_after_each_fuzzer_pass, |
| spv_validator_options validator_options) |
| : target_env_(target_env), |
| consumer_(std::move(consumer)), |
| binary_in_(binary_in), |
| initial_facts_(initial_facts), |
| donor_suppliers_(donor_suppliers), |
| random_generator_(std::move(random_generator)), |
| enable_all_passes_(enable_all_passes), |
| repeated_pass_strategy_(repeated_pass_strategy), |
| validate_after_each_fuzzer_pass_(validate_after_each_fuzzer_pass), |
| validator_options_(validator_options), |
| num_repeated_passes_applied_(0), |
| ir_context_(nullptr), |
| fuzzer_context_(nullptr), |
| transformation_context_(nullptr), |
| transformation_sequence_out_() {} |
| |
| Fuzzer::~Fuzzer() = default; |
| |
| template <typename FuzzerPassT, typename... Args> |
| void Fuzzer::MaybeAddRepeatedPass(uint32_t percentage_chance_of_adding_pass, |
| RepeatedPassInstances* pass_instances, |
| Args&&... extra_args) { |
| if (enable_all_passes_ || |
| fuzzer_context_->ChoosePercentage(percentage_chance_of_adding_pass)) { |
| pass_instances->SetPass(MakeUnique<FuzzerPassT>( |
| ir_context_.get(), transformation_context_.get(), fuzzer_context_.get(), |
| &transformation_sequence_out_, std::forward<Args>(extra_args)...)); |
| } |
| } |
| |
| template <typename FuzzerPassT, typename... Args> |
| void Fuzzer::MaybeAddFinalPass(std::vector<std::unique_ptr<FuzzerPass>>* passes, |
| Args&&... extra_args) { |
| if (enable_all_passes_ || fuzzer_context_->ChooseEven()) { |
| passes->push_back(MakeUnique<FuzzerPassT>( |
| ir_context_.get(), transformation_context_.get(), fuzzer_context_.get(), |
| &transformation_sequence_out_, std::forward<Args>(extra_args)...)); |
| } |
| } |
| |
| bool Fuzzer::ApplyPassAndCheckValidity(FuzzerPass* pass) const { |
| pass->Apply(); |
| return !validate_after_each_fuzzer_pass_ || |
| fuzzerutil::IsValidAndWellFormed(ir_context_.get(), validator_options_, |
| consumer_); |
| } |
| |
| Fuzzer::FuzzerResult Fuzzer::Run() { |
| // Check compatibility between the library version being linked with and the |
| // header files being used. |
| GOOGLE_PROTOBUF_VERIFY_VERSION; |
| |
| assert(ir_context_ == nullptr && fuzzer_context_ == nullptr && |
| transformation_context_ == nullptr && |
| transformation_sequence_out_.transformation_size() == 0 && |
| "'Run' must not be invoked more than once."); |
| |
| spvtools::SpirvTools tools(target_env_); |
| tools.SetMessageConsumer(consumer_); |
| if (!tools.IsValid()) { |
| consumer_(SPV_MSG_ERROR, nullptr, {}, |
| "Failed to create SPIRV-Tools interface; stopping."); |
| return {Fuzzer::FuzzerResultStatus::kFailedToCreateSpirvToolsInterface, |
| std::vector<uint32_t>(), protobufs::TransformationSequence()}; |
| } |
| |
| // Initial binary should be valid. |
| if (!tools.Validate(&binary_in_[0], binary_in_.size(), validator_options_)) { |
| consumer_(SPV_MSG_ERROR, nullptr, {}, |
| "Initial binary is invalid; stopping."); |
| return {Fuzzer::FuzzerResultStatus::kInitialBinaryInvalid, |
| std::vector<uint32_t>(), protobufs::TransformationSequence()}; |
| } |
| |
| // Build the module from the input binary. |
| ir_context_ = |
| BuildModule(target_env_, consumer_, binary_in_.data(), binary_in_.size()); |
| assert(ir_context_); |
| |
| // The fuzzer will introduce new ids into the module. The module's id bound |
| // gives the smallest id that can be used for this purpose. We add an offset |
| // to this so that there is a sizeable gap between the ids used in the |
| // original module and the ids used for fuzzing, as a readability aid. |
| // |
| // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2541) consider the |
| // case where the maximum id bound is reached. |
| auto minimum_fresh_id = ir_context_->module()->id_bound() + kIdBoundGap; |
| fuzzer_context_ = |
| MakeUnique<FuzzerContext>(random_generator_.get(), minimum_fresh_id); |
| |
| transformation_context_ = MakeUnique<TransformationContext>( |
| MakeUnique<FactManager>(ir_context_.get()), validator_options_); |
| transformation_context_->GetFactManager()->AddInitialFacts(consumer_, |
| initial_facts_); |
| |
| RepeatedPassInstances pass_instances{}; |
| |
| // The following passes are likely to be very useful: many other passes |
| // introduce synonyms, irrelevant ids and constants that these passes can work |
| // with. We thus enable them with high probability. |
| MaybeAddRepeatedPass<FuzzerPassObfuscateConstants>(90, &pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassApplyIdSynonyms>(90, &pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassReplaceIrrelevantIds>(90, &pass_instances); |
| |
| do { |
| // Each call to MaybeAddRepeatedPass randomly decides whether the given pass |
| // should be enabled, and adds an instance of the pass to |pass_instances| |
| // if it is enabled. |
| MaybeAddRepeatedPass<FuzzerPassAddAccessChains>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassAddBitInstructionSynonyms>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassAddCompositeExtract>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassAddCompositeInserts>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassAddCompositeTypes>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassAddCopyMemory>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassAddDeadBlocks>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassAddDeadBreaks>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassAddDeadContinues>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassAddEquationInstructions>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassAddFunctionCalls>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassAddGlobalVariables>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassAddImageSampleUnusedComponents>( |
| &pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassAddLoads>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassAddLocalVariables>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassAddLoopPreheaders>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassAddLoopsToCreateIntConstantSynonyms>( |
| &pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassAddOpPhiSynonyms>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassAddParameters>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassAddRelaxedDecorations>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassAddStores>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassAddSynonyms>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassAddVectorShuffleInstructions>( |
| &pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassConstructComposites>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassCopyObjects>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassDonateModules>(&pass_instances, |
| donor_suppliers_); |
| MaybeAddRepeatedPass<FuzzerPassDuplicateRegionsWithSelections>( |
| &pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassExpandVectorReductions>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassFlattenConditionalBranches>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassInlineFunctions>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassInvertComparisonOperators>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassMakeVectorOperationsDynamic>( |
| &pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassMergeBlocks>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassMergeFunctionReturns>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassMutatePointers>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassOutlineFunctions>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassPermuteBlocks>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassPermuteFunctionParameters>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassPermuteInstructions>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassPropagateInstructionsDown>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassPropagateInstructionsUp>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassPushIdsThroughVariables>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassReplaceAddsSubsMulsWithCarryingExtended>( |
| &pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassReplaceBranchesFromDeadBlocksWithExits>( |
| &pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassReplaceCopyMemoriesWithLoadsStores>( |
| &pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassReplaceCopyObjectsWithStoresLoads>( |
| &pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassReplaceLoadsStoresWithCopyMemories>( |
| &pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassReplaceParameterWithGlobal>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassReplaceLinearAlgebraInstructions>( |
| &pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassReplaceOpPhiIdsFromDeadPredecessors>( |
| &pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassReplaceOpSelectsWithConditionalBranches>( |
| &pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassReplaceParamsWithStruct>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassSplitBlocks>(&pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassSwapBranchConditionalOperands>( |
| &pass_instances); |
| MaybeAddRepeatedPass<FuzzerPassWrapRegionsInSelections>(&pass_instances); |
| // There is a theoretical possibility that no pass instances were created |
| // until now; loop again if so. |
| } while (pass_instances.GetPasses().empty()); |
| |
| RepeatedPassRecommenderStandard pass_recommender(&pass_instances, |
| fuzzer_context_.get()); |
| |
| std::unique_ptr<RepeatedPassManager> repeated_pass_manager = nullptr; |
| switch (repeated_pass_strategy_) { |
| case RepeatedPassStrategy::kSimple: |
| repeated_pass_manager = MakeUnique<RepeatedPassManagerSimple>( |
| fuzzer_context_.get(), &pass_instances); |
| break; |
| case RepeatedPassStrategy::kLoopedWithRecommendations: |
| repeated_pass_manager = |
| MakeUnique<RepeatedPassManagerLoopedWithRecommendations>( |
| fuzzer_context_.get(), &pass_instances, &pass_recommender); |
| break; |
| case RepeatedPassStrategy::kRandomWithRecommendations: |
| repeated_pass_manager = |
| MakeUnique<RepeatedPassManagerRandomWithRecommendations>( |
| fuzzer_context_.get(), &pass_instances, &pass_recommender); |
| break; |
| } |
| |
| do { |
| if (!ApplyPassAndCheckValidity( |
| repeated_pass_manager->ChoosePass(transformation_sequence_out_))) { |
| return {Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule, |
| std::vector<uint32_t>(), protobufs::TransformationSequence()}; |
| } |
| } while (ShouldContinueFuzzing()); |
| |
| // Now apply some passes that it does not make sense to apply repeatedly, |
| // as they do not unlock other passes. |
| std::vector<std::unique_ptr<FuzzerPass>> final_passes; |
| MaybeAddFinalPass<FuzzerPassAdjustBranchWeights>(&final_passes); |
| MaybeAddFinalPass<FuzzerPassAdjustFunctionControls>(&final_passes); |
| MaybeAddFinalPass<FuzzerPassAdjustLoopControls>(&final_passes); |
| MaybeAddFinalPass<FuzzerPassAdjustMemoryOperandsMasks>(&final_passes); |
| MaybeAddFinalPass<FuzzerPassAdjustSelectionControls>(&final_passes); |
| MaybeAddFinalPass<FuzzerPassAddNoContractionDecorations>(&final_passes); |
| MaybeAddFinalPass<FuzzerPassInterchangeSignednessOfIntegerOperands>( |
| &final_passes); |
| MaybeAddFinalPass<FuzzerPassInterchangeZeroLikeConstants>(&final_passes); |
| MaybeAddFinalPass<FuzzerPassPermutePhiOperands>(&final_passes); |
| MaybeAddFinalPass<FuzzerPassSwapCommutableOperands>(&final_passes); |
| MaybeAddFinalPass<FuzzerPassToggleAccessChainInstruction>(&final_passes); |
| for (auto& pass : final_passes) { |
| if (!ApplyPassAndCheckValidity(pass.get())) { |
| return {Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule, |
| std::vector<uint32_t>(), protobufs::TransformationSequence()}; |
| } |
| } |
| // Encode the module as a binary. |
| std::vector<uint32_t> binary_out; |
| ir_context_->module()->ToBinary(&binary_out, false); |
| |
| return {Fuzzer::FuzzerResultStatus::kComplete, std::move(binary_out), |
| std::move(transformation_sequence_out_)}; |
| } |
| |
| bool Fuzzer::ShouldContinueFuzzing() { |
| // There's a risk that fuzzing could get stuck, if none of the enabled fuzzer |
| // passes are able to apply any transformations. To guard against this we |
| // count the number of times some repeated pass has been applied and ensure |
| // that fuzzing stops if the number of repeated passes hits the limit on the |
| // number of transformations that can be applied. |
| assert( |
| num_repeated_passes_applied_ <= |
| fuzzer_context_->GetTransformationLimit() && |
| "The number of repeated passes applied must not exceed its upper limit."); |
| if (ir_context_->module()->id_bound() >= fuzzer_context_->GetIdBoundLimit()) { |
| return false; |
| } |
| if (num_repeated_passes_applied_ == |
| fuzzer_context_->GetTransformationLimit()) { |
| // Stop because fuzzing has got stuck. |
| return false; |
| } |
| auto transformations_applied_so_far = |
| static_cast<uint32_t>(transformation_sequence_out_.transformation_size()); |
| if (transformations_applied_so_far >= |
| fuzzer_context_->GetTransformationLimit()) { |
| // Stop because we have reached the transformation limit. |
| return false; |
| } |
| // If we have applied T transformations so far, and the limit on the number of |
| // transformations to apply is L (where T < L), the chance that we will |
| // continue fuzzing is: |
| // |
| // 1 - T/(2*L) |
| // |
| // That is, the chance of continuing decreases as more transformations are |
| // applied. Using 2*L instead of L increases the number of transformations |
| // that are applied on average. |
| auto chance_of_continuing = static_cast<uint32_t>( |
| 100.0 * (1.0 - (static_cast<double>(transformations_applied_so_far) / |
| (2.0 * static_cast<double>( |
| fuzzer_context_->GetTransformationLimit()))))); |
| if (!fuzzer_context_->ChoosePercentage(chance_of_continuing)) { |
| // We have probabilistically decided to stop. |
| return false; |
| } |
| // Continue fuzzing! |
| num_repeated_passes_applied_++; |
| return true; |
| } |
| |
| } // namespace fuzz |
| } // namespace spvtools |