ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

cangjie_stdx stdx.aspect_cj AOP 开发实战:InsertAtEntry、InsertAtExit 与 ReplaceFuncBody 的两次编译插桩

cangjie_stdx stdx.aspect_cj AOP 开发实战:InsertAtEntry、InsertAtExit 与 ReplaceFuncBody 的两次编译插桩 标准库网络密码学并发编程【免费下载链接】cangjie_stdx仓颉编程语言提供了 stdx 模块该模块提供了网络、安全等领域的通用能力。项目地址https://gitcode.com/Cangjie/cangjie_stdx点击查看免费下载本篇指南基于 cangjie_stdx 仓库的 AOP 开发示例文档完整讲解stdx.aspect_cj包提供的三个切面注解InsertAtEntry、InsertAtExit、ReplaceFuncBody的使用方法、两次编译收集 织入的cjc命令流程与预期运行结果并结合 收集插件 与 织入插件 的源码剖析切面信息文件.annoinfo的生成与织入时机。读完本文你可以独立完成“在指定函数入口/出口插桩、整体替换函数体”的 AOP 场景并理解其底层 CHIR 级别的实现机制。stdx.aspect_cj 概述注解 两个编译插件stdx.aspect_cj包提供了仓颉语言中面向切面编程Aspect Oriented Programming, AOP的注解能力配合libstdx.collect_aspects和libstdx.weave_aspects两个编译插件可以对目标函数进行入口插桩、退出前插桩、替换函数体。三个注解类的定义位于 aspect_cj.cj注解类切面能力关键参数InsertAtEntry在注解所指定方法的入口织入对被注解标注函数的调用packageName、className、methodName、isStatic、recursive、funcTypeStrInsertAtExit在注解所指定方法的退出点织入对被注解标注函数的调用同上ReplaceFuncBody将注解所指定方法的方法体整体替换为对被注解标注函数的调用packageName、className、methodName、isStatic、recursive无funcTypeStr需要注意的参数语义注解标注在“切面函数”你要织入进去的代码所在函数上而注解参数描述的是“被织入的目标函数”packageName目标函数所属包名如default、std.coreclassName目标函数是成员函数时填所属类名是全局函数时填空字符串methodName目标函数名isStatic目标函数是否为静态成员函数recursive目标函数是成员函数时是否对子类 override 版本也织入全局函数场景应填falsefuncTypeStr仅 Insert 系列目标函数的类型字符串不含空格自定义类型需写全包名如(Int64,std.core.Object)-Unit不含隐式this形参。这两个编译插件以动态库形式随 stdx 提供不同平台对应.so/.dll/.dylib使用流程固定为两步第一次编译使用libstdx.collect_aspects编译时收集所有切面、连接点信息落盘为.annoinfo文件第二次编译使用libstdx.weave_aspects读取已收集的信息把切面织入到连接点。包概述文档 中同时注明stdx.aspect_cj当前处于开发阶段尚不具备完整功能。下文所有用法均以当前仓库的文档与源码为准。InsertAtEntry函数入口插桩示例下面是使用InsertAtEntry在指定函数入口插桩的完整示例文件aop_demo1.cjpackage AOP_demo1 import stdx.aspect_cj.* import std.time.DateTime InsertAtEntry[packageName: AOP_demo1, className: , methodName: printCurrentTime, isStatic: false, recursive: false, funcTypeStr: ()-Unit] public func printCurrentTimeImpl() { println(----- ${DateTime.now()} -----) } public func printCurrentTime() { println(hi) println(bye) } main() { printCurrentTime() 0 }示例中printCurrentTimeImpl是切面函数InsertAtEntry参数指明目标为同包下的全局函数printCurrentTimeclassName为空、isStatic: falsefuncTypeStr: ()-Unit与目标函数签名一致。织入后每次调用printCurrentTime时函数体第一条语句就变为对printCurrentTimeImpl的调用。Linux 平台编译命令需设置CANGJIE_STDX_PATH指向 stdx 安装路径cjc aop_demo1.cj -L $CANGJIE_STDX_PATH --import-path $CANGJIE_STDX_PATH -lstdx.aspect_cj --plugin$CANGJIE_STDX_PATH/libstdx.collect_aspects.so -o main # 第一次编译收集切面 cjc aop_demo1.cj -L $CANGJIE_STDX_PATH --import-path $CANGJIE_STDX_PATH -lstdx.aspect_cj --plugin$CANGJIE_STDX_PATH/libstdx.weave_aspects.so -o main # 第二次编译织入切面运行结果可能如下----- 2025-06-03T00:00:18.994507-07:00 ----- hi bye时间戳先于hi/bye打印正说明插桩代码被织入到了函数入口。从源码看织入插件对InsertAtEntry的处理是在目标函数体的entryBlock起始位置创建一次createApply调用见 weave_aspects.cj因此该调用会先于原函数体任何语句执行。InsertAtExit函数退出前插桩示例下面是使用InsertAtExit在指定函数退出前插桩的完整示例文件aop_demo2.cjpackage AOP_demo2 import stdx.aspect_cj.* import std.time.DateTime InsertAtExit[packageName: AOP_demo2, className: , methodName: printCurrentTime, isStatic: false, recursive: false, funcTypeStr: ()-std.core.String] public func printCurrentTimeImpl() { println(----- ${DateTime.now()} -----) } public func printCurrentTime() { println(hi) println(bye) done } main() { println(printCurrentTime()) 0 }与入口插桩的差异点目标函数printCurrentTime返回std.core.String因此funcTypeStr写为()-std.core.String切面函数printCurrentTimeImpl本身返回UnitInsert 系列要求切面函数返回类型为Unit。编译命令与上文相同仅文件名改为aop_demo2.cjcjc aop_demo2.cj -L $CANGJIE_STDX_PATH --import-path $CANGJIE_STDX_PATH -lstdx.aspect_cj --plugin$CANGJIE_STDX_PATH/libstdx.collect_aspects.so -o main # 第一次编译收集切面 cjc aop_demo2.cj -L $CANGJIE_STDX_PATH --import-path $CANGJIE_STDX_PATH -lstdx.aspect_cj --plugin$CANGJIE_STDX_PATH/libstdx.weave_aspects.so -o main # 第二次编译织入切面运行结果可能如下hi bye ----- 2025-06-03T00:04:59.996469-07:00 ----- done时间戳出现在hi/bye之后、返回值被打印之前即插桩发生在函数退出前。从源码看织入插件通过InsertAtExitVisitor遍历目标函数的 CHIR 中间表示对每一个exit表达式在其之前插入切面函数调用见 InsertAtExitVisitor因此无论函数有多少个出口多个 return、异常路径等每个出口前都会触发插桩。ReplaceFuncBody替换函数体示例下面是使用ReplaceFuncBody将指定函数体替换为切面函数调用的完整示例文件aop_demo3.cjpackage AOP_demo3 import stdx.aspect_cj.* import std.time.DateTime ReplaceFuncBody[packageName: AOP_demo3, className: , methodName: printCurrentTime, isStatic: false, recursive: false] public func printCurrentTimeImpl(original: (String) - String) { println(----- ${DateTime.now()} -----) println(original(abc)) println(----- end -----) def } public func printCurrentTime(x: String): String { println(x) 456 } main() { println(printCurrentTime(123)) 0 }ReplaceFuncBody与 Insert 系列有两处本质区别注解参数中没有funcTypeStr——目标函数类型由切面函数最后一个形参闭包类型隐式确定。规格要求切面函数必须额外添加一个形参其类型与被织入函数相同代表被织入函数原始版本的闭包本例即original: (String) - String切面函数的返回类型必须与被织入函数相同本例均为String。编译命令同上文件名为aop_demo3.cjcjc aop_demo3.cj -L $CANGJIE_STDX_PATH --import-path $CANGJIE_STDX_PATH -lstdx.aspect_cj --plugin$CANGJIE_STDX_PATH/libstdx.collect_aspects.so -o main # 第一次编译收集切面 cjc aop_demo3.cj -L $CANGJIE_STDX_PATH --import-path $CANGJIE_STDX_PATH -lstdx.aspect_cj --plugin$CANGJIE_STDX_PATH/libstdx.weave_aspects.so -o main # 第二次编译织入切面运行结果可能如下----- 2025-06-03T00:04:59.996469-07:00 ----- abc 456 ----- end ----- def逐行解读main调用printCurrentTime(123)但函数体已被替换为对printCurrentTimeImpl的包装调用——先打印时间戳original(abc)执行原始函数体打印abc返回456再打印456随后打印----- end -----切面函数返回def作为替换后printCurrentTime的返回值最终被main打印。从源码看织入插件对ReplaceFuncBody的处理是为原函数新建一个同签名的 wrapper 函数替换它wrapper 体中分配返回槽位、调用切面函数把原函数自身作为最后一个实参传入、再把结果写入返回槽位同时原函数的符号名会被改写为附加.original后缀见 weave_aspects.cj从而保证原始实现仍可通过闭包被调用。收集与织入两阶段管线的底层机制第一阶段collect_aspects 收集切面信息第一次编译时libstdx.collect_aspects.so中的CollectAspects插件collect_aspects.cj会遍历当前包的所有函数对带 AOP 注解的函数执行一系列合法性检查任一检查失败即编译报错例如同一个函数不能同时标注InsertAtEntry/InsertAtExit和ReplaceFuncBodycheckAndCollect带注解的函数必须是public、具有外部链接且不能是foreign、抽象函数或泛型函数/泛型类型的成员checkAspectFunction定义了切面的包中只允许定义基本类型整型、浮点、Rune、Bool字面值的全局变量否则报错globalVarsArePrimitiveLiterals实例成员函数不能织入全局函数或静态成员函数recursive为true时className不可为空checkAnnotationArgsInsert 系列切面函数返回类型必须是Unit且形参列表须与目标函数一致checkInsertAnnoFuncTypeReplaceFuncBody切面函数至少 1 个形参最后一个形参必须是函数类型且其返回类型与目标函数返回类型一致checkReplaceBodyAnnoFuncType。检查通过后插件把每条切面记录写入.annoinfo文件文件名为当前包名-目标包名.annoinfowriteAnnoInfoToFile每条记录按行依次存储 13 个字段当前包名、包初始化函数 mangled 名、注解类名随后是 5 个“连接点”字段目标包名、方法 mangled 名、形参数量、是否成员函数、是否静态和 6 个“目标”字段目标包名、类名、方法名、函数类型字符串、是否静态、是否递归。文件名中编码“目标包名”是关键设计它决定了这条切面信息在哪个包的编译过程中被消费。第二阶段weave_aspects 织入切面第二次编译时libstdx.weave_aspects.so中的WeaveAspects插件weave_aspects.cj的工作流程见 run读取切面信息遍历当前目录仅读取文件名匹配*当前包名.annoinfo的记录readAnnoInfo。这解释了为什么示例中切面函数与目标函数位于同一个包——被织入的包编译时才会消费对应记录调用上游包初始化在当前包的 package init 入口开头织入对所有相关包初始化函数的调用callUpstreamPkgInitFunc确保切面所在包的静态初始化先于运行期执行匹配连接点对当前包每个函数用包名、类名含recursive时沿继承链向上匹配、方法名、是否静态、完整函数类型字符串逐项比对meets执行织入按注解类型分别走“入口插入 apply”、“exit 前插入 apply”或“替换为 wrapper 调用”三条路径weaveFuncBody。若目标函数不在当前包插件会自动补一个imported函数声明再发起调用getCallee。两个插件本身通过stdx.plugin的CHIRPlugin宏注册宏会为其生成继承CHIRPluginBase的初始化逻辑并在静态初始化时调用PluginManager.registerCHIRPluginregister.cj。编译器加载--plugin指定的动态库后最终经由executeCHIRPlugins反序列化包级 CHIR、依次运行所有已注册插件再重新序列化输出execute.cj。这也解释了两次编译命令完全相同、仅--plugin参数不同即可切换“收集/织入”阶段的原因。规格与约束速查以下约束汇总自 stdx.aspect_cj 包概述使用时务必遵守标注范围注解暂不支持标注在泛型函数上也不支持织入泛型函数注解只允许标注在public函数上注解可以标注在全局函数上支持织入另一个全局函数、实例成员函数、静态成员函数注解可以标注在静态成员函数上支持织入全局函数、实例成员函数、静态成员函数注解可以标注在实例成员函数上支持织入同类型的其它实例成员函数。全局变量定义约束定义了切面的包中只允许定义基本类型整型、浮点、Rune、Bool字面值的全局变量否则编译报错若需要使用超规格的全局变量需要将其定义在另外的包再导入使用以避免编译后可能产生的循环依赖。形参约束被标注InsertAtEntry/InsertAtExit的函数其返回类型只能是Unit被标注ReplaceFuncBody的函数其返回类型应和被织入的函数的返回类型相同被标注的函数如果没有形参织入后总是被无参调用被标注的函数如果有形参其形参列表应和被织入函数的源码形参列表相同。特别地如果被织入的函数是实例成员函数需要将this参数显式地写在形参中对于被标注ReplaceFuncBody的函数需要额外添加一个形参形参的类型和被织入的函数相同该形参表示被织入函数原始版本的闭包。参考路径示例文档doc/libs_stdx/aspect_cj/aspect_cj_samples/aspect_cj_sample.md包概述与规格doc/libs_stdx/aspect_cj/aspect_cj_package_overview.md注解类 APIdoc/libs_stdx/aspect_cj/aspect_cj_package_api/aspect_cj_package_classes.md注解定义源码src/stdx/aspect_cj/aspect_cj.cj收集插件src/stdx/aspect_cj/plugins/collect_aspects/collect_aspects.cj织入插件src/stdx/aspect_cj/plugins/weave_aspects/weave_aspects.cj插件注册宏src/stdx/plugin/register.cj插件执行入口src/stdx/plugin/manager/execute.cj赞分享标准库网络密码学并发编程【免费下载链接】cangjie_stdx仓颉编程语言提供了 stdx 模块该模块提供了网络、安全等领域的通用能力。项目地址https://gitcode.com/Cangjie/cangjie_stdx点击查看免费下载相关推荐stdx.aspect_cj 注解类 API仓颉语言 AOP 函数插桩与替换实战指南stdx.aspect_cj 注解类 API仓颉语言 AOP 函数插桩与替换实战指南 stdx.aspect_cj 包是 cangjie_stdx 中提供面向标准库网络密码学并发编程仓颉 stdx.aspect_cj 面向切面编程实战注解定义切面编译期两遍插桩与函数体替换仓颉 stdx.aspect_cj 面向切面编程实战注解定义切面编译期两遍插桩与函数体替换 本文围绕 stdx.aspect_cj 包展开讲解如何用 I标准库网络密码学并发编程AFL LLVM 编译器级插桩llvm_mode实战指南构建、插桩选项与高级特性AFL LLVM 编译器级插桩llvm_mode实战指南构建、插桩选项与高级特性 本文系统讲解 AFL 基于 LLVM 的编译器级插桩方案llv应用安全测试漏洞扫描创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表