LLVM PASS-PWN-前置

news2024/11/13 10:31:24

文章目录

  • 参考
  • 环境搭建
  • 基础知识![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/dced705dcbb045ceb8df2237c9b0fd71.png)
    • LLVM IR
      • 实例
        • 1. **.ll 格式(人类可读的文本格式)**
        • 2. **.bc 格式(二进制格式)**
        • 3. **内存表示**
  • LLVM IR的处理
      • 示例函数
      • 编译为 LLVM IR
      • 解释
  • 流程
  • 调试
  • IR结构
    • LLVMContext
    • moudle
    • Value,User和Use
    • GlobalVariable
    • Function
    • BasicBlock
    • Instruction
    • 不同Instruction的创建
    • alloca
      • alloca命令是AllocaInst类型,继承关系是
      • 类的继承关系
      • `AllocaInst` 的构造函数
      • 参数说明
      • 使用示例
        • 创建一个 `AllocaInst`
      • 解释
    • store
      • 1. `StoreInst` 的继承结构
      • 2. `StoreInst` 的构造函数
      • 3. 代码示例
      • 4. 解释
  • load
      • 1. `LoadInst` 的继承结构
      • 2. `LoadInst` 的构造函数
      • 3. 代码示例
    • add
      • 1. `BinaryOperator` 的创建
      • 2. `BinaryOps` 枚举
      • 3. 代码示例解析
      • 4. 解释
    • icmp
      • 1. `ICmpInst` 的构造函数
      • 2. `Predicate` 枚举
      • 3. 代码示例解析
      • 4. 解释
    • branch
      • 1. `BranchInst` 的创建方法
      • 2. 代码示例解析
      • 3. 解释
    • ret
      • 1. `ReturnInst` 的创建方法
      • 2. 代码示例解析
      • 3. 解释
    • call
      • 1. `CallInst` 的内部结构
      • 2. `CallInst` 的创建方法
      • 重载方法的分类与解释
        • 1. **基本创建方法**
        • 2. **带参数的创建方法**
        • 3. **带参数和操作数包的创建方法**
        • 4. **插入到基本块末尾的创建方法**
        • 5. **使用`FunctionCallee`类型的创建方法**
        • 6. **带操作数包和替换操作数包的创建方法**
        • 实例

参考

https://bbs.kanxue.com/thread-274259.htm#msg_header_h2_1

http://www.blackbird.wang/2022/08/30/LLVM-PASS%E7%B1%BBpwn%E9%A2%98%E6%80%BB%E7%BB%93/

LLVM基础知识

https://buaa-se-compiling.github.io/miniSysY-tutorial/pre/design_hints.html

https://blog.csdn.net/qq_45323960/article/details/129691707?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171928250416800178515048%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=171928250416800178515048&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_ecpm_v1~rank_v31_ecpm-2-129691707-null-null.nonecase&utm_term=LLVM&spm=1018.2226.3001.4450

https://xz.aliyun.com/t/11762?time__1311=mqmx0DBDcD2DuiCD%2FQbKBKFxr73BPhD&alichlgref=https%3A%2F%2Fwww.google.com.hk%2F

环境搭建

我用kali本机只能下载有15和以上的,其他的缺少依赖项,懒得弄了,直接用docker搭建ubuntu20.04的可以下载

sudo apt install clang-8
sudo apt install llvm-8
 
sudo apt install clang-10
sudo apt install llvm-10
 
sudo apt install clang-12
sudo apt install llvm-12

基础知识在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

LLVM IR

LLVM IR即代码的中间表示,有三种形式:

LLVM IR(Intermediate Representation,中间表示)是LLVM编译器框架中的一种中间代码表示形式。LLVM IR有三种主要的表示形式:

  1. .ll 格式:人类可读的文本格式。
  2. .bc 格式:适合机器存储和处理的二进制格式。
  3. 内存表示:LLVM编译器在运行时使用的内存中的数据结构。

实例

假设我们有一个简单的C语言代码:

int add(int a, int b) {
    return a + b;
}

编译这个代码时,LLVM会将其转换为LLVM IR。我们可以通过不同的方式查看和存储这个LLVM IR。

1. .ll 格式(人类可读的文本格式)

这是LLVM IR的文本表示形式,适合人类阅读和编辑。你可以通过命令行工具clangllvm-dis生成这个格式。

; ModuleID = 'example.c'
source_filename = "example.c"

define i32 @add(i32 %a, i32 %b) {
entry:
  %0 = add i32 %a, %b
  ret i32 %0
}

在这个例子中:

  • define i32 @add(i32 %a, i32 %b) 定义了一个返回类型为i32(32位整数)的函数add,它有两个参数ab,类型都是i32
  • entry: 是函数的入口基本块。
  • %0 = add i32 %a, %b 表示将ab相加,并将结果存储在临时变量%0中。
  • ret i32 %0 返回结果%0
2. .bc 格式(二进制格式)

这是LLVM IR的二进制表示形式,适合机器存储和处理。它通常比文本格式更紧凑,适合在编译器内部传递或存储在磁盘上。

你可以通过clangllvm-as工具生成这个格式:

clang -emit-llvm -c example.c -o example.bc

生成的example.bc文件是二进制格式,无法直接阅读,但可以通过llvm-dis工具将其转换回人类可读的.ll格式:

llvm-dis example.bc -o example.ll
3. 内存表示

当LLVM编译器在运行时处理代码时,LLVM IR会以内存中的数据结构形式存在。这种表示形式是LLVM编译器内部使用的,通常是通过C++对象和数据结构来表示的。

例如,函数add在内存中可能表示为一个llvm::Function对象,基本块entry表示为一个llvm::BasicBlock对象,指令%0 = add i32 %a, %b表示为一个llvm::Instruction对象。

这些内存中的对象和数据结构允许LLVM进行各种优化和代码生成操作。开发者可以通过LLVM的C++ API来操作这些内存表示,例如添加、删除或修改指令,或者进行各种优化。

.c -> .ll:clang -emit-llvm -S a.c -o a.ll
.c -> .bc: clang -emit-llvm -c a.c -o a.bc
.ll -> .bc: llvm-as a.ll -o a.bc
.bc -> .ll: llvm-dis a.bc -o a.ll
.bc -> .s: llc a.bc -o a.s

LLVM IR的处理

LVM Pass可用于对代码进行优化或者对代码插桩(插入新代码),LLVM的核心库中提供了一些Pass类可以继承,通过实现它的一些方法,可以对传入的LLVM IR进行遍历并操作。

LLVM对IR中的函数、基本块(basic block)以及基本块内的指令的处理

示例函数

假设我们有一个简单的 C 语言函数:

void example_function(int x, int y) {
    int result;
    if (x > y) {
        result = x + y;
    } else {
        result = x * y;
    }
    printf("The result is %d\n", result);
}

编译为 LLVM IR

将上述函数编译为 LLVM IR 可能会产生类似以下的中间表示:

