/*
C++로 작성된 C용 단일 파일 헤더 전용 라이브 다시 로드 솔루션:
참고: 이 저장소에서 중요한 파일은 cr.h
뿐입니다.
이 파일에는 마크다운 문서, 라이선스, 구현 및 공개 API가 포함되어 있습니다. 이 저장소의 다른 모든 파일은 지원 파일이므로 무시해도 됩니다.
vcpkg 종속성 관리자를 사용하여 cr을 다운로드하고 설치할 수 있습니다.
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
./vcpkg install cr
vcpkg의 cr 포트는 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_close
와의 일관성을 위해 cr_plugin_open
대신 cr_plugin_load
더 이상 사용하지 않습니다. 문제 #49를 참조하세요.CR_BAD_IMAGE
추가했습니다. 파일 생성(컴파일러 또는 복사)이 느린 경우 이런 일이 발생할 수 있습니다.CR_BAD_IMAGE
참조). 이제 버전은 충돌 처리기에서 한 번 감소한 다음 롤백 중에 또 한 번 감소한 다음 다시 충돌합니다. 불완전한 이미지로 인한 롤백은 두 버전을 잘못 롤백하지 않으며, 이미지가 유효할 때까지(복사 또는 컴파일러가 쓰기를 완료할 때까지) 동일한 버전에서 로드를 다시 시도하면서 계속됩니다. version
정보가 이제 다른 값이 되므로 CR_UNLOAD
중에 사용되는 경우 cr
의 현재 사용에 영향을 미칠 수 있습니다. 두 개의 간단한 샘플은 samples
디렉터리에서 찾을 수 있습니다.
첫 번째는 인스턴스와 기본 충돌 처리 테스트 간에 작동하는 몇 가지 기본 정적 상태를 보여주는 간단한 콘솔 애플리케이션입니다. 출력으로 인쇄는 무슨 일이 일어나고 있는지 보여주기 위해 사용됩니다.
두 번째는 Dear ImGui를 사용하여 OpenGL 애플리케이션을 실시간으로 다시 로드하는 방법을 보여줍니다. 일부 상태는 호스트 측에 있지만 대부분의 코드는 게스트 측에 있습니다.
샘플과 테스트에서는 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)
이는 동적 로드 가능 바이너리 진입점 함수에 대한 함수 포인터입니다.
인수
host
에서 guest
로 전달될 컨텍스트에 대한 ctx
포인터입니다. 자세한 내용은 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
분할 오류입니다. Linux/OSX에서는 SIGSEGV
, Windows에서는 EXCEPTION_ACCESS_VIOLATION
;CR_ILLEGAL
불법적인 지시를 한 경우. Linux/OSX에서는 SIGILL
, Windows에서는 EXCEPTION_ILLEGAL_INSTRUCTION
;CR_ABORT
중단, Linux/OSX에서는 SIGBRT
, Windows에서는 사용되지 않습니다.CR_MISALIGN
버스 오류, Linux/OSX의 SIGBUS
또는 Windows의 EXCEPTION_DATATYPE_MISALIGNMENT
;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
정의 이 정의는 host
에 cr.h
포함하기 전에 사용해야 합니다. CR_HOST
정의되지 않은 경우 cr.h
guest
구현에서 사용할 공용 API 헤더 파일로 작동합니다.
선택적으로 CR_HOST
자동 정적 상태 관리( CR_STATE
)를 위한 safety
작동 모드를 구성하는 방법으로 다음 값 중 하나로 정의될 수도 있습니다.
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의 realloc을 무시합니다. 기본값: #define CR_REALLOC(ptr, 크기) ::realloc(ptr, 크기)CR_MALLOC
: libc의 malloc을 무시합니다. 기본값: #define CR_MALLOC(크기) ::malloc(크기)CR_FREE
: libc의 사용 가능 여부를 재정의합니다. 기본값: #define CR_FREE(ptr) ::free(ptr)CR_DEBUG
: CR_ERROR, CR_LOG, CR_TRACE에 디버그 메시지를 출력합니다.CR_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 )A: 제가 이것을 만든 이유를 여기에서 읽어보세요.
A: 힙에 할당된 모든 데이터는 게스트와 dll 간에 런타임을 공유하여 동일한 할당자 인스턴스로 해제되어야 하므로 애플리케이션 호스트와 dll이 모두 동적 런타임(/MD 또는 /MDd)을 사용하고 있는지 확인하세요. 호스트에서는 동일한 할당자가 사용된다는 것을 보장합니다.
답: 그렇습니다. 이는 Windows에서 문제 없이 작동합니다. Linux 및 OSX에서는 충돌 처리에 문제가 있을 수 있습니다.
어떤 이유로 cr
전에 dll을 로드해야 하는 경우 Visual Studio는 여전히 PDB에 대한 잠금을 유지할 수 있습니다. 이 문제가 발생할 수 있으며 해결책은 여기에 있습니다.
먼저, 공유 라이브러리에 여전히 연결되어 빌드 시스템이 방해를 받지 않는지 확인하세요. 잘못될 수 있는 일이 너무 많기 때문에 오직 cr
만이 공유 라이브러리를 처리할 수 있는지 확인해야 합니다. Linux에서 무슨 일이 일어나고 있는지 찾는 방법에 대한 자세한 내용을 보려면 이 문제를 확인하세요.
cr
C
리로더이며 C를 다루는 경우 간단한 작업이 대부분 작동할 것이라고 가정합니다.
문제는 링커가 코드에서 수행하는 변경 사항의 양에 따라 작업을 재정렬하는 방법을 결정하는 것입니다. 점진적이고 지역화된 변경의 경우 아무런 문제가 없었으며 일반적으로 일반 C 코드를 작성해도 문제가 거의 없었습니다. 이제 상황이 더 복잡해지고 C++에 접해지기 시작하면 더 위험해집니다. 복잡한 작업이 필요한 경우 RCCPP를 확인하고 여기에서 이 PDF와 cr
에 대한 내 원래 블로그 게시물을 읽어 보시기 바랍니다.
이러한 모든 정보를 통해 귀하의 사용 사례에 더 적합한 것이 무엇인지 결정할 수 있습니다.
cr
후원자 MacOSX에 대한 cr
포트를 후원합니다.
대니 그레인
로카스 쿠프스티스
노아 라인하트
니클라스 룬드베리
세페르 타그디시안
로버트 가브리엘 자카보스키
@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