LLVM:为函数添加一个参数

本文详细介绍了如何在LLVM中为已有的函数添加参数,包括创建新函数声明、替换函数调用、更改返回值引用、函数基本块链接以及函数内部参数引用的更新。主要涉及对FunctionType、CallInst、IRBuilder等LLVMAPI的使用。
摘要由CSDN通过智能技术生成

1.问题描述

使用LLVM对于源码进行处理的时候产生了一个这样的需求:为每一个函数都添加一个参数,且这个参数会通过main函数传入后一直传递下去(别问我为什么不是用全局变量,要是能这样解决就不用这么麻烦了)。大致结果如下所示:

//the function need to add a argument
void func(int arg1, int arg2);
//the function after adding the argument
void func(int arg0, int arg1, int arg2);

在LLVM中给函数添加一个参数是复杂的,涉及到多处的更改。首先是函数声明部分,函数声明在LLVM中是无法直接修改的;接着是对于函数使用部分,这里只考虑最常见的 call 指令其他的函数指针引用,invoke指令就不进行考虑,处理起来会很麻烦。

2.解决思路

  • 函数声明:直接修改不太可能,那么可以创建一个新的函数声明,新的函数声明中在参数列表的开头添加一个参数。
  • 函数使用:需要遍历旧函数的所有使用者,将对于旧函数的引用更改为对于新函数的使用。同时需要考虑传递问题,由于这个参数是通过main一路传递过来的,所以直接使用父函数的第0个参数(本篇中都是0开始计数)即可。
  • 函数体:由于新创建的函数只是一个declaration而没有definition,所以要将旧函数的block节引用,然后将其link到新函数中。

3.过程

下面的代码注释中我详细说明了实现的步骤,不过既然是中文博客,后面论述中我会逐步将其翻译为中文。

    /*below is add a new arg to a function
    1. create a new function declaration added a formal argument to it(this is becasue the
    llvm don't support change function directly)
    2. change the function call in the original funciton (because the block is the original 
    funciton block, the function call don't change)
    3. (if the function has a return value,) modify the referrence of this return value
    4. unlink the block of original to the new function declaration
    5. change the referrence of formal argument. we known the following format in IR
        define i32 @_Z4foo1i_new(i32* %0, i32 %1) 
        %2 = alloca i32, align 4
        %3 = alloca i32, align 4
        store i32 %0, i32* %2, align 4  ;there shold be %1 other than %0
        store i32 1, i32* %3, align 4
    always store the actual argument to a local variable, so I just find the store instruction
    and get the second oprand(getoprand(1)), and erase the original store instruction, and create
    a new store with the correct formal argument referrence. 
       why I don't use the same mathod as the return value referrence change(point 3), when I try it, the
    member function "getNumUse", which return how many Users are using it, return 0, so I just
    struggle to find new method like above.
    * Parameter 0 : the Module the process use
    * Parameter 1 : the function need to add
    * return value is change or not
    */
    bool AddAParameter(Module &M, Function &func);

3.1 Step 1

第一步,创建一个新函数declaration,这个函数是在原函数的基础上添加了一个Argument。创建函数之前首先要获取原函数的返回值类型参数列表变参标志

需求是在函数参数列表的开头添加一个参数,所以对于返回的原函数参数列表要进行修改。通过params()函数返回的参数列表存放ArrayRef中,这是一个只读的东西,与C++中的vector类似,需要将其转化为vector,方便向其中添加参数类型。

    //step 1, create a new function declaration adding a new parameter
    //get the original formal argument list, and generate a new list
    FunctionType* FuncType = func.getFunctionType();
    Type* RetType = FuncType->getReturnType();
    ArrayRef<Type*> ArgArray = FuncType->params();
    bool IsVarArg = FuncType->isVarArg();
    //add a parameter to the declaration
    std::vector<Type*> NewArgArray;
    for(Type * Arg : ArgArray) {
        NewArgArray.push_back(Arg);
    }

