/*
用于 C 的单文件头实时重新加载解决方案,用 C++ 编写:
注意:此存储库中唯一重要的文件是cr.h
。
该文件包含 markdown 中的文档、许可证、实现和公共 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_load
并改用cr_plugin_open
。请参阅问题 #49。CR_BAD_IMAGE
,以防二进制文件即使其时间戳发生更改仍未准备好。如果生成文件(编译器或复制)很慢,则可能会发生这种情况。CR_BAD_IMAGE
)。现在,版本在崩溃处理程序中减少了一次,然后在回滚期间减少了一次,然后再次增加。由于图像不完整而导致的回滚不会错误地回滚两个版本,它将继续在同一版本上重试加载,直到图像有效(副本或编译器完成写入)。如果在CR_UNLOAD
期间使用version
信息,这可能会影响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)
这是指向动态可加载二进制入口点函数的函数指针。
论点
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
分段错误。 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
将作为公共 API 头文件在guest
实现中使用。
(可选) 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, size) ::realloc(ptr, size)CR_MALLOC
:覆盖 libc 的 malloc。默认值:#define CR_MALLOC(size) ::malloc(size)CR_FREE
:覆盖 libc 的 free。默认值:#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 )答:阅读我为什么在这里做这个。
答:确保您的应用程序主机和 dll 都使用动态运行时(/MD 或 /MDd),因为堆中分配的任何数据都必须使用相同的分配器实例来释放,方法是在来宾和来宾之间共享运行时主机您将保证使用相同的分配器。
答:是的。这应该可以在 Windows 上正常工作。在 Linux 和 OSX 上可能存在崩溃处理问题
如果出于任何原因必须在cr
之前加载 dll,Visual Studio 可能仍会锁定 PDB。您可能遇到这个问题,解决方案就在这里。
首先,确保您的构建系统不会因仍然链接到共享库而受到干扰。有很多事情可能会出错,您需要确保只有cr
才能处理您的共享库。在 Linux 上,有关如何查找正在发生的情况的更多信息,请检查此问题。
cr
是C
重新加载器,处理 C 时它假设简单的事情大部分都会起作用。
问题是链接器将如何决定根据您在代码中所做的更改量来重新安排内容。对于增量和本地化更改,我从来没有遇到过任何问题,一般来说,通过编写普通的 C 代码,我几乎没有遇到任何问题。现在,当事情开始变得更加复杂并接近 C++ 时,它的风险就变得更大。如果您需要做复杂的事情,我建议检查 RCCPP 并阅读此 PDF 和我关于cr
的原始博客文章。
有了所有这些信息,您将能够决定哪个更适合您的用例。
cr
赞助商赞助将cr
移植到 MacOSX。
丹尼·格莱因
罗卡斯·库普斯蒂斯
诺亚·莱因哈特
尼克拉斯·伦德伯格
塞佩尔·塔格迪西安
罗伯特·加布里埃尔·贾卡博斯基
@pixelherodev
亚历山大
我们欢迎所有的贡献,没有任何小事可以贡献,甚至欢迎修正一个字母的拼写错误。
我们唯一需要的是彻底测试、维护代码风格并保持文档最新。
此外,接受并同意在同一许可证下发布任何贡献。
麻省理工学院许可证 (MIT)
版权所有 (c) 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