最近在学习angr,里面有用到vex ir,之前对vex ir不了解,打算系统学习一遍。
1.code blocks:
代码被分割成很多小的代码片段(类型为'IRSB'),每个块表示1~50条指令。IRSB块是单入口,多出口。每个IRSB包含以下三种数据:
2.statements and expression
Statements(‘IRStmt'):代表有着副作用的操作,eg. 寄存器写,存储和分配给临时变量。
Expressiongs('IRExpr') :代表着没有副作用的操作,eg,算术运算,加载,常量
3.storage of guest state
“guest state”包含guest machine(即我们正在模拟的机器)的guest寄存器。默认情况下,它存储在VEX库用户提供的内存块中,通常称guest state(area)。要对这些寄存器进行操作,必须首先将它们从guest state读取(“Get”)为一个临时值。之后,可以将它们写回("put") guest state。
Get和Put的特征是进入guest状态的字节偏移量、一个有效给出被引用guest寄存器标识的小整数和一个指示要传输的值大小的类型。
基本的“Get”和“Put”操作足以对guest 上的正常固定寄存器建模。guest stated 的选定区域可以视为寄存器的循环数组(类型:“IRRegArray”),可以在运行时对其进行索引。这是用“GetI”和“PutI”原语完成的。这是描述旋转寄存器文件所必需的,例如x87 FPU堆栈、SPARC寄存器窗口和Itanium寄存器文件。
4.例子:考虑如下x86指令
可能的IR代码如下
IMark只是IR声明,不代表实际的指令,指明了代码的地址0x24f275,原始指令的长度7,数字0和12偏移到%eax和%ebx的guest state。(这里的0代表%eax吧)
本例中的五个 statements 是:
VEX ir 类型定义
一个常量,存储为带标记的联合体,tag 表示这是一个什么样的常数Ico是一个union,它控制着各个领域。如果 IRConst'c' 的c.tag等于Ico_U32,则它是32位常量,其值可以使用'c.Ico.U32'访问
7.call targets
描述要调用的帮助函数。名字部分纯粹是为了漂亮的打印而不是实际使用。regparms=n告诉后端被调用方已被声明为“__attribute__(regparm(n))”,尽管间接使用VEX_REGPARM(n)宏。在某些目标(x86)上,后端需要构造一个非标准序列来调用这样声明的函数。
mcx_mask 是一种记忆检查的标准。它指出在计算结果的定义时,哪些参数应该被视为“总是定义的”。mcx_mask的位0对应于args[0],位1对应于args[1]等。如果设置了位,则从定义性检查中排除相应的arg(因此“mcx”中的“x”)。
它们的含义在下面的IRExpr注释中解释。存储为标记的联合。 “标签”指示这是一种表达。 “ Iex”是持有字段的联合。 如果IRExpr'e'具有等于Iex_Load的e.tag,则它是一个负载表达式,并且可以使用'e.Iex.Load。<fieldname>'访问这些字段。
Memory Bus Events
Compare and Swap
basic block
类型环境:一堆语句、表达式等都是不完整的,没有一个环境指示每个IRTemp的类型。所以这就提供了一个。IR临时变量实际上只是无符号整数,因此它提供一个数组0。。n_类型使用-1个。
本片文章主要参考angr官方文档:
简介:
1.code blocks:
代码被分割成很多小的代码片段(类型为'IRSB'),每个块表示1~50条指令。IRSB块是单入口,多出口。每个IRSB包含以下三种数据:
-a type enviroment, 它指示IRSB中存在的每个临时值的类型。
-a list of statement, 表示对应的代码
-a jump that exits from the end the IRSB:退出IRSB
2.statements and expression
Statements(‘IRStmt'):代表有着副作用的操作,eg. 寄存器写,存储和分配给临时变量。
Expressiongs('IRExpr') :代表着没有副作用的操作,eg,算术运算,加载,常量
3.storage of guest state
“guest state”包含guest machine(即我们正在模拟的机器)的guest寄存器。默认情况下,它存储在VEX库用户提供的内存块中,通常称guest state(area)。要对这些寄存器进行操作,必须首先将它们从guest state读取(“Get”)为一个临时值。之后,可以将它们写回("put") guest state。
Get和Put的特征是进入guest状态的字节偏移量、一个有效给出被引用guest寄存器标识的小整数和一个指示要传输的值大小的类型。
基本的“Get”和“Put”操作足以对guest 上的正常固定寄存器建模。guest stated 的选定区域可以视为寄存器的循环数组(类型:“IRRegArray”),可以在运行时对其进行索引。这是用“GetI”和“PutI”原语完成的。这是描述旋转寄存器文件所必需的,例如x87 FPU堆栈、SPARC寄存器窗口和Itanium寄存器文件。
4.例子:考虑如下x86指令
可能的IR代码如下
------ IMark(0x24F275, 7, 0) ------
t3 = GET:I32(0) # get %eax, a 32-bit integer
t2 = GET:I32(12) # get %ebx, a 32-bit integer
t1 = Add32(t3,t2) # addl
PUT(0) = t1 # put %eax
IMark只是IR声明,不代表实际的指令,指明了代码的地址0x24f275,原始指令的长度7,数字0和12偏移到%eax和%ebx的guest state。(这里的0代表%eax吧)
本例中的五个 statements 是:
addl %edx,4(%eax)
------ IMark(0x4000ABA, 3, 0) ------
t3 = Add32(GET:I32(0),0x4:I32) //0表示%eax寄存器
t2 = LDle:I32(t3)
t1 = GET:I32(8) //8表示%edx寄存器
t0 = Add32(t2,t1)
STle(t3) = t0
“LDle"和"STle"中的'le'是'little-endian'的缩写
------ IMark(0x24F275, 7, 0) ------
t3 = GET:I32(0) # get %eax, a 32-bit integer
t2 = GET:I32(12) # get %ebx, a 32-bit integer
t1 = Add32(t3,t2) # addl
PUT(0) = t1 # put %eax
IMark只是IR声明,不代表实际的指令,指明了代码的地址0x24f275,原始指令的长度7,数字0和12偏移到%eax和%ebx的guest state。(这里的0代表%eax吧)
本例中的五个 statements 是:
addl %edx,4(%eax)
------ IMark(0x4000ABA, 3, 0) ------
t3 = Add32(GET:I32(0),0x4:I32) //0表示%eax寄存器
t2 = LDle:I32(t3)
t1 = GET:I32(8) //8表示%edx寄存器
t0 = Add32(t2,t1)
STle(t3) = t0
“LDle"和"STle"中的'le'是'little-endian'的缩写
addl %edx,4(%eax)
------ IMark(0x4000ABA, 3, 0) ------
t3 = Add32(GET:I32(0),0x4:I32) //0表示%eax寄存器
t2 = LDle:I32(t3)
t1 = GET:I32(8) //8表示%edx寄存器
t0 = Add32(t2,t1)
STle(t3) = t0
“LDle"和"STle"中的'le'是'little-endian'的缩写
typedef
enum {
Ity_INVALID=0x1100,
Ity_I1,
Ity_I8,
Ity_I16,
Ity_I32,
Ity_I64,
Ity_I128, /* 128-bit scalar */
Ity_F16, /* 16 bit float */
Ity_F32, /* IEEE 754 float */
Ity_F64, /* IEEE 754 double */
Ity_D32, /* 32-bit Decimal floating point */
Ity_D64, /* 64-bit Decimal floating point */
Ity_D128, /* 128-bit Decimal floating point */
Ity_F128, /* 128-bit floating point; implementation defined */
Ity_V128, /* 128-bit SIMD */
Ity_V256 /* 256-bit SIMD */
}
IRType;
/* 打印 IRType */
extern void ppIRType ( IRType );
/*获取 IRType 字节大小 */
extern Int sizeofIRType ( IRType );
/* 将 1/2/4/8 转换成Ity_I{8,16,32,64} */
extern IRType integerIRTypeOfSize ( Int szB );
IREndness用于加载IRExprs和存储IRStmts。
typedef
enum {
Iend_LE=0x1200, /* little endian */
Iend_BE /* big endian */
}
IREndness;
typedef
enum {
Ity_INVALID=0x1100,
Ity_I1,
Ity_I8,
Ity_I16,
Ity_I32,
Ity_I64,
Ity_I128, /* 128-bit scalar */
Ity_F16, /* 16 bit float */
Ity_F32, /* IEEE 754 float */
Ity_F64, /* IEEE 754 double */
Ity_D32, /* 32-bit Decimal floating point */
Ity_D64, /* 64-bit Decimal floating point */
Ity_D128, /* 128-bit Decimal floating point */
Ity_F128, /* 128-bit floating point; implementation defined */
Ity_V128, /* 128-bit SIMD */
Ity_V256 /* 256-bit SIMD */
}
IRType;
/* 打印 IRType */
extern void ppIRType ( IRType );
/*获取 IRType 字节大小 */
extern Int sizeofIRType ( IRType );
/* 将 1/2/4/8 转换成Ity_I{8,16,32,64} */
extern IRType integerIRTypeOfSize ( Int szB );
IREndness用于加载IRExprs和存储IRStmts。
typedef
enum {
Iend_LE=0x1200, /* little endian */
Iend_BE /* big endian */
}
IREndness;
常量
IRConsts在“ Const”和“ Exit” IRExprs中使用。
typedef
enum {
Ico_U1=0x1300,
Ico_U8,
Ico_U16,
Ico_U32,
Ico_U64,
Ico_F32, /* 32-bit IEEE754 floating */
Ico_F32i, /* 32-bit unsigned int to be interpreted literally
as a IEEE754 single value. */
Ico_F64, /* 64-bit IEEE754 floating */
Ico_F64i, /* 64-bit unsigned int to be interpreted literally
as a IEEE754 double value. */
Ico_V128, /* 128-bit restricted vector constant, with 1 bit
(repeated 8 times) for each of the 16 x 1-byte lanes */
Ico_V256 /* 256-bit restricted vector constant, with 1 bit
(repeated 8 times) for each of the 32 x 1-byte lanes */
}
IRConstTag;
一个常量,存储为带标记的联合体,tag 表示这是一个什么样的常数Ico是一个union,它控制着各个领域。如果 IRConst'c' 的c.tag等于Ico_U32,则它是32位常量,其值可以使用'c.Ico.U32'访问
7.call targets
描述要调用的帮助函数。名字部分纯粹是为了漂亮的打印而不是实际使用。regparms=n告诉后端被调用方已被声明为“__attribute__(regparm(n))”,尽管间接使用VEX_REGPARM(n)宏。在某些目标(x86)上,后端需要构造一个非标准序列来调用这样声明的函数。
mcx_mask 是一种记忆检查的标准。它指出在计算结果的定义时,哪些参数应该被视为“总是定义的”。mcx_mask的位0对应于args[0],位1对应于args[1]等。如果设置了位,则从定义性检查中排除相应的arg(因此“mcx”中的“x”)。
typedef
struct {
Int regparms;
const HChar* name;
void* addr;
UInt mcx_mask;
}
IRCallee;
/* Create an IRCallee. */
extern IRCallee* mkIRCallee ( Int regparms, const HChar* name, void* addr );
/* Deep-copy an IRCallee. */
extern IRCallee* deepCopyIRCallee ( const IRCallee* );
/* Pretty-print an IRCallee. */
extern void ppIRCallee ( const IRCallee* );
typedef struct _IRQop IRQop; /* forward declaration */
typedef struct _IRTriop IRTriop; /* forward declaration */
对于每种类型的表达式,可以使用ppIrExpr()来显示
不同种类的表达式:
typedef
enum {
Iex_Binder=0x1900,
Iex_Get,
Iex_GetI,
Iex_RdTmp,
Iex_Qop,
Iex_Triop,
Iex_Binop,
Iex_Unop,
Iex_Load,
Iex_Const,
Iex_ITE,
Iex_CCall,
Iex_VECRET,
Iex_BBPTR
}
IRExprTag;
它们的含义在下面的IRExpr注释中解释。存储为标记的联合。 “标签”指示这是一种表达。 “ Iex”是持有字段的联合。 如果IRExpr'e'具有等于Iex_Load的e.tag,则它是一个负载表达式,并且可以使用'e.Iex.Load。<fieldname>'访问这些字段。
struct _IRExpr {
IRExprTag tag;
union {
/* 仅用于Vex内的模式匹配。不应出现在vex之外 */
struct {
Int binder;
} Binder;
/*在guest state的固定偏移量处读取来宾寄存器。ppIRExpr输出:GET:<ty>(<offset>),例如GET:I32(0)*/
struct {
Int offset; /*guest state的偏移 */
IRType ty; /* 要读取值的类型 */
} Get;
/*
在guest state下以非固定偏移量读取来guest存器。这允许循环索引到guest state的一部分,这对于建模guest存器的身份直到运行时才知道的情况至关重要。一个例子是x87 FP寄存器堆栈。
要作为循环数组处理的guest state的一部分在IRRegArray'descr'字段中描述。它保存数组中第一个元素的偏移量、每个元素的类型和元素的数量。
数组索引是相当间接地表示的,以一种使优化变得容易的方式:作为可变部分(“ix”字段)和恒定偏移量(“bias”字段)的总和。
由于索引是循环的,实际使用的数组索引计算为(ix+bias)%数组中的元素数。
举个列子如下:
(96:8xF64)[t39,-7]
描述一个由8个F64类型值组成的数组,第一个值的guest state偏移量为96。此数组的索引位置是(t39-7)%8。
正确地获得数组大小/类型是很重要的,因为IR优化会密切关注这些信息,以便在单独的GetI和PutI事件之间建立混叠/非混叠,后者用于确定何时可以重新排序等。输入不正确的信息将导致模糊的IR优化错误。*/
ppIRExpr output: GETI<descr>[<ix>,<bias]
eg. GETI(128:8xI8)[t1,0]
struct {
IRRegArray* descr; /* 被视为循环的guest state的一部分*/
IRExpr* ix; /* 索引到数组的可变部分 */
Int bias; /* 索引到数组的固定部分*/
} GetI;
/*临时变量的存储
ppIRExpr output: t<tmp>, eg. t1*/
struct{
ITRemp tmp; /*临时变量号*/
}RdTmp;
/*四元运算。*/
ppIRExpr output: <op>(<arg1>, <arg2>, <arg3>, <arg4>),
eg. MAddF64r32(t1, t2, t3, t4)
struct {
IRQop* details;
} Qop;
/*三元运算。
ppIRExpr output: <op>(<arg1>, <arg2>, <arg3>),
eg. MulF64(1, 2.0, 3.0)
*/
struct {
IRTriop* details;
} Triop;
/*二元运算。
ppIRExpr output: <op>(<arg1>, <arg2>), eg. Add32(t1,t2)
*/
struct {
IROp op; /* op-code */
IRExpr* arg1; /* operand 1 */
IRExpr* arg2; /* operand 2 */
} Binop;
/*一元运算:ppIRExpr output: <op>(<arg>), eg. Neg8(t1)*/
struct {
IROp op; /* op-code */
IRExpr* arg; /* operand */
} Unop;
/*加载内存数据——一个普通的加载,而不是一个链接的加载。加载链接(和存储条件)由IRStmt.LLSC表示,因为加载链接有副作用,因此在语义上不是有效的IRExpr。
ppIRExpr output: LD<end>:<ty>(<addr>), eg. LDle:I32(t1)*/
struct {
IREndness end; /* Endian-ness of the load */
IRType ty; /* Type of the loaded value */
IRExpr* addr; /* Address being loaded from */
} Load;
/*常量表达式
ppIRExpr output: <con>, eg. 0x4:I32*/
struct {
IRConst* con; /* The constant itself */
} Const;
/*函数调用
‘cee'中的'name’是函数名字,主要是为了更好的打印效果,'cee'中的'addr'保存函数地址。
‘args' 是以NULL作为终结符的数字。IRType,表示参数类型。必须和函数调用相匹配。
函数调用必须满足以下几个条件
-没有副作用,函数结果只取决于传递过去的参数
-它可能不会查看或修改任何guest state,因为这样会隐藏插入器的guest state转换
-它可能无法访问guest内存,因为这会隐藏来自检测程序的guest内存事务
-它不能假定参数是按特定顺序计算的。未指定求值顺序。
原则上,允许arg向量包含一个IRExpr_VECRET(),尽管不是IRExpr_BBPTR()。但是,目前没有要求clean helper调用能够返回V128或V256值。因此这是不允许的。
ppIRExpr output: <cee>(<args>):<retty>
eg. foo{0x80489304}(t1, t2):I32*/
struct {
IRCallee* cee; /* 调用的函数. */
IRType retty; /* 返回类型. */
IRExpr** args; /* 表达式向量. */
} CCall;
/*三元if-then-else运算符。如果cond不为零,则返回iftrue,否则返回iffalse。请注意,它是严格的,即在所有情况下都会对iftrue和iffalse进行评估。
ppIRExpr output: ITE(<cond>,<iftrue>,<iffalse>),
eg. ITE(t6,t7,t8)
*/
struct {
IRExpr* cond; /* Condition */
IRExpr* iftrue; /* True expression */
IRExpr* iffalse; /* False expression */
} ITE;
} Iex;
};
IRConsts在“ Const”和“ Exit” IRExprs中使用。
typedef
enum {
Ico_U1=0x1300,
Ico_U8,
Ico_U16,
Ico_U32,
Ico_U64,
Ico_F32, /* 32-bit IEEE754 floating */
Ico_F32i, /* 32-bit unsigned int to be interpreted literally
as a IEEE754 single value. */
Ico_F64, /* 64-bit IEEE754 floating */
Ico_F64i, /* 64-bit unsigned int to be interpreted literally
as a IEEE754 double value. */
Ico_V128, /* 128-bit restricted vector constant, with 1 bit
(repeated 8 times) for each of the 16 x 1-byte lanes */
Ico_V256 /* 256-bit restricted vector constant, with 1 bit
(repeated 8 times) for each of the 32 x 1-byte lanes */
}
IRConstTag;
一个常量,存储为带标记的联合体,tag 表示这是一个什么样的常数Ico是一个union,它控制着各个领域。如果 IRConst'c' 的c.tag等于Ico_U32,则它是32位常量,其值可以使用'c.Ico.U32'访问
7.call targets
描述要调用的帮助函数。名字部分纯粹是为了漂亮的打印而不是实际使用。regparms=n告诉后端被调用方已被声明为“__attribute__(regparm(n))”,尽管间接使用VEX_REGPARM(n)宏。在某些目标(x86)上,后端需要构造一个非标准序列来调用这样声明的函数。
mcx_mask 是一种记忆检查的标准。它指出在计算结果的定义时,哪些参数应该被视为“总是定义的”。mcx_mask的位0对应于args[0],位1对应于args[1]等。如果设置了位,则从定义性检查中排除相应的arg(因此“mcx”中的“x”)。
typedef
struct {
Int regparms;
const HChar* name;
void* addr;
UInt mcx_mask;
}
IRCallee;
/* Create an IRCallee. */
extern IRCallee* mkIRCallee ( Int regparms, const HChar* name, void* addr );
/* Deep-copy an IRCallee. */
extern IRCallee* deepCopyIRCallee ( const IRCallee* );
/* Pretty-print an IRCallee. */
extern void ppIRCallee ( const IRCallee* );
typedef struct _IRQop IRQop; /* forward declaration */
typedef struct _IRTriop IRTriop; /* forward declaration */
对于每种类型的表达式,可以使用ppIrExpr()来显示
不同种类的表达式:
typedef
enum {
Iex_Binder=0x1900,
Iex_Get,
Iex_GetI,
Iex_RdTmp,
Iex_Qop,
Iex_Triop,
Iex_Binop,
Iex_Unop,
Iex_Load,
Iex_Const,
Iex_ITE,
Iex_CCall,
Iex_VECRET,
Iex_BBPTR
}
IRExprTag;
它们的含义在下面的IRExpr注释中解释。存储为标记的联合。 “标签”指示这是一种表达。 “ Iex”是持有字段的联合。 如果IRExpr'e'具有等于Iex_Load的e.tag,则它是一个负载表达式,并且可以使用'e.Iex.Load。<fieldname>'访问这些字段。
struct _IRExpr {
IRExprTag tag;
union {
/* 仅用于Vex内的模式匹配。不应出现在vex之外 */
struct {
Int binder;
} Binder;
/*在guest state的固定偏移量处读取来宾寄存器。ppIRExpr输出:GET:<ty>(<offset>),例如GET:I32(0)*/
struct {
Int offset; /*guest state的偏移 */
IRType ty; /* 要读取值的类型 */
} Get;
/*
在guest state下以非固定偏移量读取来guest存器。这允许循环索引到guest state的一部分,这对于建模guest存器的身份直到运行时才知道的情况至关重要。一个例子是x87 FP寄存器堆栈。
要作为循环数组处理的guest state的一部分在IRRegArray'descr'字段中描述。它保存数组中第一个元素的偏移量、每个元素的类型和元素的数量。
数组索引是相当间接地表示的,以一种使优化变得容易的方式:作为可变部分(“ix”字段)和恒定偏移量(“bias”字段)的总和。
由于索引是循环的,实际使用的数组索引计算为(ix+bias)%数组中的元素数。
举个列子如下:
(96:8xF64)[t39,-7]
描述一个由8个F64类型值组成的数组,第一个值的guest state偏移量为96。此数组的索引位置是(t39-7)%8。
正确地获得数组大小/类型是很重要的,因为IR优化会密切关注这些信息,以便在单独的GetI和PutI事件之间建立混叠/非混叠,后者用于确定何时可以重新排序等。输入不正确的信息将导致模糊的IR优化错误。*/
ppIRExpr output: GETI<descr>[<ix>,<bias]
eg. GETI(128:8xI8)[t1,0]
struct {
IRRegArray* descr; /* 被视为循环的guest state的一部分*/
IRExpr* ix; /* 索引到数组的可变部分 */
Int bias; /* 索引到数组的固定部分*/
} GetI;
/*临时变量的存储
ppIRExpr output: t<tmp>, eg. t1*/
struct{
ITRemp tmp; /*临时变量号*/
}RdTmp;
/*四元运算。*/
ppIRExpr output: <op>(<arg1>, <arg2>, <arg3>, <arg4>),
eg. MAddF64r32(t1, t2, t3, t4)
struct {
IRQop* details;
} Qop;
/*三元运算。
ppIRExpr output: <op>(<arg1>, <arg2>, <arg3>),
eg. MulF64(1, 2.0, 3.0)
*/
struct {
IRTriop* details;
} Triop;
/*二元运算。
ppIRExpr output: <op>(<arg1>, <arg2>), eg. Add32(t1,t2)
*/
struct {
IROp op; /* op-code */
IRExpr* arg1; /* operand 1 */
IRExpr* arg2; /* operand 2 */
} Binop;
/*一元运算:ppIRExpr output: <op>(<arg>), eg. Neg8(t1)*/
struct {
IROp op; /* op-code */
IRExpr* arg; /* operand */
} Unop;
/*加载内存数据——一个普通的加载,而不是一个链接的加载。加载链接(和存储条件)由IRStmt.LLSC表示,因为加载链接有副作用,因此在语义上不是有效的IRExpr。
ppIRExpr output: LD<end>:<ty>(<addr>), eg. LDle:I32(t1)*/
struct {
IREndness end; /* Endian-ness of the load */
IRType ty; /* Type of the loaded value */
IRExpr* addr; /* Address being loaded from */
} Load;
/*常量表达式
ppIRExpr output: <con>, eg. 0x4:I32*/
struct {
IRConst* con; /* The constant itself */
} Const;
/*函数调用
‘cee'中的'name’是函数名字,主要是为了更好的打印效果,'cee'中的'addr'保存函数地址。
‘args' 是以NULL作为终结符的数字。IRType,表示参数类型。必须和函数调用相匹配。
函数调用必须满足以下几个条件
-没有副作用,函数结果只取决于传递过去的参数
-它可能不会查看或修改任何guest state,因为这样会隐藏插入器的guest state转换
-它可能无法访问guest内存,因为这会隐藏来自检测程序的guest内存事务
-它不能假定参数是按特定顺序计算的。未指定求值顺序。
原则上,允许arg向量包含一个IRExpr_VECRET(),尽管不是IRExpr_BBPTR()。但是,目前没有要求clean helper调用能够返回V128或V256值。因此这是不允许的。
ppIRExpr output: <cee>(<args>):<retty>
eg. foo{0x80489304}(t1, t2):I32*/
struct {
IRCallee* cee; /* 调用的函数. */
IRType retty; /* 返回类型. */
IRExpr** args; /* 表达式向量. */
} CCall;
/*三元if-then-else运算符。如果cond不为零,则返回iftrue,否则返回iffalse。请注意,它是严格的,即在所有情况下都会对iftrue和iffalse进行评估。
ppIRExpr output: ITE(<cond>,<iftrue>,<iffalse>),
eg. ITE(t6,t7,t8)
*/
struct {
IRExpr* cond; /* Condition */
IRExpr* iftrue; /* True expression */
IRExpr* iffalse; /* False expression */
} ITE;
} Iex;
};
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2020-3-31 09:31
被wwzzww编辑
,原因:
上传的附件: