shepherd's Blog

[ARM 11차 B조] 14.06.14 스터디 후기 본문

Kernel분석

[ARM 11차 B조] 14.06.14 스터디 후기

shepherd.dev 2015. 7. 8. 10:27

6월 14일 9주차 스터디 후기

 

1. Study history

 - arch/arm/boot/compressed/head.S 진행 중

 - linux stable 3.14.4 진행 중

 

2. 이슈사항

 -1. 리틀-엔디안 방식으로 인한 로드 방법 차이

 해당 코드에서 로드하는 r10은 LC0에서 받아온 값으로 압축 풀린 커널의 크기가 들어있는 메모리의 주소이다. 해당 사이즈를 받아와서 레지스터에 저장해둡니다. 리틀 엔디안의 경우 메모리에서 레지스터로 값을 로드할 경우 상위 주소와 하위 주소가 다른 문제가 생긴다.

 예를 들어 커널 사이즈 0x12345678가 리틀 엔디안 방식에서 메모리에 저장된다면 하위주소 <- 0x78563412 -> 상위주소로 저장됩니다. 이 상태에서 빅엔디안에서 해당 메모리에 있는 값을 레지스터로 r9로 불러온다면, 상위주소 <- 0x78563412 -> 하위주소 그대로 불러와지게 되기 때문에 아래와 같은 루틴으로 원하는 값을 레지스터에 적재한다.


1

2

3

4

5

6

7

