기본적으로 ARM MMU 와 페이지테이블 워킹에 관한문서들을 좀 봤는데, 크게 도움되지 않았고, 결과적으로 아래의 ARM 공식 홈페이지에 나와있는 설명을 통해 개발할 수 있었다. 내가 본 문서는 ARM9 프로세서의 MMU Page Table Waking 과정이었는데, 이것이 Cortex-A9 에서도
크게 달라지지 않아서 그대로 적용된 것 같다.
이것이 ARM MMU 의 페이지테이블 워킹에 대한 전체적인 흐름도이다. 먼저 TTB 는 페이지 디렉토리라고 보면되는데(TTB base 가 CR3 와 같은 레지스터인 셈) x86 과는 다르게 페이지 디렉토리의 크기가 16KB 이고, 4바이트짜리 Descriptor 가 총 4096 개가 들어있어서 가상주소 상위 12비트를 통해 인덱싱 된다. 일단 TTB 를 물리메모리상에서 찾아서 그 내용을 XMD 로 덤프하여 눈으로 확인부터 해야할거 같아서 그쪽부터 조사를 했다. TTB 는 다른 아키텍쳐들과 마찬가지로 Context 별로 존재하는데 커널의 마스터 TTB 를 어떻게 찾아야 하나 하고 구글링을 하다보니 아래와 같이 MRC 라는 어셈블리 명령으로 CR3 를 가져오듯이 가져올 수 있다는것을 알 수 있었다. 실제로 테스트해보니 작동하는듯 했다.
2) Coprocessor 내부의 Register들을 ARM Register에 읽거나 반대로 써서 장난치는 경우. (Coprocessor 내부 Register ↔ ARM Register) Coprocessor 명령은 ARM core와 Coprocessor와의 통신에 관련된 Register로서, PSR명령과 유사한 형식입니다. Coprocessor의 Register들과 직접적인 Register 값 교환을 하는 명령어는 MRC, MCR, 이고요. M X ← Y 형식입니다. R은 Core의 일반 Register이고요, C는 coprocessor의 Register를 의미합니다. 그러니까, MRC는 R←C로서 R:=Coprocessor 이고요, MCR은 C←R로서, C:=Register 값을 의미합니다. 예를 들어, MCR 코프로세스번호, 무조건 0, 레지스터번호, Coprocessor 레지스터번호, c0, 무조건 0의 형태를 따라서 명령을 주면 되며 MCR p15, 0, r4, c2, c0, 0 으로 명령을 주면 coprocessor 15번 2번 register에 r4 값을 전송하라는 뜻이 됨을 상기 하시면 됨다~ |
ARM 에서는 MMU 가 일종의 Coprocessor 로 취급되고, TTB base 와 같은 페이지 테이블 워킹에 필요한 레지스터가 MMU 내부에 자체적으로 보관되어서, ARM CPU 가 이것을 요청하는 명령, 로드하는 명령등을 내려서 접근하는것 같다. 아무튼 MRC, MCR 두 명령은 ARM 코프로세서의 레지스터를 읽어오고 업데이트하는 명령이었는데 형식이 정해져 있었다. 어쨋든 이런식으로 insmod 의 TTBR 을 R0 에 로드해올 수 있었는데, 커널의 마스터 TTB 의 물리메모리주소는 이렇게해서 구할수 없다. 커널 TTB를 구하려면 해당 어셈블리 코드를 커널 초기화코드상에 넣고 재컴파일하던지 소스를 뒤져봐야 하는데, 귀찮아서 그냥 리버싱을 통해 찾아보기로 했다.
일단 커널 페이지 디렉토리처럼 분명 커널 TTB 가 초기의 물리메모리 페이지 어딘가에 매핑될것이라고 생각했다. 일단 당연히 페이지 경계에서 시작을 할것이므로 페이지 단위로 물리메모리를 스캐닝 해봤다. 그러다보니 아래처럼 매우 페이지테이블스러운 부분을 발견했다.
하위 12비트가 모두 똑같고 상위비트만 순차적으로 증가하는형태... 값의 상위비트들이 주소와일치하는형태... 딱 선형매핑을 위한 페이지테이블 엔트리스러운 부분이었다. 분명 이부분이 페이지 디렉토리라고 생각을 했는데, 이때는 TTB 가 16K 라는것을 생각하지 못해서 삽질을 했다. 메모리 스플릿이 3:1이니까 분명 0x50007c00 쯤부터 커널 페이지 매핑이 있고, 나머지는 0 일거라 생각했는데 나중에 이건 페이지디렉토리가 4K 일때의 이야기라는걸 깨닳았다. 일단 유저:커널 가상주소 공간이 3:1 스플릿인데 Translation Table 이 4바이트 엔트리를 4096개 같는다면... 16K 의 공간인데... 0x50007000 에서부터 페이지테이블 엔트리 스로운놈들이 나타나기 시작한다...? 그럼 0x50004000 부터가 TTB base 고 3:1 스플릿으로 0x50007000 부터가 커널 페이지매핑이고 0x50008000 부터가 .text 시작이니까 딱이지 않나? 싶어서 이것이 옳다는 가정하에 직접 손으로 가상주소 변환을 해보니 딱 맞았다. 아무튼 일종의 리버싱을 통해 커널 TTB 가 0x50004000 이라는것을 알아낼 수 있었다.
만약 TTB 의 엔트리가 Section Descriptor 라면 엔트리의 상위 12비트와 가상주소의 나머지 비트들을 concat 하여 곧바로 물리주소가 완성된다. 커널 lowmem 부분이 모두 이렇게 매핑되어있다.
3 의 경우 2와 거의 비슷한데, Coarse Pagetable Descriptor 의 상위 22 비트로 Second Level Pagetable 의 base 주소를 가리키고 가상주소의 중간 2바이트에 해당하는 8비트로 Second Level Pagetable 을 다시 인덱싱 하는것까지는 같지만, Second Level Pagetable Descriptor 의 상위 20비트와 가상주소의 하위 12비트를 concate 하여 최종적인 물리메모리주소가 계산된다. 그림으로 보면 아래와 같다.
4 의 경우에는 Coarse Pagetable Descriptor 의 상위 20 비트로 Second Level Pagetable 의 base 주소를 가리키고 가상주소의 19 ~ 10번째 10비트로 Second Level Pagetable 을 인덱싱 한다. 그리고 Second Level Pagetable Descriptor 의 상위 22비트와 가상주소의 하위 10비트를 concate 하여 최종적인 물리메모리주소가 계산된다. 그림으로 보면 아래와 같다.
처음에는 Section Paging 만 구현하였는데, 커널 정적메모리영역은 이것만 사용하지만 모듈을 올리는 메모리영역(vmalloc) 의 경우 Coarse Page Table 로 매핑을 한다... 그래서 그 두개까지만 구현했다. Fine Paging 은 구현하지 않았다. 시간이 남으면 구현해도 되지만 바쁘므로..
아무튼 이렇게해서 ARM Software MMU 의 구현을 마쳤다.
'Programming' 카테고리의 다른 글
Linux vmarea structure (0) | 2013.09.11 |
---|---|
IDA FLIRT Symbol Recovery (0) | 2013.09.10 |
Linux Kernel 3.8.x Structure Definitions (0) | 2013.08.28 |
IDAPython adding System.map symbols to kernel image (0) | 2013.08.14 |
Linux kernel slab(SLUB) memory allocator (0) | 2013.08.11 |