티스토리 뷰
이 글은 파이썬 언어로 작성한 프로그램을 C/C++ 언어로 변환할 수 있을까? 하는 물음에서 시작되었다. 파이썬 언어의 장점도 많지만 속도가 느리고 메모리 사용량이 많다는 단점, 그리고 소스코드 배포로 인한 보안성 취약 등을 언급하는 사람들이 있다.
물론 파이썬 소스 코드를 배포하지 않고 컴파일한 파이썬 바이트코드(*.pyc)로 배포하는 방법이 있지만, 이것 또한 바이트코드 디컴파일러(예, https://github.com/rocky/python-uncompyle6/)를 사용하면 주석을 제외한 대부분의 코드를 복원할 수 있다.
파이썬 소스 코드를 보호하는 다른 방법 중의 하나는 코드를 읽기 어렵게 만드는 난독화(obfuscation)를 수행하는 것으로 도구를 사용하여 함수 이름이나 변수명을 거의 읽기 어렵게 만드는 과정을 거쳐 배포하는 것이다.(예, https://github.com/liftoff/pyminifier, https://github.com/astrand/pyobfuscate) 이 방법은 소스코드 자체는 사람이 읽기 어렵게 변형되지만, 파이썬 인터프리터 입장에서는 난독화 이전과 달라진 것이 없다.
파이썬의 성능 제약 문제를 일부 해소하면서 소스 코드도 보호하는 접근이 있었다. 파이썬 컴파일러 누이카(Nuitka, https://nuitka.net/)라는 프로젝트가 있었다. 파이썬 언어로 만든 파이썬 컴파일러로 파이썬 코드를 파싱 하면서 일단 C언어 형태로 만들고, 이것을 C/C++ 컴파일러를 통해서 컴파일하여 실행프로그램으로 만드는 방식이다. 바이너리 실행파일을 배포하므로 당연히 소스코드에 대한 보안성을 높일 수 있고 C/C++로 변환하여 컴파일한 코드와 libpython를 사용하여 파이썬 인터프리터가 동작하는 것과 유사하게 동작하면서도 일부 성능 향상의 효과를 볼 수 있다. 변환한 C/C++ 코드는 디버깅이나 프로파일링에 활용할 수도 있다.
pip install Nuitka
Collecting Nuitka
Downloading Nuitka-1.9.5.tar.gz (3.9 MB)
---------------------------------------- 3.9/3.9 MB 5.8 MB/s eta 0:00:00
Installing build dependencies ... done
Getting requirements to build wheel ... done
Preparing metadata (pyproject.toml) ... done
Collecting ordered-set>=4.1.0 (from Nuitka)
Downloading ordered_set-4.1.0-py3-none-any.whl (7.6 kB)
Collecting zstandard>=0.15 (from Nuitka)
Downloading zstandard-0.22.0-cp310-cp310-win_amd64.whl.metadata (3.0 kB)
Downloading zstandard-0.22.0-cp310-cp310-win_amd64.whl (511 kB)
---------------------------------------- 511.6/511.6 kB 4.0 MB/s eta 0:00:00
Building wheels for collected packages: Nuitka
Building wheel for Nuitka (pyproject.toml) ... done
Created wheel for Nuitka: filename=Nuitka-1.9.5-cp310-cp310-win_amd64.whl size=3240406 sha256=22decafda547788a3081201dc23050ceeee905d74bc9a3f314a8459f5f7ab453
Stored in directory: c:\users\dylab\appdata\local\pip\cache\wheels\f1\b1\4b\d9a84c08081fb014c2951b49fb383c480364163c08f698ae9b
Successfully built Nuitka
Installing collected packages: zstandard, ordered-set, Nuitka
Successfully installed Nuitka-1.9.5 ordered-set-4.1.0 zstandard-0.22.0
누이카의 설치는 "pip install Nuitka"로 간단히 설치할 수 있다.
누이카를 실행하면 위의 그림처럼 "PASS 1" 단계에서는 지정한 메인 프로그램부터 import로 지정한 모듈까지 자동적으로 검색하면서 파이썬 코드를 C언어로 변환한다. import를 따라가면서 분석하는 옵션은 "--follow-imports"이고 기본적으로 ON이다.
C언어로 변환 작업이 끝나면 위의 그림처럼 C/C++ 컴파일러를 통해서 C 컴파일 및 링크 작업을 수행하여 실행 프로그램을 제작한다.
필자는 다음의 명령으로 누이카를 실행했고 그 결과는 메인 프로그램 이름(예제에서는 Beremiz)으로 *.exe와 *.cmd 파일이 생성되었고 위의 그림처럼 *.build 라는 폴더에서 바이너리 생성 과정 중 만들어진 C 소스 코드와 컴파일 오브젝트를 확인할 수 있었다.
python -m nuitka --mingw64 --include-module=BeremizIDE --windows-company-name=mycompany --windows-product-version=1.0 Beremiz.py
빌드 과정 없이 C언어 소스 코드만 생성하려면 "--generate-c-only" 옵션을 사용하면 된다. 문제는 누이카가 소스를 분석하면서 import 구문에 사용한 모듈들을 위의 그림처럼 자동으로 추출하여 C언어로 변환하지만 __import__(모듈명)처럼 동적으로 모듈을 import 하는 경우에는 컴파일러가 어떤 모듈을 임포트 하는지 알 수 없으므로 이런 경우는 " --include-module="과 같은 옵션을 사용하여 필요한 패키지나 모듈을 포함시킨다.
def LoadExtensions(self):
for extfilename in self.extensions:
from util.TranslationCatalogs import AddCatalog
from util.BitmapLibrary import AddBitmapFolder
extension_folder = os.path.split(os.path.realpath(extfilename))[0]
sys.path.append(extension_folder)
AddCatalog(os.path.join(extension_folder, "locale"))
AddBitmapFolder(os.path.join(extension_folder, "images"))
execfile(extfilename, self.globals())
static PyObject *impl___main__$$$function__12_LoadExtensions(PyThreadState *tstate, struct Nuitka_FunctionObject const *self, PyObject **python_pars) {
// Preserve error status for checks
#ifndef __NUITKA_NO_ASSERT__
NUITKA_MAY_BE_UNUSED bool had_error = HAS_ERROR_OCCURRED(tstate);
#endif
// Local variable declarations.
PyObject *par_self = python_pars[0];
PyObject *var_extfilename = NULL;
PyObject *var_AddCatalog = NULL;
PyObject *var_AddBitmapFolder = NULL;
PyObject *var_extension_folder = NULL;
PyObject *tmp_for_loop_1__for_iterator = NULL;
PyObject *tmp_for_loop_1__iter_value = NULL;
struct Nuitka_FrameObject *frame_2431b6500371d5739f654820119ef72d;
NUITKA_MAY_BE_UNUSED char const *type_description_1 = NULL;
PyObject *exception_type = NULL;
PyObject *exception_value = NULL;
PyTracebackObject *exception_tb = NULL;
NUITKA_MAY_BE_UNUSED int exception_lineno = 0;
NUITKA_MAY_BE_UNUSED nuitka_void tmp_unused;
PyObject *exception_keeper_type_1;
PyObject *exception_keeper_value_1;
PyTracebackObject *exception_keeper_tb_1;
NUITKA_MAY_BE_UNUSED int exception_keeper_lineno_1;
static struct Nuitka_FrameObject *cache_frame_2431b6500371d5739f654820119ef72d = NULL;
PyObject *tmp_return_value = NULL;
PyObject *exception_keeper_type_2;
PyObject *exception_keeper_value_2;
PyTracebackObject *exception_keeper_tb_2;
NUITKA_MAY_BE_UNUSED int exception_keeper_lineno_2;
// Actual function body.
// Tried code:
if (isFrameUnusable(cache_frame_2431b6500371d5739f654820119ef72d)) {
Py_XDECREF(cache_frame_2431b6500371d5739f654820119ef72d);
#if _DEBUG_REFCOUNTS
if (cache_frame_2431b6500371d5739f654820119ef72d == NULL) {
count_active_frame_cache_instances += 1;
} else {
count_released_frame_cache_instances += 1;
}
count_allocated_frame_cache_instances += 1;
#endif
cache_frame_2431b6500371d5739f654820119ef72d = MAKE_FUNCTION_FRAME(tstate, codeobj_2431b6500371d5739f654820119ef72d, module___main__, sizeof(void *)+sizeof(void *)+sizeof(void *)+sizeof(void *)+sizeof(void *));
#if _DEBUG_REFCOUNTS
} else {
count_hit_frame_cache_instances += 1;
#endif
}
//......
위의 코드는 파이썬 메인 프로그램에 있는 함수와 C언어로 변환된 함수의 일부 소스 코드로 변환된 C 소스 코드를 보면 거의 암호 수준이다. C 소스 코드는 누이카 자체적으로 바이너리 실행 파일을 만들기 위한 중간 파일일 뿐이지, 파이썬 언어와 C언어 소스 간의 변환하고는 거리가 멀다. 누이카가 만든 C 소스 코드는 직접 수정하여 재사용할 수 있는 코드라고 보기는 어렵다. 파이썬 코드의 성능 개선과 보안성에 초점을 맞추는 것이 적절하다. 결과적으로 파이썬 소스 코드를 도구를 사용하여 재사용 가능한 C/C++ 소스코드로 변환하는 것에는 한계가 있고 파이썬 코드를 참조하여 C/C++ 코드로 재작성하는 것이 적절한 선택으로 보인다.
'프로그래밍' 카테고리의 다른 글
QSplitter로 QT 창 나누기 (0) | 2024.01.02 |
---|---|
파이썬 프로그램 분석 도구 비즈트레이서(VizTracer) 사용하기 (0) | 2023.12.22 |
파이썬 프로그램의 병목/정체 구간 찾기, 프로파일러 사용하기 (0) | 2023.09.05 |
모드버스(Modbus) 응용 개발을 위한 준비 (0) | 2023.08.12 |
C#에서 워드 문서 만들기 및 수정하기 (0) | 2023.07.31 |