python 에서 sdk 를 배포할때

Cute_Security15·2025년 3월 19일
0

linux dev

목록 보기
11/13

상황

arducam 에서는 B4010 (Arducam ToF Camera / Device Code : UC-981 Rev.B)
ToF 카메라 모듈 제어를 위한 python 예제코드를 제공하고 있다.

이 예제코드를 사용하려면 python 패키지 'ArducamDepthCamera' 를 설치해야 한다.

python 코드가 어떻게 작성되어 있는지 궁금하여, ArducamDepthCamera 를 열어보려 했더니
특이한 이름의 .so 파일과, 처음보는 파일들이 존재하고 있다.

root@raspberrypi:~# python3
Python 3.11.2 (main, Sep 14 2024, 03:00:30) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import ArducamDepthCamera as ac
>>> print(ac.__file__)
/usr/local/lib/python3.11/dist-packages/ArducamDepthCamera/__init__.py
>>>

root@raspberrypi:/usr/local/lib/python3.11/dist-packages/ArducamDepthCamera# ls -l
total 492
-rw-r--r-- 1 root root 469817 Mar 19 11:43 ArducamDepthCamera.cpython-311-aarch64-linux-gnu.so
-rw-r--r-- 1 root root  21808 Mar 19 11:43 ArducamDepthCamera.pyi
-rw-r--r-- 1 root root    128 Mar 19 11:43 __init__.py
drwxr-xr-x 2 root root   4096 Mar 19 14:23 __pycache__
root@raspberrypi:/usr/local/lib/python3.11/dist-packages/ArducamDepthCamera#

또한 상위폴더에는 뭔가, 직접 사용하는듯한 so 파일들이 여럿 발견된다

root@raspberrypi:/usr/local/lib/python3.11/dist-packages/ArducamDepthCamera# cd ..
root@raspberrypi:/usr/local/lib/python3.11/dist-packages# cd ArducamDepthCamera.libs/
root@raspberrypi:/usr/local/lib/python3.11/dist-packages/ArducamDepthCamera.libs# ls -l
total 11240
-rw-r--r-- 1 root root   66617 Mar 19 11:43 libarducam_config_parser-7bca5332.so
-rw-r--r-- 1 root root 4836225 Mar 19 11:43 libarducam_evk_sdk-665d9580.so.1.0.4
-rw-r--r-- 1 root root  277833 Mar 19 11:43 libgomp-6eb3f1d0.so.1.0.0
-rwxr-xr-x 1 root root 6323865 Mar 19 11:43 libsi4c-50f152a4.so
root@raspberrypi:/usr/local/lib/python3.11/dist-packages/ArducamDepthCamera.libs#

python 에서 so 를 가져다 쓸수 있는 구조인듯 한데, 컨셉을 파악해본다.

확인한 내용

어떤 파일을 볼지에 대해

먼저 pycache 는 무시
내부에 있는건 python bytecode (.pyc) 로, init.py 를 그대로 빌드한 것이다.

## pycdc decompiler 로 확인 (cf. uncompyle6 / decompyle3 는 python 3.11 미지원)

root@raspberrypi:~/pycdc/build# ./pycdc /usr/local/lib/python3.11/dist-packages/ArducamDepthCamera/__pycache__/__init__.cpython-311.pyc
# Source Generated with Decompyle++
# File: __init__.cpython-311.pyc (Python 3.11)

from ArducamDepthCamera import *
from ArducamDepthCamera import __version__ as __tof_version__
__version__ = __tof_version__
root@raspberrypi:~/pycdc/build#

다음은 pyi 파일
이건 pip install 시 생성된 타입정보 파일이라고 한다.

ArducamDepthCamera.pyi this file is auto generated? or python source code written by human?

ChatGPT의 말:
The ArducamDepthCamera.pyi file is a stub file, typically used for type hinting in Python. It is usually auto-generated but can also be written manually by a developer.

직접 stubgen 으로 생성해보았다. --> pyi 파일도 무시

