데모
중요 : MacOS 10.14에서 시작하여 라이브러리에 사용 된 접근 방식은 더 이상 작동하지 않으며 Apple이 낮은 수준의 루틴을 제한 한 것처럼 보입니다. 적어도 우분투 20.04에서 Linux에서는 여전히 잘 작동합니다.
Jet-Live는 C ++ "핫 코드 재 장전"을위한 라이브러리입니다. X86-64 명령 세트와 함께 CPU로 구동되는 64 비트 시스템에서 Linux 및 Modern MacOS (10.12+ 추측)에서 작동합니다. 함수의 재 장전 외에도 코드가 다시로드 된 후 앱의 정적 및 글로벌 상태를 변경하지 않아도 될 수 있습니다 ( "작동 방식"을 참조하십시오. Clang 6.0.1/7.0.1, LLD-7, GCC 6.4.0/7.3.0, GNU LD 2.30, CMAKE 3.10.2, NINJA 1.8.2, XCODE 8.3.3, CMAKE 3.8.2로 4.1 및 MACOS 10.13.6으로 Ubuntu 18.04에서 테스트되었습니다.
중요 : 이 라이브러리에서는 RCCPP 또는 CR과 같은 특별한 방식으로 코드를 구성하도록 강요하지 않으므로 재 장전 가능한 코드를 일부 공유 라이브러리로 분리 할 필요가 없으므로 Jet-Live는 모든 프로젝트에서 가장 방해가되지 않는 방식으로 작동해야합니다.
Windows와 비슷한 것이 필요한 경우 Blink를 사용해보십시오. Windows를 지원할 계획이 없습니다.
c++11 호환 컴파일러가 필요합니다. 또한 여러 개의 종속성이 묶여 있으며, 대부분은 헤더 전용 또는 단일 H/CPP 쌍 라이브러리입니다. 자세한 내용은 lib 디렉토리를 참조하십시오.
이 라이브러리는 CMake 및 Make 또는 Ninja 빌드 시스템을 기반으로 한 프로젝트에 가장 적합하며 기본값은 이러한 도구에 미세 조정됩니다. cmakelists.txt는 compile_commands.json 에 대한 set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 옵션을 추가합니다. 이것은 중요하고 피할 수 없습니다. 자세한 내용은 cmakelists.txt를 참조하십시오. 닌자를 사용하는 경우 닌자를 실행할 때 -d keepdepfile 닌자 플래그를 추가하면 소스와 헤더 파일 사이의 종속성을 추적해야합니다.
include ( path /to/jet-live/cmake/jet_live_setup.cmake) # setup needed compiler and linker flags, include this file in your root CMakeLists.txt
set (JET_LIVE_BUILD_EXAMPLE OFF )
set (JET_LIVE_SHARED ON ) # if you want to
add_subdirectory ( path /to/jet-live)
target_link_libraries (your-app- target jet-live)jet::Live 클래스 인스턴스를 만듭니다liveInstance->update()liveInstance->tryReload() 호출하십시오.중요 : 이 라이브러리는 스레드 안전하지 않습니다. 후드 아래의 스레드를 사용하여 컴파일러를 실행하지만 동일한 스레드에서 모든 라이브러리 메소드를 호출해야합니다.
또한이 라이브러리는 최적화되고 상감 된 기능 및 변수를 다루지 않기 위해 디버그 빌드 ( -O0 , 제거되지 않음, -fvisibility=hidden 것임없이)와 함께 사용합니다. 고도로 최적화 된 스트리핑 빌드에서 어떻게 작동하는지 모르겠습니다. 대부분은 전혀 작동하지 않을 것입니다.
개인적으로 나는 이것을 이렇게 사용합니다. 내 응용 프로그램에 tryReload 가 할당되는 Ctrl+r 바로 가기가 있습니다. 또한 내 앱 앱은 기본 런 루프에서 update 호출하고 onCodePreLoad 및 onCodePostLoad 이벤트에 대해서도 일부 객체를 재현하거나 일부 기능을 재평가합니다.
Ctrl+r 누릅니다 Jet-Live는 파일 변경을 모니터링하고, 변경된 파일을 다시 컴파일하며, tryReload 라고 불리는 경우에만 모든 현재 컴파일 프로세스가 새 코드를 완료하고 다시로드 할 때까지 기다립니다. 각 업데이트에서 tryReload 호출하지 마십시오. 예상대로 작동하지 않으며 소스 코드를 다시로드 할 준비가 된 경우에만 호출하십시오.
코드 편집기와 앱 사이에서 앞뒤로 전환하지 않으려면 Shell Command kill -s USR1 $(pgrep <your_app_name>) 실행하는 키보드 바로 가기를 구성 할 수 있습니다. SIGUSR1 신호가 수신되면 라이브러리가 코드를 재 장전합니다. 적어도 EMACS, Xcode, Clion 및 VScode에서 작동하지만 다른 편집자 및 IDE에서 달성 할 수 있다고 확신합니다. 디버거가 lldb 이고이 신호를 잡고 앱을 중지하는 경우이 명령을 ~/.lldbinit 파일에 추가하십시오.
breakpoint set --name main
breakpoint command add
process handle -n true -p true -s false SIGUSR1
continue
DONE
MACOS에서는 Make 및 Ninja를 제외하고 cmake -G Xcode Generator를 사용할 수 있습니다. 이 경우 xcpretty gem을 설치하십시오.
gem install xcpretty
간단한 예제 앱이 있습니다.
git clone https://github.com/ddovod/jet-live.git && cd jet-live
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Debug .. && make
./example/example hello Command를 시도하십시오. 함수를 수정 한 후 reload 명령을 실행하는 것을 잊지 마십시오.
포괄적이지 않고 지속적으로 업데이트하는 테스트 스위트가 있습니다. 그것을 실행하려면 :
git clone https://github.com/ddovod/jet-live.git && cd jet-live
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Debug -DJET_LIVE_BUILD_TESTS=ON .. && make
../tools/tests/test_runner.py -b . -s ../tests/src/구현 :
compile_commands.json 파일을 재현하여 새 .CPP 파일이 생성 된 후)구현됩니다 :
전혀 구현되지 않습니다.
Jet-Live는 Cmake 및 Make/Ninja 도구와 함께 작동하도록 미세 조정되었지만 다른 빌드 도구에이를 채택하려면 일부 측면에서 '동작을 사용자 정의하는 방법이 있습니다. 출처 및 문서를 참조하십시오. 또한 도서관에서 이벤트를 받기 위해 자신만의 청취자를 만들어내는 것이 좋습니다. ILiveListener 및 LiveConfig 의 문서를 참조하십시오.
중요 : ILiveListener::onLog 사용하여 라이브러리에서 모든 메시지를 기록하여 무언가 잘못되었는지 확인하는 것이 좋습니다.
라이브러리는이 실행 파일의 엘프 헤더와 섹션을 읽고 모든로드 된 공유 라이브러리를 읽고 모든 기호를 찾아서 그 중 어느 것이 푹 빠질 수 있는지 (함수) 또는 전송/재배치 (정적/글로벌 변수)를 찾으려고합니다. 또한 기호 크기와 "실제"주소를 찾습니다.
제트기 와는 별도로 compile_commands.json 실행 파일 근처에서 또는 '부모 디렉토리를 재귀 적으로 찾으려고합니다. 이 파일을 사용하면 다음과 같습니다.
.o (개체) 파일 경로.d (Depfile) 파일 경로 모든 컴파일 장치가 구문 분석되면 모든 소스 파일의 가장 일반적인 디렉토리를 구별하고 소스 파일, 종속성 및 compile_commands.json 과 같은 일부 서비스 파일이있는 모든 디렉토리를 시청하기 시작합니다.
그 외에도 라이브러리는 각 편집 장치에 대한 모든 종속성을 찾으려고합니다. 기본적으로 객체 파일 근처의 Depfiles를 읽습니다 ( -MD 컴파일러 옵션 참조). 객체 파일이 다음에 있다고 가정합니다.
/home/coolhazker/projects/some_project/build/main.cpp.o
Jet-Live는 다음에서 Depfile을 찾으려고합니다.
/home/coolhazker/projects/some_project/build/main.cpp.o.d
or
/home/coolhazker/projects/some_project/build/main.cpp.d
시청 디렉토리하에있는 모든 종속성을 선택하므로 /usr/include/elf.h 와 같은 것은이 파일이 .CPP 파일 중 일부에 실제로 포함되어 있어도 종속성으로 취급되지 않습니다.
이제 라이브러리가 초기화됩니다.
다음으로, 일부 소스 파일을 편집하고 저장하면 Jet-Live는 즉시 백그라운드에서 모든 종속 파일의 컴파일을 시작합니다. 기본적으로 동시 컴파일 프로세스 수는 4이지만 구성 할 수 있습니다. ILiveListener::onLog Method of Listener를 사용하여 성공과 오류에 대해 기록 할 것입니다. 이미 컴파일 중일 때 (또는 대기열에서 대기) 일부 파일의 컴파일을 트리거하는 경우 이전 컴파일 프로세스가 죽고 새로운 프로세스가 큐에 추가되므로 컴파일이 완료되고 새로운 코드를 변경하지 않기를 기다리지 않아도됩니다. 또한 각 파일이 컴파일되면 컴파일러의 새로운 버전에 새로운 종속성이있는 경우 컴파일러가 Depfile을 재현 할 수 있으므로 컴파일 된 파일의 종속성을 업데이트합니다.
Live::tryReload 호출 할 때 라이브러리는 미완성 된 컴파일 프로세스를 기다린 다음 모든 축적 된 새 객체 XXX 이 공유 라이브러리에 링크되어 lib_reloadXXX.so 라는 이름으로 실행 파일 근처에 배치됩니다. 따라서 lib_reloadXXX.so so에는 모든 새 코드가 포함되어 있습니다.
Jet-Live는 dlopen 사용 하여이 라이브러리를로드하고 ELF/Mach-O 헤더 및 섹션을 읽고 모든 기호를 찾습니다. 또한이 새 라이브러리를 구성하는 데 사용 된 객체 파일의 재배치 정보를로드합니다. 이후:
memcpy Memory입니다. 중요 : ILiveListener::onCodePreLoad 이벤트가 lib_reloadXXX.so 프로세스 메모리에로드되기 직전에 시작됩니다. ILiveListener::onCodePostLoad 이벤트는 모든 코드로드 Machinery가 완료된 직후에 발사됩니다.
기능 후보에 대한 자세한 내용은 여기를 참조하십시오. 이 라이브러리는 멋진 서브 후크 라이브러리를 사용하여 기존에서 새로운 기능으로 기능 흐름을 리디렉션합니다. 32 비트 플랫폼에서 기능은 5 바이트 길이가 길어야한다는 것을 알 수 있습니다. 64 비트에는 적어도 14 바이트가 필요하며, 예를 들어 빈 스터브 기능은 아마도 14 바이트에 맞지 않을 것입니다. 내 관찰에서 Clang은 기본적으로 16 바이트 함수 정렬로 코드를 생성합니다. GCC는 기본적 으로이 작업을 수행하지 않으므로 GCC의 경우 -falign-functions=16 플래그가 사용됩니다. 즉, 2 개의 함수의 시작 사이의 간격은 16 바이트가 아니기 때문에 모든 함수를 연결할 수 있습니다.
새로운 버전의 기능은 이미 응용 프로그램에 살고있는 정적 및 글로벌을 사용해야합니다. 왜 중요한가요? 당신이 있다고 가정합니다 (약간의 합성 예, 어쨌든) :
// Singleton.hpp
class Singleton
{
public:
static Singleton& instance ();
};
int veryUsefulFunction ( int value);
// Singleton.cpp
Singleton& Singleton::instance ()
{
static Singleton ins;
return ins;
}
int veryUsefulFunction ( int value)
{
return value * 2 ;
} 그런 다음 다음과 같은 SMTH로 veryUsefulFunction 을 업데이트하려고합니다.
int veryUsefulFunction ( int value)
{
return value * 3 ;
} 이제는 인수가 3을 곱합니다. 그러나 전체 Singleton.cpp 다시로드되고 Singleton::instance 함수가 새 버전을 호출하기 위해 연결되기 때문에 lib_reloadXXX.so . so는 새로운 정적 변수 static Singleton ins 포함 할 것입니다. 이는 초기화 된 Singleton::instance() 코드를 다시 호출 한 후에는 다시 한 번에 싱턴스를 호출하지 않을 것입니다. 그렇기 때문에 모든 정적 및 글로벌을 새 코드로 이전하고 통계의 가드 변수를 전송해야합니다. 정적 및 글로벌과 관련된 대부분의 링크 시간 재배치는 32 비트입니다. 따라서 새 코드가있는 공유 라이브러리가 응용 프로그램에서 메모리에 너무 멀리로드되면 이러한 방식으로 변수를 재배치 할 수 없습니다. 이를 해결하기 위해 새로운 공유 라이브러리는 특수 링커 플래그를 사용하여 연결되어 가상 메모리의 특정 사전 계산 위치에로드 할 수 있습니다 (Apple LD의 -image_base , LLVM LLD 및 -Ttext-segment + -z max-page-size 의 --image-base + -Z GNU LD 링커 플래그 참조).
또한 재 장전 가능한 코드에서 데이터 유형의 메모리 레이아웃을 변경하려고하면 앱이 중단 될 수 있습니다.
이 클래스의 인스턴스가 힙이나 스택에 할당 된 것으로 가정합니다.
class SomeClass
{
public:
void calledEachUpdate () {
m_someVar1++;
}
private:
int m_someVar1 = 0 ;
}편집하고 이제는 다음과 같습니다.
class SomeClass
{
public:
void calledEachUpdate () {
m_someVar1++;
m_someVar2++;
}
private:
int m_someVar1 = 0 ;
int m_someVar2 = 0 ;
} 코드가 다시로드되면 이미 할당 된 객체마다 데이터 레이아웃이 있고 m_someVar2 인스턴스 변수가 없기 때문에 충돌이 발생할 수 있습니다. 그러나 새로운 버전의 calledEachUpdate 는 실제로 랜덤 데이터를 수정하는 것을 수정하려고 시도합니다. 이 경우 onCodePreLoad 콜백 에서이 인스턴스를 삭제하고 onCodePostLoad 콜백에서 다시 만들어야합니다. 그 상태의 올바른 양도는 당신에게 달려 있습니다. 정적 데이터 구조 레이아웃을 변경하려고 시도하면 동일한 효과가 발생합니다. 다형성 클래스 (vtable) 및 캡처가있는 람다 (캡처는 Lambdas의 데이터 필드 내부에 저장됨)에 대해서도 마찬가지입니다.
MIT
중고 라이브러리의 라이센스는 디렉토리 및 소스 코드를 참조하십시오.