ldrb r9, [r10, #0] @ r9 = 0x00000078

ldrb lr, [r10, #1] @ lr = 0x00000056

orr r9, r9, lr, lsl #8 @ r9 = 0x00005678

ldrb lr, [r10, #2] @ lr = 0x00000034

ldrb r10, [r10, #3] @ r10 = 0x00000012

orr r9, r9, lr, lsl #16  @ r9 = 0x00345678

orr r9, r9, r10, lsl #24 @ r9 = 0x12345678 

참고http://www.iamroot.org/xe/Kernel_8_ARM/60066#comment_60161 

 

-2. DTB 관련 이슈

 

  아래 코드를 보면  lr에 r6(_edata)에서 데이터를 불러오고, 해당 부분을 어떤 sig와 비교해서 dtb_check_done으로 넘어갈지 결정합니다. 그럼 _edata[0]에는 dtb와 관련된 무언가가 있는 것 같습니다.


1
2
3
4
5

ldr lr, [r6, #0]

/*! r6 = _edata */

ldr r1, =0xedfe0dd0 @ sig is 0xd00dfeed big endian

cmp lr, r1

bne dtb_check_done


 그 무언가는 다음의 파라미터에서 유추해볼 수 있을것 같습니다. 두번째 파라미터에 r6(_edata)를 넣어줍니다. void* fdt로 fdt와 관련이 있는것 같습니다. 스터디 마지막에 잠깐 해당 함수를 보았을 때, for_each_tag를 통해 atag에 저장된 tag를 atag_list에 받아와서 fdt 관련 초기화를 해 주는것 같았습니다.

 이 다음부터는 이제 다음 스터디에서 이어나갈 부분이니 여기서 끊어가겠습니다.

 

1
2
3
4
5
int atags_to_fdt(void *atag_list, void *fdt, int total_space) { 
...
for_each_tag(atag, atag_list) { 
...
}}

cs

 

 다음 스터디 시작이 atags_to_fdt 분석부터 시작입니다. 스터디 atags_to_fdt로 분기하는 부분을 건너 뛰고 바로 dtb_check_done 라벨로 넘어갔습니다. atag, fdt 등 디바이스 트리에 대해 좀 알아봐야 할 것 같습니다. 관련 링크 아래에 남겨둡니다.

  • 디바이스 트리 참고 홈페이지
  1. (PDF)Device Tree for Dummies - eLinux.org
  2. http://linuxfactory.or.kr/archives/116
  3. http://devicetree.org/Device_Tree_Usage
  4. https://www.kernel.org/doc/Documentation/devicetree/usage-model.txt
  5. http://en.wikipedia.org/wiki/Device_tree
  6. http://elinux.org/Device_Tree

질문 2-1

 _edata(.bss section)에는 무엇이 저장되어있나?  DTB sig와 함께 DTB와 관련된 정보를 담고 있는것 같아 보이는데 정확히 무엇인지 잘 모르겠습니다.

- 2-1답변(문준영 님)

 디바이스 트리를 커널에 넘겨 줄 때, 커널 이미지 바로 뒤(_edata)에 붙여서 넘겨주는 방법이 있습니다. 근데 권장되는 방법은 아니라더군요. 이게 아마 APPENDED_DTB 컨피그랑 관련 되어 있을텐데, Kconfig에서 찾아서 읽어보시면 될 것 같습니다.-3. 재배치 이슈

 

 이제 dtb_check_done 라벨 시작입니다. dtb 체크가 끝났고, 재배치를 하기 위한 작업을 진행합니다. 현재의 메모리 상태를 보면 다음과 같습니다.

dtb_check_done에 도달했을 때의 메모리

--------------------------------+-----------------------+ <--- r10 (malloc_end) 

| | malloc space |

| +-----------------------+ (malloc_start)

| | .stack (_end)

| (DTB information) +-----------------------+ <--- sp(r13) = _end

| DTB sig (_edata[0]) | .bss ( _edata) |

+-------------------------------+-----------------------+ <--- r6 = _edata

| | | <--- vmlinux의 기타 섹션(piggy, got, ...)

+-------------------------------+-----------------------+ 

| 크기를 저장한 레지스터 | |

| +-- wont_overwrite ---+

| | ... |

| +--- restart ----+ <--- .text

| +-----------------------+ 0x50c0_0000 <--- zImage가 로드될 위치

| | 압축풀린 |

| | 커널 이미지

| +-----------------------+ 0x5000_8000 <---r4 = TEXT_OFFSET

| r9 = 압축풀린 커널의 크기 | |

|-------------------------------+-----------------------+ 0x5000_0000



 위의 코드에서 재배치가 필요한 경우인지 검사합니다. 첫번째 단계에서 페이지 디렉토리 크기까지 더해준 r10 (sp + malloc space)과 r4를 비교합니다. 

- r4 = 압축풀린 커널이 위치할 주소

- r10 = sp + malloc space(0x10000) + page diretory

 

첫번째 비교할 조건을 만족할 경우의 메모리
|-------------------------------+-----------------------+
| | 압축풀린 |
| | 커널 이미지 |
|-------------------------------+-----------------------+  <---r4 = TEXT_OFFSET
| +-----------------------+ (malloc_end) <--- r10 
| | malloc space | 
| +-----------------------+ (malloc_start)
+-------------------------------+ .stack |  
| (DTB information) +-----------------------+ <--- sp(r13) = _end
| DTB sig (_edata[0] ) | .bss | 
+-------------------------------+-----------------------+ <--- r6 = _edata
| | | <--- vmlinux의 기타 섹션들(piggy, got, ...)
+-------------------------------+-----------------------+
| 크기를 저장한 레지스터 | | 
| +--- wont_overwrite  ---+ 
| | ... | 
| +---   restart       ---+ <--- .text
|-------------------------------+-----------------------+ 0x50c0_0000 <--- zImage가 로드될 위치
 
 
 
 
cs
 
 위의 그림대로 r4가 r10보다 커서 r4에 커널이미지를 압축 해제한다면, 압축 해제된 이미지가 압축된 이미지를 덮어쓰지 않으므로, 재배치가 필요 없어보입니다.
 
 두번째 비교는 압축 풀린 커널의 크기를 더해줘서, 압축 해제가 완료되었을 때 이미지의 끝 주소(r10)와 wont_overwrite의 주소(r9)를 비교합니다.
 r9 = 압축해제된 이미지의 끝 주소
 r10 = wont_overwirte의 주소
 
 다음 그림에서 두가지 경우로 wont_overwrite로 분기 결정을 내린다.
 
두번째 비교할 때 메모리
+-------------------------------+-----------------------+ (malloc_end) 
| | malloc space | 
| +-----------------------+ (malloc_start)
| | .stack |  
| (DTB information) +-----------------------+ <--- sp(r13) = _end
| DTB sig (_edata[0] ) | .bss | 
+-------------------------------+-----------------------+ <--- r6 = _edata
| | | <--- vmlinux의 기타 섹션들(piggy, got, ...)
+-------------------------------+-----------------------+
| 크기를 저장한 레지스터 | | <--- r9( 재배치 필요 )
| +--- wont_overwrite ----+ <--- r10
| | ... | <--- r9( 재배치 필요없음)
| +-----   restart   -----+ <--- .text
| +-----------------------+ 0x50c0_0000 <--- zImage가 로드될 위치
| | 압축풀린 |
| | 커널 이미지 |
| +-----------------------+ 0x5000_8000 <---r4 = TEXT_OFFSET
| | |
|-------------------------------+-----------------------+ 0x5000_0000
cs
 
 위의 그림에서 확인해보면, 이미지 압축을 풀었을 경우 해당 이미지가 wont_overwrite 라벨을 덮어쓰지 않는다면 재배치를 해줄 필요가 없다고 보고있다. 압축 해제 함수는 wont_overwrite 다음 not_relocated 라벨에서 불러지는데 그전까지의 코드는 이미 실행했고, 다시 실행할 필요가 없기 때문에 wont_overwrite 코드를 기준으로 하는 것 같다.
 
-4. 얼라인 관련 이슈
 
 코드 재배치 과정에서 얼라인 맞추는 작업을 많이 해줍니다. 해당 작업으로 재배치 결과 얼라인 된 상태의 메모리를 
 
1
2
3
4
5
6
7
8
9
10
11
 add r10, r10, #((reloc_code_end - restart + 256) & ~255)
 /*! r10 = r10 + (restart 이 후 코드 크기 + 0x100 ) & 0xffff_ff00 */
 /*! 주 목적은 얼라인 맞추기 */
 bic r10, r10, #255
 /*! r10 = r10 & !(0xff) */
 /* Get start of code we want to copy and align it down. */
 adr r5, restart
 /*! r5 = &restart */
 bic r5, r5, #31
 /*! 하위 5비트 클리어 */
 /*! 8byte 얼라인을 위한 비트 클리어 */
cs
 
-5. 코드 재배치 실시
 
 코드 재배치를 위해 레지스터들을 조절한 뒤 zImage를 재배치해줍니다. 우선 아래 코드를 실행하기전 현재 메모리의 상태입니다. 
 
재배치를 위한 레지스터 조절 전 메모리 상태
+---------------------------------+---------------------+ (malloc_end) <--- r10 
| | malloc space| 
| +-----------------------+ (malloc_start)
| | .stack|  
| (DTB information) +-----------------------+ <--- sp(r13) = _end
| DTB sig (_edata[0] ) | .bss
+-------------------------------+-----------------------+ <--- r6 = _edata
| || <--- vmlinux의 기타 섹션들(piggy, got, ...)
+-------------------------------+-----------------------+
| 크기를 저장한 레지스터 | |
| +---  wont_overwrite ---+
| | ... |
| +-----    restart ------+ <--- r5(얼라인 된 상태)
| +-----------------------+ 0x50c0_0000 <--- zImage가 로드될 위치
| | 압축풀린 |
| | 커널 이미지 |
| +-----------------------+ 0x5000_8000 <---r4 = TEXT_OFFSET
| | |
|-------------------------------+-----------------------+ 0x5000_0000
cs
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*!
 * r5 = restart 주소
 * r6 = _edata 주소
 * r10 = malloc end
 */
 sub r9, r6, r5      @ size to copy
 /*! r9 = _edata - (restart addr)&!0x1f */
             add r9, r9, #31     @ rounded up to a multiple
             bic r9, r9, #31     @ ... of 32 bytes
             /*! 5비트 단위 올림 for 얼라인 */
            /*! restart ~ _edata 까지 32bytes 얼라인 */
 /*! r9 = restart ~ _edata(복사할 영역 사이즈) */
 add r6, r9, r5
 add r9, r9, r10
 /*! 
 * r6 = restart + 복사할 영역 사이즈(정렬된 _edata)
 * r9 = malloc end + 복사할 영역 사이즈(정렬된 restart)
 */
cs
 
위의 코드를 실행 한뒤 레지스터
+-------------------------------+-----------------------+ <--- r9 = 재배치할 영역의 끝 주소 
| | 재배치할 영역 | 
| +-----------------------+  
| | 빈 영역 | 
| +-----------------------+ (malloc_end) <--- r10 
| | malloc space | 
| +-----------------------+ (malloc_start)
| | .stack |  
| (DTB information) +-----------------------+ <--- sp(r13) = _end
| DTB sig (_edata[0] ) | .bss | 
+-------------------------------+-----------------------+ <--- r6 = 복사될 영역의 끝 주소
| | | <--- vmlinux의 기타 섹션들(piggy, got, ...)
+-------------------------------+-----------------------+
| 크기를 저장한 레지스터 | |
| +---  wont_overwrite ---+
| | ... |
| +-----     restart -----+ <--- r5(얼라인 된 상태)
| +-----------------------+ 0x50c0_0000 <--- zImage가 로드될 위치
| | 압축풀린 |
| | 커널 이미지 |
| r9 = _edata - restart addr +-----------------------+ 0x5000_8000 <---r4 = TEXT_OFFSET
| (재배치할 영역 크기) | |
|-------------------------------+-----------------------+ 0x5000_0000
cs
 
 이제 재배치를 위한 주소 설정이 끝났습니다. 다음 코드로 재배치를 시작합니다.
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*!
 * 재배치 루프(1: ldmdb ~ bhi 1b)
 * r6(edata)부터 복사해서 r9(재배치할 영역 끝 주소) 붙여넣기
 */
 1:      ldmdb   r6!, {r0 - r3, r10 - r12, lr}
 cmp r6, r5
 stmdb   r9!, {r0 - r3, r10 - r12, lr}
 bhi 1b
 
 /* Preserve offset to relocated code. */ 
 sub r6, r9, r6
 /*! r6 = 재배치 영역까지 오프셋 */
#ifndef CONFIG_ZBOOT_ROM 
 /* cache_clean_flush may use the stack, so relocate it */
 add sp, sp, r6
 /*! sp 위치 조절 */
 tst r4, #1
 /*! 
 * not_angel의 재배치 검사 과정에서 cache_on을 안 해줬을 때
 * r4에 1이 세트되어있음. 이 경우 z bit에 셋이 되어있지 않으므로
 * eq 실행 안됨.
 */
 /*!
 * 현재 아키텍처에 맞는 캐쉬 플러쉬 함수 콜
 * cache_on이 실행 되었을 경우 cache_clean_flush 실행
 */
 bleq    cache_clean_flush
 
 adr r0, BSYM(restart)
 add r0, r0, r6
 mov pc, r0
cs
 
 r6부터 데이터를 복사 한 뒤 r9에 붙여넣어 restart 라벨이 올때까지 복사를 시작합니다. 복사가 다 된 뒤에 sp의 위치도 조절해줘서 앞으로 사용할 스택의 위치도 조절해 줍니다.
 재배치가 완료되면 캐쉬관련 검사를 실행해 준 뒤 pc를 재배치된 restart 위치로 옮겨 restart부터 다시 시작하게 됩니다.
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  위의 코드를 실행 한뒤 레지스터
┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃    (DTB information)      ┃ stack              ┃
┃    DTB sig (_edata[0] )   ┣━━━━━━━━━━━┫ <--- sp(r13) = 재배치 완료 후 sp
┣━━━━━━━━━━━━━━━╋                    ┃
┃                           ┃ 재배치 완료         ┃ <--- pc = restart부터 다시 시작
┃                           ┣━━━━━━━━━━━┫ <--- r9 = 재배치 완료 후 r9 
┃                           ┃ 빈 영역             ┃
┃                           ┣━━━━━━━━━━━┫(malloc_end) <--- r10 
┃                           ┃ malloc space     ┃
┃                           ┣━━━━━━━━━━━┫(malloc_start)
┃                           ┃ .stack             ┃
┃                           ┣━━━━━━━━━━━┫
┃                           ┃ .bss             ┃
┃                           ┣━━━━━━━━━━━┫<--- _edata
┃                           ┃                    ┃<--- vmlinux의 기타 섹션들(piggy, got, ...)
┣━━━━━━━━━━━━━━━╋━━━━━━━━━━━┫
┃크기를 저장한 레지스터       ┃                    ┃
┃                           ┣━ wont_overwrite ━┫
┃                           ┃   ...         ┃
┃                           ┣━━ restart ━━┫
┃                           ┣━━━━━━━━━━━┫ 0x50c0_0000 <--- zImage가 로드될 위치
┃                           ┃압축풀린        
┃r6 = 재배치 오프셋          ┃커널 이미지    
┃r9 = _edata - restart addr ┣━━━━━━━━━┫ 0x5000_8000 <---r4 = TEXT_OFFSET
┃   (재배치할 영역 크기)      ┃                    
┗━━━━━━━━━━━━━━━┻━━━━━━━━━┛ 0x5000_0000
cs
 
 이번 후기는 여기서 끝입니다. 메모리 위치에 따른 재배치 사항이기 때문에 메모리를 유심히 보시면 쉽게쉽게 넘어갈 수 있습니다.


'Kernel분석' 카테고리의 다른 글

kernel 분석 환경설정(arm)  (0) 2015.07.07
iamroot kernel 분석 스터디  (0) 2015.07.07