root@user-virtual-machine:~/cython2# pip install mypy
...
root@user-virtual-machine:~/cython2# stubgen -m mymodule
Processed 1 modules
Generated out/mymodule.pyi
root@user-virtual-machine:~/cython2# cd out/
root@user-virtual-machine:~/cython2/out# ls
mymodule.pyi
root@user-virtual-machine:~/cython2/out# 
root@user-virtual-machine:~/cython2/out# cat mymodule.pyi
import _cython_3_0_12

PI: float
__test__: dict
add: _cython_3_0_12.cython_function_or_method
divide: _cython_3_0_12.cython_function_or_method
multiply: _cython_3_0_12.cython_function_or_method
subtract: _cython_3_0_12.cython_function_or_method
root@user-virtual-machine:~/cython2/out#

그럼 남은 파일은 특이한 이름을 가진, ArducamDepthCamera.cpython-311-aarch64-linux-gnu.so 이다.

gotcha

먼저 gotcha 가 한가지 있다.

cython 과 CPython 은 다른것이다.

  • cython : .pyx 확장자를 갖는 언어로, python 코드를 CPython 확장모듈(.so) 로 바꾸어 준다.
  • CPython : apt-get install python3 할때 설치되는 python 구현체

따라서 .so 이름에 cpython 이 붙는건, cython 으로 생성된 .so 라는 강한 심증을 가질수 있다.

2가지 테스트

그렇다면, 다음 2가지 테스트로 확인 과정을 수행하였다.

1) cython 으로 .so 생성, python 테스트 프로그램에서 import

## .so 로 만들 python 스크립트
root@user-virtual-machine:~/cython# cat mymodule.pyx
# mymodule.pyx

# Function to add two numbers
def add(int a, int b):
    return a + b

# Function to subtract two numbers
def subtract(int a, int b):
    return a - b

# Function to multiply two numbers
def multiply(int a, int b):
    return a * b

# Function to divide two numbers
def divide(int a, int b):
    if b != 0:
        return a / b
    else:
        return "Cannot divide by zero"

# A constant value
PI = 3.14159

## cythonize 를 호출할 스크립트
root@user-virtual-machine:~/cython# cat setup.py
from setuptools import setup
from Cython.Build import cythonize

setup(
    ext_modules=cythonize("mymodule.pyx")
)

## setup.py 실행명령
root@user-virtual-machine:~/cython# cat run.sh
#!/bin/bash

python3 setup.py build_ext --inplace

## cython 으로 만든 .so 를 사용할 파이선 테스트 프로그램
root@user-virtual-machine:~/cython# cat main.py
# main.py

import mymodule

result_add = mymodule.add(5, 3)

print(f"Add : {result_add}")

cythonize 로 만들어진 CPython 확장모듈(.so) 을, 일반 파이선 프로그램에서 정상적으로 import 하는걸 확인

root@user-virtual-machine:~/cython# ./run.sh
Compiling mymodule.pyx because it changed.
[1/1] Cythonizing mymodule.pyx
/usr/local/lib/python3.10/dist-packages/Cython/Compiler/Main.py:381: FutureWarning: Cython directive 'language_level' not set, using '3str' for now (Py3). This has changed from earlier releases! File: /root/cython/mymodule.pyx
  tree = Parsing.p_module(s, pxd, full_module_name)
running build_ext
building 'mymodule' extension
creating build
creating build/temp.linux-x86_64-3.10
x86_64-linux-gnu-gcc -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O2 -Wall -g -fstack-protector-strong -Wformat -Werror=format-security -g -fwrapv -O2 -g -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -fPIC -I/usr/include/python3.10 -c mymodule.c -o build/temp.linux-x86_64-3.10/mymodule.o
creating build/lib.linux-x86_64-3.10
x86_64-linux-gnu-gcc -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-Bsymbolic-functions -g -fwrapv -O2 -Wl,-Bsymbolic-functions -g -fwrapv -O2 -g -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 build/temp.linux-x86_64-3.10/mymodule.o -o build/lib.linux-x86_64-3.10/mymodule.cpython-310-x86_64-linux-gnu.so
copying build/lib.linux-x86_64-3.10/mymodule.cpython-310-x86_64-linux-gnu.so ->
root@user-virtual-machine:~/cython#
root@user-virtual-machine:~/cython# ls
build  main.py  mymodule.c  mymodule.cpython-310-x86_64-linux-gnu.so  mymodule.pyx  run.sh  setup.py
root@user-virtual-machine:~/cython#
root@user-virtual-machine:~/cython# python3 main.py
Add : 8
root@user-virtual-machine:~/cython#

