發(fā)實(shí)戰(zhàn):InsertAtEntry、InsertAtExit 與 ReplaceFuncBody 的兩次編譯插樁)
標(biāo)準(zhǔn)庫(kù)網(wǎng)絡(luò)密碼學(xué)并發(fā)編程【免費(fèi)下載鏈接】cangjie_stdx倉(cāng)頡編程語(yǔ)言提供了 stdx 模塊該模塊提供了網(wǎng)絡(luò)、安全等領(lǐng)域的通用能力。項(xiàng)目地址https://gitcode.com/Cangjie/cangjie_stdx點(diǎn)擊查看免費(fèi)下載本篇指南基于 cangjie_stdx 倉(cāng)庫(kù)的 AOP 開(kāi)發(fā)示例文檔完整講解stdx.aspect_cj包提供的三個(gè)切面注解InsertAtEntry、InsertAtExit、ReplaceFuncBody的使用方法、兩次編譯收集 織入的cjc命令流程與預(yù)期運(yùn)行結(jié)果并結(jié)合 收集插件 與 織入插件 的源碼剖析切面信息文件.annoinfo的生成與織入時(shí)機(jī)。讀完本文你可以獨(dú)立完成“在指定函數(shù)入口/出口插樁、整體替換函數(shù)體”的 AOP 場(chǎng)景并理解其底層 CHIR 級(jí)別的實(shí)現(xiàn)機(jī)制。stdx.aspect_cj 概述注解 兩個(gè)編譯插件stdx.aspect_cj包提供了倉(cāng)頡語(yǔ)言中面向切面編程Aspect Oriented Programming, AOP的注解能力配合libstdx.collect_aspects和libstdx.weave_aspects兩個(gè)編譯插件可以對(duì)目標(biāo)函數(shù)進(jìn)行入口插樁、退出前插樁、替換函數(shù)體。三個(gè)注解類的定義位于 aspect_cj.cj注解類切面能力關(guān)鍵參數(shù)InsertAtEntry在注解所指定方法的入口織入對(duì)被注解標(biāo)注函數(shù)的調(diào)用packageName、className、methodName、isStatic、recursive、funcTypeStrInsertAtExit在注解所指定方法的退出點(diǎn)織入對(duì)被注解標(biāo)注函數(shù)的調(diào)用同上ReplaceFuncBody將注解所指定方法的方法體整體替換為對(duì)被注解標(biāo)注函數(shù)的調(diào)用packageName、className、methodName、isStatic、recursive無(wú)funcTypeStr需要注意的參數(shù)語(yǔ)義注解標(biāo)注在“切面函數(shù)”你要織入進(jìn)去的代碼所在函數(shù)上而注解參數(shù)描述的是“被織入的目標(biāo)函數(shù)”packageName目標(biāo)函數(shù)所屬包名如default、std.coreclassName目標(biāo)函數(shù)是成員函數(shù)時(shí)填所屬類名是全局函數(shù)時(shí)填空字符串methodName目標(biāo)函數(shù)名isStatic目標(biāo)函數(shù)是否為靜態(tài)成員函數(shù)recursive目標(biāo)函數(shù)是成員函數(shù)時(shí)是否對(duì)子類 override 版本也織入全局函數(shù)場(chǎng)景應(yīng)填falsefuncTypeStr僅 Insert 系列目標(biāo)函數(shù)的類型字符串不含空格自定義類型需寫(xiě)全包名如(Int64,std.core.Object)-Unit不含隱式this形參。這兩個(gè)編譯插件以動(dòng)態(tài)庫(kù)形式隨 stdx 提供不同平臺(tái)對(duì)應(yīng).so/.dll/.dylib使用流程固定為兩步第一次編譯使用libstdx.collect_aspects編譯時(shí)收集所有切面、連接點(diǎn)信息落盤(pán)為.annoinfo文件第二次編譯使用libstdx.weave_aspects讀取已收集的信息把切面織入到連接點(diǎn)。包概述文檔 中同時(shí)注明stdx.aspect_cj當(dāng)前處于開(kāi)發(fā)階段尚不具備完整功能。下文所有用法均以當(dāng)前倉(cāng)庫(kù)的文檔與源碼為準(zhǔn)。InsertAtEntry函數(shù)入口插樁示例下面是使用InsertAtEntry在指定函數(shù)入口插樁的完整示例文件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是切面函數(shù)InsertAtEntry參數(shù)指明目標(biāo)為同包下的全局函數(shù)printCurrentTimeclassName為空、isStatic: falsefuncTypeStr: ()-Unit與目標(biāo)函數(shù)簽名一致??椚牒竺看握{(diào)用printCurrentTime時(shí)函數(shù)體第一條語(yǔ)句就變?yōu)閷?duì)printCurrentTimeImpl的調(diào)用。Linux 平臺(tái)編譯命令需設(shè)置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 # 第二次編譯織入切面運(yùn)行結(jié)果可能如下----- 2025-06-03T00:00:18.994507-07:00 ----- hi bye時(shí)間戳先于hi/bye打印正說(shuō)明插樁代碼被織入到了函數(shù)入口。從源碼看織入插件對(duì)InsertAtEntry的處理是在目標(biāo)函數(shù)體的entryBlock起始位置創(chuàng)建一次createApply調(diào)用見(jiàn) weave_aspects.cj因此該調(diào)用會(huì)先于原函數(shù)體任何語(yǔ)句執(zhí)行。InsertAtExit函數(shù)退出前插樁示例下面是使用InsertAtExit在指定函數(shù)退出前插樁的完整示例文件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 }與入口插樁的差異點(diǎn)目標(biāo)函數(shù)printCurrentTime返回std.core.String因此funcTypeStr寫(xiě)為()-std.core.String切面函數(shù)printCurrentTimeImpl本身返回UnitInsert 系列要求切面函數(shù)返回類型為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 # 第二次編譯織入切面運(yùn)行結(jié)果可能如下hi bye ----- 2025-06-03T00:04:59.996469-07:00 ----- done時(shí)間戳出現(xiàn)在hi/bye之后、返回值被打印之前即插樁發(fā)生在函數(shù)退出前。從源碼看織入插件通過(guò)InsertAtExitVisitor遍歷目標(biāo)函數(shù)的 CHIR 中間表示對(duì)每一個(gè)exit表達(dá)式在其之前插入切面函數(shù)調(diào)用見(jiàn) InsertAtExitVisitor因此無(wú)論函數(shù)有多少個(gè)出口多個(gè) return、異常路徑等每個(gè)出口前都會(huì)觸發(fā)插樁。ReplaceFuncBody替換函數(shù)體示例下面是使用ReplaceFuncBody將指定函數(shù)體替換為切面函數(shù)調(diào)用的完整示例文件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 系列有兩處本質(zhì)區(qū)別注解參數(shù)中沒(méi)有funcTypeStr——目標(biāo)函數(shù)類型由切面函數(shù)最后一個(gè)形參閉包類型隱式確定。規(guī)格要求切面函數(shù)必須額外添加一個(gè)形參其類型與被織入函數(shù)相同代表被織入函數(shù)原始版本的閉包本例即original: (String) - String切面函數(shù)的返回類型必須與被織入函數(shù)相同本例均為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 # 第二次編譯織入切面運(yùn)行結(jié)果可能如下----- 2025-06-03T00:04:59.996469-07:00 ----- abc 456 ----- end ----- def逐行解讀main調(diào)用printCurrentTime(123)但函數(shù)體已被替換為對(duì)printCurrentTimeImpl的包裝調(diào)用——先打印時(shí)間戳original(abc)執(zhí)行原始函數(shù)體打印abc返回456再打印456隨后打印----- end -----切面函數(shù)返回def作為替換后printCurrentTime的返回值最終被main打印。從源碼看織入插件對(duì)ReplaceFuncBody的處理是為原函數(shù)新建一個(gè)同簽名的 wrapper 函數(shù)替換它wrapper 體中分配返回槽位、調(diào)用切面函數(shù)把原函數(shù)自身作為最后一個(gè)實(shí)參傳入、再把結(jié)果寫(xiě)入返回槽位同時(shí)原函數(shù)的符號(hào)名會(huì)被改寫(xiě)為附加.original后綴見(jiàn) weave_aspects.cj從而保證原始實(shí)現(xiàn)仍可通過(guò)閉包被調(diào)用。收集與織入兩階段管線的底層機(jī)制第一階段collect_aspects 收集切面信息第一次編譯時(shí)libstdx.collect_aspects.so中的CollectAspects插件collect_aspects.cj會(huì)遍歷當(dāng)前包的所有函數(shù)對(duì)帶 AOP 注解的函數(shù)執(zhí)行一系列合法性檢查任一檢查失敗即編譯報(bào)錯(cuò)例如同一個(gè)函數(shù)不能同時(shí)標(biāo)注InsertAtEntry/InsertAtExit和ReplaceFuncBodycheckAndCollect帶注解的函數(shù)必須是public、具有外部鏈接且不能是foreign、抽象函數(shù)或泛型函數(shù)/泛型類型的成員checkAspectFunction定義了切面的包中只允許定義基本類型整型、浮點(diǎn)、Rune、Bool字面值的全局變量否則報(bào)錯(cuò)globalVarsArePrimitiveLiterals實(shí)例成員函數(shù)不能織入全局函數(shù)或靜態(tài)成員函數(shù)recursive為true時(shí)className不可為空checkAnnotationArgsInsert 系列切面函數(shù)返回類型必須是Unit且形參列表須與目標(biāo)函數(shù)一致checkInsertAnnoFuncTypeReplaceFuncBody切面函數(shù)至少 1 個(gè)形參最后一個(gè)形參必須是函數(shù)類型且其返回類型與目標(biāo)函數(shù)返回類型一致checkReplaceBodyAnnoFuncType。檢查通過(guò)后插件把每條切面記錄寫(xiě)入.annoinfo文件文件名為當(dāng)前包名-目標(biāo)包名.annoinfowriteAnnoInfoToFile每條記錄按行依次存儲(chǔ) 13 個(gè)字段當(dāng)前包名、包初始化函數(shù) mangled 名、注解類名隨后是 5 個(gè)“連接點(diǎn)”字段目標(biāo)包名、方法 mangled 名、形參數(shù)量、是否成員函數(shù)、是否靜態(tài)和 6 個(gè)“目標(biāo)”字段目標(biāo)包名、類名、方法名、函數(shù)類型字符串、是否靜態(tài)、是否遞歸。文件名中編碼“目標(biāo)包名”是關(guān)鍵設(shè)計(jì)它決定了這條切面信息在哪個(gè)包的編譯過(guò)程中被消費(fèi)。第二階段weave_aspects 織入切面第二次編譯時(shí)libstdx.weave_aspects.so中的WeaveAspects插件weave_aspects.cj的工作流程見(jiàn) run讀取切面信息遍歷當(dāng)前目錄僅讀取文件名匹配*當(dāng)前包名.annoinfo的記錄readAnnoInfo。這解釋了為什么示例中切面函數(shù)與目標(biāo)函數(shù)位于同一個(gè)包——被織入的包編譯時(shí)才會(huì)消費(fèi)對(duì)應(yīng)記錄調(diào)用上游包初始化在當(dāng)前包的 package init 入口開(kāi)頭織入對(duì)所有相關(guān)包初始化函數(shù)的調(diào)用callUpstreamPkgInitFunc確保切面所在包的靜態(tài)初始化先于運(yùn)行期執(zhí)行匹配連接點(diǎn)對(duì)當(dāng)前包每個(gè)函數(shù)用包名、類名含recursive時(shí)沿繼承鏈向上匹配、方法名、是否靜態(tài)、完整函數(shù)類型字符串逐項(xiàng)比對(duì)meets執(zhí)行織入按注解類型分別走“入口插入 apply”、“exit 前插入 apply”或“替換為 wrapper 調(diào)用”三條路徑weaveFuncBody。若目標(biāo)函數(shù)不在當(dāng)前包插件會(huì)自動(dòng)補(bǔ)一個(gè)imported函數(shù)聲明再發(fā)起調(diào)用getCallee。兩個(gè)插件本身通過(guò)stdx.plugin的CHIRPlugin宏注冊(cè)宏會(huì)為其生成繼承CHIRPluginBase的初始化邏輯并在靜態(tài)初始化時(shí)調(diào)用PluginManager.registerCHIRPluginregister.cj。編譯器加載--plugin指定的動(dòng)態(tài)庫(kù)后最終經(jīng)由executeCHIRPlugins反序列化包級(jí) CHIR、依次運(yùn)行所有已注冊(cè)插件再重新序列化輸出execute.cj。這也解釋了兩次編譯命令完全相同、僅--plugin參數(shù)不同即可切換“收集/織入”階段的原因。規(guī)格與約束速查以下約束匯總自 stdx.aspect_cj 包概述使用時(shí)務(wù)必遵守標(biāo)注范圍注解暫不支持標(biāo)注在泛型函數(shù)上也不支持織入泛型函數(shù)注解只允許標(biāo)注在public函數(shù)上注解可以標(biāo)注在全局函數(shù)上支持織入另一個(gè)全局函數(shù)、實(shí)例成員函數(shù)、靜態(tài)成員函數(shù)注解可以標(biāo)注在靜態(tài)成員函數(shù)上支持織入全局函數(shù)、實(shí)例成員函數(shù)、靜態(tài)成員函數(shù)注解可以標(biāo)注在實(shí)例成員函數(shù)上支持織入同類型的其它實(shí)例成員函數(shù)。全局變量定義約束定義了切面的包中只允許定義基本類型整型、浮點(diǎn)、Rune、Bool字面值的全局變量否則編譯報(bào)錯(cuò)若需要使用超規(guī)格的全局變量需要將其定義在另外的包再導(dǎo)入使用以避免編譯后可能產(chǎn)生的循環(huán)依賴。形參約束被標(biāo)注InsertAtEntry/InsertAtExit的函數(shù)其返回類型只能是Unit被標(biāo)注ReplaceFuncBody的函數(shù)其返回類型應(yīng)和被織入的函數(shù)的返回類型相同被標(biāo)注的函數(shù)如果沒(méi)有形參織入后總是被無(wú)參調(diào)用被標(biāo)注的函數(shù)如果有形參其形參列表應(yīng)和被織入函數(shù)的源碼形參列表相同。特別地如果被織入的函數(shù)是實(shí)例成員函數(shù)需要將this參數(shù)顯式地寫(xiě)在形參中對(duì)于被標(biāo)注ReplaceFuncBody的函數(shù)需要額外添加一個(gè)形參形參的類型和被織入的函數(shù)相同該形參表示被織入函數(shù)原始版本的閉包。參考路徑示例文檔doc/libs_stdx/aspect_cj/aspect_cj_samples/aspect_cj_sample.md包概述與規(guī)格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插件注冊(cè)宏src/stdx/plugin/register.cj插件執(zhí)行入口src/stdx/plugin/manager/execute.cj贊分享標(biāo)準(zhǔn)庫(kù)網(wǎng)絡(luò)密碼學(xué)并發(fā)編程【免費(fèi)下載鏈接】cangjie_stdx倉(cāng)頡編程語(yǔ)言提供了 stdx 模塊該模塊提供了網(wǎng)絡(luò)、安全等領(lǐng)域的通用能力。項(xiàng)目地址https://gitcode.com/Cangjie/cangjie_stdx點(diǎn)擊查看免費(fèi)下載相關(guān)推薦stdx.aspect_cj 注解類 API倉(cāng)頡語(yǔ)言 AOP 函數(shù)插樁與替換實(shí)戰(zhàn)指南stdx.aspect_cj 注解類 API倉(cāng)頡語(yǔ)言 AOP 函數(shù)插樁與替換實(shí)戰(zhàn)指南 stdx.aspect_cj 包是 cangjie_stdx 中提供面向標(biāo)準(zhǔn)庫(kù)網(wǎng)絡(luò)密碼學(xué)并發(fā)編程倉(cāng)頡 stdx.aspect_cj 面向切面編程實(shí)戰(zhàn)注解定義切面編譯期兩遍插樁與函數(shù)體替換倉(cāng)頡 stdx.aspect_cj 面向切面編程實(shí)戰(zhàn)注解定義切面編譯期兩遍插樁與函數(shù)體替換 本文圍繞 stdx.aspect_cj 包展開(kāi)講解如何用 I標(biāo)準(zhǔn)庫(kù)網(wǎng)絡(luò)密碼學(xué)并發(fā)編程AFL LLVM 編譯器級(jí)插樁llvm_mode實(shí)戰(zhàn)指南構(gòu)建、插樁選項(xiàng)與高級(jí)特性AFL LLVM 編譯器級(jí)插樁llvm_mode實(shí)戰(zhàn)指南構(gòu)建、插樁選項(xiàng)與高級(jí)特性 本文系統(tǒng)講解 AFL 基于 LLVM 的編譯器級(jí)插樁方案llv應(yīng)用安全測(cè)試漏洞掃描創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考