올해 초 안드로이드용 리눅스 커널 소스코드 받는 법에 대하여 포스팅한 적이 있다. (링크)

그런데 9월 경에 안드로이드 Git 서버가 해킹을 당하고 나서 커널 소스코드를 받을 수 있는 방법이

봉쇄되어 버렸고(서버 복구 작업에 의해) 한동안 코드를 받을 수가 없었다.

그런데 오늘 확인해 보니 드디어 안드로이드의 커널을 받을 수 있도록 오픈이 되었다. (링크)

Git에서 소스코드를 받는 방법은 주소만 다를 뿐 다른 부분은 전과 동일하다.
1. git clone 실행
  [taehyo@mdbuild:~/ics-4.0/kernel]# 
git clone https://android.googlesource.com/kernel/common.git [복사할 디렉터리명]

2. git branch -a 로 다운로드 가능한 브랜치 확인
  [taehyo@mdbuild:~/ics-4.0/kernel]# git branch -a

3. git checkout으로 소스코드 체크아웃
  [taehyo@mdbuild:~/ics-4.0/kernel]# git checkout -b android-3.0 remotes/origin/android-3.0


진저브레드용 커널을 받고자 한다면 remotes/origin/android-2.6.39 브랜치를

아이스크림샌드위치용 커널을 받고자 한다면 remotes/origin/android-3.0 브랜치를 선택하면 된다.


http://taehyo.egloos.com/4170886

'Embedded Lab > linux, x86' 카테고리의 다른 글

[find]  (0) 2012.10.18
[Makefile의 미리 정의된 옵션과 타겟]  (0) 2012.10.18
[모듈 만들어 올리기]  (0) 2012.10.16
[error가 있는지 확인 출력]  (0) 2012.10.15
[디스크 공간 할당 방법]  (0) 2012.10.14
Posted by cyj4369
,

예제: hello.c 모듈


1) hello.c C 소스 코드를 만들자.(Makefile로 컴파일 하기 위해서는 디렉토리의 경로에 한글이나 공백이 없어야 한다.)


$ mkdir demo; cd demo

$ vi hello.c


2) 다음의 라인을 추가하자.


#include <linux/init.h>

#include <linux/module.h>

#include <linux/kernel.h>


static int __init hello_start(void)

{

printk(KERN_INFO "Loading hello module...\n");

printk(KERN_INFO "Hello world\n");

return 0;

}


static void __exit hello_end(void)

{

printk(KERN_INFO "Goodbye Mr.\n");

}


module_init(hello_start);

module_exit(hello_end);


3) 파일을 저장하고, 새로운 Makefile을 다음과 같이 만들자.(hello.c와 Makefile은 같은 디렉토리에 있어야 한다.)


$ vi Makefile


다음 라인들을 추가하자.


obj-m = hello.o

KVERSION = $(shell uname -r)

all:

     make -C /lib/modules/$(KVERSION)/build M=$(PWD) modules

clean:

     make -C /lib/modules/$(KVERSION)/build M=$(PWD) clean


     부분은 탭으로 띄워야 한다

4) 저장하고 파일을 닫자.


5) hello.c모듈을 컴파일하자.


$ make


6) 루트 계정으로 전환하고 모듈을 로드하자.


$ sudo insmod hello.ko


7) 로드된 모듈을 확인하자.


$ lsmod | less


8) /var/log/message 파일에서 메세지를 확인하자.


9) 모듈을 언로드하자.


$ rmmod hello


10) 리눅스 시스템이 부팅할때 모듈을 로드할 수 있도록, /etc/modules 파일을 수정하자.

이파일은 부팅시 로드할 커널 모듈의 이름을 가지고 있다. 먼저 해당 모듈을 /lib/modules/$(uname -r)/kernel/drivers 에 복사하고, 다음의 단계들을 밝아 나가자.


(a) hello모듈의 디렉토리를 만들자


$ mkdir -p /lib/modules/$(uname -r)/kernel/drivers/hello


(b) 모듈을 복사하자.


$ cp hello.ko /lib/modules/$(uname -r)/kernel/drivers/hello/


(c) /etc/modules파일을 편집하자.


$ vi /etc/modules


(d) 다음 라인을 추가하자.


hello


(e) 변경된 사항을 살펴보기 위해서 재부팅하자. 그리고 lsmod나 dmesg 를 사용해서 로드된 모듈을 확인하자.


$ cat /proc/modules


OR


$ lsmod | less







-c: 커널 모듈은 독립적으로 실행 가능한 파일이 아니며, insmod를 사용하여 실행 시간 중에 커널에 링크되는 오브젝트 파일 이다. 결론적으로 모듈은 -c옵션을 주고 컴파일 해야 한다. 


-O2: 커널은 인라인 함수를 주로 사용하기 때문에 모듈은 이 옵션 플래그를 사용해야 한다. 이 옵션을 사용하지 않은 경우 어떤 어셈블러 매크로는 함수 호출 시 정상적으로 작동하지 않을 것이다. insmod는 커널에서 원하는 함수를 찾지 못하고 결국 모듈의 적재는 실패할 것이다. 


-W -Wall: 프로그램에서의 실수는 당신의 시스템을 다운 시킬 수도 있다. 컴파일러 경고 기능은 항상 켜둬라, 이것은 모듈 컴파일 뿐 아니라 당신의 모든 컴파일 행위에 적용된다. 


-isystem /lib/modules/uname -r/build/include: 컴파일 대상이 되는 커널의 헤더를 사용해야만 한다. 기본적인 /usr/include/linux를 사용하는 것은 작동하지 않을 것이다. 


-DKERNEL:이 심볼을 정의 하는 것은 헤더 파일에 이 코드가 유저 프로세스로 동작하지 않고 커널 모드에서 작동한다는 사실을 알린다. 


-DMODULE: 이 심볼은 헤더 파일에 커널 모듈을 위한 올바른 정의를 하게 한다. 








2.6 커널에서는 linux/init.h 헤더파일이 추가되었습니다. 그리고 init_module()과 cleanup_module() 이라는함수 이름 규약을 더이상 따르지 않아도 됩니다. 초기화 함수와 종료 함수는 자신이 원하는 이름으로 작성하면 되고, module_init()과 module_exit()매크로에서 초기화 함수와 종료 함수의 이름만 지정해주면 됩니다. 물론 2.4 커널의 init_module(), cleanup_module()을 사용해도 되지만, 현재 커널 해커들은 이들 함수를 전혀 사용하고 있지 않습니다. 2.4.20 이후 버전의 커널에서는 위와 같은 2.6 형식의 코드를 사용하고, gcc를 사용해서 모듈로 빌드할 수 있습니다. 2.4.20 이후 버전과 2.6 커널의 모듈은 동일한 코드를 사용할 수 있으며, 빌드 과정만 다릅니다.


2.6 커널부터는 커널 빌드에 대해서 kbuild 시스템을 사용합니다. 따라서 모듈을 빌드하기 위해서 gcc를 사용할 수 없고, Makefile을 정의하고, kbuild 시스템으로 모듈을 빌드해야 합니다.

Posted by cyj4369
,

#include <stdio.h>
#include <errno.h>
#include <string.h>

int main(){
    printf("%s", strerror(errno));
    return 0;
}

Posted by cyj4369
,

지난 시간까지 파일과 디렉터리에 관한 전반적인 내용을 다뤘다. 
파일과 파일시스템, 파일에 대한 정보를 가지고 있는 파일 디스크립터, 파일의 구조, 디렉터리의 구조에 대해서 살펴봤는데 이번시간에는 디스크 공간 할당 방법에 대해서 살펴보자.