2) 1번 테스트 + cython 으로 생성된 .so 에서 ctypes 를 활용해 다른 .so 를 사용하도록 구성

## .so 로 만들 python 스크립트
root@user-virtual-machine:~/cython2# cat mymodule.pyx
# mymodule.pyx

import ctypes

sdk = ctypes.CDLL("./sdk/build/lib/libsdk.so")

# Function to add two numbers
def add(int a, int b):
    sdk.main()

    return a + b

# Function to subtract two numbers
def subtract(int a, int b):
    return a - b

# Function to multiply two numbers
def multiply(int a, int b):
    return a * b

# Function to divide two numbers
def divide(int a, int b):
    if b != 0:
        return a / b
    else:
        return "Cannot divide by zero"

# A constant value
PI = 3.14159

## 다른 .so (sdk)
root@user-virtual-machine:~/cython2# cd sdk
root@user-virtual-machine:~/cython2/sdk# ls
CMakeLists.txt  main.c
root@user-virtual-machine:~/cython2/sdk# cat main.c
#include <stdio.h>

int main() {
        printf("hello cython\n");

        return 0;
}
root@user-virtual-machine:~/cython2/sdk#
root@user-virtual-machine:~/cython2/sdk# cat CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(sdk)

add_library(sdk SHARED main.c)

set_target_properties(sdk PROPERTIES
        LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
)
root@user-virtual-machine:~/cython2/sdk#

CPython 확장모듈(.so) 에서 정상적으로 libsdk.so 를 사용한걸 확인

root@user-virtual-machine:~/cython2# ./run.sh
Compiling mymodule.pyx because it changed.
[1/1] Cythonizing mymodule.pyx
/usr/local/lib/python3.10/dist-packages/Cython/Compiler/Main.py:381: FutureWarning: Cython directive 'language_level' not set, using '3str' for now (Py3). This has changed from earlier releases! File: /root/cython2/mymodule.pyx
  tree = Parsing.p_module(s, pxd, full_module_name)
running build_ext
building 'mymodule' extension
creating build
creating build/temp.linux-x86_64-3.10
x86_64-linux-gnu-gcc -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O2 -Wall -g -fstack-protector-strong -Wformat -Werror=format-security -g -fwrapv -O2 -g -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -fPIC -I/usr/include/python3.10 -c mymodule.c -o build/temp.linux-x86_64-3.10/mymodule.o
creating build/lib.linux-x86_64-3.10
x86_64-linux-gnu-gcc -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-Bsymbolic-functions -g -fwrapv -O2 -Wl,-Bsymbolic-functions -g -fwrapv -O2 -g -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 build/temp.linux-x86_64-3.10/mymodule.o -o build/lib.linux-x86_64-3.10/mymodule.cpython-310-x86_64-linux-gnu.so
copying build/lib.linux-x86_64-3.10/mymodule.cpython-310-x86_64-linux-gnu.so ->
root@user-virtual-machine:~/cython2# ls
build  main.py  mymodule.c  mymodule.cpython-310-x86_64-linux-gnu.so  mymodule.pyx  run.sh  sdk  setup.py
root@user-virtual-machine:~/cython2#
root@user-virtual-machine:~/cython2# python3 main.py
hello cython
Add : 8
root@user-virtual-machine:~/cython2#

결론

ArducamDepthCamera 에서는 cython 과 ctypes 를 활용해

1) 효과적으로 python 예제 수행에 필요한 패키지를 제공함과 동시에
2) 패키지 내부코드는 감추는 구성을 사용하였다.

profile
관심분야 : Filesystem, Data structure, user/kernel IPC

관련 채용 정보