参数添加完毕之后就可以通过FunctionType类来生成一个新的函数类型。最后通过getOrInsertFunction函数向module中添加一个新的函数声明,这个新函数的名称则是在原函数的后面添加了“_new”。

    NewArgArray.insert(NewArgArray.begin(), PPType);
    //use the new argument list create a new function declaration
    FunctionType * NewFuncType = FunctionType::get(RetType, NewArgArray, IsVarArg);
    FunctionCallee NewFuncCallee = M.getOrInsertFunction(func.getName().str() + "_new", NewFuncType);
    Function* NewFunc = dyn_cast<Function>(NewFuncCallee.getCallee());

3.2 Step 2

第二步,替换原函数使用,既然已经创建了一个新的函数声明,那么就应该将原函数的函数声明替换为新函数,LLVM中提供了User对象方便寻找使用者。

替换的过程很简单,由于只需要考虑call指令,只需要将原函数的使用者向CallInst进行投影就可以了。通过CallInst对象的成员函数来获取原函数的实参列表,然后本函数的参数0添加在参数列表的开头(都是由main传递过来)。

然后就是对于call指令进行替换,尽管CallInst对象提供了setCalledFunction成员函数,这个成员函数用来设置参数一致的函数调用很方便,参数不同的函数就有些无能为力了。所以这里采用的方法是先创建,后删除。获取一个IRBuilder<>,将插入位置设置为要删除的call指令,然后创建一个新的call指令,call的对象是刚刚创建的新函数。

    //step 2, replace the call of the original function
    for(auto start = func.user_begin(); ; start++) {
        if(start.atEnd()) {
            llvm::outs() << "function user is an end\n"; 
            break;
        }
        llvm::outs() << "the function user : \n" << **start << "\n";
        CallInst* CallIns = dyn_cast<CallInst>(*start);
        //get the original actual argument list
        std::vector<Value*> NewArg;
        for(auto &arg : CallIns->args()) {
            NewArg.push_back(dyn_cast<Value>(arg));
        }
        //Value* Zero = ConstantPointerNull::get(PointerType::getUnqual(Type::getInt64Ty(context)));
        Function* Parent = dyn_cast<Function>(*start);
        Value* ArgValue = NewFunc->getArg(0);
        //insert a new value to the start
        NewArg.insert(NewArg.begin(), ArgValue);
        //create new call instruction
        IRBuilder<> CallBuilder(CallIns);
        CallInst* NewRetUse = CallBuilder.CreateCall(NewFunc, NewArg);

        //step 3, replace the return value referrence with new funciton
        for(auto CStart = CallIns->user_begin(); ;CStart++) { ... }
        CallIns->eraseFromParent();
    }

3.3 Step 3

第三步,更改函数返回值引用,这是在第二步的循环体中一起完成的,我之所以要将其设置为单独的一步,是因为我在编写该函数的过程中Debug过程中发现返回值的Bug才将这部分添加上去的。

这里遍历的是之前得到的CallInst对象实例的使用者,如果该函数返回值是void则不会有这一步。直接使用replaceUsesOfWith函数来进行替换。这里啰嗦一句对于这个函数的使用要慎重,在.ll文件中相同的内容在IR分析时会使用相同的内存地址存放,当对其进行修改时其他引用的部分也会发生变化。使用时尽量不要对于某个指令中的某个参数进行替换,最起码要到指令这个层面。

        //step 3, replace the return value referrence with new funciton
        for(auto CStart = CallIns->user_begin(); ;CStart++) {
            if(CStart.atEnd()) {
                llvm::outs() << "use return value is an end\n";
                break;
            }
            Instruction* RetUser = dyn_cast<Instruction>(*CStart);
            RetUser->replaceUsesOfWith(CallIns, NewRetUse);
        }

3.4 Step 4

第四步,函数基本块链接更改,将原本链接到原函数的基本块解除链接,然后重新链接到新函数上。

