-
โซลูชันการรีโหลดสดส่วนหัวของไฟล์เดียวเท่านั้นสำหรับ C เขียนด้วย C ++:
หมายเหตุ: ไฟล์เดียวที่สำคัญในพื้นที่เก็บข้อมูลนี้คือ cr.h
ไฟล์นี้มีเอกสารประกอบใน markdown, ใบอนุญาต, การใช้งาน และ API สาธารณะ ไฟล์อื่นๆ ทั้งหมดในที่เก็บนี้รองรับไฟล์และสามารถละเว้นได้อย่างปลอดภัย
คุณสามารถดาวน์โหลดและติดตั้ง cr โดยใช้ตัวจัดการการพึ่งพา vcpkg:
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
./vcpkg install cr
พอร์ต cr ใน vcpkg ได้รับการปรับปรุงให้ทันสมัยโดยสมาชิกทีม Microsoft และผู้สนับสนุนชุมชน หากเวอร์ชันล้าสมัย โปรดสร้างปัญหาหรือดึงคำขอบนที่เก็บ vcpkg
แอปพลิเคชันโฮสต์ (แบบบาง) ที่ปฏิบัติการได้จะใช้ cr
เพื่อจัดการการโหลดแอปพลิเคชันจริงแบบสดในรูปแบบของไบนารีที่โหลดได้แบบไดนามิก โฮสต์จะมีลักษณะดังนี้:
#define CR_HOST // required in the host only and before including cr.h
#include "../cr.h"
int main ( int argc , char * argv []) {
// the host application should initalize a plugin with a context, a plugin
cr_plugin ctx ;
// the full path to the live-reloadable application
cr_plugin_open ( ctx , "c:/path/to/build/game.dll" );
// call the update function at any frequency matters to you, this will give
// the real application a chance to run
while (! cr_plugin_update ( ctx )) {
// do anything you need to do on host side (ie. windowing and input stuff?)
}
// at the end do not forget to cleanup the plugin context
cr_plugin_close ( ctx );
return 0 ;
}
ในขณะที่แขก (สมัครจริง) จะเป็นเช่น:
CR_EXPORT int cr_main ( struct cr_plugin * ctx , enum cr_op operation ) {
assert ( ctx );
switch ( operation ) {
case CR_LOAD : return on_load (...); // loading back from a reload
case CR_UNLOAD : return on_unload (...); // preparing to a new reload
case CR_CLOSE : ...; // the plugin will close and not reload anymore
}
// CR_STEP
return on_update (...);
}
CR_INITIAL_FAILURE
หากปลั๊กอินเริ่มแรกขัดข้อง โฮสต์จะต้องกำหนดเส้นทางถัดไป และเราจะไม่โหลดปลั๊กอินที่เสียหายอีกครั้ง cr_plugin_load
เพื่อสนับสนุน cr_plugin_open
เพื่อความสอดคล้องกับ cr_plugin_close
ดูฉบับที่ #49CR_BAD_IMAGE
ในกรณีที่ไฟล์ไบนารียังไม่พร้อมแม้ว่าจะมีการเปลี่ยนแปลงการประทับเวลาก็ตาม กรณีนี้อาจเกิดขึ้นได้หากการสร้างไฟล์ (คอมไพเลอร์หรือการคัดลอก) ทำช้าCR_BAD_IMAGE
) ตอนนี้เวอร์ชันจะลดลงหนึ่งครั้งในตัวจัดการข้อขัดข้อง และอีกครั้งหนึ่งในระหว่างการย้อนกลับ จากนั้นจึงชนอีกครั้ง การย้อนกลับเนื่องจากภาพที่ไม่สมบูรณ์จะไม่ย้อนกลับสองเวอร์ชันอย่างไม่ถูกต้อง แต่จะดำเนินต่อไปในเวอร์ชันเดียวกันและลองโหลดซ้ำจนกว่ารูปภาพจะถูกต้อง (คัดลอกหรือคอมไพเลอร์เขียนเสร็จแล้ว) สิ่งนี้อาจส่งผลกระทบต่อการใช้งาน cr
ในปัจจุบัน หากใช้ข้อมูล version
ระหว่าง CR_UNLOAD
เนื่องจากตอนนี้จะเป็นค่าที่แตกต่างกัน ตัวอย่างง่ายๆ สองตัวอย่างสามารถพบได้ในไดเร็กทอรี samples
อย่างแรกคือแอปพลิเคชันคอนโซลธรรมดาที่แสดงสถานะคงที่พื้นฐานบางอย่างที่ทำงานระหว่างอินสแตนซ์และการทดสอบการจัดการข้อขัดข้องขั้นพื้นฐาน การพิมพ์เพื่อส่งออกใช้เพื่อแสดงสิ่งที่เกิดขึ้น
ส่วนที่สองสาธิตวิธีการโหลดแอปพลิเคชัน opengl แบบสดโดยใช้ Dear ImGui บางรัฐอาศัยอยู่ในฝั่งโฮสต์ในขณะที่โค้ดส่วนใหญ่อยู่ฝั่งแขก
ตัวอย่างและการทดสอบใช้ระบบการสร้าง fips มันต้องใช้ Python และ CMake
$ ./fips build # will generate and build all artifacts
$ ./fips run crTest # To run tests
$ ./fips run imgui_host # To run imgui sample
# open a new console, then modify imgui_guest.cpp
$ ./fips make imgui_guest # to build and force imgui sample live reload
int (*cr_main)(struct cr_plugin *ctx, enum cr_op operation)
นี่คือตัวชี้ฟังก์ชันไปยังฟังก์ชันจุดเข้าไบนารีที่โหลดได้แบบไดนามิก
ข้อโต้แย้ง
ctx
ไปยังบริบทที่จะถูกส่งจาก host
ไป guest
ซึ่งมีข้อมูลอันมีค่าเกี่ยวกับเวอร์ชันที่โหลดในปัจจุบัน สาเหตุของความล้มเหลว และข้อมูลผู้ใช้ สำหรับข้อมูลเพิ่มเติมโปรดดูที่ cr_plugin
operation
การที่กำลังดำเนินการ โปรดดูที่ cr_op
กลับ
CR_USER
0 หรือค่าบวกที่จะถูกส่งไปยังกระบวนการ host
bool cr_plugin_open(cr_plugin &ctx, const char *fullpath)
โหลดและเริ่มต้นปลั๊กอิน
ข้อโต้แย้ง
ctx
บริบทที่จะจัดการข้อมูลภายในของปลั๊กอินและข้อมูลผู้ใช้fullpath
เต็มพร้อมชื่อไฟล์ไปยังไบนารีที่โหลดได้สำหรับปลั๊กอินหรือ NULL
กลับ
true
ในกรณีที่ประสบความสำเร็จ มิฉะนั้นจะ false
void cr_set_temporary_path(cr_plugin& ctx, const std::string &path)
กำหนดเส้นทางชั่วคราวที่จะวางสำเนาชั่วคราวของปลั๊กอิน ควรถูกเรียกทันทีหลังจาก cr_plugin_open()
หากไม่ได้ตั้งค่าพาธ temporary
สำเนาชั่วคราวของไฟล์จะถูกคัดลอกไปยังไดเร็กทอรีเดียวกันกับที่ไฟล์ต้นฉบับตั้งอยู่
ข้อโต้แย้ง
ctx
บริบทที่จะจัดการข้อมูลภายในของปลั๊กอินและข้อมูลผู้ใช้path
แบบเต็มไปยังไดเร็กทอรีที่มีอยู่ซึ่งจะใช้สำหรับจัดเก็บสำเนาปลั๊กอินชั่วคราว int cr_plugin_update(cr_plugin &ctx, bool reloadCheck = true)
ฟังก์ชันนี้จะเรียกใช้ฟังก์ชันปลั๊กอิน cr_main
ควรเรียกบ่อยเท่าที่ตรรกะหลัก/แอปพลิเคชันต้องการ
ข้อโต้แย้ง
ctx
ข้อมูลบริบทปลั๊กอินปัจจุบันreloadCheck
เป็นทางเลือก: ทำการตรวจสอบดิสก์ (stat()) เพื่อดูว่าไลบรารีไดนามิกจำเป็นต้องโหลดซ้ำหรือไม่กลับ
cr_main
void cr_plugin_close(cr_plugin &ctx)
ล้างสถานะภายในเมื่อไม่จำเป็นต้องใช้ปลั๊กอินอีกต่อไป
ข้อโต้แย้ง
ctx
ข้อมูลบริบทปลั๊กอินปัจจุบัน cr_op
Enum ระบุประเภทของขั้นตอนที่ host
ดำเนินการ:
CR_LOAD
กำลังดำเนินการโหลดที่เกิดจากการรีโหลด สามารถใช้เพื่อกู้คืนสถานะภายในที่บันทึกไว้CR_STEP
การอัปเดตแอปพลิเคชัน นี่เป็นการดำเนินการปกติและบ่อยที่สุดCR_UNLOAD
การยกเลิกการโหลดสำหรับการโหลดปลั๊กอินซ้ำจะดำเนินการ ทำให้แอปพลิเคชันมีโอกาสจัดเก็บข้อมูลที่จำเป็นเพียงครั้งเดียวCR_CLOSE
ใช้เมื่อปิดปลั๊กอิน ซึ่งทำงานเหมือนกับ CR_UNLOAD
แต่ไม่ควรคาดหวัง CR_LOAD
ในภายหลัง cr_plugin
โครงสร้างบริบทอินสแตนซ์ของปลั๊กอิน
p
สำหรับข้อมูล cr ภายในuserdata
อาจใช้ข้อมูลผู้ใช้เพื่อส่งข้อมูลระหว่างการโหลดซ้ำversion
สำหรับการโหลดซ้ำแต่ละครั้งที่สำเร็จ โดยเริ่มต้นที่ 1 สำหรับการโหลดครั้งแรก เวอร์ชันจะเปลี่ยนไปในระหว่างกระบวนการจัดการข้อขัดข้องfailure
ที่ใช้โดยระบบป้องกันการชนจะเก็บรหัสข้อผิดพลาดความล้มเหลวล่าสุดที่ทำให้เกิดการย้อนกลับ ดู cr_failure
สำหรับข้อมูลเพิ่มเติมเกี่ยวกับค่าที่เป็นไปได้ cr_failure
หากเกิดข้อขัดข้องในไบนารีที่โหลดได้ ตัวจัดการข้อขัดข้องจะระบุสาเหตุของข้อขัดข้องด้วยข้อใดข้อหนึ่งต่อไปนี้:
CR_NONE
ไม่มีข้อผิดพลาด;CR_SEGFAULT
ข้อผิดพลาดในการแบ่งส่วน SIGSEGV
บน Linux/OSX หรือ EXCEPTION_ACCESS_VIOLATION
บน Windows;CR_ILLEGAL
ในกรณีที่มีคำสั่งที่ผิดกฎหมาย SIGILL
บน Linux/OSX หรือ EXCEPTION_ILLEGAL_INSTRUCTION
บน Windows;CR_ABORT
ยกเลิก SIGBRT
บน Linux/OSX ไม่ได้ใช้บน WindowsCR_MISALIGN
Bus, SIGBUS
บน Linux/OSX หรือ EXCEPTION_DATATYPE_MISALIGNMENT
บน Windows;CR_BOUNDS
เป็น EXCEPTION_ARRAY_BOUNDS_EXCEEDED
, Windows เท่านั้นCR_STACKOVERFLOW
คือ EXCEPTION_STACK_OVERFLOW
, Windows เท่านั้นCR_STATE_INVALIDATED
ความล้มเหลวด้านความปลอดภัยในการจัดการ CR_STATE
แบบคงที่;CR_BAD_IMAGE
ปลั๊กอินไม่ใช่รูปภาพที่ถูกต้อง (เช่น คอมไพเลอร์อาจยังเขียนอยู่)CR_OTHER
สัญญาณอื่นๆ เฉพาะ Linux เท่านั้นCR_USER
(สำหรับค่าลบที่ส่งคืนจาก cr_main
); CR_HOST
กำหนด ควรใช้การกำหนดนี้ก่อนที่จะรวม cr.h
ใน host
หากไม่ได้กำหนด CR_HOST
cr.h
จะทำงานเป็นไฟล์ส่วนหัว API สาธารณะที่จะใช้ในการใช้งาน guest
นอกจากนี้ CR_HOST
ยังอาจถูกกำหนดเป็นค่าใดค่าหนึ่งต่อไปนี้เป็นวิธีการกำหนดค่าโหมดการทำงานด้าน safety
สำหรับการจัดการสถานะคงที่อัตโนมัติ ( CR_STATE
):
CR_SAFEST
จะตรวจสอบที่อยู่และขนาดของส่วนข้อมูลสถานะระหว่างการโหลดซ้ำ หากมีสิ่งใดเปลี่ยนแปลง โหลดจะย้อนกลับCR_SAFE
จะตรวจสอบเฉพาะขนาดของส่วนสถานะ ซึ่งหมายความว่าที่อยู่ของสถิติอาจมีการเปลี่ยนแปลง (และทางที่ดีควรหลีกเลี่ยงการถือตัวชี้ไปที่เนื้อหาคงที่)CR_UNSAFE
จะไม่ตรวจสอบสิ่งใดนอกจากขนาดของส่วนพอดี อาจไม่แม่นยำโดยไม่จำเป็น (การขยายขนาดเป็นที่ยอมรับได้ แต่การลดขนาดไม่ได้) นี่เป็นพฤติกรรมเริ่มต้นCR_DISABLE
ปิดใช้งานการจัดการสถานะคงที่อัตโนมัติโดยสมบูรณ์ CR_STATE
ใช้เพื่อแท็กตัวแปรคงที่ส่วนกลางหรือเฉพาะที่ที่จะบันทึกและกู้คืนระหว่างการโหลดซ้ำ
การใช้งาน
static bool CR_STATE bInitialized = false;
คุณสามารถกำหนดมาโครเหล่านี้ก่อนที่จะรวม cr.h ในโฮสต์ (CR_HOST) เพื่อปรับแต่งการจัดสรรหน่วยความจำ cr.h และลักษณะการทำงานอื่นๆ:
CR_MAIN_FUNC
: เปลี่ยนสัญลักษณ์ 'cr_main' เป็นชื่อฟังก์ชันที่ผู้ใช้กำหนด ค่าเริ่มต้น: #define CR_MAIN_FUNC "cr_main"CR_ASSERT
: แทนที่การยืนยัน ค่าเริ่มต้น: #define CA_ASSERT(e) assert(e)CR_REALLOC
: แทนที่การจัดสรรใหม่ของ libc ค่าเริ่มต้น: #define CR_REALLOC(ptr, ขนาด) ::realloc(ptr, ขนาด)CR_MALLOC
: แทนที่ malloc ของ libc ค่าเริ่มต้น: #define CR_MALLOC(ขนาด) ::malloc(ขนาด)CR_FREE
: แทนที่ libc ฟรี ค่าเริ่มต้น: #define CR_FREE(ptr) ::free(ptr)CR_DEBUG
: ส่งออกข้อความดีบักใน CR_ERROR, CR_LOG และ CR_TRACECR_ERROR
: บันทึกข้อความดีบักไปยัง stderr ค่าเริ่มต้น (CR_DEBUG เท่านั้น): #define CR_ERROR(...) fprintf(stderr, VA_ARGS )CR_LOG
: บันทึกข้อความดีบัก ค่าเริ่มต้น (CR_DEBUG เท่านั้น): #define CR_LOG(...) fprintf(stdout, VA_ARGS )CR_TRACE
: พิมพ์การเรียกใช้ฟังก์ชัน ค่าเริ่มต้น (CR_DEBUG เท่านั้น): #define CR_TRACE(...) fprintf(stdout, "CR_TRACE: %sn", FUNCTION )ตอบ: อ่านว่าทำไมฉันถึงทำสิ่งนี้ที่นี่
ตอบ: ตรวจสอบให้แน่ใจว่าทั้งโฮสต์แอปพลิเคชันและ dll ของคุณใช้รันไทม์แบบไดนามิก (/MD หรือ /MDd) เนื่องจากข้อมูลใดๆ ที่จัดสรรในฮีปจะต้องถูกปล่อยให้เป็นอิสระด้วยอินสแตนซ์ตัวจัดสรรเดียวกัน โดยการแชร์รันไทม์ระหว่างแขกและ โฮสต์ คุณจะรับประกันว่ามีการใช้ตัวจัดสรรเดียวกัน
ก. ใช่. สิ่งนี้ควรจะทำงานได้โดยไม่มีปัญหาบน Windows บน Linux และ OSX อาจมีปัญหาในการจัดการข้อขัดข้อง
หากคุณต้องโหลด dll ก่อน cr
ไม่ว่าด้วยเหตุผลใดก็ตาม Visual Studio อาจยังคงล็อค PDB ไว้ คุณอาจประสบปัญหานี้และวิธีแก้ไขอยู่ที่นี่
ขั้นแรก ตรวจสอบให้แน่ใจว่าระบบบิลด์ของคุณไม่ได้รบกวนการลิงก์ไปยังไลบรารีที่แบ่งใช้ของคุณ มีหลายสิ่งที่อาจผิดพลาดได้ และคุณต้องแน่ใจว่ามีเพียง cr
เท่านั้นที่จะจัดการกับไลบรารี่ที่แชร์ของคุณ บน Linux หากต้องการข้อมูลเพิ่มเติมเกี่ยวกับวิธีค้นหาสิ่งที่เกิดขึ้น ให้ตรวจสอบปัญหานี้
cr
คือ C
reloader และการจัดการกับ C โดยถือว่าสิ่งง่าย ๆ ส่วนใหญ่จะใช้งานได้
ปัญหาคือวิธีที่ตัวเชื่อมโยงจะตัดสินใจจัดเรียงสิ่งต่าง ๆ ตามจำนวนการเปลี่ยนแปลงที่คุณทำในโค้ด สำหรับการเปลี่ยนแปลงแบบค่อยเป็นค่อยไปและแบบท้องถิ่นนั้น ฉันไม่เคยมีปัญหาใดๆ เลย โดยทั่วไปแล้วฉันแทบไม่มีปัญหาใดๆ เลยด้วยการเขียนโค้ด C ปกติ ตอนนี้ เมื่อสิ่งต่าง ๆ เริ่มซับซ้อนมากขึ้นและมีขอบเขต C++ มันก็จะมีความเสี่ยงมากขึ้น หากคุณต้องการทำสิ่งที่ซับซ้อน ฉันขอแนะนำให้ตรวจสอบ RCCPP และอ่าน PDF นี้และโพสต์บล็อกต้นฉบับของฉันเกี่ยวกับ cr
ที่นี่
ด้วยข้อมูลทั้งหมดนี้ คุณจะตัดสินใจได้ว่าอันไหนเหมาะกับกรณีการใช้งานของคุณมากกว่า
cr
.สปอนเซอร์ สำหรับการสนับสนุนพอร์ต cr
ไปยัง MacOSX
แดนนี่ กรีน
โรคัส คุปสตีส
โนอาห์ ไรน์ฮาร์ต
นิคลาส ลุนด์เบิร์ก
เซเปียร์ ทากดิเซียน
โรเบิร์ต กาเบรียล ยาคาบอสกี้
@pixelherodev
อเล็กซานเดอร์
เรายินดีรับ ทุก การมีส่วนร่วม ไม่มีสิ่งเล็กๆ น้อยๆ ให้มีส่วนร่วม แม้แต่การแก้ไขตัวพิมพ์ผิดตัวเดียวก็ยินดี
สิ่งเดียวที่เราต้องการคือการทดสอบอย่างละเอียด รักษารูปแบบโค้ด และรักษาเอกสารให้ทันสมัยอยู่เสมอ
พร้อมทั้งยอมรับและตกลงที่จะปล่อยผลงานใดๆ ภายใต้ใบอนุญาตเดียวกัน
ใบอนุญาต MIT (MIT)
ลิขสิทธิ์ (c) 2017 Danny Angelo Carminati Grein
อนุญาตให้บุคคลใดก็ตามที่ได้รับสำเนาของซอฟต์แวร์นี้และไฟล์เอกสารที่เกี่ยวข้อง ("ซอฟต์แวร์") อนุญาตโดยไม่เสียค่าใช้จ่าย เพื่อจัดการกับซอฟต์แวร์โดยไม่มีข้อจำกัด รวมถึงแต่ไม่จำกัดเพียงสิทธิ์ในการใช้ คัดลอก ปรับเปลี่ยน ผสาน เผยแพร่ แจกจ่าย ให้อนุญาตช่วง และ/หรือขายสำเนาของซอฟต์แวร์ และอนุญาตให้บุคคลที่ได้รับซอฟต์แวร์นี้สามารถทำได้ ภายใต้เงื่อนไขต่อไปนี้:
ประกาศเกี่ยวกับลิขสิทธิ์ข้างต้นและประกาศการอนุญาตนี้จะรวมอยู่ในสำเนาทั้งหมดหรือส่วนสำคัญของซอฟต์แวร์
ซอฟต์แวร์นี้มีให้ "ตามที่เป็น" โดยไม่มีการรับประกันใดๆ ทั้งโดยชัดแจ้งหรือโดยนัย ซึ่งรวมถึงแต่ไม่จำกัดเพียงการรับประกันความสามารถในการค้าขาย ความเหมาะสมสำหรับวัตถุประสงค์เฉพาะ และการไม่ละเมิด ไม่ว่าในกรณีใดผู้เขียนหรือผู้ถือลิขสิทธิ์จะต้องรับผิดต่อการเรียกร้องค่าเสียหายหรือความรับผิดอื่นใดไม่ว่าในการกระทำของสัญญาการละเมิดหรืออย่างอื่นที่เกิดขึ้นจากหรือเกี่ยวข้องกับซอฟต์แวร์หรือการใช้งานหรือข้อตกลงอื่น ๆ ใน ซอฟต์แวร์.
* /
#ifndef __CR_H__
#define __CR_H__
//
// Global OS specific defines/customizations
//
#if defined( _WIN32 )
#define CR_WINDOWS
#define CR_PLUGIN ( name ) "" name ".dll"
#elif defined( __linux__ )
#define CR_LINUX
#define CR_PLUGIN ( name ) "lib" name ".so"
#elif defined( __APPLE__ )
#define CR_OSX
#define CR_PLUGIN ( name ) "lib" name ".dylib"
#else
#error "Unknown/unsupported platform, please open an issue if you think this
platform should be supported."
#endif // CR_WINDOWS || CR_LINUX || CR_OSX
//
// Global compiler specific defines/customizations
//
#if defined( _MSC_VER )
#if defined( __cplusplus )
#define CR_EXPORT extern "C" __declspec(dllexport)
#define CR_IMPORT extern "C" __declspec(dllimport)
#else
#define CR_EXPORT __declspec(dllexport)
#define CR_IMPORT __declspec(dllimport)
#endif
#endif // defined(_MSC_VER)
#if defined( __GNUC__ ) // clang & gcc
#if defined( __cplusplus )
#define CR_EXPORT extern "C" __attribute__((visibility("default")))
#else
#define CR_EXPORT __attribute__((visibility("default")))
#endif
#define CR_IMPORT
#endif // defined(__GNUC__)
#if defined( __MINGW32__ )
#undef CR_EXPORT
#if defined( __cplusplus )
#define CR_EXPORT extern "C" __declspec(dllexport)
#else
#define CR_EXPORT __declspec(dllexport)
#endif
#endif
// cr_mode defines how much we validate global state transfer between
// instances. The default is CR_UNSAFE, you can choose another mode by
// defining CR_HOST, ie.: #define CR_HOST CR_SAFEST
enum cr_mode {
CR_SAFEST = 0 , // validate address and size of the state section, if
// anything changes the load will rollback
CR_SAFE = 1 , // validate only the size of the state section, this means
// that address is assumed to be safe if avoided keeping
// references to global/static states
CR_UNSAFE = 2 , // don't validate anything but that the size of the section
// fits, may not be identical though
CR_DISABLE = 3 // completely disable the auto state transfer
};
// cr_op is passed into the guest process to indicate the current operation
// happening so the process can manage its internal data if it needs.
enum cr_op {
CR_LOAD = 0 ,
CR_STEP = 1 ,
CR_UNLOAD = 2 ,
CR_CLOSE = 3 ,
};
enum cr_failure {
CR_NONE , // No error
CR_SEGFAULT , // SIGSEGV / EXCEPTION_ACCESS_VIOLATION
CR_ILLEGAL , // illegal instruction (SIGILL) / EXCEPTION_ILLEGAL_INSTRUCTION
CR_ABORT , // abort (SIGBRT)
CR_MISALIGN , // bus error (SIGBUS) / EXCEPTION_DATATYPE_MISALIGNMENT
CR_BOUNDS , // EXCEPTION_ARRAY_BOUNDS_EXCEEDED
CR_STACKOVERFLOW , // EXCEPTION_STACK_OVERFLOW
CR_STATE_INVALIDATED , // one or more global data section changed and does
// not safely match basically a failure of
// cr_plugin_validate_sections
CR_BAD_IMAGE , // The binary is not valid - compiler is still writing it
CR_INITIAL_FAILURE , // Plugin version 1 crashed, cannot rollback
CR_OTHER , // Unknown or other signal,
CR_USER = 0x100 ,
};
struct cr_plugin ;
typedef int ( * cr_plugin_main_func )( struct cr_plugin * ctx , enum cr_op operation );
// public interface for the plugin context, this has some user facing
// variables that may be used to manage reload feedback.
// - userdata may be used by the user to pass information between reloads
// - version is the reload counter (after loading the first instance it will
// be 1, not 0)
// - failure is the (platform specific) last error code for any crash that may
// happen to cause a rollback reload used by the crash protection system
struct cr_plugin {
void * p ;
void * userdata ;
unsigned int version ;
enum cr_failure failure ;
unsigned int next_version ;
unsigned int last_working_version ;
};
#ifndef CR_HOST
// Guest specific compiler defines/customizations
#if defined( _MSC_VER )
#pragma section(".state", read, write)
#define CR_STATE __declspec(allocate(".state"))
#endif // defined(_MSC_VER)
#if defined( CR_OSX )
#define CR_STATE __attribute__((used, section("__DATA,__state")))
#else
#if defined( __GNUC__ ) // clang & gcc
#define CR_STATE __attribute__((section(".state")))
#endif // defined(__GNUC__)
#endif
#else // #ifndef CR_HOST
// Overridable macros
#ifndef CR_LOG
# ifdef CR_DEBUG
# include
# define CR_LOG (...) fprintf(stdout, __VA_ARGS__)
# else
# define CR_LOG (...)
# endif
#endif
#ifndef CR_ERROR
# ifdef CR_DEBUG
# include
# define CR_ERROR (...) fprintf(stderr, __VA_ARGS__)
# else
# define CR_ERROR (...)
# endif
#endif
#ifndef CR_TRACE
# ifdef CR_DEBUG
# include
# define CR_TRACE fprintf(stdout, "CR_TRACE: %sn", __FUNCTION__);
# else
# define CR_TRACE
# endif
#endif
#ifndef CR_MAIN_FUNC
# define CR_MAIN_FUNC "cr_main"
#endif
#ifndef CR_ASSERT
# include
# define CR_ASSERT ( e ) assert(e)
#endif
#ifndef CR_REALLOC
# include
# define CR_REALLOC ( ptr , size ) ::realloc(ptr, size)
#endif
#ifndef CR_FREE
# include
# define CR_FREE ( ptr ) ::free(ptr)
#endif
#ifndef CR_MALLOC
# include
# define CR_MALLOC ( size ) ::malloc(size)
#endif
#if defined( _MSC_VER )
// we should probably push and pop this
# pragma warning(disable:4003) // not enough actual parameters for macro 'identifier'
#endif
#define CR_DO_EXPAND ( x ) x##1337
#define CR_EXPAND ( x ) CR_DO_EXPAND(x)
#if CR_EXPAND ( CR_HOST ) == 1337
#define CR_OP_MODE CR_UNSAFE
#else
#define CR_OP_MODE CR_HOST
#endif
#include
#include // duration for sleep
#include // memcpy
#include
#include // this_thread::sleep_for
#if defined( CR_WINDOWS )
#define CR_PATH_SEPARATOR '\'
#define CR_PATH_SEPARATOR_INVALID '/'
#else
#define CR_PATH_SEPARATOR '/'
#define CR_PATH_SEPARATOR_INVALID '\'
#endif
static void cr_split_path ( std :: string path , std :: string & parent_dir ,
std :: string & base_name , std :: string & ext ) {
std:: replace ( path . begin (), path . end (), CR_PATH_SEPARATOR_INVALID ,
CR_PATH_SEPARATOR );
auto sep_pos = path . rfind ( CR_PATH_SEPARATOR );
auto dot_pos = path . rfind ( '.' );
if ( sep_pos == std :: string :: npos ) {
parent_dir = "" ;
if ( dot_pos == std :: string :: npos ) {
ext = "" ;
base_name = path ;
} else {
ext = path . substr ( dot_pos );
base_name = path . substr ( 0 , dot_pos );
}
} else {
parent_dir = path . substr ( 0 , sep_pos + 1 );
if ( dot_pos == std :: string :: npos || sep_pos > dot_pos ) {
ext = "" ;
base_name = path . substr ( sep_pos + 1 );
} else {
ext = path . substr ( dot_pos );
base_name = path . substr ( sep_pos + 1 , dot_pos - sep_pos - 1 );
}
}
}
static std :: string cr_version_path ( const std :: string & basepath ,
unsigned version ,
const std :: string & temppath ) {
std:: string folder , fname , ext ;
cr_split_path ( basepath , folder , fname , ext );
std:: string ver = std :: to_string ( version );
#if defined( _MSC_VER )
// When patching PDB file path in library file we will drop path and leave only file name.
// Length of path is extra space for version number. Trim file name only if version number
// length exceeds pdb folder path length. This is not relevant on other platforms.
if ( ver . size () > folder . size ()) {
fname = fname . substr ( 0 , fname . size () - ( ver . size () - folder . size () - 1 ));
}
#endif
if (! temppath . empty ()) {
folder = temppath ;
}
return folder + fname + ver + ext ;
}
namespace cr_plugin_section_type {
enum e { state , bss , count };
}
namespace cr_plugin_section_version {
enum e { backup , current , count };
}
struct cr_plugin_section {
cr_plugin_section_type :: e type = {};
intptr_t base = 0 ;
char * ptr = 0 ;
int64_t size = 0 ;
void * data = nullptr ;
};
struct cr_plugin_segment {
char * ptr = 0 ;
int64_t size = 0 ;
};
// keep track of some internal state about the plugin, should not be messed
// with by user
struct cr_internal {
std :: string fullname = {};
std:: string temppath = {};
time_t timestamp = {};
void * handle = nullptr ;
cr_plugin_main_func main = nullptr ;
cr_plugin_segment seg = {};
cr_plugin_section data [ cr_plugin_section_type :: count ]
[ cr_plugin_section_version :: count ] = {};
cr_mode mode = CR_SAFEST ;
};
static bool cr_plugin_section_validate ( cr_plugin & ctx ,
cr_plugin_section_type :: e type ,
intptr_t vaddr , intptr_t ptr ,
int64_t size );
static void cr_plugin_sections_reload ( cr_plugin & ctx ,
cr_plugin_section_version :: e version );
static void cr_plugin_sections_store ( cr_plugin & ctx );
static void cr_plugin_sections_backup ( cr_plugin & ctx );
static void cr_plugin_reload ( cr_plugin & ctx );
static int cr_plugin_unload ( cr_plugin & ctx , bool rollback , bool close );
static bool cr_plugin_changed ( cr_plugin & ctx );
static bool cr_plugin_rollback ( cr_plugin & ctx );
static int cr_plugin_main ( cr_plugin & ctx , cr_op operation );
void cr_set_temporary_path ( cr_plugin & ctx , const std :: string & path ) {
auto pimpl = ( cr_internal * ) ctx . p ;
pimpl -> temppath = path ;
}
#if defined( CR_WINDOWS )
// clang-format off
#ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
#endif
#include
#include
// clang-format on
#if defined( _MSC_VER )
#pragma comment(lib, "dbghelp.lib")
#endif
using so_handle = HMODULE ;
#ifdef UNICODE
# define CR_WINDOWS_ConvertPath ( _newpath , _path ) std::wstring _newpath(cr_utf8_to_wstring(_path))
static std :: wstring cr_utf8_to_wstring ( const std :: string & str ) {
int wlen = MultiByteToWideChar ( CP_UTF8 , 0 , str . c_str (), -1 , 0 , 0 );
wchar_t wpath_small [ MAX_PATH ];
std:: unique_ptr < wchar_t [] > wpath_big ;
wchar_t * wpath = wpath_small ;
if ( wlen > _countof ( wpath_small )) {
wpath_big = std :: unique_ptr < wchar_t [] > ( new wchar_t [ wlen ]);
wpath = wpath_big . get ();
}
if ( MultiByteToWideChar ( CP_UTF8 , 0 , str . c_str (), -1 , wpath , wlen ) != wlen ) {
return L"" ;
}
return wpath ;
}
#else
# define CR_WINDOWS_ConvertPath ( _newpath , _path ) const std::string &_newpath = _path
#endif // UNICODE
static time_t cr_last_write_time ( const std :: string & path ) {
CR_WINDOWS_ConvertPath ( _path , path );
WIN32_FILE_ATTRIBUTE_DATA fad ;
if (! GetFileAttributesEx ( _path . c_str (), GetFileExInfoStandard , & fad )) {
return -1 ;
}
if ( fad . nFileSizeHigh == 0 && fad . nFileSizeLow == 0 ) {
return -1 ;
}
LARGE_INTEGER time ;
time . HighPart = fad . ftLastWriteTime . dwHighDateTime ;
time . LowPart = fad . ftLastWriteTime . dwLowDateTime ;
return static_cast < time_t > ( time . QuadPart / 10000000 - 11644473600LL );
}
static bool cr_exists ( const std :: string & path ) {
CR_WINDOWS_ConvertPath ( _path , path );
return GetFileAttributes ( _path . c_str ()) != INVALID_FILE_ATTRIBUTES ;
}
static bool cr_copy ( const std :: string & from , const std :: string & to ) {
CR_WINDOWS_ConvertPath ( _from , from );
CR_WINDOWS_ConvertPath ( _to , to );
return CopyFile ( _from . c_str (), _to . c_str (), FALSE) ? true : false;
}
static void cr_del ( const std :: string & path ) {
CR_WINDOWS_ConvertPath ( _path , path );
DeleteFile ( _path . c_str ());
}
// If using Microsoft Visual C/C++ compiler we need to do some workaround the
// fact that the compiled binary has a fullpath to the PDB hardcoded inside
// it. This causes a lot of headaches when trying compile while debugging as
// the referenced PDB will be locked by the debugger.
// To solve this problem, we patch the binary to rename the PDB to something
// we know will be unique to our in-flight instance, so when debugging it will
// lock this unique PDB and the compiler will be able to overwrite the
// original one.
#if defined( _MSC_VER )
#include
#include
#include
#include
static std :: string cr_replace_extension ( const std :: string & filepath ,
const std :: string & ext ) {
std:: string folder , filename , old_ext ;
cr_split_path ( filepath , folder , filename , old_ext );
return folder + filename + ext ;
}
template < class T >
static T struct_cast ( void * ptr , LONG offset = 0 ) {
return reinterpret_cast < T > ( reinterpret_cast < intptr_t > ( ptr ) + offset );
}
// RSDS Debug Information for PDB files
using DebugInfoSignature = DWORD ;
#define CR_RSDS_SIGNATURE 'SDSR'
struct cr_rsds_hdr {
DebugInfoSignature signature ;
GUID guid ;
long version ;
char filename [ 1 ];
};
static bool cr_pe_debugdir_rva ( PIMAGE_OPTIONAL_HEADER optionalHeader ,
DWORD & debugDirRva , DWORD & debugDirSize ) {
if ( optionalHeader -> Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC ) {
auto optionalHeader64 =
struct_cast < PIMAGE_OPTIONAL_HEADER64 > ( optionalHeader );
debugDirRva =
optionalHeader64 -> DataDirectory [ IMAGE_DIRECTORY_ENTRY_DEBUG ]
. VirtualAddress ;
debugDirSize =
optionalHeader64 -> DataDirectory [ IMAGE_DIRECTORY_ENTRY_DEBUG ]. Size ;
} else {
auto optionalHeader32 =
struct_cast < PIMAGE_OPTIONAL_HEADER32 > ( optionalHeader );
debugDirRva =
optionalHeader32 -> DataDirectory [ IMAGE_DIRECTO