/*
حل إعادة التحميل المباشر لرأس ملف واحد فقط للغة C، مكتوب بلغة C++:
ملحوظة: الملف الوحيد المهم في هذا المستودع هو cr.h
.
يحتوي هذا الملف على وثائق تخفيض السعر والترخيص والتنفيذ وواجهة برمجة التطبيقات العامة. جميع الملفات الأخرى الموجودة في هذا المستودع هي ملفات داعمة ويمكن تجاهلها بأمان.
يمكنك تنزيل وتثبيت 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
. راجع العدد رقم 49.CR_BAD_IMAGE
في حالة ما إذا كان الملف الثنائي لا يزال غير جاهز حتى لو تغير الطابع الزمني الخاص به. قد يحدث هذا إذا كان إنشاء الملف (المترجم أو النسخ) بطيئًا.CR_BAD_IMAGE
). الآن يتم تقليل الإصدار مرة واحدة في معالج التعطل ثم مرة أخرى أثناء الاستعادة ثم يتم صدمه مرة أخرى. لن يؤدي التراجع بسبب صورة غير مكتملة إلى التراجع عن نسختين بشكل غير صحيح، وسيستمر في نفس الإصدار في إعادة محاولة التحميل حتى تصبح الصورة صالحة (انتهى النسخ أو المترجم من الكتابة إليها). قد يؤثر هذا على الاستخدامات الحالية لـ cr
إذا تم استخدام معلومات version
أثناء CR_UNLOAD
حيث ستكون الآن قيمة مختلفة. يمكن العثور على عينتين بسيطتين في دليل samples
.
الأول هو تطبيق وحدة تحكم بسيط يوضح بعض الحالات الثابتة الأساسية التي تعمل بين المثيلات واختبارات التعامل مع الأعطال الأساسية. يتم استخدام الطباعة للإخراج لإظهار ما يحدث.
يوضح الجزء الثاني كيفية إعادة التحميل المباشر لتطبيق opengl باستخدام Dear ImGui. تعيش بعض الولايات في الجانب المضيف بينما يكون معظم الكود في جانب الضيف.
تستخدم العينات والاختبارات نظام بناء fips. يتطلب بايثون و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
يشير التعداد إلى نوع الخطوة التي يتم تنفيذها بواسطة 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
Abort، SIGBRT
على Linux/OSX، غير مستخدم على Windows؛CR_MISALIGN
، 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) يؤكد(e)CR_REALLOC
: تجاوز إعادة تخصيص libc. الافتراضي: #define CR_REALLOC(ptr, size) ::realloc(ptr, size)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
والتعامل مع C يفترض أن الأشياء البسيطة ستعمل في الغالب.
المشكلة هي كيف سيقرر الرابط إعادة ترتيب الأشياء وفقًا لمقدار التغييرات التي تجريها في الكود. بالنسبة للتغييرات المتزايدة والمحلية، لم أواجه أية مشكلات مطلقًا، بشكل عام، لم أواجه أي مشكلات على الإطلاق عند كتابة كود C العادي. الآن، عندما تبدأ الأمور في أن تصبح أكثر تعقيدًا وتقترب من لغة C++، يصبح الأمر أكثر خطورة. إذا كنت بحاجة إلى القيام بأشياء معقدة، أقترح عليك التحقق من RCCPP وقراءة ملف PDF ومنشور مدونتي الأصلي حول cr
هنا.
مع كل هذه المعلومات، ستتمكن من تحديد أيهما أفضل لحالة الاستخدام الخاصة بك.
cr
لرعاية منفذ cr
إلى MacOSX.
داني جرين
روكاس كوبستيس
نوح رينهارت
نيكلاس لوندبرج
سبهر تغديسيان
روبرت غابرييل جاكابوسكي
@pixelherodev
الكسندر
نحن نرحب بجميع المساهمات، وليس هناك أشياء بسيطة للمساهمة بها، حتى إصلاح الأخطاء المطبعية بحرف واحد مرحب به.
الشيء الوحيد الذي نطلبه هو إجراء اختبار شامل والحفاظ على نمط التعليمات البرمجية وتحديث الوثائق.
وكذلك القبول والموافقة على إطلاق أي مساهمة بموجب نفس الترخيص.
رخصة معهد ماساتشوستس للتكنولوجيا (MIT)
حقوق الطبع والنشر (ج) 2017 داني أنجيلو كارميناتي جرين
يُمنح الإذن مجانًا لأي شخص يحصل على نسخة من هذا البرنامج وملفات الوثائق المرتبطة به ("البرنامج")، للتعامل في البرنامج دون قيود، بما في ذلك، على سبيل المثال لا الحصر، حقوق الاستخدام والنسخ والتعديل والدمج. ونشر و/أو توزيع وترخيص من الباطن و/أو بيع نسخ من البرنامج، والسماح للأشخاص الذين تم توفير البرنامج لهم بالقيام بذلك، وفقًا للشروط التالية:
يجب تضمين إشعار حقوق الطبع والنشر أعلاه وإشعار الإذن هذا في جميع النسخ أو الأجزاء الكبيرة من البرنامج.
يتم توفير البرنامج "كما هو"، دون أي ضمان من أي نوع، صريحًا أو ضمنيًا، بما في ذلك، على سبيل المثال لا الحصر، ضمانات القابلية للتسويق والملاءمة لغرض معين وعدم الانتهاك. لا يتحمل المؤلفون أو أصحاب حقوق الطبع والنشر بأي حال من الأحوال المسؤولية عن أي مطالبة أو أضرار أو مسؤولية أخرى، سواء في إجراء العقد أو الضرر أو غير ذلك، الناشئة عن أو خارج أو فيما يتعلق بالبرنامج أو الاستخدام أو المعاملات الأخرى في برمجة.
* /
#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