เกริ่นนำ
เมื่อใช้ไลบรารี C/C++ ที่ไม่ใช่แซนด์บ็อกซ์ ตัวลิงก์เพื่อให้แน่ใจว่าฟังก์ชันที่จำเป็นทั้งหมดจะพร้อมใช้งานหลังจากการคอมไพล์ คุณจึงไม่ต้องกังวลว่าการเรียกใช้ API อาจล้มเหลวขณะรันไทม์หรือไม่
แต่เมื่อใช้ไลบรารีแซนด์บ็อกซ์ การดำเนินการของไลบรารีจะอยู่ในกระบวนการที่แยกต่างหาก ความล้มเหลวในการเรียก API จะต้องมีการตรวจสอบปัญหาทุกชนิดที่เกี่ยวข้องกับการส่งผ่านเลเยอร์ RPC บางครั้งข้อผิดพลาดเกี่ยวกับเลเยอร์ RPC อาจไม่เป็นที่สนใจ เช่น เมื่อทำการประมวลผลข้อมูลจำนวนมากและเพิ่งรีสตาร์ทแซนด์บ็อกซ์
อย่างไรก็ตาม จากเหตุผลที่กล่าวไว้ข้างต้น สิ่งสำคัญคือต้องขยายการตรวจสอบข้อผิดพลาดที่เกิดขึ้นเป็นประจำของค่าการส่งคืนของการเรียก API ที่ทำแซนด์บ็อกซ์ ให้รวมการตรวจสอบว่ามีการส่งคืนข้อผิดพลาดบนเลเยอร์ RPC หรือไม่ นี่คือสาเหตุที่ต้นแบบฟังก์ชันไลบรารีทั้งหมดแสดงค่า ::sapi::StatusOr<T>
แทนที่จะเป็น T ในกรณีที่เรียกใช้ฟังก์ชันไลบรารีไม่สำเร็จ (เช่น เนื่องจากมีการละเมิดแซนด์บ็อกซ์) ค่าที่ส่งกลับจะมีรายละเอียดเกี่ยวกับข้อผิดพลาดที่เกิดขึ้น
การจัดการข้อผิดพลาดของเลเยอร์ RPC หมายความว่าการเรียกแต่ละครั้งไปยังไลบรารีแซนด์บ็อกซ์ตามด้วยการตรวจสอบเพิ่มเติมของเลเยอร์ RPC ของ SAPI เพื่อจัดการกับสถานการณ์เลวร้ายเหล่านี้ SAPI จึงมีโมดูลธุรกรรม SAPI (transaction.h) อยู่ด้วย โมดูลนี้ประกอบด้วยคลาส ::sapi::Transaction
และตรวจสอบว่าการเรียกฟังก์ชันทั้งหมดไปยังไลบรารีแซนด์บ็อกซ์เสร็จสมบูรณ์โดยไม่มีปัญหาระดับ RPC หรือส่งกลับข้อผิดพลาดที่เกี่ยวข้อง
ธุรกรรม SAPI
SAPI จะแยกโค้ดโฮสต์ออกจากไลบรารีแซนด์บ็อกซ์และทำให้ผู้โทรสามารถรีสตาร์ทหรือล้มเลิกคำขอการประมวลผลข้อมูลที่มีปัญหาได้ ธุรกรรม SAPI จะล้ำหน้าไปอีกขั้นและทำซ้ำกระบวนการที่ล้มเหลวโดยอัตโนมัติ
คุณใช้ธุรกรรม SAPI ได้ 2 วิธี ได้แก่ รับค่าจาก ::sapi::Transaction
โดยตรง หรือการใช้ตัวชี้ฟังก์ชันที่ส่งไปยัง ::sapi::BasicTransaction
ธุรกรรม SAPI กำหนดโดยการลบล้างฟังก์ชัน 3 รายการต่อไปนี้
วิธีการทำธุรกรรม SAPI | |
---|---|
::sapi::Transaction::Init() |
ซึ่งคล้ายกับการเรียกใช้วิธีการเริ่มต้นของไลบรารี C/C++ โดยทั่วไป ระบบจะเรียกใช้เมธอดเพียงครั้งเดียวระหว่างการทำธุรกรรมแต่ละครั้งไปยังไลบรารีแซนด์บ็อกซ์ เว้นแต่ว่าจะเริ่มธุรกรรมใหม่ ในกรณีของการรีสตาร์ท ระบบจะเรียกใช้เมธอดอีกครั้ง โดยไม่คำนึงถึงจำนวนการรีสตาร์ทที่เกิดขึ้นก่อนหน้านี้ |
::sapi::Transaction::Main() |
เมธอดจะถูกเรียกใช้สำหรับการเรียกไปยัง ::sapi::Transaction::Run() แต่ละครั้ง |
::sapi::Transaction::Finish() |
ซึ่งคล้ายกับการเรียกใช้วิธีการล้างข้อมูลของไลบรารี C/C++ ทั่วไป ระบบจะเรียกเมธอดเพียงครั้งเดียวระหว่างการทำลายออบเจ็กต์ธุรกรรม SAPI |
การใช้งานคลังปกติ
ในโปรเจ็กต์ที่ไม่มีไลบรารีแซนด์บ็อกซ์ รูปแบบปกติเมื่อจัดการกับไลบรารีจะมีลักษณะดังนี้
LibInit();
while (data = NextDataToProcess()) {
result += LibProcessData(data);
}
LibClose();
ไลบรารีจะเริ่มต้น แล้วใช้ฟังก์ชันที่ส่งออกของไลบรารี และสุดท้ายจะมีการเรียกฟังก์ชันสิ้นสุด/ปิดเพื่อล้างสภาพแวดล้อม
การใช้ไลบรารีแซนด์บ็อกซ์
ในโปรเจ็กต์ที่มีไลบรารีแซนด์บ็อกซ์ โค้ดจากการใช้งานไลบรารีปกติจะแปลเป็นข้อมูลโค้ดต่อไปนี้เมื่อใช้ธุรกรรมที่มีโค้ดเรียกกลับ
// Overwrite the Init method, passed to BasicTransaction initialization
::absl::Status Init(::sapi::Sandbox* sandbox) {
// Instantiate the SAPI Object
LibraryAPI lib(sandbox);
// Instantiate the Sandboxed Library
SAPI_RETURN_IF_ERROR(lib.LibInit());
return ::absl::OkStatus();
}
// Overwrite the Finish method, passed to BasicTransaction initialization
::absl::Status Finish(::sapi::Sandbox *sandbox) {
// Instantiate the SAPI Object
LibraryAPI lib(sandbox);
// Clean-up sandboxed library instance
SAPI_RETURN_IF_ERROR(lib.LibClose());
return ::absl::OkStatus();
}
// Wrapper function to process data, passed to Run method call
::absl::Status HandleData(::sapi::Sandbox *sandbox, Data data_to_process,
Result *out) {
// Instantiate the SAPI Object
LibraryAPI lib(sandbox);
// Call the sandboxed function LibProcessData
SAPI_ASSIGN_OR_RETURN(*out, lib.LibProcessData(data_to_process));
return ::absl::OkStatus();
}
void Handle() {
// Use SAPI Transactions by passing function pointers to ::sapi::BasicTransaction
::sapi::BasicTransaction transaction(Init, Finish);
while (data = NextDataToProcess()) {
::sandbox2::Result result;
// call the ::sapi::Transaction::Run() method
transaction.Run(HandleData, data, &result);
// ...
}
// ...
}
คลาสธุรกรรมจะต้องเริ่มต้นไลบรารีอีกครั้งในกรณีที่เกิดข้อผิดพลาดระหว่างการเรียกใช้ handle_data
โปรดดูข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ในส่วนถัดไป
เริ่มทำธุรกรรมอีกครั้ง
หากการเรียก API ไลบรารีที่ทำแซนด์บ็อกซ์ทำให้เกิดข้อผิดพลาดระหว่างการดำเนินการตามเมธอดธุรกรรม SAPI (ดูตารางด้านบน) ธุรกรรมจะรีสตาร์ท จำนวนเริ่มต้นของการรีสตาร์ทจะกำหนดโดย kDefaultRetryCnt
ใน transaction.h
ตัวอย่างของข้อผิดพลาดที่เพิ่มขึ้นซึ่งจะทำให้เกิดการรีสตาร์ท ได้แก่:
- มีการละเมิดแซนด์บ็อกซ์
- กระบวนการแซนด์บ็อกซ์ขัดข้อง
- ฟังก์ชันแซนด์บ็อกซ์แสดงรหัสข้อผิดพลาดเนื่องจากข้อผิดพลาดของไลบรารี
ขั้นตอนการรีสตาร์ทจะดำเนินการตามโฟลว์ Init()
และ Main()
ปกติ และหากการเรียกเมธอด ::sapi::Transaction::Run()
ซ้ำส่งข้อผิดพลาดกลับมา วิธีการทั้งหมดจะแสดงผลข้อผิดพลาดกลับไปยังผู้เรียกใช้
การจัดการข้อผิดพลาดเกี่ยวกับแซนด์บ็อกซ์หรือ RPC
อินเทอร์เฟซของไลบรารีแซนด์บ็อกซ์ที่สร้างขึ้นโดยอัตโนมัติจะพยายามมีความใกล้เคียงกับต้นแบบฟังก์ชันไลบรารี C/C++ ดั้งเดิมมากที่สุด อย่างไรก็ตาม ไลบรารีแซนด์บ็อกซ์ต้องสามารถส่งสัญญาณข้อผิดพลาดของแซนด์บ็อกซ์หรือ RPC
ซึ่งทำได้โดยการใช้ประเภทการแสดงผล ::sapi::StatusOr<T>
(หรือ ::sapi::Status
สำหรับฟังก์ชันที่แสดงผล void
) แทนการส่งคืนค่าผลลัพธ์ของฟังก์ชันแซนด์บ็อกซ์โดยตรง
นอกจากนี้ SAPI ยังมีมาโครที่สะดวกสบายในการตรวจสอบและโต้ตอบกับออบเจ็กต์สถานะ SAPI อีกด้วย มาโครเหล่านี้จะกำหนดไว้ในไฟล์ส่วนหัว status_macro.h
ข้อมูลโค้ดต่อไปนี้เป็นบางส่วนจากตัวอย่างผลรวมและสาธิตการใช้สถานะ SAPI และมาโคร
// Instead of void, use ::sapi::Status
::sapi::Status SumTransaction::Main() {
// Instantiate the SAPI Object
SumApi f(sandbox());
// ::sapi::StatusOr<int> sum(int a, int b)
SAPI_ASSIGN_OR_RETURN(int v, f.sum(1000, 337));
// ...
// ::sapi::Status sums(sapi::v::Ptr* params)
SumParams params;
params.mutable_data()->a = 1111;
params.mutable_data()->b = 222;
params.mutable_data()->ret = 0;
SAPI_RETURN_IF_ERROR(f.sums(params.PtrBoth()));
// ...
// Gets symbol address and prints its value
int *ssaddr;
SAPI_RETURN_IF_ERROR(sandbox()->Symbol(
"sumsymbol", reinterpret_cast<void**>(&ssaddr)));
::sapi::v::Int sumsymbol;
sumsymbol.SetRemote(ssaddr);
SAPI_RETURN_IF_ERROR(sandbox()->TransferFromSandboxee(&sumsymbol));
// ...
return ::sapi::OkStatus();
}
รีสตาร์ทแซนด์บ็อกซ์
ไลบรารีที่แซนด์บ็อกซ์จำนวนมากจัดการกับการป้อนข้อมูลที่ละเอียดอ่อนของผู้ใช้ หากไลบรารีที่ทำแซนด์บ็อกซ์เกิดความเสียหายในบางจุดและจัดเก็บข้อมูลระหว่างการเรียกใช้ ข้อมูลที่ละเอียดอ่อนนี้จะมีความเสี่ยง ตัวอย่างเช่น หากไลบรารี Imagemagick เวอร์ชันที่ทำแซนด์บ็อกซ์เริ่มส่งภาพของการเรียกใช้ครั้งก่อนออกมา
คุณไม่ควรนำแซนด์บ็อกซ์มาใช้ซ้ำในการเรียกใช้หลายครั้งเพื่อหลีกเลี่ยงสถานการณ์ดังกล่าว หากต้องการหยุดการใช้แซนด์บ็อกซ์ซ้ำ โค้ดโฮสต์จะสามารถเริ่มต้นกระบวนการใหม่ของไลบรารีแซนด์บ็อกซ์โดยใช้ ::sapi::Sandbox::Restart()
หรือ ::sapi::Transaction::Restart()
เมื่อใช้ธุรกรรม SAPI
การรีสตาร์ทจะทำให้การอ้างอิงไปยังกระบวนการไลบรารีแซนด์บ็อกซ์ไม่ถูกต้อง ซึ่งหมายความว่าข้อบ่งชี้ไฟล์ที่ส่งหรือหน่วยความจำที่จัดสรรจะไม่มีอีกต่อไป