define void @example_function(i32 %x, i32 %y) {
entry:
  %result = alloca i32
  %x_gt_y = icmp sgt i32 %x, %y
  br i1 %x_gt_y, label %iftrue, label %iffalse

iftrue:
  %add_result = add i32 %x, %y
  br label %after_if

iffalse:
  %mul_result = mul i32 %x, %y
  br label %after_if

after_if:
  %phi_result = phi i32 [ %add_result, %iftrue ], [ %mul_result, %iffalse ]
  store i32 %phi_result, i32* %result
  %print_result = call void @printf(i8* getelementptr inbounds ([17 x i8], [17 x i8]* @.str, i32 0, i32 0), i32 %phi_result)
  ret void

@.str = private unnamed_addr constant [17 x i8] c"The result is %d\n\00"

解释

  • 函数(Function): @example_function 是一个函数,它接受两个整数参数 xy 并打印它们的运算结果。

  • 基本块(Basic Block): 一个基本块是一段连续的指令序列,控制流不能从中中断。在这个示例中,我们有三个基本块:

    • entry: 函数的入口点,分配局部变量空间并进行条件跳转。
    • iftrue: 当 x > y 时执行的代码块。
    • iffalse: 当 x <= y 时执行的代码块。
    • after_if: 无论哪个条件分支被执行之后的合并点。
  • 基本块内的指令(Instructions): 每个基本块包含了一系列的指令。例如,在 entry 基本块中,我们有:

    • %result = alloca i32:分配一个整数类型的局部变量 result
    • %x_gt_y = icmp sgt i32 %x, %y:比较 x 是否大于 y
    • br i1 %x_gt_y, label %iftrue, label %iffalse:根据条件跳转到 iftrue 或者 iffalse 基本块。

iftrue 基本块中,我们有:

  • %add_result = add i32 %x, %y:如果 x > y,则计算 x + y
  • br label %after_if:无条件跳转到 after_if 基本块。

iffalse 基本块中,我们有:

  • %mul_result = mul i32 %x, %y:如果 x <= y,则计算 x * y
  • br label %after_if:无条件跳转到 after_if 基本块。

after_if 基本块中,我们有:

  • %phi_result = phi i32 [ %add_result, %iftrue ], [ %mul_result, %iffalse ]:选择 iftrueiffalse 中的结果作为最终结果。
  • store i32 %phi_result, i32* %result:将结果存储到局部变量 result 中。
  • %print_result = call void @printf(...):调用 printf 函数打印结果。
  • ret void:返回空值,结束函数。

流程

  • LLVM PASS就是去处理IR文件,通过opt利用写好的so库优化已有的IR,形成新的IR。

  • LLVM PASS类PWN就是opt加载pass.so文件,对IR代码进行转换和优化这个过程中存在的漏洞加以利用。这里需要注意的是.so文件是不会被pwn的,我们pwn的是加载.so文件的程序——opt。所以我们需要对opt进行常规的检查。

CTF题目一般会给出所需版本的opt文件(可用./opt --version查看版本)或者在README文档中告知opt版本。安装好llvm后,可在/usr/lib/llvm-xx/bin/opt路径下找到对应llvm版本的opt文件(一般不开PIE保护)。

搜索vtable定位到虚表,最下面的函数就是重写的虚函数runOnFunction
在这里插入图片描述

调试

clang-8 -emit-llvm -S exp.c -o exp.bc或者
clang-8 -emit-llvm -S exp.c -o exp.ll
opt-8 -load ./VMPass.so -VMPass ./exp.bc

调试opt然后跟进到so文件
在这里插入图片描述
opt并不会一开始就将so模块加载进来,会执行一些初始化函数才会加载so模块。
调试的时候可以把断点下载llvm::Pass::preparePassManager,程序执行到这里的时候就已经加载了LLVMHello.so文件(或者到main+11507),我们就可以根据偏移进一步将断点下在LLVMHello.so文件里面
查看vmmap,发现已经加载进来,然后可以更加偏移断在runOnFunction上
在这里插入图片描述
在这里插入图片描述

成功
在这里插入图片描述
使用脚本

from pwn import *
import sys
import os

os.system("clang-8 -emit-llvm -S exp.c -o exp.bc")

p = gdb.debug(["./opt-8",'-load','./VMPass.so','-VMPass','./exp.bc'],"b llvm::Pass::preparePassManager\nc")
p.interactive()

IR结构

LLVM IR数据结构分析

LLVMContext

  • 一个全局数据
  • 只能通过 llvm::getGlobalContext();创建赋值给一个LLVMContext变量
  • 删除了拷贝构造函数和拷贝赋值函数

moudle

  • 主要是包含函数和全局变量的两个链表
  • 创建一个Module,需要一个名字和一个LLVMContext
  • Module操作函数链表的成员函数 begin() end () size() empty() functions(),直接拿到函数链表的函数getFunctionList() ,查找模块内函数链表中函数的函数getFunction
 iterator_range<iterator> functions() {
    return make_range(begin(), end());
  }
  
  FunctionListType       &getFunctionList()           { return FunctionList; }
  Function *getFunction(StringRef Name) const;
  • -Module操作全局变量的成员函数 global_begin() global_end () global_size() global_empty() globals()直接拿到全局变量链表的函数getGlobalList()
  iterator_range<global_iterator> globals() {
    return make_range(global_begin(), global_end());
  }
GlobalListType         &getGlobalList()             { return GlobalList; }
  • 插入或查找函数 StringRef Name:函数名 Type *RetTy:返回值类型 ArgsTy… Args:每个参数的类型 FunctionType *T:函数类型(其实就是参数类型和返回值类型的集合),可以通过get方法构造 isVarArg:是是否支持可变参数
FunctionCallee getOrInsertFunction(StringRef Name, FunctionType *T);

template <typename... ArgsTy>
FunctionCallee getOrInsertFunction(StringRef Name, Type *RetTy,
                                     ArgsTy... Args);
                                     
返回值类型是FunctionCallee,成员为一个Value指针(就是具体的函数Function 指针)和一个FunctionType指针                                     
class FunctionCallee {

private:
  FunctionType *FnTy = nullptr;
  Value *Callee = nullptr;
};

可通过如下方法获得
FunctionType *getFunctionType() { return FnTy; }
Value *getCallee() { return Callee; }


FunctionType 只是一个函数类型信息
class FunctionType : public Type {
  static FunctionType *get(Type *Result,
                           ArrayRef<Type*> Params, bool isVarArg);


Function 才是具体的函数
FunctionCallee Module::getOrInsertFunction(StringRef Name, FunctionType *Ty,
                                           AttributeList AttributeList) {
  // See if we have a definition for the specified function already.
  GlobalValue *F = getNamedValue(Name);
  if (!F) {
    // Nope, add it
    Function *New = Function::Create(Ty, GlobalVariable::ExternalLinkage,
                                     DL.getProgramAddressSpace(), Name);
    if (!New->isIntrinsic())       // Intrinsics get attrs set on construction
      New->setAttributes(AttributeList);
    FunctionList.push_back(New);
    return {Ty, New}; // Return the new prototype.
  }

但使用由于原来是  Value *会需要转换
Function *customFunc = dyn_cast<Function>(callee.getCallee());                                    

Value,User和Use

刚看的时候真抽象

  • User用Value,这个使用的行为就是Use,同时Value里面有个Uselist记录了哪些User用过它们
class Value {
  Use *UseList;
  ……
  }
  使用Value的Use以双向链表连接起来
  
class Use {
private:
  Value *Val = nullptr;
  Use *Next = nullptr;
  Use **Prev = nullptr;
  User *Parent = nullptr;
  
Val:指向被使用的Value
Next:指向下一个Use
Prev:指向上一个Prev
Parent:指向User

  • Use会放在User结构体前,一种是以固定个数的Use,以数组的形式放在User前,另一种是不定个数的Use,一个Use放在User前,这个Use的Prev指针指向Use数组,然后可以通过一个Use找到其他Use,通过getOperandList的不同处理可以看出
// HasHungOffUses是Value的成员  

const Use *getOperandList() const {
    return HasHungOffUses ? getHungOffOperands() : getIntrusiveOperands();
  }

const Use *getHungOffOperands() const {
    return *(reinterpret_cast<const Use *const *>(this) - 1);
  }
const Use *getIntrusiveOperands() const {
    return reinterpret_cast<const Use *>(this) - NumUserOperands;
  }

GlobalVariable

  • GlobalVariable由GlobalObject和ilist_node_base组成
  • GlobalObject是一个容器,其中的GlobalValue包含了Value、ValueType、Parent等属性
  • Globallist中的prev和next和GlobalVariable中的ilist_node_base双向连接

在这里插入图片描述

  • 通过GlobalVariable创建,下面一种方式除了创建还会将该GlobalVariable添加到模块中,同时禁止了拷贝构造函数和赋值
GlobalVariable(Type *Ty, bool isConstant, LinkageTypes Linkage,
               Constant *Initializer = nullptr, const Twine &Name = "",
               ThreadLocalMode = NotThreadLocal, unsigned AddressSpace = 0,
               bool isExternallyInitialized = false);
  
GlobalVariable(Module &M, Type *Ty, bool isConstant, LinkageTypes Linkage,
               Constant *Initializer, const Twine &Name = "",
               GlobalVariable *InsertBefore = nullptr,
               ThreadLocalMode = NotThreadLocal,
               Optional<unsigned> AddressSpace = None,
               bool isExternallyInitialized = false);
  
GlobalVariable(const GlobalVariable &) = delete;
GlobalVariable &operator=(const GlobalVariable &) = delete;

Function

  • 包含GlobalObject和illst_node_base和一个Arguments指针,指向Argument数组,一个BasicBlock双链表
    在这里插入图片描述

  • BasicBlock迭代器:begin() end() size() empty() &front() &back() begin返回指向BasicBlock集合的第一个元素的迭代器。front是第一个元素的引用。end和back同理

  • Argument迭代器:arg_begin() arg_end() getArg(unsigned i) args()

返回一个迭代器范围
iterator_range<arg_iterator> args() {
  return make_range(arg_begin(), arg_end());
}
  • Function创建
static Function *Create(FunctionType *Ty, LinkageTypes Linkage,
                          const Twine &N, Module &M);

static Function *Create(FunctionType *Ty, LinkageTypes Linkage,
                          unsigned AddrSpace, const Twine &N = "",
                          Module *M = nullptr) {
    return new Function(Ty, Linkage, AddrSpace, N, M);
  }


static Function *Create(FunctionType *Ty, LinkageTypes Linkage,
                          const Twine &N = "", Module *M = nullptr) {
    return new Function(Ty, Linkage, static_cast<unsigned>(-1), N, M);
  }
  • FunctionType是Type的子类,ReturnType和ParamType都存在Type类型的ContainedTys成员里,这是一个Type数组
class Type {
protected:
  unsigned NumContainedTys = 0;
  Type * const *ContainedTys = nullptr;

可以从getXXXType函数中看出来

// Function
Type *getReturnType() const { return getFunctionType()->getReturnType(); }

// FunctionType
FunctionType *getFunctionType() const {
    return cast<FunctionType>(getValueType());
}
Type *getReturnType() const { return ContainedTys[0]; }
Type *getParamType(unsigned i) const { return ContainedTys[i+1]; }

对于函数的入口的basicblock

const BasicBlock       &getEntryBlock() const   { return front(); }
      BasicBlock       &getEntryBlock()         { return front(); }
  • 设置和获取函数调用规定 getCallingConv():返回Function的调用约定。 setCallingConv(CC):设置Function的调用约定为CC
    CallingConv::ID是一个枚举类型,表示不同的调用约定。
    getSubclassDataFromValue()是一个内部函数,用于从Function对象中提取一些数据。在这个例子中,它返回了Function的一些位掩码,其中的一部分表示调用约定。
    getCallingConv()通过移位和按位与运算从这些位掩码中提取出调用约定的值。
    setCallingConv(CC)首先将CC转换为无符号整数,然后检查它是否在一个有效的范围内。如果有效,它会更新Function的位掩码以反映新的调用约定。
CallingConv::ID getCallingConv() const {
  return static_cast<CallingConv::ID>((getSubclassDataFromValue() >> 4) &
                                      CallingConv::MaxID);
}
void setCallingConv(CallingConv::ID CC) {
  auto ID = static_cast<unsigned>(CC);
  assert(!(ID & ~CallingConv::MaxID) && "Unsupported calling convention");
  setValueSubclassData((getSubclassDataFromValue() & 0xc00f) | (ID << 4));
}

BasicBlock

  • BasicBlock内包括Value和ilist_node_base和Instlist和Parent,Instlist包含指令链表
    在这里插入图片描述
  • 创建一个BasicBlock
static BasicBlock *Create(LLVMContext &Context, const Twine &Name = "",
                          Function *Parent = nullptr,
                          BasicBlock *InsertBefore = nullptr) {
  return new BasicBlock(Context, Name, Parent, InsertBefore);
}
当InsertBefore为NULL时默认插入Function末尾

BasicBlock::BasicBlock(LLVMContext &C, const Twine &Name, Function *NewParent,
                       BasicBlock *InsertBefore)
  : Value(Type::getLabelTy(C), Value::BasicBlockVal), Parent(nullptr) {

  if (NewParent)
      // Insert unlinked basic block into a function. Inserts an unlinked basic block into Parent. If InsertBefore is provided, inserts before that basic block, otherwise inserts at the end.
    insertInto(NewParent, InsertBefore);
  else
    assert(!InsertBefore &&
           "Cannot insert block before another block with no function!");

  setName(Name);
}
  • Instruction迭代器:begin() end() rbegin() rend() size() empty() front() back()

    获取Instruction所属Function

const Function *getParent() const { return Parent; }
      Function *getParent()       { return Parent; }

Instruction

  • 包含Value和illist_node_base和Parent
    在这里插入图片描述
  • 获取父BasicBlock
inline const BasicBlock *getParent() const { return Parent; }
inline       BasicBlock *getParent()       { return Parent; }
  • 获取指令的操作码
unsigned getOpcode() const { return getValueID() - InstructionVal; }
  • 返回指令的另一个实例

    没有名字: 在LLVM中,值(包括指令)可以有名字,用于调试和识别。克隆的指令不会自动获得原指令的名字,所以它是匿名的。
    没有Parent: 克隆的指令不属于任何BasicBlock。在LLVM中,指令是包含在BasicBlock中的,而BasicBlock又属于Function。一个新克隆的指令还没有被插入到任何BasicBlock中,因此它的Parent(即它所在的BasicBlock)是nullptr。

Instruction *clone() const;
  • 指令替换
void ReplaceInstWithInst(BasicBlock::InstListType &BIL, BasicBlock::iterator &BI, Instruction *I);

  - `BIL`: 这是`BasicBlock`的指令列表,代表了指令所在的序列。
  - `BI`: 这是指向要被替换指令的迭代器。
  - `I`: 这是要替换成的新指令。
void ReplaceInstWithInst(Instruction *From, Instruction *To); // 不更新迭代器,会段错误

- **参数解释**:
  - `From`: 要被替换的旧指令。
  - `To`: 新的指令。

不同Instruction的创建

alloca

alloca命令是AllocaInst类型,继承关系是

  • alloca命令是AllocaInst类型,继承关系是
AllocaInst->UnaryInstruction->Instruction

AllocaInst 是 LLVM 中用来表示栈上内存分配指令的类。它是继承自 UnaryInstructionInstruction 的一个子类。它的主要作用是在当前函数的栈帧上分配内存空间,通常用于存储局部变量。

下面是一些关于如何使用 AllocaInst 类的详细说明和示例:

类的继承关系

AllocaInstInstruction 的子类,具体继承关系如下:

  • AllocaInst -> UnaryInstruction -> Instruction -> User -> Value

AllocaInst 的构造函数

AllocaInst 提供了多个构造函数,允许你在创建对象时指定不同的参数。常用的构造函数包括:

  1. 类型和地址空间

    AllocaInst(Type *Ty, unsigned AddrSpace, const Twine &Name, Instruction *InsertBefore);
    AllocaInst(Type *Ty, unsigned AddrSpace, const Twine &Name, BasicBlock *InsertAtEnd);
    
  2. 类型、地址空间和数组大小

    AllocaInst(Type *Ty, unsigned AddrSpace, Value *ArraySize, Align Align, const Twine &Name = "", Instruction *InsertBefore = nullptr);
    AllocaInst(Type *Ty, unsigned AddrSpace, Value *ArraySize, Align Align, const Twine &Name, BasicBlock *InsertAtEnd);
    

参数说明

  • Type *Ty: 要分配的内存类型。
  • unsigned AddrSpace: 地址空间,可以通过 ModulegetDataLayout().getAllocaAddrSpace() 方法获取。
  • Value *ArraySize: 数组大小,通常使用 ConstantInt 表示单个元素时大小为 1。
  • Align Align: 内存对齐,可以指定对齐值。
  • const Twine &Name: 指令的名称。
  • Instruction *InsertBefore: 将新指令插入到某个指令之前。
  • BasicBlock *InsertAtEnd: 将新指令插入到基本块结束处。

使用示例

创建一个 AllocaInst
// 创建一个上下文
LLVMContext context;

// 获取数据布局和地址空间
Module *module = /* 获取或创建一个 Module */;
unsigned addrSpace = module->getDataLayout().getAllocaAddrSpace();

// 创建一个基本块
BasicBlock *entryBlock = /* 获取或创建一个 BasicBlock */;

// 创建一个常量整数,表示数组大小
Value* intValue = ConstantInt::get(context, APInt(32, 1));

// 创建 AllocaInst 实例
AllocaInst *allocaInst = new AllocaInst(
    IntegerType::get(context, 32), // 类型为 int32
    addrSpace,                     // 地址空间
    intValue,                      // 数组大小
    Align(4),                      // 对齐
    "",                            // 名称
    entryBlock                     // 插入位置
);

// 设置对齐
allocaInst->setAlignment(Align(4));

解释

  • 地址空间: 通过 ModuleDataLayout 获取到 AllocaAddrSpace
  • 数组大小: 使用 ConstantInt::get 创建 ConstantInt 表示分配单个元素。
  • 对齐: 可以在构造时指定对齐,也可以之后通过 setAlignment 设置。

这种用法在 LLVM IR 中生成如下指令:

%1 = alloca i32, align 4

store

在LLVM中,StoreInst 是一种用于执行存储操作的指令,表示将一个值存储到指定的内存位置。它继承自 Instruction 类,并且比 Instruction 多了一个 SSID 成员,用于表示同步作用域(SyncScope::ID),这在多线程环境中非常重要。

1. StoreInst 的继承结构

StoreInst 继承自 Instruction,因此它包含了 Instruction 类的所有成员,并且额外增加了一个 SSID 成员。SSIDSyncScope::ID 类型的,它用于指定同步范围(如系统范围或单线程范围),在存储操作涉及到线程同步时,这个字段会派上用场。

2. StoreInst 的构造函数

StoreInst 提供了多个构造函数,允许用户创建包含不同参数的存储指令。常见的构造函数如下:

  • StoreInst(Value *Val, Value *Ptr, Instruction *InsertBefore);
  • StoreInst(Value *Val, Value *Ptr, BasicBlock *InsertAtEnd);
  • StoreInst(Value *Val, Value *Ptr, bool isVolatile, Instruction *InsertBefore);
  • StoreInst(Value *Val, Value *Ptr, bool isVolatile, BasicBlock *InsertAtEnd);
  • StoreInst(Value *Val, Value *Ptr, bool isVolatile, Align Align, Instruction *InsertBefore = nullptr);
  • StoreInst(Value *Val, Value *Ptr, bool isVolatile, Align Align, BasicBlock *InsertAtEnd);
  • StoreInst(Value *Val, Value *Ptr, bool isVolatile, Align Align, AtomicOrdering Order, SyncScope::ID SSID = SyncScope::System, Instruction *InsertBefore = nullptr);
  • StoreInst(Value *Val, Value *Ptr, bool isVolatile, Align Align, AtomicOrdering Order, SyncScope::ID SSID, BasicBlock *InsertAtEnd);

这些构造函数的参数通常包括:

  • Val:要存储的值。
  • Ptr:存储目标(即内存地址)。
  • isVolatile:是否为易失性存储。若为 true,编译器在优化过程中不能移除或重新排序此存储操作。
  • Align:对齐方式。
  • AtomicOrdering:原子操作的顺序(如顺序一致性、获取-释放等)。
  • SSID:同步作用域的ID,默认是系统范围的 SyncScope::System
  • InsertBeforeInsertAtEnd:指定指令插入的位置,可以是插入到某一指令之前或某个基本块的末尾。

3. 代码示例

StoreInst *st0 = new StoreInst(param1, ptr4, false, entryBlock);
st0->setAlignment(Align(4));
  • param1: 是一个指向 Value 的指针,表示要存储的值。
  • ptr4: 是一个指向 Value 的指针,表示存储的目标地址,也就是 param1 将被存储到这个地址中。
  • false: 表示这个存储操作不是易失性的(非 volatile)。
  • entryBlock: 是一个 BasicBlock,表示指令将插入到这个基本块的末尾。
  • st0->setAlignment(Align(4)): 设置存储操作的对齐方式为 4 字节。

4. 解释

这段代码的作用是创建一个 StoreInst 对象,该对象表示将 param1 的值存储到 ptr4 指向的内存地址中,并且这个存储操作将被插入到基本块 entryBlock 的末尾。

  • StoreInst *st0 = new StoreInst(param1, ptr4, false, entryBlock);: 这行代码创建了一个新的 StoreInst 实例,表示将 param1 存储到 ptr4 中,并将其插入到 entryBlock 的末尾。
  • st0->setAlignment(Align(4));: 这行代码设置了存储操作的对齐方式为 4 字节。对齐方式在内存操作中很重要,特别是在处理不同架构和优化时。

load

在LLVM中,LoadInst 是一种用于从内存中加载值的指令。与 StoreInst 类似,LoadInst 继承自 Instruction,并且比 Instruction 多了一个 SSID 成员,用于表示同步作用域(SyncScope::ID)。这个成员在多线程环境中非常重要,因为它用于指定加载操作的同步范围。

1. LoadInst 的继承结构

LoadInst 继承自 UnaryInstruction,而 UnaryInstruction 继承自 InstructionLoadInstInstruction 多了一个 SSID 成员,它用于指定同步作用域。这在处理多线程程序时非常关键,特别是涉及到原子操作和内存序列时。

2. LoadInst 的构造函数

LoadInst 提供了多种构造函数,允许根据不同的需求来创建加载指令。常见的构造函数包括:

  • LoadInst(Type *Ty, Value *Ptr, const Twine &NameStr, Instruction *InsertBefore);
  • LoadInst(Type *Ty, Value *Ptr, const Twine &NameStr, BasicBlock *InsertAtEnd);
  • LoadInst(Type *Ty, Value *Ptr, const Twine &NameStr, bool isVolatile, Instruction *InsertBefore);
  • LoadInst(Type *Ty, Value *Ptr, const Twine &NameStr, bool isVolatile, BasicBlock *InsertAtEnd);
  • LoadInst(Type *Ty, Value *Ptr, const Twine &NameStr, bool isVolatile, Align Align, Instruction *InsertBefore = nullptr);
  • LoadInst(Type *Ty, Value *Ptr, const Twine &NameStr, bool isVolatile, Align Align, BasicBlock *InsertAtEnd);
  • LoadInst(Type *Ty, Value *Ptr, const Twine &NameStr, bool isVolatile, Align Align, AtomicOrdering Order, SyncScope::ID SSID = SyncScope::System, Instruction *InsertBefore = nullptr);
  • LoadInst(Type *Ty, Value *Ptr, const Twine &NameStr, bool isVolatile, Align Align, AtomicOrdering Order, SyncScope::ID SSID, BasicBlock *InsertAtEnd);

这些构造函数的参数意义如下:

  • Ty: 加载数据的类型。
  • Ptr: 指向要从中加载数据的内存位置的指针。
  • NameStr: 指令的名称(通常用于调试)。
  • isVolatile: 是否为易失性加载操作。若为 true,编译器在优化过程中不能移除或重新排序此加载操作。
  • Align: 对齐方式。
  • AtomicOrdering: 原子操作的顺序(如顺序一致性、获取-释放等)。
  • SSID: 同步作用域的ID,默认是系统范围的 SyncScope::System
  • InsertBeforeInsertAtEnd: 指定指令插入的位置,可以是插入到某一指令之前或某个基本块的末尾。

3. 代码示例

LoadInst *ld0 = new LoadInst(IntegerType::get(context, 32), ptr4, "",false, entryBlock);
ld0->setAlignment(Align(4));
  • IntegerType::get(context, 32): 创建一个32位宽的整数类型。
  • ptr4: 是一个指向 Value 的指针,表示要从中加载数据的内存地址。
  • "": 表示指令的名称为空字符串。
  • false: 表示这个加载操作不是易失性的(非 volatile)。
  • entryBlock: 是一个 BasicBlock,表示指令将插入到这个基本块的末尾。

ld0->setAlignment(Align(4)): 设置加载操作的对齐方式为4字节。

add

在LLVM中,BinaryOperator 是一个用于表示二元算术操作(如加法、减法、乘法等)的类。它继承自 Instruction 类,并且不引入额外的数据成员(即没有自己的 data 域)。BinaryOperator 主要用于创建和管理二元运算指令。

1. BinaryOperator 的创建

BinaryOperator 提供了静态工厂方法 Create,用于创建具体的二元操作指令。常用的两个 Create 方法如下:

static BinaryOperator *Create(BinaryOps Op, Value *S1, Value *S2,
                              const Twine &Name = Twine(),
                              Instruction *InsertBefore = nullptr);

static BinaryOperator *Create(BinaryOps Op, Value *S1, Value *S2,
                              const Twine &Name, BasicBlock *InsertAtEnd);
  • Op: 表示要执行的二元操作类型,这个类型是 BinaryOps 枚举值。例如,加法操作是 Instruction::Add
  • S1S2: 这是两个操作数,通常是指向 Value 对象的指针,表示要参与运算的值。
  • Name: 是一个 Twine 类型的对象,用于设置操作指令的名称。这个参数是可选的,通常用于生成更具可读性的LLVM IR代码。
  • InsertBefore: 指定将这条指令插入到现有指令之前。
  • InsertAtEnd: 指定将这条指令插入到基本块的末尾。

2. BinaryOps 枚举

BinaryOps 是一个枚举类型,它定义了所有的二元操作指令类型。这个枚举包括了各种常见的操作符,如加法 (Add)、减法 (Sub)、乘法 (Mul)、除法 (Div) 等。

枚举的定义通常在 Instruction.def 文件中通过宏展开生成,例如:

enum BinaryOps {
#define FIRST_BINARY_INST(N) BinaryOpsBegin = N,
#define HANDLE_BINARY_INST(N, OPC, CLASS) OPC = N,
#define LAST_BINARY_INST(N) BinaryOpsEnd = N+1
#include "llvm/IR/Instruction.def"
};

其中 Instruction::Add 是一个 BinaryOps 枚举值,表示加法操作。

3. 代码示例解析

BinaryOperator *add1 = BinaryOperator::Create(Instruction::Add, ld0, ld1, "", entryBlock);
  • Instruction::Add: 这是一个 BinaryOps 枚举值,表示加法操作。
  • ld0ld1: 这两个值分别是两个操作数,通常是 Value* 类型的指针,表示要相加的两个值。这些值可能是从之前的 LoadInst 或其他指令中获取的。
  • "": 这是指令的名称,用于生成的LLVM IR中。如果没有特别指定,可以留空字符串。
  • entryBlock: 这是一个指向 BasicBlock 的指针,表示将指令插入到该基本块的末尾。

4. 解释

这段代码的作用是创建一个加法操作的 BinaryOperator 对象,表示将 ld0ld1 这两个操作数相加,并将生成的加法指令插入到 entryBlock 基本块的末尾。

  • 创建加法指令: BinaryOperator::Create(Instruction::Add, ld0, ld1, "", entryBlock) 创建了一个加法指令,将 ld0ld1 相加。
  • 插入基本块: 这个加法指令会被插入到 entryBlock 的末尾。

这条指令对应的LLVM IR可能类似于:

%add1 = add i32 %ld0, %ld1
  • %add1 是生成的加法指令的结果。
  • i32 表示操作数是32位整数。
  • %ld0%ld1 是要相加的两个操作数。

icmp

在LLVM中,ICmpInst 是用于表示整数比较操作的指令。它继承自 CmpInst,并且没有增加额外的数据成员。ICmpInst 使用 Predicate 来指定具体的比较类型,例如等于、不等于、大于、小于等。

1. ICmpInst 的构造函数

ICmpInst 提供了多个构造函数,用于创建整数比较指令。这些构造函数的作用是生成一条比较指令,将其插入到指定的位置(例如某个基本块的末尾)。

以下是构造函数原型的详细说明:

ICmpInst(
  Instruction *InsertBefore,  // 要插入的指令之前的位置
  Predicate pred,             // 比较谓词,表示比较类型
  Value *LHS,                 // 左操作数
  Value *RHS,                 // 右操作数
  const Twine &NameStr = ""   // 指令名称
)
  • InsertBefore: 指定将比较指令插入到哪条现有指令之前。
  • pred: 指定比较的类型,例如 ICMP_EQ 表示等于,ICMP_SGT 表示有符号大于等。
  • LHS: 左操作数(通常是一个 Value 指针)。
  • RHS: 右操作数(通常是一个 Value 指针)。
  • NameStr: 可选参数,指令的名称,通常用于调试或生成的LLVM IR的可读性。

2. Predicate 枚举

Predicate 枚举定义了所有可能的比较操作符。对于整数比较,常见的值包括:

  • ICMP_EQ: 等于
  • ICMP_NE: 不等于
  • ICMP_UGT: 无符号大于
  • ICMP_UGE: 无符号大于或等于
  • ICMP_ULT: 无符号小于
  • ICMP_ULE: 无符号小于或等于
  • ICMP_SGT: 有符号大于
  • ICMP_SGE: 有符号大于或等于
  • ICMP_SLT: 有符号小于
  • ICMP_SLE: 有符号小于或等于

这些枚举值用于指定在比较操作中应该执行哪种类型的比较。

3. 代码示例解析

ICmpInst *icmp = new ICmpInst(
  *entryBlock,                         // 指令插入的基本块
  ICmpInst::ICMP_SGT,                  // 使用有符号大于的比较
  add1,                                // 左操作数
  ConstantInt::get(context, APInt(32, 100)),  // 右操作数,常量100
  ""
);
  • *entryBlock: 这表示要将指令插入到 entryBlock 基本块的末尾。
  • ICmpInst::ICMP_SGT: 这里指定了有符号大于 (signed greater than) 的比较谓词,表示将比较 add1100,判断 add1 是否大于 100
  • add1: 这是左操作数,通常是一个 Value* 类型的指针,它可能是之前某个计算指令的结果。
  • ConstantInt::get(context, APInt(32, 100)): 这是右操作数,表示一个32位宽、值为100的常量整数。ConstantInt::get 生成一个 ConstantInt 对象,这是LLVM中表示常量整数的方式。

4. 解释

这段代码的作用是创建一条整数比较指令,它会判断 add1 是否大于100。假设 add1 的类型是32位整数,那么生成的LLVM IR指令可能类似于:

%cmp = icmp sgt i32 %add1, 100
  • %cmp 是生成的比较指令的结果。
  • icmp sgt i32 表示这是一个有符号大于(signed greater than)的比较操作。
  • %add1 是左操作数,100 是右操作数。

branch

在LLVM中,BranchInst 是用于表示分支(跳转)操作的指令。BranchInst 没有额外的数据成员(即没有自己的 data 域),它的所有状态和行为都通过继承自 Instruction 类来实现。BranchInst 可以表示无条件分支(跳转到一个目标块)或者有条件分支(根据条件选择跳转到不同的目标块)。

1. BranchInst 的创建方法

BranchInst 提供了多个静态方法 Create,用于创建分支指令。具体的方法如下:

  • 无条件分支: 只跳转到一个目标块。

    static BranchInst *Create(BasicBlock *IfTrue, Instruction *InsertBefore = nullptr);
    static BranchInst *Create(BasicBlock *IfTrue, BasicBlock *InsertAtEnd);
    
    • IfTrue: 目标基本块,指定无条件跳转的目的地。
    • InsertBefore: 将指令插入到指定的现有指令之前。
    • InsertAtEnd: 将指令插入到指定基本块的末尾。
  • 有条件分支: 根据条件跳转到不同的目标块。

    static BranchInst *Create(BasicBlock *IfTrue, BasicBlock *IfFalse,
                              Value *Cond, Instruction *InsertBefore = nullptr);
    static BranchInst *Create(BasicBlock *IfTrue, BasicBlock *IfFalse,
                              Value *Cond, BasicBlock *InsertAtEnd);
    
    • IfTrue: 条件为真的时候跳转的目标块。
    • IfFalse: 条件为假的时候跳转的目标块。
    • Cond: 条件值(通常是一个比较结果,例如 ICmpInst 的结果)。
    • InsertBefore: 将指令插入到指定的现有指令之前。
    • InsertAtEnd: 将指令插入到指定基本块的末尾。

2. 代码示例解析

BranchInst::Create(block10, block19, icmp, entryBlock);
  • block10: 这是条件为真时将要跳转的目标基本块。
  • block19: 这是条件为假时将要跳转的目标基本块。
  • icmp: 这是条件值,通常是一个 Value*,表示分支条件。在这个例子中,它可能是一个 ICmpInst 的结果。
  • entryBlock: 这是一个 BasicBlock*,指定将该分支指令插入到的基本块。在这个例子中,分支指令会插入到 entryBlock 的末尾。

3. 解释

这段代码的作用是创建一条有条件的分支指令,它将根据 icmp 计算的条件跳转到 block10block19

  • 条件判断与跳转: 如果 icmp 结果为真(即条件成立),则跳转到 block10。如果结果为假(即条件不成立),则跳转到 block19
  • 插入位置: 这条指令会被插入到 entryBlock 基本块的末尾。

生成的LLVM IR代码可能类似于:

br i1 %icmp, label %block10, label %block19
  • br 表示分支指令。
  • i1 %icmp 表示条件值,i1 是1位的布尔值类型,%icmp 是条件值。
  • label %block10 是条件为真时跳转的目标基本块。
  • label %block19 是条件为假时跳转的目标基本块。

ret

在LLVM中,ReturnInst 是用于表示函数返回操作的指令。ReturnInst 是一个没有额外数据成员的类(即无data域),它主要通过继承自 Instruction 类来实现功能。ReturnInst 可以表示带有返回值的返回操作(例如从函数返回一个整数)或不带返回值的返回操作(返回 void 类型)。

1. ReturnInst 的创建方法

ReturnInst 提供了一些静态方法 Create,用于创建返回指令。具体的创建方法如下:

static ReturnInst* Create(LLVMContext &C, Value *retVal = nullptr,
                          Instruction *InsertBefore = nullptr) {
  return new(!!retVal) ReturnInst(C, retVal, InsertBefore);
}

static ReturnInst* Create(LLVMContext &C, Value *retVal,
                          BasicBlock *InsertAtEnd) {
  return new(!!retVal) ReturnInst(C, retVal, InsertAtEnd);
}

static ReturnInst* Create(LLVMContext &C, BasicBlock *InsertAtEnd) {
  return new(0) ReturnInst(C, InsertAtEnd);
}
  • LLVMContext &C: LLVM上下文对象,管理LLVM中的全局数据。
  • Value *retVal: 可选参数,表示要返回的值。如果返回void,则这个值为 nullptr
  • Instruction *InsertBefore: 可选参数,指示将返回指令插入到某条现有指令之前。
  • BasicBlock *InsertAtEnd: 可选参数,指示将返回指令插入到某个基本块的末尾。

2. 代码示例解析

ReturnInst::Create(context, ld20, block15);
  • context: 这是LLVM的上下文对象(LLVMContext),用于管理LLVM中的全局数据。
  • ld20: 这是一个Value*类型的指针,表示要返回的值。在这个例子中,ld20 可能是一个加载指令(LoadInst)的结果,表示从某个内存位置加载的值。
  • block15: 这是一个指向 BasicBlock 的指针,表示将返回指令插入到 block15 基本块的末尾。

3. 解释

这段代码的作用是创建一条返回指令,它将 ld20 作为返回值,并将这条返回指令插入到 block15 基本块的末尾。

  • 返回值: 如果 ld20 表示一个整数值(假设是一个 i32 类型的值),那么这条返回指令将返回这个整数值。
  • 插入位置: 返回指令会被插入到 block15 基本块的末尾。

生成的LLVM IR代码可能类似于:

ret i32 %ld20
  • ret i32 %ld20 表示返回一个32位整数,%ld20 是返回的值。

如果没有返回值(即返回 void 类型),代码会是:

ReturnInst::Create(context, block15);

生成的LLVM IR代码将类似于:

ret void

call

在LLVM中,CallInst 是用来表示函数调用指令的类。它继承自 CallBase,而 CallBase 本身继承自 InstructionCallInst 主要用于表示调用函数的操作,并且可以选择性地传递参数。由于函数调用在程序中的重要性,CallInst 提供了丰富的构造函数来适应不同的使用场景。

1. CallInst 的内部结构

CallInst 继承自 CallBase,而 CallBase 具有自己的数据成员。这些数据成员包括:

  • CalledOperandOpEndIdx: 这是一个静态成员,通常用于管理操作数的索引。
  • Attrs: 它是存储调用属性(如参数属性)的数据结构。
  • FTy: 指向函数类型 (FunctionType) 的指针,表示被调用函数的类型。

这些成员帮助管理与函数调用相关的元数据、参数和操作数。

2. CallInst 的创建方法

你提供的代码片段展示了LLVM中的CallInst类的多个静态工厂方法的声明。CallInst是LLVM IR(Intermediate Representation)中的一种指令类型,用于表示函数调用。这个类有许多重载的Create方法,用于创建CallInst对象。重载方法的存在使得在不同的上下文和需求下,可以灵活地创建函数调用指令。

重载方法的分类与解释

这些重载方法大致可以分为几类,主要根据参数的不同来进行分类。下面是对每一类的解释:

1. 基本创建方法
  • 方法签名:
    static CallInst *Create(FunctionType *Ty, Value *F, const Twine &NameStr = "", Instruction *InsertBefore = nullptr);
    
  • 作用:
    • 这是最基本的创建方法,用于创建一个简单的函数调用指令。
    • 参数Ty表示函数的类型,F是实际的函数(或函数指针),NameStr是可选的名称字符串,InsertBefore是可选的参数,用于指定将指令插入到哪条指令之前。
  • 用例:
    • 适用于不需要传递参数的函数调用场景,或者函数没有参数。
2. 带参数的创建方法
  • 方法签名:
    static CallInst *Create(FunctionType *Ty, Value *Func, ArrayRef<Value *> Args, const Twine &NameStr, Instruction *InsertBefore = nullptr);
    
  • 作用:
    • 这个方法用于创建带有参数的函数调用指令。
    • 参数ArgsValue的数组,表示调用函数时传递的参数。
  • 用例:
    • 适用于函数有参数的场景。
3. 带参数和操作数包的创建方法
  • 方法签名:
    static CallInst *Create(FunctionType *Ty, Value *Func, ArrayRef<Value *> Args, ArrayRef<OperandBundleDef> Bundles = None, const Twine &NameStr = "", Instruction *InsertBefore = nullptr);
    
  • 作用:
    • 这个方法不仅可以传递参数,还可以传递“操作数包”(OperandBundleDef),这些包可以包含额外的元数据或附加信息。
  • 用例:
    • 适用于需要传递额外元数据或操作数包的高级场景。
4. 插入到基本块末尾的创建方法
  • 方法签名:
    static CallInst *Create(FunctionType *Ty, Value *F, const Twine &NameStr, BasicBlock *InsertAtEnd);
    
  • 作用:
    • 这个方法用于创建一个调用指令,并将其插入到指定基本块的末尾。
    • 参数InsertAtEnd是一个BasicBlock,表示要插入的基本块。
  • 用例:
    • 适用于在基本块末尾插入指令的场景。
5. 使用FunctionCallee类型的创建方法
  • 方法签名:
    static CallInst *Create(FunctionCallee Func, const Twine &NameStr = "", Instruction *InsertBefore = nullptr);
    
  • 作用:
    • FunctionCallee是LLVM中的一个便利类型,封装了函数类型和函数的指针,简化了函数调用指令的创建。
  • 用例:
    • 适用于使用FunctionCallee类型的场景,降低手动获取函数类型的复杂度。
6. 带操作数包和替换操作数包的创建方法
  • 方法签名:
    static CallInst *Create(CallInst *CI, ArrayRef<OperandBundleDef> Bundles, Instruction *InsertPt = nullptr);
    static CallInst *CreateWithReplacedBundle(CallInst *CI, OperandBundleDef Bundle, Instruction *InsertPt = nullptr);
    
  • 作用:
    • 这些方法用于创建一个新的调用指令,基于已有的调用指令CI,并添加或替换操作数包。
  • 用例:
    • 适用于需要复制现有指令并修改其操作数包的场景。
实例
Function* myAddFunc = module->getFunction("myadd");
Value *arg[] = {old_ope->getOperand(0), old_ope->getOperand(1)};
CallInst *myaddCall = CallInst::Create(myAddFunc, arg, "");
  1. 从当前 LLVM 模块中获取名为 "myadd" 的函数指针。
  2. 通过从某个现有操作(old_ope)中提取两个操作数,作为调用 "myadd" 函数的参数。
  3. 创建一个新的函数调用指令,调用 "myadd" 函数,并传递提取的两个参数。

最后,myaddCall 是创建的调用指令对象,它可以在 LLVM IR 中被插入到合适的位置,以便在生成的代码中执行这个函数调用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2141204.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

无心剑英译张九龄《望月怀远》

望月怀远 Watching the Moon and Missing You Far Away 张九龄 By Zhang Jiuling 海上生明月&#xff0c;天涯共此时 情人怨遥夜&#xff0c;竟夕起相思 灭烛怜光满&#xff0c;披衣觉露滋 不堪盈手赠&#xff0c;还寝梦佳期 The bright moon rises from the sea, So far apart…

【宠物小精灵之收服(待更新)】

题目 代码 #include <bits/stdc.h> using namespace std; int f[1010][510]; int main() {int n, m, k;cin >> n >> m >> k;int c 0;for(int i 1; i < k; i){int cost, hp;cin >> cost >> hp;for(int j n; j > cost; j--){for(i…

java技术栈介绍

Java技术栈是一个庞大而丰富的生态系统&#xff0c;它包含了从基础语言特性到高级框架、库和工具的整个集合。这个技术栈为开发者提供了构建各种类型应用&#xff08;包括企业级应用、Web应用、移动应用、大数据应用等&#xff09;所需的全部组件。以下是对Java技术栈的一个更详…

【webpack4系列】编写可维护的webpack构建配置(四)

文章目录 构建配置包设计功能模块设计和目录结构设计功能模块设计目录结构设计 使用ESLint规范构建脚本冒烟测试介绍和实际运用冒烟测试 (smoke testing)冒烟测试执行判断构建是否成功判断基本功能是否正常 单元测试和测试覆盖率测试框架编写单元测试用例单元测试接入测试覆盖率…

新发布!Streamlab X系列第二版:短视频电影直播全能主题,赋能苹果CMS

Streamlab X系列第二版强势登陆&#xff0c;专为苹果CMS设计的短视频与电影直播融合的多功能主题模板震撼首发&#xff01; 这款主题以其非凡的适应性和极致的视觉效果&#xff0c;重新定义了网站构建的边界。采用独家精心研发的框架&#xff0c;它能够无缝跨越从移动设备到超…

在实际LabVIEW开发中,哪些算法是常用的?

在LabVIEW的实际开发中&#xff0c;常用的算法主要集中在数据处理、控制系统、信号处理、图像处理等领域。以下是一些常用算法的介绍&#xff1a; 1. PID控制算法 PID&#xff08;比例-积分-微分&#xff09;控制是LabVIEW中常用的算法之一&#xff0c;广泛应用于工业自动化和…

【四】k8s部署 TDengine集群

k8s部署 TDengine集群 目录 k8s部署 TDengine集群 一、在 Kubernetes 上部署 TDengine 集群 第一步&#xff1a;创建命名空间 第二步&#xff1a;从yaml创建有状态服务 StatefulSet 第三步&#xff1a;配置 Service 服务 二、集群测试 一、在 Kubernetes 上部署 TDengine…

【数据结构-扫描线】力扣57. 插入区间

给你一个 无重叠的 &#xff0c;按照区间起始端点排序的区间列表 intervals&#xff0c;其中 intervals[i] [starti, endi] 表示第 i 个区间的开始和结束&#xff0c;并且 intervals 按照 starti 升序排列。同样给定一个区间 newInterval [start, end] 表示另一个区间的开始和…

Java数据存储结构——二叉查找树

文章目录 22.1.2二叉查找树22.1.2.1 概述22.1.2.1二叉查找树添加节点22.1.2.2二叉查找树查找节点22.1.2.3 二叉树遍历22.1.2.4 二叉查找树的弊端 22.1.2二叉查找树 22.1.2.1 概述 二叉查找树,又称二叉排序树或者二叉搜索树 二叉查找树的特点&#xff1a; 每一个节点上最多有…

25. 网格模型(三角形概念)

给大家演示网格模型Mesh渲染自定义几何体BufferGeometry的顶点坐标,通过这样一个例子帮助大家建立**三角形(面)**的概念 三角形(面) 网格模型Mesh其实就一个一个三角形(面)拼接构成。使用网格模型Mesh渲染几何体geometry&#xff0c;就是几何体所有顶点坐标三个为一组&#x…

【农信网-注册/登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

支持升降压型、升压、降压、60V的1.2MHz频率LED恒流驱动器LGS63040、LGS63042

前言&#xff1a; 一款支持升降压的LED驱动器。适合单节锂电池使用。当然不仅于此。SOT23-5封装的外形和丝印 特性 宽输入电压、宽输出电压范围&#xff1a;3.0V-60V 支持 PWM 调光及模拟调光 内置 60V/350mΩ低侧金属氧化物半导体场效应晶体管 1.2MHz固定工作频率 逐周期峰值…

MiniMaxi-共创智能新体验新手入门

新手快速入门 注册指南 个人用户 直接注册即可。 企业团队 主账号&#xff1a;注册时填写的姓名与手机号将成为企业账号的管理员。子账号&#xff1a;在用户中心创建&#xff0c;数量不限。 主账号与子账号权益 相同权益&#xff1a;子账号享有与主账号相同的使用权益和速…

积分电路和滤波电路的主要区别点和应用场合

文章目录 前言一、滤波电路的分类二、有源滤波器和无源滤波器的优缺点和实用范围三、积分电路3.1 无源积分电路3.2 RC充放电的电路响应3.2.1 RC电路的零状态响应3.2.2 RC电路的零输入响应3.2.3 RC电路的全响应3.2.4 选取合适的时间常数四 、无源RC低通滤波器4.3.1 截止频率推导…

AI 最佳实践全栈式从0到1开发个人博客系统

以下是「豆包 MarsCode 体验官」优秀文章&#xff0c;作者我喺小VIE。 前言 近年来随着人工智能&#xff08;AI&#xff09;大模型的迅猛发展&#xff0c;大模型在自然语言处理、计算机视觉、语音识别等领域的表现逐渐达到甚至超越人类的水平。在大模型的应用场景之下&#xf…

Gradio 中如何让 Chatbot 自动滚动

在 Gradio 中&#xff0c; Chatbot 是对话组件&#xff0c;接受 history 参数&#xff0c;在目前版本中 &#xff08;gradio4.44.0&#xff09;&#xff0c;不支持自动滚动&#xff0c;用起来很不方便&#xff0c;该功能在社区中已经提出了&#xff0c;目前该功能还没有发布。本…

Android Framework(六)WMS-窗口显示流程——窗口内容绘制与显示

文章目录 窗口显示流程明确目标 窗户内容绘制与显示流程窗口Surface状态完整流程图 应用端处理finishDrawingWindow 的触发 system_service处理WindowState状态 -- COMMIT_DRAW_PENDING本次layout 流程简述 窗口显示流程 目前窗口的显示到了最后一步。 在 addWindow 流程中&…

【ESP32】Arduino开发 | 中断矩阵+按键输入中断例程

对于中断矩阵的详细介绍会放在ESP-IDF开发文章中&#xff0c;跳转栏目目录可以找到对应文章。 1. API 1.1 绑定GPIO中断 attachInterrupt(uint8_t pin, voidFuncPtr handler, int mode); pin&#xff1a;管脚号&#xff1b;handler&#xff1a;中断处理函数&#xff1b;mode…

【OJ刷题】双指针问题5

这里是阿川的博客&#xff0c;祝您变得更强 ✨ 个人主页&#xff1a;在线OJ的阿川 &#x1f496;文章专栏&#xff1a;OJ刷题入门到进阶 &#x1f30f;代码仓库&#xff1a; 写在开头 现在您看到的是我的结论或想法&#xff0c;但在这背后凝结了大量的思考、经验和讨论 目录 1…

【Scala入门学习】基本数据类型和变量声明

1. 基本数据类型 scala 的基本类型有 9种&#xff1a; Byte、Char、Short、Int、Long、Float、Double、Boolean、Unit Scala中没有基本数据类型的概念&#xff0c;所有的类型都是对象。 AnyVal&#xff1a;代表所有基本类型。 AnyRef&#xff1a;代表所以引用类型&#xff…