这一步实现时只需要遍历原函数的所有基本块,然后调用removeFromParentinsertInto这两个函数进行操作就可以了。

    //step 4, unlink the block of original function and link it to the new function
    Function::BasicBlockListType &list = func.getBasicBlockList();
    int i = list.size();
    for(auto start = list.begin(), end = list.end(); start != end; start++){
        i--;
        if(i < 0)
            break;
        auto block = dyn_cast<BasicBlock>(start);
        block->removeFromParent();
        block->insertInto(NewFunc);
    }

3.5 Step 5

第五步,函数内参数引用更改,这一步需要解决的问题是新函数将参数添加到了参数列表的开头,而函数中的代码却还是按照原函数的方式使用参数,所以需要将参数的使用向后移动一位。如果新参数添加到参数列表的末尾则此步骤就没有意义了。

函数中参数的使用如果直接更改的话会很麻烦,不过还在LLVM在生成IR文件时有一个习惯——在函数的开头添加局部变量来保存参数内容,后面不会使用参数,而是直接使用局部变量中的内容。所以对于参数引用的更改就变成了对于局部变量引用的更改。

创建一个alloc来申请一个局部变量,然后创建一个store指令将参数0存入其中,接着下一个store指令将其中的内容更改为参数1,后面以此类推。这里更改参数的方式没有使用replaceUsesOfWith函数,而是创建新的,删除旧的的方式。这样操作之后函数内部对于参数的引用就是正确的了。

    //step 5, change the formal argument referrence
    AllocaInst* Test = dyn_cast<AllocaInst>(NewFunc->getEntryBlock().begin());
    IRBuilder<> AllcBuilder(&*NewFunc->getEntryBlock().getFirstInsertionPt());
    AllocaInst* Allc = AllcBuilder.CreateAlloca(PPType);
    //there is AllocaInst in the function, so I can find a store instruction
    if(Test) {
        StoreInst* tmp;
        for(auto &ins : NewFunc->getEntryBlock()) {
            StoreInst* StoIns = dyn_cast<StoreInst>(&ins);
            if(StoIns == nullptr)
                continue;
            tmp = StoIns;    
            break;
        }
        
        Allc->setAlignment(tmp->getAlign());
        IRBuilder<>StorBuilder(tmp);
        Instruction* InsNext = tmp;  
        StorBuilder.CreateStore(NewFunc->getArg(0), Allc)->setAlignment(tmp->getAlign());
        for(int i = 1; i < NewFunc->arg_size(); i++) {
            IRBuilder<>StorBuilder(InsNext);
            StorBuilder.CreateStore(NewFunc->getArg(i), InsNext->getOperand(1));
            llvm::outs() << "erase start\n";  
            InsNext = dyn_cast<Instruction>(InsNext->eraseFromParent());
            llvm::outs() << "erase finish\n";  
        }
    }
    //there is no AllocInst, so I just insert a store instrction behind the new create alloc
    else {
        AllcBuilder.CreateStore(NewFunc->getArg(0), Allc);
    }

3.6 整体代码

下面是AddAParameter函数的完整代码。

