arch/kernel/head.S (3) – pv_table 을 이용한 가상/물리주소 변환 원리

가. Immediate 및 Rotate

pv_table 을 이해하려면 먼저 add/sub 등 instruction 의 rotate field 와 immediate field 의 사용법을 기본적으로 이해해야 합니다.
ADD_SUB instruction bit field.png
32 bit 안에 add r0, r1, #0x81000000 와 같이 instruction type 뿐 아니라 condition, cpsr update 여부, source 와 target register, 값이 큰 constant value 를 다 우겨넣으려니 당연히 제한이 있습니다.
constant value(immediate value)를 위해 할당된 것은 맨 아래 단 1 byte 인데 큰수를 표현해야 하는 경우가 있습니다.
add 하려는 값이 0x10 같이 작은 값이 아니라 1 byte 범위를 넘어갈 때는 다른 꽁수를 써야 합니다.
이런 상황에서 1 byte 만으로 32 bit 범위의 값(0xffffffff)을 모두 표현할 수 있도록 해주는 것이 rotate field 입니다.
rotate field 는 4 bit 로 이루어지므로 0 ~ 15, 즉 16 가지의 다른 값을 표현할 수 있습니다.
16 가지의 다른 값이 각각 2 bit 의 이동을 의미하게 한다면 하위 1 byte 값들의 위치를 이동시켜서 32 bit 중 특정 위치에 있는 것처럼 표현함으로써 큰 값을 의미하게 할 수 있습니다.
즉, 만약 rotate field 값이 4 이고 끝의 immediate value 가 0x81 이라면, 0x81 값을 2*4 = 8 bit 만큼 rotate 시켜서 0x81000000 라는 큰 값을 표현할 수 있습니다.
마지막 rotate + immediate  12 bit 만으로 32 bit 범위의 큰 값을 표현할 수 있도록 한 것입니다.
(비록 끝자리가 1 로 떨어지는 수를 표현하지는 못하고 2 bit 단위의 값이긴 하지만...)
만약 Add instruction 이 아래와 같이 생겼다면...

0xe2822481  = 1110 0010 1000 0010 0010 0100 (0x4) 1000 0001 (0x81)

이는 결국 맨끝의 1000 0001 을 0100 곱하기 2 만큼 rotate 한 수(immediate value) 만큼을 source register 에 더하겠다는 의미로 약속한 것입니다.
결국 아래와 같은 instruction 을 32 bit 내에 표현하게 되면 위처럼 rotate 값을 4 로 설정해야 가능한 것입니다.

add r0, r1, #0x81000000

add 연산 뿐 아니라 sub 이나 bic 등 일부 연산들에서 rotate 와 immediate field 는 모두 이런 방식으로 사용합니다.

나. __pv_stub

이번에는 pv_table 이 언제 생기는지를 알려면 __pv_stub inline assembly 매크로를 이해해야 합니다.
__pv_stub 매크로는 virt_to_phys -> __virt_to_phys -> __pv_stub 의 과정으로 호출됩니다.
나중에 분석을 하게 되겠지만, virt_to_phys 함수는 가상주소가 들어있는 register 를 넘겨주면 그에 대한 물리주소를 실시간으로 빠르게 계산해서 리턴해주는 함수입니다.
가상주소는 빌드 당시에 정해져있겠지만 커널이미지를 올리는 물리주소는 일정치 않을 수 있으므로, 이는 수행 중에 실시간으로 계산할 수 밖에 없을 것입니다.
그것도 아주 빠르게요. 이를 위해 pv_table 을 사용하는 것입니다.

static inline phys_addr_t virt_to_phys(const volatile void *x)
{
    return __virt_to_phys((unsigned long)(x));
}

static inline unsigned long __virt_to_phys(unsigned long x)
{
    unsigned long t;
    __pv_stub(x, t, "add", __PV_BITS_31_24);
    return t;
}

#define __pv_stub(from,to,instr,type)           \
    __asm__("@ __pv_stub\n"             \
    "1: " instr "   %0, %1, %2\n"       \
    "   .pushsection .pv_table,\"a\"\n"     \
    "   .long   1b\n"               \
    "   .popsection\n"              \
    : "=r" (to)                 \
    : "r" (from), "I" (type))

__pv_stub 함수의 이해를 위해서 inline assembly syntax 좀 확인하고 가겠습니다.

__pv_stub inline macro.png

__pv_stub 코드는 두 가지 관점에서 봐야 합니다.
하나는 빌드될 때의 관점, 나머지 하나는 실행될 때의 관점입니다.
커널을 빌드할 때 컴파일러는 위의 inline 코드를 보고 2 개의 결과물을 만들어냅니다.
하나는 code section 에서 1 이라는 label 자리에 add instruction (virt_to_phys 의 경우)을 생성합니다.

또 하나는 .pushsection 과 .popsection 이라는 directive 를 통해서 pv_table 에 label 1 의 가상주소를 넣어둡니다.
빌드하는 순간이므로 가상주소를 넣어두게 됩니다.

