Egloos | Log-in
F/OSS study
F/OSS study
[Linux] 모듈 버전 관리
Linux: 2.6.32


커널 모듈은 ELF 형식의 오브젝트 파일이며 insmod 혹은 modprobe 명령을 통해 커널에 로드되면
그 때서야 커널과 링크된다.
모듈에서 사용할 수 있는 커널 API들은 EXPORT_SYMBOL()과 같은 매크로를 통해 명시적으로 선언하는데
이러한 심볼 정보는 커널 내의 __ksymtab 섹션에 별도로 저장되어 읽기 전용 데이터로 로드되므로
모듈 로드 시 해당 섹션을 검사하여 동적 링크를 수행할 수 있다.

모듈은 빌드한 커널 버전과 실제로 동작 중인 커널 버전이 같다면 아무 문제없이 잘 사용할 수 있다.
모듈의 소스 코드가 공개되어 있다면 이후에 커널 버전이 변경된 경우 그에 맞추어 다시 컴파일 해 준다면
(물론 필요에 따라 소스를 수정할 수도 있을 것이다) 다시 사용할 수 있다.
하지만 소스가 없이 바이너리 모듈 만이 존재한다면 동작 중인 커널 버전이 변경된 경우 해당 모듈을 사용할 수 있을지 알 수 없을 것이다.
좀 더 정확히 말하면 커널이 변경되면서 모듈이 사용하던 API의 인터페이스가 변경되었다면 문제가 발생하게 될 것이고
그렇지 않다면 문제 없이 사용할 수도 있을 것이다.

단순히 커널 버전 만을 가지고 비교한다면 해당 모듈이 사용한 특정 API가 변경되었는지를 추적하기는 힘들어지므로
(이 경우 커널 버전이 일치하지 않는다면 모든 모듈을 사용할 수 없다고 판단해야 할 것이다.)
그리 현명하지 못한 방법인 듯 하다. 만약 각각의 심볼에 대해 버전을 관리할 수 있다면
보다 정확히 해당 모듈을 사용할 수 있을지 판단할 수 있게된다.
바로 이것이 모듈 버전 관리의 핵심 아이디어이다.

모듈 버전 관리 기능은 커널 설정 시 CONFIG_MODVERSIONS 항목을 선택해서 활성화시킬 수 있다.
("Enable loadable module support" -> "Module vesioning support")
여기서 말하는 모듈 버전은 0.0.1과 같은 형식의 사람이 알기 쉬운 형식이 아니라
해당 API의 prototype에 해당하는 문자열에 대해 CRC를 계산한 32비트 정수 값이다.
(즉, 모듈 프로그램 시 MODULE_VERSION() 매크로를 통해 정의한 소스 버전과는 관계가 없다!)

모듈 버전 관리 기능을 활성화시키지 않으면 단순히 커널 버전과 몇 가지 정보 만을 비교한다.
이를 위해서 커널의 version magic이 존재하는데 (include/linux/vermagic.h의 VERMAGIC_STRING 참조)
이는 커널 버전은 물론 모듈 구현에 중요한 영향을 미치는 몇 가지 설정 항목,
특히나 SMP 및 선점 기능 활성화 여부 및 모듈 언로드 및 모듈 버전 관리 기능 활성화 정보가 포함된다.
다음 명령을 이용하면 이를 확인할 수 있다.

$ modinfo <모듈 이름> | grep vermagic
vermagic:       2.6.32 SMP preempt mod_unload modversions 586

모듈 관리 기능을 활성화 했다면
version magic 중에서 커널 버전은 무시되고 나머지 설정 정보 만을 검사한다.

모듈 내의 각 심볼의 버전을 관리하기 위해서
커널 빌드 시 scripts 디렉터리에 있는 genksyms와 modpost라는 두 프로그램을 이용한다.
먼저 다음과 같은 간단한 모듈 소스가 있다고 가정하자.

modA.c:
#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("GPL");

int modA_var;
EXPORT_SYMBOL(modA_var);

void modA_func(const char *caller, int dummy)
{
    printk("%s called from %s\n", __func__, caller);
}
EXPORT_SYMBOL(modA_func);

static int mod_init(void)
{
    printk("init module A\n");
    modA_func(__func__, 0);
    return 0;
}

static void mod_exit(void)
{
    printk("exit module A\n");
}

module_init(mod_init);
module_exit(mod_exit);

genksyms는 각 소스 파일을 컴파일하는 단계에서 실행되며 export된 심볼의 CRC 값을 계산하는 작업을 수행한다.
(이 부분은 커널 컴파일 시 V 옵션을 설정해도 표시되지 않는다!)

