Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

การเขียนไบต์โค้ดใหม่

สำหรับการเขียนไบต์โค้ดใหม่ มี generator สองตัว generator แต่ละตัวถูกใช้ด้วยเหตุผลเฉพาะขณะปล่อย instrumentation InitGenerator จะถูกรันเป็นตัวแรก เพื่อปล่อยส่วนของสคริปต์ .mm ที่ต้องดำรงอยู่ก่อน actions ของโพรบใดๆ จะถูกปล่อย เช่น ฟังก์ชันและสถานะโกลบอล จากนั้น InstrGenerator จะถูกรันเป็นตัวที่สอง เพื่อปล่อยโพรบขณะท่องไบต์โค้ด app.wasm (ซึ่งแทนด้วย IR ในหน่วยความจำ)

generator ทั้งสองใช้ emitter ที่ปล่อยโค้ด Wasm โดย emitter ใช้ยูทิลิตี้ที่รวมตรรกะการปล่อย Wasm ไว้ที่ utils.rs

1. InitGenerator

init_generator.rs ท่อง AST เพื่อปล่อยฟังก์ชันและตัวแปรที่ต้องดำรงอยู่ก่อนปล่อยโพรบ ฟังก์ชัน run เป็นจุดเข้าของ generator นี้ ซึ่งทำตามดีไซน์แพตเทิร์น visitor มีแหล่งข้อมูลออนไลน์ที่ดีมากมายที่สอนเรื่อง visitor pattern หากจะเป็นประโยชน์กับผู้อ่าน

generator นี้ปล่อยฟังก์ชันและตัวแปร Wasm ใหม่เข้าไปในโปรแกรม พร้อม ID ของ Wasm ที่เกี่ยวข้อง ID เหล่านี้ถูกเก็บไว้ใน SymbolTable เพื่อใช้ขณะรัน InstrGenerator เมื่อปล่อยคำสั่งที่เรียกฟังก์ชันที่ปล่อยแล้ว หรือทำการดำเนินการบางอย่างกับโกลบอลที่ปล่อยแล้ว ชื่อของสัญลักษณ์นั้นจะถูกค้นหาใน SymbolTable เพื่อนำ ID ที่บันทึกไว้มาใช้ในคำสั่งที่ถูกปล่อย

2. InstrGenerator

instr_generator.rs เรียกเข้า emitter เพื่อท่องแอปพลิเคชันทีละขั้นเพื่อค้นหาตำแหน่งที่สอดคล้องกับอีเวนต์ของโพรบใน AST ของ .mm เมื่อพบตำแหน่งที่ถูกโพรบ generator จะปล่อยโค้ด Wasm เข้าไปในแอปพลิเคชัน ณ จุดนั้นผ่านยูทิลิตี้ของ emitter

ลำดับการฉีดโพรบ

เมื่อมีโพรบหลายตัวแมตช์ตำแหน่งไบต์โค้ดเดียวกัน ลำดับการฉีดถูกกำหนดโดยปัจจัยอิสระสองอย่าง

หมวดหมู่การฉีดเดียวกัน: ลำดับในสคริปต์

โพรบที่อยู่ในหมวดหมู่การฉีดเดียวกัน (เช่น โพรบ opcode:call:before สองตัว หรือโพรบ func:entry สองตัว) จะถูกฉีดตามลำดับที่ปรากฏในสคริปต์

สิ่งนี้ถูกรับประกันโดยใช้ IndexMap แทน HashMap ในทุกระดับของลำดับชั้นโพรบ — Provider.packages, Package.events, Event.probes ใน provider_handler.rs และ SimpleProv.pkgs, SimplePkg.evts, SimpleEvt.modes ที่สอดคล้องกันใน simple_ast.rs IndexMap รักษาลำดับการแทรก ซึ่งเท่ากับลำดับที่โพรบถูกแยกวิเคราะห์จากสคริปต์ หากใช้ HashMap ธรรมดาแทน ลำดับการวนซ้ำจะไม่แน่นอน ทำให้การรับประกันลำดับนี้เสียไปอย่างเงียบๆ

หมวดหมู่การฉีดต่างกัน: โมเดลสองรอบของ wirm

wirm (ไลบรารี Wasm IR ที่อยู่เบื้องหลัง) ใช้โมเดลการฉีดสองรอบ:

  • รอบที่ 1 — โหมดปกติ (before, after, alt): ถูกฉีดระหว่างการท่องไบต์โค้ด ด้วยการเรียก app_iter.before() / .after() / .alternate()
  • รอบที่ 2 — โหมดพิเศษ/เชิงโครงสร้าง (func_entry, func_exit, block_entry, block_exit, เป้าหมายการแตกกิ่ง ฯลฯ): ถูกแก้ไขให้ชัดเจนและฉีดตอน encode โดยรอบที่สองของ wirm เอง

