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

ส่วนต่อประสานเอนจินของ Whamm (wei)

สำหรับ wei มี generator หนึ่งตัวชื่อ WeiGenerator อยู่ใน generator/mod.rs generator นี้ท่อง AST ของ MetadataCollector และปล่อยมอนิเตอร์ที่พกพาได้และครบในตัวเองสำหรับ wei

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

WeiGenerator

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

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

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

การส่งออกของ wei

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

⟨export-name⟩   ::=     ⟨match-rule⟩⟨predicate⟩? ⟨params⟩?
⟨match-rule⟩    ::=     'wasm':'opcode':('OPCODE'|'*')
                        'wasm':'func':('entry'|'exit')
                        'wasm':'block':('start'|'end')
                        'wasm':'exit'
⟨predicate⟩     ::=     ‘/’ ⟨call⟩‘/’
⟨call⟩          ::=     ‘$’ ID ⟨params⟩?
⟨params⟩        ::=     ‘(’ ‘)’
                        | ‘(’ ⟨param⟩( ‘,’ ⟨param⟩)* ‘)’
⟨param⟩         ::=     ‘argN’ | ‘immN’ | ‘localN’ | ⟨call⟩| ‘pc’ | ‘fid’ | ‘frame’

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

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

นี่คือตัวอย่างการส่งออกฟังก์ชันของโพรบ:

(export "wasm:opcode:array.new / $20(is_func_end, opname) / ($21(fname), fid, pc)" (func 22))

เมื่อเห็นเช่นนี้ เอนจินจะหาออปโคด array.new ทั้งหมดในแอปพลิเคชันเป้าหมาย มันจะประเมินฟังก์ชันเพรดิเคตที่ส่งออกด้วยชื่อ “$20” และส่งสถานะ is_func_end กับ op_name ไปให้ ถ้าประเมินเป็นจริง เอนจินจะรันฟังก์ชัน “$21” โดยส่งสถานะที่ขอไป และจำผลลัพธ์ของฟังก์ชันนี้ไว้ โพรบจะถูกแนบที่ตำแหน่ง array.new นี้ และเอนจินจะส่งผลลัพธ์ของการรัน “$21” รวมทั้ง fid และ pc ตามลำดับนั้น

มี optimization หลายอย่างที่เอนจินทำได้เพื่อให้ instrumentation รันเร็วเมื่อใช้ wei optimization เหล่านี้ถูกอภิปรายไว้ในเปเปอร์นี้: https://doi.org/10.1145/3763124

คุณอาจสังเกตด้วยว่า wei ไม่รองรับการจับคู่แบบ glob ทั่วไป เพื่อไม่ให้เอนจินต้องนำฟีเจอร์นี้ไปใช้ Whamm จึงขยาย glob ไปสู่อีเวนต์ที่แมตช์ต่ำสุดแทน

โพรบที่ทับซ้อนกัน

โพรบหลายตัวในสคริปต์อาจกำหนดเป้าหมายอีเวนต์เดียวกัน (เช่น โพรบ wasm:opcode:call:before สองตัว) สำหรับการเขียนไบต์โค้ดใหม่ เรื่องนี้ไม่เป็นปัญหา — เนื้อของแต่ละโพรบถูกฉีดแยกกันที่จุดเรียก แต่สำหรับ wei ฟังก์ชันโพรบที่ส่งออกแต่ละตัวถูกระบุด้วยชื่อที่ส่งออก ถ้าโพรบสองตัวสร้างชื่อที่ส่งออกเหมือนกัน เอนจินจะเห็นการส่งออกซ้ำกัน และพฤติกรรมจะไม่ถูกนิยาม

เพื่อจัดการเรื่องนี้ WeiGenerator จะรวมโพรบที่มีลายเซ็นชื่อที่ส่งออกเหมือนกันก่อนปล่อย คีย์ของการรวมเข้ารหัสทุกอย่างที่กำหนดรูปร่างของชื่อที่ส่งออก:

  • สตริงกฎ (เช่น wasm:opcode:call:before)
  • ชนิดพารามิเตอร์ของฟังก์ชันเนื้อโพรบ — ได้มาจาก metadata.body_args; โพรบที่ขอสถานะแอปพลิเคชันต่างกัน (เช่น localN คนละชนิด หรือตัวแปรเอนจินต่างกัน) ให้ลายเซ็นเนื้อโพรบต่างกัน จึงถูกแยกไว้
  • ชนิดพารามิเตอร์ของฟังก์ชันเพรดิเคต — จาก metadata.pred_args; รวมเฉพาะเมื่อฟังก์ชันเพรดิเคตแยกต่างหากจะถูกปล่อย (เพรดิเคตแบบสแตติก ไม่ใช่พลวัต)
  • ชนิดพารามิเตอร์ของฟังก์ชัน init/alloc — จาก metadata.init_args
  • ว่ามีฟังก์ชัน alloc หรือไม่ (คือโพรบมีตัวแปร unshared)
  • จำนวนฟังก์ชันการเรียกไลบรารีแบบสแตติก

โพรบสองตัวที่มีคีย์ตรงกันจะถูกรวมเป็นโพรบเดียวที่เนื้อเป็นผลต่อกันของเนื้อทั้งสอง ถ้าโพรบองค์ประกอบมีเพรดิเคต เนื้อของมันจะถูกห่อเป็น if <pred> { <body> } เพื่อให้เพรดิเคตถูกประเมินตอนรันไทม์ภายในฟังก์ชันที่รวมแล้ว แทนที่จะเป็นการส่งออกตรวจล่วงหน้าแยกต่างหาก สิ่งนี้ทำให้เพรดิเคตที่ถูก inline ทั้งหมดกลายเป็น “พลวัต” ในแง่ว่าถูกตรวจภายในเนื้อ ไม่ได้ส่งออกเป็นฟังก์ชันเพรดิเคตแยก

การจัดการเพิ่มเติมที่ทำระหว่างการรวม:

  • การแมปนามแฝง @static ใหม่ — การอ้างอิงตัวแปร @staticN ของแต่ละโพรบถูกเปลี่ยนเลขตามออฟเซตของการเรียกไลบรารีแบบสแตติกของโพรบนั้นในรายการที่รวมแล้ว (เช่น @static0 ของโพรบ B กลายเป็น @static2 ถ้าโพรบ A มีส่วนทำให้เกิดการเรียกแบบสแตติก 2 ครั้ง)
  • การแยกตัวแปร unshared ที่ซ้ำกัน — ถ้าโพรบสองตัวประกาศตัวแปร unshared ชื่อเดียวกัน ตัวแปรของโพรบที่มาทีหลังจะถูกเปลี่ยนชื่อด้วยคำต่อท้ายเป็นตัวเลข (foo → foo_1 ฯลฯ) และการอ้างอิงทั้งหมดในเนื้อและตรรกะ init ของมันถูกเขียนใหม่ให้สอดคล้องกัน