실제 수행 과정은 다음과 같이 이루어진다.
커널 소스 컴파일 시 C 파일을 오브젝트 파일로 컴파일 할 때 export된 심볼이 있는지 검사한다.
modA.c라는 파일이 있다고 하면 먼저 .tmp_modA.o라는 오브젝트 파일로 컴파일한 후
해당 파일이 (EXPORT_SYMBOL() 등을 통해 생성된) __ksymtab 섹션을 포함하는지 확인한다.
이는 다음과 같이 간단히 검사할 수 있다.

objdump -h .tmp_modA.o | grep -q __ksymtab

__ksymtab 섹션을 포함하지 않는다면 단순히 .tmp_modA.o 파일의 이름을 modA.o로 바꾸면 된다.
포함한다면 genksyms를 호출하여 해당 심볼이 선언된 부분을 찾아서 타입 정보를 추출한다.
genksyms는 preprocessing된 파일을 입력으로 받는데 __GENKSYM__ 심볼이 정의되면
EXPORT_SYMBOL() 매크로가 확장되지 않으므로 소스 상에서 이를 확인할 수 있다.
genksyms가 EXPORT_SYMBOL() 매크로를 확인하면 내부적으로 다음과 같은 형태의 타입 정보를 출력한다.

modA_var int modA_var
modA_func void modA_func ( const char * , int )

그리고 이러한 타입 정보를 문자열로 인식하여 한 바이트씩 CRC를 계산하고 이를 표준 출력으로 내보낸다.
위의 modA 파일의 경우 출력 결과는 아래와 같다.

__crc_modA_var = 0xc2a853a9 ;
__crc_modA_func = 0xaff6da1a ;

이를 링커 심볼로 인식하도록 하여 다시 오브젝트 파일을 빌드하면 심볼의 값을 동적으로 변경할 수 있다.
이제 EXPORT_SYMBOL() 매크로의 내용을 자세히 살펴보자.