이렇게 커널 빌드를 할때 모든 __pv_stub 매크로가 나오는 코드들을 만나면 위와 같은 2 가지 결과물들을 모두 만듭니다.
따라서, 빌드가 끝나면 pv_table section 에는 __pv_stub 들이 만들어내는 label 1 들의 가상주소들이 table 형태로 주루륵 들어가있는 상태가 됩니다.

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .head.text        PROGBITS        c0008000 008000 0001c0 00  AX  0   0  4
  [ 2] .text             PROGBITS        c00081c0 0081c0 36125c 00  AX  0   0 64
  [ 3] .rodata           PROGBITS        c036a000 36a000 0e7c68 00   A  0   0 64
  ...
  [20] .init.smpalt      PROGBITS        c04b6a04 4bea04 0001f0 00   A  0   0  4
  [21] .init.pv_table    PROGBITS        c04b6bf4 4bebf4 0002f8 00   A  0   0  1
  [22] .init.data        PROGBITS        c04b6ef0 4beef0 00ea14 00  WA  0   0  8
  [23] .data..percpu     PROGBITS        c04c6000 4ce000 001d00 00  WA  0   0 64
  [24] .data             PROGBITS        c04c8000 4d0000 038260 00  WA  0   0 64
  [25] __tracepoint_str  PROGBITS        c0500260 508260 00000c 00  WA  0   0  4
  [26] .bss              NOBITS          c0500280 50826c 041d18 00  WA  0   0 64

위의 section header 정보에 보면 .init.pv_table section 이 이 부분에 해당됩니다.
크기가 0x2f8(760)이니까 190 개의 주소값들이 build 가 끝났을 때 table 에 들어가 있겠군요.

그러면, __pv_stub 을 실행 관점에서 살펴보면, directive 들은 이미 빌드과정에서 모두 pv_table 생성에서 수행된 것들이고 실행 시에는 add 라는 instruction 단 하나밖에 없습니다.
.pushsection, .popsection 와 같은 directive 들은 빌드 시에만 사용될 뿐이고 실행 시에는 관련이 없습니다.

만약 virt_to_phys 함수를 사용했다면 이 함수를 호출한 곳에는 inline 으로 아래와 같은 코드 단 한줄만이 들어가 있습니다.(vmlinux 의 object dump 를 보면 확인할 수 있습니다.) __pv_stub 에서 생성한 add instruction 이죠.

0xe2822481  add r0, r2, #-21.....    @ 0x81000000

물론 앞에서 설명한대로 이 instruction 을 가리키는 가상주소가 pv_table 에 들어가 있습니다.
이제 코드 실행 시에 이를 그대로 실행하면 가상주소를 물리주소로 바꾸어줄 수 있냐하면 그렇지 않습니다.

위에서 __virt_to_phys 함수를 보면 __pv_stub 을 호출할 때 __PV_BITS_31_24(0x81000000)을 넘겨주고 있습니다.

분명히 가상주소를 물리주소로 바꾸어주는 함수인데 add 하는 immediate 값이 0x81000000 로 fix 된 값을 사용한다는게 좀 이상하죠.

이미지 로드 위치에 따라 물리주소가 어디일지 유동적인데, define 값으로 그냥 사용한다는 것은 물리주소와 가상주소의 차이(delta offset)가 제대로 계산되지 않는다는 의미와 같으므로 이대로 실행하면 주소 변환을 정확히 할 수 없을 것입니다.

즉, 해당 add instruction 을 통해 가상->물리 주소 변환을 정확하게 하려면 어떤 식으로건 delta offset 을 반영할 수 있도록 명령어가 수정되어야 합니다. 특히, 변환을 위해 더해주는 값인 immediate 값 말이죠.

그래서, 해당 add instruction 을 실행하기 전에 kernel/head.S 에서 하는 일이 있습니다.

바로 __fixup_pv_table 이죠.

다. __fixup_pv_table

__fixup_pv_table 은 빌드시에 __pv_stub 에서 차곡차곡 쌓아두었던 pv_table 의 Entry 들이 가리키는 instruction 들의 immediate value 값을, 가상주소와 물리주소의 차이인 delta offset >> 24 값으로 변경하여 해당 instruction 이 정상적인 virt_to_phys 의 역할을 수행하게 만듭니다.
해당 instruction 이 만약 add 라면 input register 값에서 delta offset 을 더한 것을 리턴하므로, 즉 가상주소를 물리주소로 변환하여 리턴하는 것입니다.

이를 개념적인 그림으로 표현하면 다음과 같습니다.

pv_table 을 이용한 instruction patch.png

즉, pv_table 의 entry 가 가리키는 instruction 의 rotate 값은 여전히 4 이므로 교체된 마지막 1 byte 값을  4*2 만큼 rotate 한 값이 해당 instruction 에서 더해줄 값이 됩니다. (delta offset)
source register 에다가 rotate 와 immediate value 를 이용하여 계산된 delta offset 값을 더한 값이 리턴값이 됩니다.
위의 그림을 보면 원래 label 1 자리의 instruction 자체의 값을 실행중에 변경하는 것이기 때문에 realtime patch 라고 하는 것입니다.

결국 위의 모든 과정을 3 단계로 간단히 정리하면...

  • build 시에 pv_table 및 instruction 생성 (pv_table entry 가 instruction 을 가리킴)
  • decompressed kernel 수행 초기 __fixup_pv_table 과정에서 해당 instruction 의 immediate 값만 delta offset 을 이용하여 변경 (패치)
  • virt_to_phys, phys_to_virt 함수 호출 시 변경된 instruction 실행

You may also like...

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x