부트로더에 대한 간단한 정리

2018. 6. 21. 01:580x04 pwnable/Linux Kernel Basic Study

728x90

좀 전까지 공부한 내용을 이해한 후 복습하는 시간을 가졌다. 


여담으로 인터럽트 13(INT13)이 많은 기능을 담고 있다는 거에 충격을 받았다.


아직까지 INT13은 이해를 하지 못하였다. 시간이 날 때마다 들여다 보기 위해 링크를 달아둔다.

--> https://en.wikipedia.org/wiki/INT_13H


Q : RAM에 대해 알고 있는가?

RAM은 기본 메모리이면서, 전원이 켜지면 임의의 주소를 읽고 쓸 수 있는 녀석이다. 

전원이 꺼지면 내용이 다 사라지기 때문에 '휘발성'이라고 불린다.


컴퓨터가 제일 처음 전원이 들어오는 순간에는 RAM에 어떠한 소프트웨어도 담겨 있지 않다. 

아울러 CPU는 RAM에 담겨 있는 프로그램만 동작 시킬 수 있다. 

그렇다면, 의문점이 생긴다. 

Q : OS는 누가 실행시키는가? 

A : BIOS 


그래 BIOS가 OS를 로드 시킨다 이거 이해 되었어. 


그러면, 또 다른 의문점이 생긴다.

Q : BIOS는 어떻게 실행이 되는데?



BIOS가 실행되는 원리에 대해 알아보았다. 


기억해두어야 하는 것이 있다.

BIOS를 실행 시키는 것은 0xFFFF0 이다. 즉 0xFFFF0은 BIOS의 실제 주소이다.


세그먼트 레지스터 중 CS, 범용 레지스터 중 IP를 사용하여 BIOS를 실행 시킬 수 있다. 


좀 더 명확하게 말하면, CPU에서 IP(Instruction Pointer)는 CS(Code Segment)에서 실행 될 명령어 오프셋을 저장하고 있다. 


책에서 공부하기로 CS는 0xFFFF, IP는 0x0000이었다. 하지만, 여기서 의문점이 생겼었다.

두 개의 값을 더하면, 0xFFFF가 되어야 하는데 BIOS 실제 주소는 0xFFFF0이었던 것이다. 


이 부분을 이해 하기 위해 조사를 해보았다. 

그 후, 아래의 그림을 통해 왜 0xFFFF0이 되는지 알게 되었다.




그리고 알게 된 지식이 있다. 

IP는 16비트 리얼 모드에서 사용되는 녀석이며, EIP는 32비트 보호 모드에서 사용되는 녀석이라는 것을...16비트와 32비트는 알았는데 리얼 모드, 보호 모드로 나뉘어지는 것을 미처 몰랐다. 이제 잊어 먹지 않아야겠다.


위 사진의 과정은 "하드웨어"에 의해 가능해진다.

만약, 0xFFFF0에 실행할 코드가 없다면, 컴퓨터는 아무런 동작을 할 수 없다.


BIOS의 특징 중에 "인터럽트 벡터 테이블과 인터럽트 서비스 루틴을 메모리에 로드"하는 기능도 있었다. 


BIOS는 메모리의 처음 1KB 영역(0x00000 ~ 0x003FF)에 인터럽트 벡터 테이블을 위치 시킨다.

BIOS 데이터는 그 다음 256바이트 영역(0x00400 ~ 0x004FF)에 위치한다.

그리고 인터럽트 서비스 루틴은 56KB 이후인 0x0E05B에 로드되게 된다. 


책에서 좋은 그림을 가져왔다. 



다음은 책에 나와있지 않아 직접 공부한 내용이다. 

위키백과로 공부하였다.

인터럽트 벡터 테이블 (Interrupt Vector Table) 

인터럽트 요청과 함께 인터럽트 핸들러와 관련 된 인터럽트 벡터들의 테이블이다. 


인터럽트 서비스 루틴의 시작주소를 찾는 3가지의 방법이 기술되어 있었다.

1. Predefined  : PC(Program Counter)에 엔트리 주소를 직접 채움 


2. fetch : PC를 간접적으로 채움 => 테이블에서 주소를 꺼내기 위해 인터럽트 벡터 테이블 내의 몇몇 엔트리의 주소를 이용. 그 이후, PC를 이 주소로 채움


3. interrupt acknowledge : 외부 장치가 CPU에게 인터럽트 핸들러 번호의 주소를 알림 


우리가 농구 할 때 패스를 중간에서 가로채는것을 인터셉트라고 한다.

인터럽트도 비슷한 개념이라고 생각하면 된다. 

현재 프로세스가 사용하고 있는 자원을 다른 녀석이 가로채는 것이다.

하지만, 인터셉트는 가로채고 다시 자신 스스로 상대팀에게 공을 돌려주진 않지만,

인터럽트는 자원을 가로채고 하고 싶은 일을 다 수행하면 다시 원래의 프로세스에게 자원을 양도하는 특징이 있다. 


인터럽트는 착한건지 나쁜건지 애매모호하다. 


이때 자원을 가로채는 행위를 "외부에서 이벤트가 발생했다"라고 표현할 수 있다.


예전에 운영체제를 배울 때 인터럽트가 나오면 단골 손님으로 따라오는 아이가 있었다.

그것은 "Polling" & "Trap" 이다.


이 두 녀석도 언급하기 위해 상황극을 만들어 보았다.


인터럽트 상황 

<식당>


손님 : "벨 누르면 주문하러 오겠지"

손님 : (벨을 누른다)


식당 주인 :  (벨 소리를 듣는다) "벨이 울렸네 하던 일 멈추고 손님에게 가야겠구나"


(손님이 주문을 다 했다.) 


식당 주인 : "주문 받았으니 다시 이전에 하던 일 진행할 수 있겠다"


폴링 상황

<병원>


의사 : 곧 있으면 회진 할 시간이구나 


(의사가 환자들에게 직접 찾아간다. )


의사 : 환자 분 이상없나요? 


환자 : 네 지금은 이상 없습니다


의사 : 네 ~ 그러면 2시간 뒤에 다시 올게요. ( 정해져 있는 시간이 있음 )


환자 : 어 그런데 갑자기 증상이 악화되면 제가 불러도 되나요? (인터럽트 가능 여부 파악) 


의사 : 그래도 됩니다.



트랩 상황

<컴퓨터>


사용자 : 숫자 공부 해야지. 음 이 숫자랑 0을 나누어 볼까?


CPU : 엇.. 0으로 나누는 행위는 하면 안되!! (divide by zero)

이건 잘못된 접근이야 강제로 행위를 종료 시켜야 겠어 



요약하면, 인터럽트는 능동적으로 프로세스가 CPU에게 상태를 보고하는 것, 폴링은 CPU가 프로세스에게 상태를 물어보는 것, 트랩은 CPU 스스로 자신의 상태를 파악하는 것이 될 수 있겠다.


구글링을 조금 해보니 Linux에서는 Interrupt와 Trap을 나누는 조건이 나와 있었는데 

하드웨어 혹은 외부 이벤트로서의 접근은 인터럽트, 소프트웨어로서의 접근은 트랩으로 분류 된다고 한다. 


학교 서적에서는 트랩과 인터럽트를 구분해두었는데  트랩과 인터럽트를 굳이 구분지을 필요는 없을 것 같기도 하다. 둘 다 문맥 저장 -> 핸들러 수행 -> 문맥 복원이라는 로직을 사용하니까.