#ifndef __GENKSYMS__
/* For every exported symbol, place a struct in the __ksymtab section */
#define __EXPORT_SYMBOL(sym, sec)                \
    extern typeof(sym) sym;                    \
    __CRC_SYMBOL(sym, sec)                    \
    static const char __kstrtab_##sym[]            \
    __attribute__((section("__ksymtab_strings"), aligned(1))) \
    = MODULE_SYMBOL_PREFIX #sym;                        \
    static const struct kernel_symbol __ksymtab_##sym    \
    __used                            \
    __attribute__((section("__ksymtab" sec), unused))    \
    = { (unsigned long)&sym, __kstrtab_##sym }

#define EXPORT_SYMBOL(sym)                    \
    __EXPORT_SYMBOL(sym, "")
#endif

여기서 __CRC_SYMBOL() 매크로는 다시 다음과 같이 정의된다.

#ifdef CONFIG_MODVERSIONS
/* Mark the CRC weak since genksyms apparently decides not to
 * generate a checksums for some symbols */
#define __CRC_SYMBOL(sym, sec)                    \
    extern void *__crc_##sym __attribute__((weak));        \
    static const unsigned long __kcrctab_##sym        \
    __used                            \
    __attribute__((section("__kcrctab" sec), unused))    \
    = (unsigned long) &__crc_##sym;
#else
#define __CRC_SYMBOL(sym, sec)
#endif

복잡하니 하나씩 살펴보자. 우선 EXPORT_SYMBOL(modA_var)는 다음과 같은 변수를 생성한다.

static const char __kstrtab_modA_var[] = "modA_var";
static const struct kernel_symbol __ksymtab_modA_var = {
  (unsigned long) &modA_var, __kstrtab_modA_var
};

kernel_symbol 구조체는 단순히 심볼의 이름과 심볼 주소를 저장하는 형태이다.
이는 버전 관리와 상관없이 모듈의 링크를 위해 필요한 기본적인 부분이다.
CONFIG_MODVERSIONS가 설정되었다면 __CRC_SYMBOL() 매크로에서 추가적으로 다음 변수를 생성한다.

extern void * __crc_modA_var;
static const unsigned long __kcrctab_modA_var = (unsigned long) &__crc_modA_var;

위에서 알 수 있듯이 __crc_modA_var 변수는 어디에도 정의되지 않았다.
(우리가 할당한 것은 __kcrctab_modA_var 변수 뿐이다.)
이는 weak reference이기 때문에 정의되지 않은 경우에는 그냥 0으로 참조될 것이다.

$ readelf -s .tmp_modA.o | grep crc_modA_var
    29: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __crc_modA_var

이것이 바로 위에서 살펴본 genksyms의 출력에 해당하는 변수이다.
다음과 같이 genksyms의 출력을 링커 스크립트로 지정하여 다시 .tmp_modA.o와 링크하면
__crc_modA_var 심볼의 값이 설정된다.
(이는 실제 실행되는 명령과는 약간 차이가 있다. 단순히 개념적으로만 파악하기 바란다.)

$ gcc -E -D__GENKSYMS__ -D__KERNEL__ modA.c | genksyms > ./tmp_modA.ver
$ ld -r -o modA.o ./tmp_modA.o -T ./tmp_modA.ver
$ readelf -s modA.o | grep crc_modA_var
    25: c2a853a9     0 NOTYPE  GLOBAL DEFAULT  ABS __crc_modA_var

사실 이 단계가 끝난 후에도 modA.o의 CRC 테이블 자체에 이 정보가 저장되진 않는다.
아직 이쪽 부분이 확실히 이해가 되질 않아서 정확한 설명은 힘들지만
어디선가 수정된 심볼 정보로 CRC 테이블을 갱신하는 부분이 있을 것이다.

modpost는 총 두 번 호출되는데 먼저 커널 이미지(vmlinux) 파일을 읽어서
export된 심볼들에 대해 버전 파일(Module.symvers)을 만들고
두 번째는 각 모듈에 대해 참조하는 (undefined) 심볼들을 추출하여
커널의 버전 파일에서 해당하는 버전 정보를 읽어서 저장한다.
추가적으로 해당 모듈에서도 export하는 심볼이 있다면 역시 버전 파일을 생성한다.

심볼 버전 파일의 형태는 다음과 같다.

$ cat Module.symvers
0xaff6da1a    modA_func    /path/to/modA    EXPORT_SYMBOL
0xc2a853a9    modA_var    /path/to/modA    EXPORT_SYMBOL

첫 번째 필드가 바로 심볼 버전인 CRC 값이고 두 번째는 심볼의 이름,
세 번째는 해당 심볼을 저장한 모듈의 경로이고 (커널에 내장된 심볼의 경우는 vmlinux로 설정된다.)
마지막은 export된 타입이다. (EXPORT_GPL, EXPORT_UNUSED 등이 가능하다.)

모듈이 참조하는 심볼 정보 및 기타 모듈에 대한 정보를 저장하기 위해서
각 모듈에 대해 <모듈 이름>.mod.c 파일을 생성한다.
위의 modA 모듈에 대해서는 다음과 같이 modA.mod.c 파일이 생성된다.

#include <linux/module.h>
#include <linux/vermagic.h>
#include <linux/compiler.h>

MODULE_INFO(vermagic, VERMAGIC_STRING);

struct module __this_module
__attribute__((section(".gnu.linkonce.this_module"))) = {
 .name = KBUILD_MODNAME,
 .init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
 .exit = cleanup_module,
#endif
 .arch = MODULE_ARCH_INIT,
};

static const struct modversion_info ____versions[]
__used
__attribute__((section("__versions"))) = {
    { 0x7560fa, "module_layout" },
    { 0xb72397d5, "printk" },
    { 0xb4390f9a, "mcount" },
};

static const char __module_depends[]
__used
__attribute__((section(".modinfo"))) =
"depends=";

이 중 __versions 배열이 바로 심볼 버전 정보를 담고 있는 부분이다.
또한 여기서 알 수 있는 것은 이 모듈이 참조하는 심볼은
module_layout, printk, mcount의 총 3개이며 각각의 버전이 함께 표시되어 있다.
이 중 실제로 소스에서 사용한 것은 printk() 하나 뿐이며
module_layout은 모듈의 구조가 동일한지 확인하기 위해 modpost가 직접 추가한 것이다.
(mcount에 대해서는 정확히 모르겠다...;;)

모듈 로드 시에는 먼저 module_layout 심볼의 버전을 확인하여
모듈의 구조가 동일한지 검사하고 그 후 version magic이 일치하는지 검사한 후
각각의 심볼에 대해 버전을 확인하는 단계로 진행된다.

마지막 부분은 모듈의 의존성 정보를 저장하는 것인데 이는 심볼 버전 검사 과정을 통해 파악할 수 있다.
만약 modB라는 모듈이 있어서 modA에서 export한 심볼을 참조한다면
해당 심볼은 커널 이미지의 심볼 버전 파일에서 찾을 수 없지만
modA의 버전 파일에서 찾을 수 있기 때문에 (위에 보다시피 버전 파일에 심볼을 소유한 모듈의 경로가 들어있다.)
modB는 modA에 의존성을 가지도록 표시된다.
즉, modB가 다음과 같이 구현되었다고 가정하면

void modB_func(const char *caller, int dummy)
{
    extern void modA_func(const char *caller, int dummy);
    printk("%s called from %s\n", __func__, caller);
    modA_func(__func__, dummy);
}

modB.mod.c 파일은 다음과 같이 생성될 것이다.
(마지막 줄을 주의깊게 살펴보자.)

static const struct modversion_info ____versions[]
__used
__attribute__((section("__versions"))) = {
    { 0x7560fa, "module_layout" },
    { 0xaff6da1a, "modA_func" },
    { 0xb72397d5, "printk" },
    { 0xb4390f9a, "mcount" },
};

static const char __module_depends[]
__used
__attribute__((section(".modinfo"))) =
"depends=modA";

마지막으로 커널 설정 시 모듈의 모든 소스 파일에 대한 버전을 포함하도록 선택하였다면
("Enable loadable module support" -> "Source checksum for all modules")
다음과 같은 부분이 추가된다.

MODULE_INFO(srcversion, "11507A9E927D9BAA5FE377C");

이는 modpost가 모듈을 구성하는 각 소스 파일의 모든 내용(단, 주석과 문자열은 제외)을 읽어서 MD4 해시값을 구한 것이다.
아직 이를 이용한 기능은 없는 듯 하고 그냥 소스 레벨에서 일치하는지 쉽게 확인하기 위해 추가된 것으로 보인다.

이제 modA가 (혹은 참조한 커널의 API가) 수정되었다고 생각해보자.
modA의 modA_func() 함수에서 dummy 인자는 사용되지 않으므로 제거할 수 있다.
이렇게 소스를 수정하여 modA를 다시 배포하였다면 이전의 modB는 (새 소스로 다시 컴파일 하지 않는 이상) 사용할 수 없다.
modB를 로드하려고 하면 다음과 같은 에러를 보게될 것이다.

insmod: error inserting 'modB.ko': -1 Unknown symbol in module

커널 메시지를 살펴보면 다음과 같은 내용을 찾을 수 있다.

modB: disagree about version of symbol modA_func
modB: unknown symbol modA_func
by namhyung | 2010/01/18 23:33 | Kernel | 트랙백 | 핑백(2) | 덧글(1)
트랙백 주소 : http://studyfoss.egloos.com/tb/5226996
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Linked at sss : [kernel] m.. at 2012/03/22 14:43

... 다. 현재 커널이 module versioning을 지원하는 지 여부는 uname -a에서 modversion이라는 문자열이 출력되는지 여부로 판단한다.http://studyfoss.egloos.com/5226996 ... more

Linked at [11차 ARM-A 16주차].. at 2014/11/16 12:05

... http://studyfoss.egloos.com/5226996</a> 링크의 처음부분에 이런 내용이 있습니다. -------------------------------------------------------------------------------------------------------------------- 모듈에서 사용할 수 있는 커널 API들은 EXPORT_SYMBOL()과 같은 매크로를 통해 명시적으로 선언하는데 이러한 심볼 정보는 ... more

Commented by namhyung at 2013/07/04 10:19
참고로 mcount()는 gcc가 -pg 옵션을 통해 호출되었을 때 profiling 정보를 생성하기 위해 모든 함수 실행 시에 자동으로 호출하는 함수입니다. CONFIG_FTRACE 옵션이 켜져 있다면 커널 빌드 시 자동으로 이 기능을 이용하게 됩니다.

:         :

:

비공개 덧글

◀ 이전 페이지 다음 페이지 ▶

카테고리
General
Application
System
Kernel
Book
Tips
태그
build git CARM elf sed kernel blktrace memory glibc perf patch CAaQA3 x86 vcs scm synchronization documentation script algorithm block-layer C binutils computer-architecture SMP compiler linux gcc bash awk emacs
전체보기
이글루 파인더

최근 등록된 덧글
informsi yang bagus dan be..
by pordanaia at 08/05
Informsi yang bagus dan b..
by jamalsu at 08/01
as inforasi yang menarik http..
by jamalsu at 07/22
최근 등록된 트랙백
Tod's Ferrari Homme
by Tods Pas Cher,Kodak did ..
Mocassin Femme
by Mocassins Homme, I got so..
natural garcinia cambogia
by
rss

skin by jiinny


X