디스크 공간 할당이란건 뭐냐면 파일을 효율적으로 저장하고 사용하기 위해 파일을 기억공간에 어떻게 할당할 것인가를 결정하는 방법이야. 
파일을 디스크 공간에 할당하는 방법에는 연속 할당 방법과 불연속 할당 방법이 있어. (불연속 할당 방법에는 섹터 단위 할당과 블록 단위 할당이 있는데 지금부터 하나씩 설명해줄께 ㅋㅋ)


연속 할당(Contiguous Allocation)은 파일을 디스크의 연속된 기억공간에 할당하는 방법으로, 생성되는 파일 크기만큼의 공간이 있어야 해.
항상 비슷하지 않아?? 저장장소에 저장시키는 방법 ㅋㅋ 연속, 불연속 ㅋㅋ 연속 할당의 특징은 아래와 같아.

- 논리적으로 연속된 레코드들이 물리적으로 인접한 공간에 저장되기 때문에 접근 시간이 빠르다.
- 디렉터리는 파일의 시작 주소와 길이에 대한 정보만 가지고 있으므로 디렉터리가 단순하고, 관리 및 구현이 용이하다.
- 파일 크기에 알맞은 연속 공간이 없을 경우 파일이 생성되지 않는다.
- 파일의 생성과 삭제가 반복되면서 단편화가 발생한다.
- 단편화를 줄이기 위해 재배치에 의한 주기적인 압축(Compaction)이 필요하다.
- 파일의 크기가 시간에 따라 변경될 경우 구현하기가 어렵다.

                                                  <연속 할당 Contiguous Allocation>

불연속할당(Non-Contiguous Allocation)은 파일의 크기가 변경될 경우 구현이 어려운 연속 할당의 단점을 보완하기 위한 것이야. 이것은 디스크 공간을 일정 단위로 나누어 할당하는 기법이지.ㅋㅋ

불연속 할당에는 섹터 단위 할당과 블록 단위 할당 방법이 있어.
 
1) 섹터 단위 할당

- 하나의 파일이 디스크의 섹터 단위로 분산되어 할당되는 방법으로, 하나의 파일에 속하는 섹터들이 연결 리스트(Linked List)로 구성되어 있다. 
- 하나의 파일에 속하는 각각의 섹터는 연결을 위해 다음 내용이 있는 곳의 포인터를 가지고 있다.
- 디렉터리는 파일의 시작과 마지막 주소에 대한 정보만 가지고 있다.
- 섹터 단위로 저장되므로 디스크의 단편화가 발생되지 않고, 디스크 압축이 불필요하다.
- 파일 생성 시 파일 크기를 알 필요가 없으며, 파일 크기만큼의 연속된 공간이 없어도 저장이 가능하다.
- 레코드를 검색할 경우 파일이 속한 레코드를 순차적으로 검색해야 하므로 탐색 시간이 오래 걸리고, 직접 접근이 불가능하다. ( 이건 단점)
- 각 섹터의 포인터가 차지하는 공간만큼 실제 데이터가 저장될 공간이 감소한다.


2) 블록 단위 할당

블록 단위 할당은 하나의 파일이 연속된 여러 개의 섹터를 묶은 블록 단위로 할당되는 방법이며 블록 체인 할당, 색인 블록 체인 할당, 블록 지향 파일 사상 기법이 있다.

가) 블록 체인 기법
 
- 블록 체인 기법은 섹터 단위 할당 기법과 비슷하나 할당 단위를 블록 단위로 구성하는 기법이다.
- 하나의 블록은 여러 개의 섹터로 구성되어 있다.
- 디렉터리는 파일의 첫 번째 블록을 가리키는 포인터를 가지고 있다.
- 하나의 블록은 데이터와 다음 블록을 가리키는 포인터로 구성되어 있다.
- 삽입과 삭제 시 포인터만 수정하면 되므로 삽입, 삭제가 간단하다.
- 순차적으로 탐색해야 되므로 속도가 느리다.(단점)

나) 색인(인덱스) 블록 체인 기법

- 색인 블록 체인 기법은 파일마다 색인 블록을 두고, 파일이 할당된 블록의 모든 포인터를 이 색인 블록에 모아 둠으로써 직접 접근을 가능하게 한 방법이다.
- 색인 블록 하나로 파일을 전부 나타낼 수 없는 경우 여러 개의 연속된 색인 블록을 서로 링크하여 사용할 수 있다.
- 디렉터리는 파일의 색인 블록에 대한 포인터를 가지고 있다.
- 색인 블록의 포인터를 사용하여 직접 접근이 가능하며, 탐색 시간이 빠르다.
- 삽입 시 색인 블록을 재구성해야 하고, 색인 블록이 차지하는 만큼의 기억장치 낭비가 발생한다.(단점)

 
다) 블록 지향 파일 사상 기법

- 블록 지향 파일 사상 기법은 포인터 대신 파일 할당 테이블(FAT, File Allocation Table)에 있는 블록 번호를 사용하는 기법이다.
- 파일 할당 테이블에는 각 블록에 해당하는 항목이 있고, 각 항목은 블록 번호에 의해 색인된다. 
- 블록 번호에 의해 색인된 테이블의 각 항복은 다음 블록의 블록 번호를 가진다. 
- 디스크 구조의 특성상 블록 번호는 실제 기억공간의 주소로 쉽게 변환할 수 있다.
- 데이터의 삽입, 삭제가 용이하다.
- 디렉터리는 파일 할당 테이블의 시작 위치를 가지고 있다.



출처

http://wingsofgod.tistory.com/190

Posted by cyj4369
,

이번 글에서는 지난시간에 알아본 세그먼테이션 과정이 윈도우에서 실제로 어떻게 진행되는지 알아보자. 프로그램이 사용하는 논리 주소가 선형 주소로 어떻게 변경되는지를 확인함으로써 윈도우에서 세그먼테이션 과정이 어떻게 진행 되는지 확인 할 수 있다. 지난번 포스트에 관해서는 아래 링크를 참조하자.

Memory Management : Segmentation 1 

Memory Management : Segmentation 2






우선 시작하기 전에, 유저 레벨에서, 그리고 커널 레벨에서 세그먼트 레지스터가 어떻게 보이는지 확인 해 보자.

[Windows 구조와 원리, 281p ~ 289p 참고] 





1. User Level


아래 스크린샷은 윈도우 Xp Sp3 - 32 Bit 에서 메모장 프로그램을 WinDBG 에 연결해서 본 레지스터 상황이다.



코드 세그먼트 셀렉터 값 : 0x1b 

데이터 세그먼트 셀렉터 값 : 0x23

이제 WinDBG에서 현재 메모장 프로그램이 사용하고 있는 셀렉터들을 dg 명령어를 이용해 확인 해 보자.



어? 0x1B 값과, 0x23 값을 가진 셀렉터는 없다. 왜 그럴까? 이유는 WinDBG 에서 dg 명령어를 통해서 보여지는 셀렉터 값들은 DPL(Descriptor Privilege Level)Bit가 모두 0인 값을 기준으로 하기 때문이다.

아래 그림을 보자. 0x1B 값에서 DPL 비트를 모두 0으로 하면 0x18값이 나오고 0x23값에서 DPL비트를 0으로 하면 0x20 값이 나온다. 빨간색으로 표시 한 부분이 레지스터 창에 보이는 값이고, 파란색으로 표시한 부분이 DPL 비트가 0이 되어 dg 명령어를 사용했을때 보이는 값이다. 0x38의 경우에는 이미 DPL 비트가 0이기 때문에 dg 명령어를 이용해도 0x38로 보인다.


FS 가 사용하는 셀렉터의 Base Address 와 Table Limit 에 대해서는 나중에 알아보기로 하고, 0x23과 0x1B가 가지는 Table Limit에 주목 해 보자. 