สองรอบนี้เป็นอิสระต่อกัน wirm จะรวมผลลัพธ์ของทั้งสองรอบตอน encode โดยโพรบจากรอบที่ 1 มาก่อนโพรบจากรอบที่ 2 ณ คำสั่งเดียวกัน นั่นหมายความว่าลำดับสัมพัทธ์ระหว่างโพรบ wasm:opcode:call:before กับโพรบ wasm:func:entry ณ ตำแหน่งเดียวกัน (เช่น call ที่เป็นคำสั่งแรกของฟังก์ชันด้วย) ถูกกำหนดตายตัวโดย wirm ไม่ใช่โดยลำดับในสคริปต์: โพรบ before จะรันก่อนโพรบ func_entry ณ ตำแหน่งนั้นเสมอ

ดู probe_mode_for_package ใน instr_generator.rs สำหรับจุดที่ whamm แมปกฎโพรบเข้ากับ wirm mode

การแพร่ค่าคงที่และการพับค่า!!

การแพร่ค่าคงที่และการพับค่าเป็น optimization ของคอมไพเลอร์ที่มีจุดประสงค์พิเศษใน Whamm มีแหล่งข้อมูลออนไลน์มากมายที่อธิบายแนวคิดเหล่านี้ หากจะเป็นประโยชน์กับผู้อ่าน

คำสั่ง whamm info ช่วยให้ผู้ใช้เห็นตัวแปรต่างๆ ที่อยู่ในสโคปเมื่อใช้กฎการแมตช์ของโพรบแบบต่างๆ ตัวแปรโกลบอลทั้งหมดนี้ถูกนิยามโดยคอมไพเลอร์ของ Whamm และควรถูกปล่อยเป็นค่าคงที่เท่านั้น หากตัวแปรถูกปล่อยเข้าไปในโปรแกรมที่ทำ instrumentation โดยตรง โดยไม่มีนิยามที่คอมไพเลอร์จัดให้ โปรแกรมจะรันไม่สำเร็จ เพราะตัวแปรนั้นจะไม่ถูกนิยาม

Whamm ใช้การแพร่ค่าคงที่และการพับค่าเพื่อแก้สถานการณ์นี้!

ฟังก์ชัน define ใน visiting_emitter.rs คือวิธีที่ค่าคงที่ของคอมไพเลอร์ถูกนิยาม ขณะท่องไบต์โค้ดของแอปพลิเคชัน ตัวแปรเหล่านี้ถูกนิยามใน emitter เพราะนิยามของมันผูกอยู่กับตำแหน่งในโปรแกรม Wasm ที่กำลังทำ instrumentation

ExprFolder ใน folding.rs ทำการแพร่ค่าคงที่และการพับค่าบนนิพจน์

เมื่อพิจารณาโพรบที่มีเพรดิเคต พฤติกรรมนี้อาจน่าสนใจทีเดียว ลองดูนิยามโพรบต่อไปนี้เป็นตัวอย่าง:

wasm:bytecode:call:alt /
    target_fn_type == "import" &&
    target_imp_module == "ic0" &&
    target_fn_name == "call_perform"
/ { ... }

ตัวแปรที่ผูกทั้งสามตัวที่ใช้ใน predicate ถูกนิยามแบบสแตติกโดยคอมไพเลอร์ และถูกจัดหาโดยอีเวนต์ call หมายความว่าทุกการใช้ตัวแปรเหล่านี้จะถูกแทนที่ด้วยค่าคงที่ และ predicate จะพับเป็น true หรือ false ถ้า predicate พับเป็น true actions ของโพรบสามารถถูกปล่อยที่ตำแหน่งที่พบโดยไม่มีเงื่อนไข ถ้า predicate พับเป็น false โพรบไม่ควรถูกปล่อย

ทีนี้ ลองดูนิยามโพรบตัวอย่างถัดไป:

wasm:bytecode:call:alt /
    target_fn_type == "import" &&
    target_imp_module == "ic0" &&
    target_fn_name == "call_new" &&
    strcmp((arg0, arg1), "bookings") &&
    strcmp((arg2, arg3), "record")
/ { ... }

predicate ของโพรบนี้ตอนนี้มีทั้งตัวแปรที่นิยามแบบสแตติกและตัวแปรที่นิยามแบบพลวัต ซึ่งถูกต้องตามความหมายทั้งหมด!

แล้วเกิดอะไรขึ้นตรงนี้? ตัวแปรที่ผูกสามตัวแรกจะถูกแพร่กลายเป็นค่าคงที่ นิพจน์จะถูกพับค่า และการตรวจความเท่าเทียมของค่าคงที่เหล่านั้นจะประเมินเป็น true หรือ false ค่าที่ลดรูปแล้วนี้จะถูกandเข้ากับส่วนที่นิยามแบบพลวัตของนิพจน์ที่ตามมา เป้าหมายเดียวกันกับตัวอย่างก่อนหน้าจึงสำเร็จได้เช่นกัน (โพรบจะถูกปล่อยหรือไม่ถูกปล่อยที่ตำแหน่งไบต์โค้ดนั้นตามข้อมูลที่กำหนดได้แบบสแตติก) แต่ครั้งนี้ actions ที่ปล่อยออกมาจะยังมีเงื่อนไข แต่เป็นเงื่อนไขที่พับค่าแล้วซึ่งมีเฉพาะส่วนพลวัตของ predicate เดิมเท่านั้น

เจ๋งใช่ไหมล่ะ??