위 그림은 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
,