우리가 이미 알고 있듯이, 프로그램에서 사용할 수 있는 가상 메모리는 0x00000000 - 0xFFFFFFFF 까지다. 이중에서 0x7FFFFFFF 까지의 2GB는 유저 레벨로 사용되고 (Ring 3), 0x80000000 - 0xFFFFFFFF 까지의 2GB 는 커널레벨로 사용된다 (Ring 0)

그런데 WinDBG에서 셀렉터들을 확인하면 DPL 값이 0인 셀렉터건(Ring 0), 3인 셀렉터건(Ring 3) 모두 Base Address 가 0x00000000 이고Table Limit 이 0xFFFFFFFF 까지다.

Ring 3 사용하는 DS 셀렉터를 이용해서도 커널 메모리에 접근할 수 있다는 뜻인가? 물론 아니다. 실제로 어플리케이션에서 포인터를 이용해 커널 메모리에 접근하려 하면 메모리 접근 에러가 난다.

이는, 윈도우가 세그먼테이션을 이용해서 메모리 접근 보호를 하고 있지 않기 때문이다. 윈도우에서는 페이징을 통해서 어플리케이션에서 커널 메모리로 접근하는 것을 제한 하고 있다.




이제 FS 에 대한 의문을 풀어보자. 왜 FS 는 Base Address 가 0x7ffdc000 이며 Table Limit 이 0xFFF ( 즉, 세그먼트당 0x1000 = 4KB ) 인걸까?

FS 는 32Bit 프로세서인 80386 부터 제공되었으며 프로그램에서 보조적인 용도로 사용할 수 있도록 CS, DS, ES, SS 외에 추가적으로 제공 해 준 레지스터다. 윈도우즈에서는 FS 레지스터를 TEB(Thread Environment Block) 을 지정하는데 사용하고 있다.
TEB 는 유저 모드에서 사용하는 스레드 정보 저장 구조체다.

( TEB 에 대해서 알고 싶다면 다음을 링크를 참고하라. PEB, TEB, EPROCESS, KPROCESS, ETHREAD, KTHREAD )

그렇다면, FS 가 TEB 를 가리키므로 현재 0x7ffdc000에 TEB 가 있다는 말이 된다. 한번 확인 해 보자.


종합해보면 다음과 같다.

"User Level에서 FS는 현재 프로그램이 사용하고 있는 스레드의 정보를 저장하고 

있는 TEB 를 가리킨다." 



그렇다면 스레드가 여러개 일때 TEB 도 여러개가 되어야 하는데, 다른 TEB 는 어느곳에 저장되는 것일까? WinDBG를 통해 확인 해 보자. WinDBG 는 현재 메모장 프로그램에 붙어있다.



메모장 프로그램의 Process ID = 514 이며, 스레드는 현재 2개를 사용하고 있다. 

하나는 Thread ID = 0x848 = 2120d, TEB at 0x7ffdd000

다른 하나는 Thread ID = 0x258 = 600d, TEB at 0x7ffdc000 (현재 Thread)

아래는 ProcExp 를 통해 확인 해 본 Thread Creation Time 이다.




재밌는 점은 

0x848 = 2120d 번 스레드가 먼저 생성 되었음에도 TEB 가 존재하는 주소 번지가 (0x7ffdd000)

0x258 = 600d 번 스레드 보다도 하위에 있다는 점이다. (0x7ffdc000)

더 늦게 생성 된 스레드의 TEB 가 더 하위 메모리에 존재 한다는 의미다. 이는 아마도 윈도우즈 운영체제에서 스레드의 개수를 최대 2048개 만큼 제한 해 놓았기 때문일 것이다.

스레드는 기본적으로 1MB의 스택을 사용하므로 가상 메모리상에서 스레드는 최대 2048개(2GB)가 될 수 있다. 스레드가 2048개라면, TEB도 2048개 여야 하고 따라서 0x1000 (TEB SIZE) x 0x800 (NUMBER OF THREAD) = 0x800000 만큼의 메모리를 차지하는데 최초의 TEB는 0x7ffdd000 에서 시작하므로 나중에 생성된 TEB 가 더 상위 메모리에 존재한다면 마지막 2048번째 TEB는 0x807dd000 에 존재하게 된다. 그러나 이 곳은 커널 영역이므로 TEB 가 존재할 수 없다. (TEB는 유저 레벨에서 사용되는 구조체다. 커널레벨에서는 ETHREAD 구조체가 사용된다.) 이런 이유에서 더 나중에 생긴 TEB 가 더 하위 메모리에 존재 한다. (복군의 추측.^^)


이처럼 각각의 스레드가 같은 FS (0x38) 를 사용하면서 세그먼트의 Base Address( = TEB의 주소) 를 다르게 한다는 의미는 스레드 스위칭이 발생할 때마다 Wiondows 에서 그 내용을 바꾸어 주고 있다는 것을 뜻한다.







2. Kernel Level


이번엔 커널 레벨에서 사용하는 셀렉터에 대해서 알아보자.



유저 레벨과 다른점은 

1. CS 가 0x8 셀렉터를 사용한다는 점.
2. DS가 유저 레벨과 같이 0x23 셀렉터를 사용 한다는 점.
2. SS가 유저 레벨에선 DS와 같은 0x23 셀렉터를 사용했지만 커널 레벨에서는 0x10 셀렉터를 사용한다는 점.
3. FS가 0x38 셀렉터가 아니라 0x30 셀렉터를 사용한다는 점.

이렇게 3가지이다.

아래는 dg 명령어를 이용해 커널에서 사용하는 셀렉터를 나열한 것이다.


궁금한 점을 하나씩 풀어보자. CS의 경우는 딱히 의문점이 없다. Ring 0이므로 당연히 DPL 값이 0인 세그먼트 셀렉터를 사용해야 한다.

2. DS가 유저 레벨과 같이 0x23 셀렉터를 사용 한다는 점.

왜 유저 레벨과 똑같이 DPL이 3인 세그먼트 셀렉터를 커널 레벨에서 사용할까? 이유는 다음과 같다. 



" 낮은 권한의 코드 세그먼트로부터 높은 권한의 코드 세그먼트로의 실행 전환은 

해당 세그먼트가 Conforming 이거나 Gate를 통해서 가능하다. 그러나 

Conforming 세그먼트이건 Non - Conforming 세그먼트이건 높은 권한을 가진 

코드 세그먼트에서 낮은 권한을 가진 세그먼트로의 실행 전환은 불가능하다. 



데이터 세그먼트의 경우에는 Non - Conforming이다. 즉, 낮은 권한을 가진 

프로그램이나 프로시저는 높은 권한을 가진 데이터 세그먼트로의 접근이 

불가능하다. 그러나 반대로, 높은 권한을 가지고 있는 프로그램이나 

프로시저로부터 낮은 권한의 데이터 세그먼트로의 접근은 가능하다. "



이렇게 인텔 프로세서에서 커널의 DPL (Descriptor Privilege Level) 값이 0인 코드 세그먼트로부터 DPL (Descriptor Privilege Level) 값이 3인 데이터 세그먼트로의 접근이 가능하기 때문에 커널을 위한 DPL (Descriptor Privilege Level) 값이 0인 데이터 세그먼트 셀렉터를 사용하지 않는 것이다.




3. SS가 유저 레벨에선 DS와 같은 0x23 셀렉터를 사용했지만 커널 레벨에서는

0x10 셀렉터를 사용한다는 점.
 



스택 세그먼트도 데이터 세그먼트의 일종이다. 하지만 SS 레지스터를 사용해 세그먼트를 선택하는 스택의 경우에는 DS 를 사용하는 경우와는 조금 다르다. 아래의 인텔 문서에 다음과 같이 나와 있다.

"프로세서는 SS 레지스터로 스택 세그먼트 셀렉터를 로드할때 권한을 검사한다. 

로드할 스택 세그먼트와 관련된 모든 권한들이 CPL (Current Privilege Level)과 