bool AddAParameter(Module &M, Function &func) {
    llvm::outs() << func.getName().str() << "\n";
    llvm::outs() << "Function Linkage : " << func.getLinkage() << "\n";
    
    if(func.isDeclaration())
        return false;
    //the customized function is ExternalLinkage(0)
    //the API funciton is LinkOnceODRLinkage(3)
    if(func.isLinkOnceODRLinkage(func.getLinkage()))
        return false;
    
    LLVMContext &context = func.getContext();
    
    PointerType* PPType = PointerType::getUnqual(IntegerType::getInt64PtrTy(context));

    //step 1, create a new function declaration adding a new parameter
    //get the original formal argument list, and generate a new list
    FunctionType* FuncType = func.getFunctionType();
    Type* RetType = FuncType->getReturnType();
    ArrayRef<Type*> ArgArray = FuncType->params();
    bool IsVarArg = FuncType->isVarArg();
    //add a parameter to the declaration
    std::vector<Type*> NewArgArray;
    for(Type * Arg : ArgArray) {
        NewArgArray.push_back(Arg);
    }
    NewArgArray.insert(NewArgArray.begin(), PPType);
    //use the new argument list create a new function declaration
    FunctionType * NewFuncType = FunctionType::get(RetType, NewArgArray, IsVarArg);
    FunctionCallee NewFuncCallee = M.getOrInsertFunction(func.getName().str() + "_new", NewFuncType);
    Function* NewFunc = dyn_cast<Function>(NewFuncCallee.getCallee());

    //step 2, replace the call of the original function
    if(func.getName().str() != "main") {
        for(auto start = func.user_begin(); ; start++) {
            if(start.atEnd()) {
                llvm::outs() << "function user is an end\n"; 
                break;
            }
            llvm::outs() << "the function user : \n" << **start << "\n";
            CallInst* CallIns = dyn_cast<CallInst>(*start);
            //get the original actual argument list
            std::vector<Value*> NewArg;
            for(auto &arg : CallIns->args()) {
                NewArg.push_back(dyn_cast<Value>(arg));
            }
            //Value* Zero = ConstantPointerNull::get(PointerType::getUnqual(Type::getInt64Ty(context)));
            Function* Parent = dyn_cast<Function>(*start);
            Value* ArgValue = NewFunc->getArg(0);
            //insert a new value to the start
            NewArg.insert(NewArg.begin(), ArgValue);
            //create new call instruction
            IRBuilder<> CallBuilder(CallIns);
            CallInst* NewRetUse = CallBuilder.CreateCall(NewFunc, NewArg);

            //step 3, replace the return value referrence with new funciton
            for(auto CStart = CallIns->user_begin(); ;CStart++) {
                if(CStart.atEnd()) {
                    llvm::outs() << "use return value is an end\n";
                    break;
                }
                Instruction* RetUser = dyn_cast<Instruction>(*CStart);
                RetUser->replaceUsesOfWith(CallIns, NewRetUse);
            }
            CallIns->eraseFromParent();
        }
    }

    //step 4, unlink the block of original function and link it to the new function
    Function::BasicBlockListType &list = func.getBasicBlockList();
    int i = list.size();
    for(auto start = list.begin(), end = list.end(); start != end; start++){
        i--;
        if(i < 0)
            break;
        auto block = dyn_cast<BasicBlock>(start);
        block->removeFromParent();
        block->insertInto(NewFunc);
    }

    //step 5, change the formal argument referrence
    AllocaInst* Test = dyn_cast<AllocaInst>(NewFunc->getEntryBlock().begin());
    IRBuilder<> AllcBuilder(&*NewFunc->getEntryBlock().getFirstInsertionPt());
    AllocaInst* Allc = AllcBuilder.CreateAlloca(PPType);
    //there is AllocaInst in the function, so I can find a store instruction
    if(Test) {
        StoreInst* tmp;
        for(auto &ins : NewFunc->getEntryBlock()) {
            StoreInst* StoIns = dyn_cast<StoreInst>(&ins);
            if(StoIns == nullptr)
                continue;
            tmp = StoIns;    
            break;
        }
        
        Allc->setAlignment(tmp->getAlign());
        IRBuilder<>StorBuilder(tmp);
        Instruction* InsNext = tmp;  
        StorBuilder.CreateStore(NewFunc->getArg(0), Allc)->setAlignment(tmp->getAlign());
        for(int i = 1; i < NewFunc->arg_size(); i++) {
            IRBuilder<>StorBuilder(InsNext);
            StorBuilder.CreateStore(NewFunc->getArg(i), InsNext->getOperand(1));
            llvm::outs() << "erase start\n";  
            InsNext = dyn_cast<Instruction>(InsNext->eraseFromParent());
            llvm::outs() << "erase finish\n";  
        }
    }
    //there is no AllocInst, so I just insert a store instrction behind the new create alloc
    else {
        AllcBuilder.CreateStore(NewFunc->getArg(0), Allc);
    }    
    return true;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值