DTB 및 재배치 코드 보호
다음 코드는 relocation을 하기 위해 필요한 주소들을 계산한다.
code relocation은 r6(source 상단)부터 r5(source 하단)로 32바이트씩 읽어서 r9(dest 상단)에 복사하면서 내려오는데 각 주소는 다음과 같이 복잡하다. 간단히 알아보고 실제 계산은 각 코드에서 비교를 해야 정확히 알 수 있다.
- N=복사할 relocation 코드 사이즈
- (_edata + DTB size – r5)한 사이즈를 상향으로 32바이트 align한 주소
- r5: source 하단 주소
- compressed 커널의 restart 주소를 32바이트 align한 주소
- r6: source 상단 주소
- r5 + N(복사할 relocation 코드 사이즈)한 주소를 상향으로 32 byte align 한 주소
- r9: dest 상단 주소
- r10(decompressed 커널의 시작) + A + B + C + N
- A=decompressed 커널 사이즈
- B=bss가 DTB 영역을 침해하지 않도록 추가한 사이즈
- bss가 _edata – wont_overwrite 영역을 초과하는 사이즈
- C=재배치 수행 중 재배치 코드가 파괴되지 않을 만큼의 사이즈
- reloc_code_end – restart 만큼의 사이즈를 상향으로 256 byte align 한 사이즈
- r10(decompressed 커널의 시작) + A + B + C + N
/* * Relocate ourselves past the end of the decompressed kernel. * r6 = _edata * r10 = end of the decompressed kernel * Because we always copy ahead, we need to do it from the end and go * backward in case the source and destination overlap. */ /* * Bump to the next 256-byte boundary with the size of * the relocation code added. This avoids overwriting * ourself when the offset is small. */ add r10, r10, #((reloc_code_end - restart + 256) & ~255) bic r10, r10, #255 /* Get start of code we want to copy and align it down. */ adr r5, restart bic r5, r5, #31 /* Relocate the hyp vector base if necessary */ #ifdef CONFIG_ARM_VIRT_EXT mrs r0, spsr and r0, r0, #MODE_MASK cmp r0, #HYP_MODE bne 1f bl __hyp_get_vectors sub r0, r0, r5 add r0, r0, r10 bl __hyp_set_vectors 1: #endif sub r9, r6, r5 @ size to copy add r9, r9, #31 @ rounded up to a multiple bic r9, r9, #31 @ ... of 32 bytes add r6, r9, r5 add r9, r9, r10
- decompressed 커널의 끝 주소(위 그림에서의 space B 포함)에 일정 부분의 공간을 추가한다.
- 기존에는 decompressed 커널의 끝에 256바이트의 space 공간을 두고 relocation되는 compressed 커널이 놓이게 되었었는데 compressed 커널의 시작 주소가 relocation되는 곳의 시작주소의 바로 밑에 바짝 붙어 있는 경우 32바이트씩 copy하면서 overwrite될 가능성과 cache flush code에서 문제가 발생할 확률이 있어서 아예 이슈가 될만한 사이즈만큼 더 띄우게 되었다.
- add r10, r10, #((reloc_code_end – restart + 256) & ~255)
- r10: 한 번 교정된 decompressed kernel의 마지막 주소에다가 relocation 전용 코드 길이만큼 추가한다.
- reloc_code_end 주소 – restart 영역에 256 바이트를 더한 후 256 byte align한 사이즈이다.
- rpi2:
- r10 += 0x900((0x928 – 0xc0 + 0x100) & 0xffffff00)
- 0x900 만큼의 space를 두었다.
- r10: 한 번 교정된 decompressed kernel의 마지막 주소에다가 relocation 전용 코드 길이만큼 추가한다.
- bic r10, r10, #255
- 256바이트 align한 주소가 최종적으로 relocated compressed 커널 영역의 시작 주소가 된다.
- adr r5, restart
- r5: compressed 커널의 restart 주소를 읽어온다.
- bic r5, r5, #31
- restart 주소의 32바이트 align 주소를 compressed 커널에서 복사할 영역의 시작 주소로 한다.
- 32바이트씩 복사할 수 있도록 32byte align한다.
- 하이퍼바이저용 벡터 베이스를 위치가 바뀔 offset 만큼 재 설정한다.
- bl __hyp_get_vectors
- 하이퍼바이저용 벡터 베이스 주소를 가져온다.
- sub r0, r0, r5
- compressed 커널이 relocation될 예정이므로 하이퍼바이저용 벡터 베이스에서 먼저 r5(compressed 커널에서 relocation할 코드 시작 주소)만큼 뺀다.
- add r0, r0, r10
- 다시 r10(compressed kernel이 relocation 되는 곳의 시작 주소) 만큼 더해 offset 조정을 완료한다.
- bl __hyp_set_vectors
- offset 조정된 주소를 다시 하이퍼바이저 벡터 베이스값으로 지정한다.
- bl __hyp_get_vectors
- r9(relocation될 곳의 relocation 영역의 끝 주소)과 r6(compress 커널에서 relocation 코드 영역의 끝 주소)를 계산한다.
- sub r9, r6, r5
- add r9, r9, 31
- bic r9, r9, 31
- r9 = round_up(r6(_edata + DTB size) + 31 후 32바이트 align
- 여기까지 계산된 r9 값이 복사할 사이즈이다.
- add r6, r9, r5
- r6 = r9(복사할 사이즈) + r5(compressed 커널에서 relocation 영역의 시작 주소)
- 마지막으로 r6에 compress 커널에서 relocation 영역의 끝 주소가 지정된다.
- add r9, r9, r10
- r9 += r10(2 개의 추가 영역을 더한 decompressed 커널의 끝 주소)
- 마지막으로 r9에 decompressed 커널의 reloocation 될 영역 끝 주소가 지정된다.
- r9: relocation될 곳의 relocation 코드 주소의 끝을 계산한다.
- 32바이트씩 복사하기 위해 r9 값을 32byte round up 하여 align 한다.
- add r6, r9, r5
- r6 = r9(compressed 커널에서 relocation할 코드의 끝 주소) + r5(compressed 커널에서 relocation할 코드 시작 주소)
재배치 코드 보호
relocation 진행
1: ldmdb r6!, {r0 - r3, r10 - r12, lr} cmp r6, r5 stmdb r9!, {r0 - r3, r10 - r12, lr} bhi 1b
- r6주소로부터 32바이트씩 읽어 r9 주소에 복사한다.
- r6주소와 r9주소는 4바이트씩 감소한다.
- r6 주소가 r5보다 큰동안 루프를 돌며 반복한다.
relocation 완료 후 restart로 다시 이동
/* Preserve offset to relocated code. */ sub r6, r9, r6 #ifndef CONFIG_ZBOOT_ROM /* cache_clean_flush may use the stack, so relocate it */ add sp, sp, r6 #endif bl cache_clean_flush adr r0, BSYM(restart) add r0, r0, r6 mov pc, r0
- sub r6, r9, r6
- r6에는 offset이 담긴다.
- 이 offset을 이용하여 잠시 뒤 재배치된 restart 루틴으로 점프해야 할 때 사용된다
- add sp, sp, r6
- ROM에서 동작한 경우가 아니면 스택 위치를 조정한다.
- 캐시 클린 플러쉬 루틴을 이용하려면 스택이 필요해서 재배치 코드의 위쪽으로 스택을 설정한다.
- bl cache_clean_flush
- 캐시를 비운다.
- 이 루틴에서 restart: 레이블로 점프한다.
- relocation이 완료되었으므로 다시 restart 부터 영역 비교를 한다.
참고
- start: | 문c
- 영역 검사를 하여 하단부가 중복된 경우만 캐시 on 보류 | 문c
- cache_on: | 문c
- cache_clean_flush: | 문c
- 영역 검사를 하여 하단부가 중복된 경우만 캐시 on 보류 | 문c
- restart: | 문c
- 중복 영역 검사를 하여 중복되지 않았다고 판단하면 wont_overwrite(decompress 준비) | 문c
- 1st restart ~ relocation 수행 과정 – (현재 글)
- 중복 영역 검사를 하여 중복되지 않았다고 판단하면 wont_overwrite(decompress 준비) | 문c
- wont_overwrite: | 문c