매치 되어야 한다. 즉, 현재 가지고 있는 권한인 CPL 과 로드할 세그먼트 셀렉터의 

RPL (Requested Privilege Level)과 해당 세그먼트를 설명하는 GDT 내의 디스크

립터가 가지고 있는 DPL (Descriptor Privilege Level)이 모두 같아야 한다. 만약, 

RPL과 DPL이 CPL와 같지 않다면, #GP가 발생한다."




4. FS가 0x38 셀렉터가 아니라 0x30 셀렉터를 사용한다는 점.


User Level (Ring 0) 에서는 FS가 0x38 셀렉터를 사용하며 TEB (Thread Environment Block) 를 가리킨다. 하지만, Kernel Level (Ring 3) 에서는 FS가 0x30 셀렉터를 사용하여 KPCR (Kernel's Processor Control Region) 를 가리킨다. 

이 말은 프로그래머가 인터럽트를 핸들링 함으로써 유저 레벨로부터 커널 레벨로 제어 이행이 수행될 경우 반드시 FS 레지스터를 커널 레벨의 세그먼트 값 0x30으로 바꿔 주어야 한다는 뜻이다. 만약 그렇게 하지 않을 경우, 커널 레벨에서 실행되는 함수들은 KPCR 혹은 KPRCB를 참조 하는 것이 아니라TEB를 참조하는 엉뚱한 상황이 발생 할 것이다.

KPCR 은 커널에서 프로세서의 정보를 관리하기 위해서 사용하는 구조체다. 따라서 프로세서가 만약 2개라면 2개 존재하고, 1개라면 1개 존재한다. WinDBG 에서 확인 해 보자. !pcr (Processor Control Region) 명령어를 이용해 확인할 수 있다.


IDT, GDT, Thread, IRQL 등에 관한 정보를 담고 있다. 아래는 KPCR 의 구조체를 WinDBG 를 통해 확인한 내용이다.

마지막 0x120에서 볼 수 있는 구조체가 중요한데, 이름이 KPRCB (Kernel's Processor Contorl Block) 이다. WinDBG를 이용해서 어떤 정보들을 담고 있는지 확인 해 보자.




KPRCB 는 우리가 흔히 커널이라 부르는 NtosKrnl.exe 에 의해서 사용되는 비 공개 구조체다. 이름에서 볼 수 있듯이 프로세서를 제어하기 위해 사용하는 구조체다. WinDBG를 통해서 확인 해 보자.


FS:[0]이 KPCR을 가리키므로, KPCR 내의 0x120에 위치하는 KPRCB 은 fs:[120]이다. KPRCB 는 Native API에 의해서 주로 쓰이는데 대표적인 예가 PsGetCurrentThread 와 IoGetCurrentProcess 다. 한번 확인 해 보자. 아래는 PsGetCurrentThread 를 어셈블리어 형태로 변환한 결과다.

FS:[124] 값을 eax로 로드 해 리턴한다. FS:[124]는 KTHREAD 구조체다.


이번엔 IoGetCurrentProcess 를 분석해 보자.

FS:[124]를 통해서 KTHREAD 를 EAX 로 옮기고, KTHREAD + 0x44 로부터 값을 얻어와 EAX로 옮기고 리턴한다.

KTHREAD 0x44는 뭘까?, WinDBG 에서 확인해보면  0x34는 _KAPC_STATE고, 여기서 0x10을 더하면 _KPROCESS가 된다.

결국 IoGetCurrentProcess는 _KPROCESS 를 리턴한다.

EPROCESS 의 첫번째 인자가 KPROCESS 이므로 결국 IoGetCurrentProcess 는 KPROCESS 또는 EPROCESS 구조체를 리턴한다고 말할 수 있다.

ETHREAD의 경우도 마찬가지다. ETHREAD 의 첫번째 인자가 KTHREAD 이므로 PsGetCurrentThread 는 KTHREAD 또는 ETHREAD를 리턴한다고 말할 수 있다.





여기까지 유저 레벨과 커널 레벨에서 사용하는 세그먼트 셀렉터에 대해서 알아보았다. 다음 시간에는 논리 주소를 선형 주소로 변환 해 봄으로써 윈도우에서 세그먼테이션이 어떻게 일어나는지 확인 해 보겠다.

Posted by cyj4369
,

이번시간의 목표는 프로그램에서 사용하는 논리 주소 (Logical Address) 를 선형 주소 (Linear Address) 로 변경 함으로써 세그먼테이션 과정이 어떻게 일어나는지 확인 하는 것이다. 세그먼테이션에 대해서는 지난번 글을 확인 하도록 하자.

Memory Management : Segmentation 1

Memory Management : Segmentation 2

Memory Management : Translating Logical Address to Linear Address 1

인텔 X86 아키텍처에서 논리 주소가 물리 주소로 변경되는 과정은 다음과 같이 요약할 수 있다.


논리주소
 -> 세그먼테이션 -> 선형주소 -> 페이징 -> 물리주소


자, 이제부터 시작 해 보자. 어느 논리 주소를 선형 주소로 변경 할 것인가? 

이전 포스트에서 언급했던 PsGetCurrentThread 함수가 위치한 논리 주소를 선형 주소로 변경 해 보자. WinDBG 에서 확인해보면 PsGetCurrentThread 함수는 0x8052993c 에 위치한다.

kd> x nt!PsGetCurrentThread

8052993c nt!PsGetCurrentThread = <no type information>

kd> u 8052993c
nt!PsGetCurrentThread:
8052993c 64a124010000    mov     eax,dword ptr fs:[00000124h]
80529942 c3              ret

kd> db 8052993c
8052993c  64 a1 24 01 00 00 c3 cc-cc cc cc cc 64 a1 24 01  d.$.........d.$.
8052994c  00 00 8b 80 68 01 00 00-c3 cc cc cc cc cc 64 a1  ....h.........d.



첫 한바이트의 값은 0x64 다. 이 함수에 접근하기 위해서 커널에서 사용하는 셀렉터는 0x08 이다.

 
이것을 셀렉터 포맷에 맞추어 분석 해 보면

kd> .formats 0x08
Evaluate e-pression:  
Hex:     00000008  
Decimal: 8  
Octal:   00000000010  
Binary:  00000000 00000000 00000000 00001000  
Chars:   ....  
Time:    Thu Jan 01 09:00:08 1970  
Float:   low 1.12104e-044 high 0  
Double:  3.95253e-323
TI 


Flag = 0, RPL = 0 이고, GDT 에서 1번째 ( GDT의 디스크립터는 0번째부터 시작한다. ) 디스크립터를 사용한다는걸 알 수 있다.WinDBG를 통해서 GDT의 위치를 확인 해 보자.

kd> r gdtrgdtr=8003f000

GDT
 를 8바이트 (디스크립터)의 사이즈 만큼씩 잘라서 내용을 보면 아래와 같다.


kd> dq 8003f000
8003f000  00000000`00000000 00cf9b00`0000ffff
8003f010  00cf9300`0000ffff 00cffb00`0000ffff
8003f020  00cff300`0000ffff 80008b04`200020ab
8003f030  ffc093df`f0000001 0040f300`00000fff
8003f040  0000f200`0400ffff 00000000`00000000
8003f050  80008954`c0000068 80008954`c0680068
8003f060  00009302`2f40ffff 0000920b`80003fff
8003f070  ff0092ff`700003ff 80009a40`0000ffff



첫번째는 00000000`00000000 으로 채워진 널 디스크립터고, 두번째가 바로 CS가 사용하고 있는 인덱스가 1인 세그먼트 디스크립터다커널에서 사용하는 GDT 디스크립터 구조체 타입으로 변경해서 확인 해 보자.


kd> dt _KGDTENTRY -r 8003f008
nt!_KGDTENTRY
   +0x000 LimitLow         : 0xffff
   +0x002 BaseLow          : 0
   +0x004 HighWord         : __unnamed
      +0x000 Bytes            : __unnamed
         +0x000 BaseMid          : 0 ''
         +0x001 Flags1           : 0x9b ''
         +0x002 Flags2           : 0xcf ''
         +0x003 BaseHi           : 0 ''
      +0x000 Bits             : __unnamed
         +0x000 BaseMid          : 0y00000000 (0)
         +0x000 Type             : 0y11011 (0x1b)
         +0x000 Dpl              : 0y00
         +0x000 Pres             : 0y1
         +0x000 LimitHi          : 0y1111
         +0x000 Sys              : 0y0
         +0x000 Reserved_0       : 0y0
         +0x000 Default_Big      : 0y1
         +0x000 Granularity      : 0y1
         +0x000 BaseHi           : 0y00000000 (0)



요약해보면 Base Address 는 0이고, G Flag = 1에 Limit Field 가 0xFFFFF 이므로, Table Limit 은 0xFFFFFFFF 다. CS 가 사용하고 있는 세그먼트는 0x00000000 ~ 0xFFFFFFFF 이므로 선형 주소를 구하기 위해서는 현재 논리 주소에다가 Base Address 인 0을 더하면 선형주소가 나온다. 따라서 우리가 원했던 0x8052993c 의 선형주소는 0x8052993c 이다.

윈도우즈에서 사용하는 코드와 데이터 세그먼트의 베이스 어드레스는 0x00000000 이다. 아래를 확인 해 보자. WinDBG 에서 dg 명령어를 이용해 커널에서 사용하는 세그먼트 셀렉터들을 나열해 본것이다.



kd> dg 0 255
                                  P Si Gr Pr Lo
Sel    Base     Limit     Type    l ze an es ng Flags
---- -------- -------- ---------- - -- -- -- -- --------
0000 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
0008 00000000 ffffffff Code RE Ac 0 Bg Pg P  Nl 00000c9b  (커널 코드)
0010 00000000 ffffffff Data RW Ac 0 Bg Pg P  Nl 00000c93 (커널 스택 세그먼트)
0018 00000000 ffffffff Code RE Ac 3 Bg Pg P  Nl 00000cfb (유저 코드)
0020 00000000 ffffffff Data RW Ac 3 Bg Pg P  Nl 00000cf3 (유저 - 커널 데이터 , 유저 스택 세그먼트 )
0028 80042000 000020ab TSS32 Busy 0 Nb By P  Nl 0000008b
0030 ffdff000 00001fff Data RW Ac 0 Bg Pg P  Nl 00000c93
0038 00000000 00000fff Data RW Ac 3 Bg By P  Nl 000004f3
0040 00000400 0000ffff Data RW    3 Nb By P  Nl 000000f2
0048 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
0050 8054c000 00000068 TSS32 Avl  0 Nb By P  Nl 00000089
0058 8054c068 00000068 TSS32 Avl  0 Nb By P  Nl 00000089
0060 00022f40 0000ffff Data RW Ac 0 Nb By P  Nl 00000093
0068 000b8000 00003fff Data RW    0 Nb By P  Nl 00000092
0070 ffff7000 000003ff Data RW    0 Nb By P  Nl 00000092
0078 80400000 0000ffff Code RE    0 Nb By P  Nl 0000009a
0080 80400000 0000ffff Data RW    0 Nb By P  Nl 00000092
0088 00000000 00000000 Data RW    0 Nb By P  Nl 00000092
0090 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
0098 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
00A0 821b23f8 00000068 TSS32 Avl  0 Nb By P  Nl 00000089
00A8 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
00B0 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
00B8 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
00C0 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
00C8 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
00D0 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
00D8 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
00E0 f86fa000 0000ffff Code RE Ac 0 Nb By P  Nl 0000009f
00E8 00000000 0000ffff Data RW    0 Nb By P  Nl 00000092
00F0 804fc698 000003b7 Code EO    0 Nb By P  Nl 00000098
00F8 00000000 0000ffff Data RW    0 Nb By P  Nl 00000092
0100 f8397400 0000ffff Data RW Ac 0 Bg By P  Nl 00000493
0108 f8397400 0000ffff Data RW Ac 0 Bg By P  Nl 00000493
0110 f8397400 0000ffff Data RW Ac 0 Bg By P  Nl 00000493


윈도우즈에서 코드와 데이터 세그먼트의 베이스 어드레스가 0이므로 선형주소 = 논리 주소 공식이 성립한다.

다음시간에는 이렇게 변환한 선형주소가 어떻게 페이징되어 물리 주소로 변경되는지에 대해서 알아 보겠다.

Posted by cyj4369
,

시스템 디스크립터, System Descriptor 



세그먼트 디스크립터의 S 비트가 클리어 ( 0 ) 되어 있다면, 해당 디스크립터가 가리키는 세그먼트는 시스템 디스크립터다. 그렇다면 시스템 디스크립터는 무엇인가? Intel 문서에서는 다음과 같이 정의한다

 - LDT 세그먼트 디스크립터

 - TSS 디스크립터

 - Call - Gate 디스크립터

 - Interrupt - Gate 디스크립터

 - Trap - Gate 디스크립터

 - Task - Gate 디스크립터


이렇게 6가지다. 시스템 디스크립터에 대한 인텔의 설명은 아래와 같다.




해석하자면, " 시스템 디스크립터 타입은 두가지 종류로 나뉜다. 하나는 시스템 세그먼트를 가리키고 있는 시스템 세그먼트 디스크립터 (System Segment Descriptor)고, 다른 하나는 코드 세그먼트 내부에 존재하는 프로시저들을 가리키는 포인터들을 가지고 있거나, TSS의 세그먼트 셀렉터를 가리키고 있는 게이트 디스크립터(Gate Descriptor)로 나뉜다. "


위 표는, 타입 비트에 따른 시스템 세그먼트의 내용이 무엇을 의미하는지를 알려준다.

이제까지 우리는 논리 주소 변환 과정인 세그먼테이션에서 사용되는 디스크립터 테이블에 대해서 알아 보았다. 그렇다면 이제까지 우리가 알아 본 세그먼트 디스크립터는 어디에 존재할까?  




세그먼트 디스크립터 테이블, Segment Descriptor Table 이다.




세그먼트 디스크립터는, 세그먼트 디스크립터 테이블에 존재하며, 세그먼트 디스크립터 테이블은 GDTR, LDTR 레지스터가 각각 가리키고 있다.

간단하게 설명하자면, 각각의 내용은 테이블에 담겨 있으며, 해당 테이블은 프로세서에서 그 테이블만 가리키기 위해서 설정 해 놓은 레지스터가 가리키고 있는 것이다.

디스크립터 테이블에 대해서 알아보기 전에, 시스템에서 Memory - Management 를 위해서 사용하는 레지스터에 대해서 알아보자.






MEMORY - MANAGEMENT REGISTERs for GDT, LDT


프로세서는 4 종류의 메모리 관리용 레지스터를 제공한다. GDTR, LDTR, IDTR, TR 이 그것들 이다. 이 레지스터를 로드하고 저장하기 위해서 프로세서는 특별한 명령어를 제공한다. 이번 글에서는 GDT와 LDT를 위한 GDTR과 LDTR에 대해서만 알아본다.



GDTR, Global Descriptor Table Register



GDTR 레지스터는 Global Descriptor Table, GDT 의 32Bit의 Base Address 와 16Bit의 Table Limit을 포함하고 있다.
Base Address 는 선형 주소상의 GDT 시작 주소를 나타내며, Table Limit 은 GDT 의 한계 주소를 나타낸다.

프로세서는 GDT를 GDTR 로 Load 하고 Store 하기 위해서 각각 LGDT, SGDT 명령어를 제공한다. 프로세서가 리셋되거나 켜졌을때 GDTR의 Base Address 는 0으로, Table Limit은 0FFFFh 로 초기화되며 Protected - Mode 로 전환시 GDT 의 Base Address 와 Table Limit을 GDTR로 로드해야 한다.



LDTR, Local Descriptor Table Register


LDT는 GDT 내에 존재하는 LDT 세그먼트 디스크립터(2번 시스템 세그먼트 디스크립터)를 통해 접근할 수 있는 세그먼트다. LDT 세그먼트의 디스크립터는 반드시 GDT 내에 존재해야 한다. LDTR 레지스터는 Segment Selector, LDT의 Base Address, LDT의 Table Limit, LDT의 Segment Attributes 을 담고 있다.

Base Address는 LDT 세그먼트의 선형주소를, Table Limit은 한계 주소를 나타낸다. 

LLDT와 SLDT 명령어를 통해 LDTR에 LDT를 Load 하고, Store 할 수 있다. LLDT가 세그먼트 셀렉터를 LDTR로 로드할때 Basee Address, Table Limit, Descriptor Attributes 가 LDT 세그먼트 디스크립터로부터 자동적으로 로드된다. 

Task Switching이 발생하면 LDTR은 새로운 세그먼트 셀렉터와 디스크립터의 내용으로 자동적으로 채워진다. 하지만, 이전의 LDTR 내용은 저장되지 않는다. 컴퓨터 전원이 켜지거나, 리셋될때 LDTR의 Base Address는 0으로, Table Limit은 0FFFFh 로 초기화 된다.





그렇다면, 이들 GDTR 과 LDTR 은 어떻게 이용되는 것일까? 

다시 주제를 디스크립터 테이블, Descriptor Table 로 돌려보자.






GDTR은 GDT를 가리키는데 사용되고, LDTR은 LDT를 가리키는데 사용된다. 세그먼트 셀렉터의 TI 비트를 통해서 접근해야 할 메모리가 Global Descriptor Table ( 0 ) 에 존재하는지, Local Descriptor Table ( 1 ) 에 존재하는지를 결정한다.

각시스템은 반드시 한개의 GDT 를 정의해야 하며, 이렇게 정의된 GDT는 시스템에 존재하는 모든 프로그램과 태스크에 의해서 사용된다. 추가적으로 시스템은 한개, 혹은 그 이상의 LDT 를 정의할 수 있다. 예를들어 LDT 는 분리되어 동작하는 각 태스크마다 정의 될 수도 있고 모든 태스크들이 같은 LDT 를 공유할 수도 있다.

GDT 는 세그먼트가 아니다. (LDT 는 세그먼트다.) GDT 는 선형 주소내에 존재하는 Data Structure (For Segment Desciptor) 이며 GDT 의 Base Address 와 Table Limit 은 GDTR 에 담겨 있다. (우리는 위에서 이미 GDTR이 이러한 기능을 한다는것을 알아 보았다.) GDT의 Base Address 는 프로세서의 성능을 위해서 8 Byte 단위로 정렬 되야 한다.

하나의 디스크립터 크기는 64 비트, 즉 8 Byte 다. GDTR의 Table Limit 비트가 16 비트이므로, 8 단위로 나누면 최대 8192(2의 13승) 만큼의 디스크립터를 GDT 내에 포함할 수 있다. 세그먼트 셀렉터의 인덱스 비트가 13 비트 인 이유도 여기에 있다. GDTR이나 LDTR 의 Table Limit 비트가 16비트이므로 8 Byte 단위의 디스크립터를 모두 가리키려면 2의 13승, 8192 만큼의 비트가 필요하기 때문이다.

GDT 의 디스크립터중 첫번째 디스크립터는 "NULL Desciprotr" 라고 불리며 사용되지 않는다. 데이터 세그먼트 셀렉터(DS, ES, FS, GS)가 널 디스크립터를 가리키는 것은 예외를 발생시키지 않지만, 널 디스크립터를 통해서 메모리에 접근하려는 시도는 #GP 를 발생시킨다. 초기화 과정에서 세그먼트 셀렉터를 널 디스크립터로 지정해 두는것은 사용되지 않은 레지스터를 통해 메모리 접근하려는 시도를 막기 위함이다. 

LDT 는 LDT 타입 시스템 세그먼트에 존재하며, GDT 는 이 LDT 를 가리키는 세그먼트 디스크립터를 포함해야 한다. LDT 가 여러개라면 개수만큼의 LDT 세그먼트 디스크립터가 GDT 내부에 존재해야 하며 LDT 디스크립터는 GDT 어느곳에나 존재할 수 있다.



(위의 문단은 영어실력의 부족으로 인해 해석할 수가 없다.)









논리(가상) 주소가 선형 주소로 바뀌는 과정, SEGMENTATION



우리는 이전 글, Memory Management : Segmentation 1 과 이번 글 통해서 세그먼테이션에서 사용되는 레지스터와 각종 구조체에 대해서 알아 보았다. 세그먼테이션 과정을 전체적으로 정리해 보자.


우리가 흔히 알고 있는 세그먼트 레지스터, 정식 명칭은 세그먼트 셀렉터Segment Selector 로서 어느 세그먼트를 선택할 지를 나타낸다.

Index 는 GDT (or LDT) 의 몇번째 세그먼트 디스크립터를 선택할지를 나타내며, TI 비트를 통해서 우리가 원하는 세그먼트 디스크립터가 GDT에 존재하는지, LDT 에 존재하는지를 결정한다.


GDTR 로 구한 GDT의 베이스 어드레스로부터, 세그먼트 셀렉트의 인덱스 X 8 만큼의 주소를 더해서 원하는 세그먼트 디스크립터에 접근 해서 세그먼트의 정보를 나타내는 디스크립터를 얻는다.

그리고 세그먼트 디스크립터로부터 세그먼트의 베이스 어드레스와 기타 정보들을 얻는다. 여기서 얻은 세그먼트의 베이스 주소를 통해 세그먼트에 접근하고, 여기에 우리가 가지고 있는 주소, 즉 논리 주소를 더해 선형 주소를 얻는다.



이것이 바로 세그먼테이션이다.

윈도우에서는 이렇게 얻어진 선형주소를 페이징을 통해 다시 실제 물리 주소를 변환하며, 만약 해당 운영체제가 페이징 기능을 사용하지 않는다면 이 선형주소는 실제 물리주소가 될 수 있다.

Posted by cyj4369
,


위 그림은 X86 CPU에서 제공하는 세그먼테이션과 페이징 기능을 통해 Logical Address 를 Physical Address 로 바꾸는 과정이다.

이번시간에는 이 부분중 Segmentation 에 대해서 알아보자. 단, 이 문서에서는 세그먼테이션이 무엇인지 설명하지 않으므로 자세한 내용은 아래 문서를 참고하라. 

Intel® 64 and IA-32 Architectures Software Developer's Manual
Combined Volumes 3A and 3B: System Programming Guide, Parts 1 and 2
 




위 그림에서 왼쪽의 세그먼테이션 부분만 잘라내서 확인해 보자.

위 그림에서 볼 수 있듯이 16 Bit 의 세그먼트 셀렉터(Segment Selector)를 이용해 디스크립터 테이블(Descriptor Table)에서 원하는 세그먼트 디스크립터(Segment Descriptor)를 선택하고 여기서 베이스 주소(Base Address)를 구해 유효 주소(Effective Address)를 더함으로써 선형 주소(Linear Address)를 얻을 수 있다.





자, 그럼 세그먼트 셀렉터(Segment Selector)란 무엇인가?


세그먼트 셀렉터는, 세그먼트 레지스터(Segment Register)다. 우리가 흔히 이야기 하는 CS, DS, SS, ES, FS, GS 등이 모두 세그먼트 셀렉터다. 세그먼트 레지스터를 이용해 디스크립터 테이블에서 세그먼트 디스크립터를 선택(Select) 하기에 세그먼트 셀렉터 라고 부르는 것이다. 그리고 이렇게 선택된 디스크립터 테이블이 세그먼트를 가리키는 역할을 한다.

아래는 Intel 에서 제공하는 문서에 나와있는 세그먼트 셀렉터의 정의다.


해석하면, " 세그먼트 셀렉터가 바로 세그먼트를 가리키는 것이 아니라 세그먼트를 정의하는 세그먼트 디스크립터를 가리킨다. "

세그먼트 셀렉터의 구조는 다음과 같다.


셀렉터의 13개 Bit ( 3 - 15 ) 는 테이블을 가리키는 인덱스로 사용된다.

TI Bit 는 이 셀렉터가 가리키는 세그먼트 디스크립터가 전역 디스크립터 테이블(GDT, Global Descriptor Table)에 있는지, 지역 디스크립터 테이블(LDT, Local Descriptor Table)에 있는지를 나타낸다. 0일경우 GDT, 1일경우 LDT이다. GDT 와 LDT는 아래에서 자세히 알아본다.

RPL Bit 는 셀렉터가 가리키는 세그먼트의 권한을 나타낸다. 익히 알고 있듯이 권한 값은 0 - 3까지의 값이며 윈도우의 경우 커널 레벨은 0, 유저레벨은 3의 값을 가진다.

우리는 보통 세그먼트 레지스터를 16Bit 라고 알고 있지만, 사실세그먼트 레지스터는 보이지 않는 부분과 보이는 부분으로 나누어져 있다.

세그먼트 레지스터는 위의 그림에서 보듯 보이는 부분(Visible Part)과 보이지 않는 부분(Hidden Part)으로 나누어져 있다. 보이는 부분을 세그먼트 셀렉터 라 부르며 보이지 않는부분까지 포함해서 세그먼트 레지스터라고 부른다. 세그먼트 레지스터가 변경될때, 즉 세그먼트 셀렉터가 새롭게 로드되면 해당 셀렉터가 가리키는 세그먼트 디스크립터를 참조해서 세그먼트의 기본 주소, 최대 주소, 접근 권한을 보이지않는 부분(Hidden Part)에 로드한다.

세그먼트 셀렉터는 16 Bit 이기에 세그먼트에 대해서 자세한 정보를 포함하고 있지 않다. 단순히 어느 디스크립터 테이블을 선택할지만을 알려준다. 따라서, 세그먼트에 대해서 자세한 정보를 포함하고 있는 구조체가 필요하다.




이것이 바로 세그먼트 디스크립터(Segment Descriptor)다. 

세그먼트 디스크립터에 대한 Intel 의 정의는 아래와 같다.


해석하자면, " 세그먼트 디스크립터는 GDT와 LDT에 존재하며 세그먼트의 위치와, 접근권한, 상태등에 대한 정보를 알려주는 구조체다. "



아래는 세그먼트 디스크럽터의 구조를 나타낸 표이다.



Base Address Field  는 세그먼트의 시작 주소다.

Segment Limit Field 는 세그먼트의 한계 주소다. 그런데, Base Address 는 32 Bit 인 반면에 Limit Field 는 20 Bit이다. 그렇다면 세그먼트의 최대 크기는 1MB란 뜻인가? 물론 당연히 아니다. (Granularity) 비트가 세팅 (1) 되어 있을 경우 단위가 4KB가 되어 최대값은 4GB 가 된다. 반대로 G 비트가 클리어 (0) 되었을 경우 단위는 1Bit가 되어 최대 1MB 의 한계 값을 가진다.

Type Field 는 이 세그먼트가 어떤 종류의 세그먼트로 사용되는지를 나타낸다. 이 필드의 값을 통해서 코드 세그먼트인지, 데이터 세그먼트인지, 각각의 경우 읽고 쓰기가 가능한지등의 값을 나타낸다.

System (Descriptor Type) Flag 가 클리어 (0) 되어있을경우 해당 디스크립터가 가리키는 세그먼트는 시스템 세그먼트로 사용된다. 반대로 세팅(1) 되어 있을 경우 해당 디스크립터가 가리키는 세그먼트는 코드 또는 데이터 세그먼트로 사용된다.

DPL (Descriptor Privilege Level) Field 는 해당 디스크립터로 접근할 수 있는 메모리의 특권 레벨을 나타낸다. 

P (Segment - Present) Flag 가 세팅 (1) 되어 있을 경우 해당 세그먼트는 물리 메모리에 올라와 있어 접근 가능함을 의미하며, 클리어 (0) 되었을 경우 해당 세그먼트 디스크립터가 가리키는 메모리는 접근할 수 없다. 아래 그림은 P 비트가 클리어 되었을 경우 세그먼트 디스크립터의 내용이 어떻게 구성되는지를 나타낸다.


위는 WinDBG 에서 확인한 GDT 디스크립터의 구조체다.




L (64 Bit Code Segment ) Flag 는 IA-32e 모드에서 디스크립터가 가리키는 세그먼트가 64 Bit - Native 코드를 포함하고 있는지를 의미한다. 만약 세팅 (1) 되어 있을 경우 해당 코드 세그먼트는 64 Bit 모드에서 실행되고 있는 것이며, 이 경우 D 비트는 클리어 (0) 되어야 한다. 반대로 코드 세그먼트에서 L 비트가 클리어 (0) 되어 있을 경우 호환 모드에서 돌아간다는 의미이며, 데이터 세그먼트의 경우에는 이 값이 반드시 0 이어야 한다. 

D / B (Default Operation Size / Default Stack Pointer Size and / or Upper Bound ) Flag 비트는 해당 세그먼트 디스크립터가 가리키는 세그먼트가 실행 가능한 코드 세그먼트인지, Expand-Down 데이터 세그먼트인지, 아니면 스택 세그먼트인지냐에 따라서 의미가 달라진다. 자세한 내용은 아래를 참고하라.


G ( Granularity ) Flag 비트는 Limit Field 에 영향을 준다. 위에서 설명 했듯이, 이 비트가 Set ( 1 ) 되어 있으면 Limit Field의 단위는 4KB 가 되고, Clear ( 0 ) 되어 있으면 단위는 Byte 가 된다.


Type Filed 는 시스템(S) 비트 연관되어 해당 세그먼트의 특성을 나타내는 역할을 한다. S 비트가 Clear ( 0 ) 되어 있으면 해당 세그먼트는 시스템 세그먼트로 사용되고, Set ( 1 ) 되어 있으면 해당 세그먼트는 코드 또는 데이터 세그먼트로 사용된다. Type Field 의 최상위 비트는 해당 세그먼트가 데이터 세그먼트인지 ( 0 ) 코드 세그먼트인지 ( 1 ) 나타낸다.


아래는 WinDBG에서 확인한 GDT 디스크립터의 구조이다.


kd> dt _KGDTENTRY -r
nt!_KGDTENTRY
   +0x000 LimitLow         : Uint2B
   +0x002 BaseLow          : Uint2B
   +0x004 HighWord         : __unnamed
      +0x000 Bytes            : __unnamed
         +0x000 BaseMid          : UChar
         +0x001 Flags1           : UChar
         +0x002 Flags2           : UChar
         +0x003 BaseHi           : UChar
      +0x000 Bits             : __unnamed
         +0x000 BaseMid          : Pos 0, 8 Bits
         +0x000 Type             : Pos 8, 5 Bits
         +0x000 Dpl              : Pos 13, 2 Bits
         +0x000 Pres             : Pos 15, 1 Bit
         +0x000 LimitHi          : Pos 16, 4 Bits
         +0x000 Sys              : Pos 20, 1 Bit
         +0x000 Reserved_0       : Pos 21, 1 Bit
         +0x000 Default_Big      : Pos 22, 1 Bit
         +0x000 Granularity      : Pos 23, 1 Bit
         +0x000 BaseHi           : Pos 24, 8 Bits






데이타 세그먼트, Data Segment

해당 세그먼트가 데이터 세그먼트일때. Type Field 의 2, 1, 0 비트는 각각 AccessdWrite - EnableExpension - Direction 을 의미한다. 데이터 세그먼트는 Read - Only, Rea - Write 둘 중 하나의 속성을 가질 수 있다. 

 데이터 세그먼트의 한 종류인 스택 세그먼트 (Stack Segment)는 반드시 Read - Write 속성을 가져야 한다. 그렇지 않을 경우에는 SS 레지스터가 Non - Writable Memory 를 셀렉터로 로드할 경우 General - Protection - Exception (#GP) 를 발생시킨다.

E (Expension - Direction) Flag  클리어 ( 0 ) 되어 있을 경우에는 메모리는 상향 확장  ( Expand - Up ) 되며, 반대로 세팅 ( 1 ) 되어 있을 경우에 메모리는 하향 확장 ( Expand - Down ) 된다.

동적으로 최대 크기가 변할 필요가 있는 스택 세그먼트와 같은 경우에는 E Flag 는 세팅 ( 1 ), Expand - Down  되어야 하며, 만약 최대 크기가 고정되어 있는 스택 세그먼트라면 클리어 ( 0 ), Expand - Up 되어야 한다.

Accessed Bit 는 해당 세그먼트가 Excutive나 운영체제에 의해서 Clear 된 이후로 다시 접근되었는지를 나타낸다. 프로세서는 해당 세그먼트를 셀렉터로 로드 할 때 마다 Accessed Bit를 세팅 한다.







코드 세그먼트, Code Setment



코드 세그먼트는 Type Field 의 최상위 비트가 1이다. 이후의 비트들은 높은 순서대로 2, 1, 0, 각각 AccessedRead - EnableConforming Flag로 해석된다. 코드 세그먼트는 Read - Enable Flag에 따라서 실행 가능하거나, 실행 가능하고 읽기 가능한 세그먼트가 될 수 있다. 

Excutable / Read 세그먼트는 ROM 과 같은 곳에서 정적 변수나 상수가 코드와 같이 존재할때 사용할 수 있다. Protected Mode 에서 코드 세그먼트는Non - Writable 이다.

Accessed 비트는 데이타 세그먼트와 같기에 따로 설명하지 않고 넘어가겠다.

Conforming (대체할 한국어 단어를 찾지 못하겠다.) Flag 는 해당 세그먼트가 낮은 권한을 가진 코드 세그먼트로부터 실행이 전환될 수 있는지를 나타낸다. 만약 실행이 전환될 세그먼트가(대상) Conforming Segment 라면 더 높은 권한을 가졌더라도 Current Previlege Level 에서 실행 가능하며, 대상 세그먼트가 Non - Conforming Segment 라면 Call Gate 나 Task Gate 를 이용하지 않는 한 #GP 를 발생한다.

시스템에서 사용하는 유틸리티 중 접근이 불가능하게 설정 되어 있는 특정한 예외를 위한 핸들러(나눗셈 예외)나 장치같은 경우는  Conforming Segment 에 로드된다. 반면 낮은 권한의 프로시저나 프로그램으로 부터 완벽하게 보호되어야 하는 것들은 Non - Conforming Segment 에 로드된다.

해석하자면, " Call 이나 Jmp 를 통해서 낮은 권한의 (산술적으로 높은) 코드 세그먼트로의 접근은 대상 세그먼트가 Conforming 이건, Non - Conforming 이건 #GP 를 발생시킨다. "


모든 데이터 세그먼트는 Non - Conforming Segment 다. 즉, 낮은 권한의 프로시저나 프로그램은 자신들이 가진 데이터 세그먼트보다 높은 권한을 가진 데이터 세그먼트에 접근할 수 없다. 하지만, 코드 세그먼트와는 다르게 데이터 세그먼트는 높은 권한으로부터 낮은 권한을 가진 세그먼트로의 접근이 가능하다. 

System Descriptor 에 대해서는 2장에서 알아본다.

Posted by cyj4369
,

Linux kernel 을 보고 있는데, 책 마다 base kernel version 이 조금씩 달라서 source code 가 많이 차이가 나더군요.

Understanding Linux Kernel : 2.6.11 version

Professional Linux Kernel Architecture : 2.6.24 version

The Linux Kernel Archives 에서 kernel source code download 할 수 있는데, 2.6.11 이나 2.6.24 와 같이 아주 예전(?) version 의 경우에는 바로 보이지 않아서 
어떻게  download 하는지 몰랐었습니다.

혹시 저같은 분을 위하여 link 적어봅니다. :) 
http://www.kernel.org/pub/linux/kernel/ 에 각 버전별로 압축되서 올라와 있으므로 원하시는 버전 찾으셔서 download 하실 수 있습니다.

http://www.kernel.org/pub/linux/kernel/v2.6/

2.6.11 source (Understanding Linux Kernel base code)

2.6.24 source (Professional Linux Kernel Architecture base code)

 

http://www.kernel.org/pub/linux/kernel/ // 2.6 이전 버전도 directory 선택하여 download 가능.

'Embedded Lab > linux, x86' 카테고리의 다른 글

[Memory Management : Segmentation 2]  (0) 2012.10.08
[Memory Management : Segmentation 1]  (0) 2012.10.08
[우분투 한글 입력기 nabi 설치 및 설정]  (0) 2012.09.25
[JTAG & UART]  (0) 2012.09.13
[Flash Memory(NOR / NAND)]  (0) 2012.09.12
Posted by cyj4369
,

******************************************************************************

1. nabi를 설치한다

$sudo apt-get install nabi

2. 설치가 완료되면 nabi를 기본 입력기로 설정한다.

$sudo im-switch -c

3. nabi를 기본으로 설정한다.

$sudo im-switch -c

4번째 정도에 nabi 설정이 있다. 설정한다.

4. reboot

X윈도우를 사용하고 있다면

ctrl + alt + backspace 를 눌러서 간단히 X윈도우만 재시작이 가능하다.

******************************************************************************



언어설정 변경하기


우측 상단의 '사용자명 - System Settings'를 클릭합니다.


'Language Support'를 클릭합니다.


'Install / Remove Languages...'를 클릭합니다.

'Korean'을 체크하고 'Apply Changes'를 클릭합니다.

'한국어'가 표시되었습니다.


여기서 '한국어'가 표시되지 않는 경우가 생길 수도 있습니다.

이 경우 로그아웃-로그인 후, '한국어'를 삭제한 뒤 재설치 해봅니다.


이제 '한국어'를 드래그하여 최상단에 위치시킵니다.

그 후 'Apply System-Wide'를 클릭합니다.

'Keyboard input method system(키보드 입력 시스템)'을 'nabi'로 바꿔줍니다.


'Regional Formats'탭을 선택합니다.

마찬가지로 '한국어'로 바꾼 후 'Apply System-Wide'를 클릭합니다.


이제 로그아웃 - 로그인합니다.


'Embedded Lab > linux, x86' 카테고리의 다른 글

[Memory Management : Segmentation 1]  (0) 2012.10.08
[리눅스 커널 소스 다운로드]  (0) 2012.10.07
[JTAG & UART]  (0) 2012.09.13
[Flash Memory(NOR / NAND)]  (0) 2012.09.12
[fork에 대한 그림 설명]  (0) 2012.09.05
Posted by cyj4369
,