본문 바로가기
OS

[32bit] 부트섹터에서 프로그램 꺼내기(1)

by 올리고당 2021. 7. 27.

목차

 


코드를 이해하기 위한 레지스터

n-bit 레지스터란, n자리의 이진수를 저장할 수 있는 하드웨어입니다. CPU에 내장되어 있습니다.

포인터 레지스터

ip : 다음에 실행할 명령어를 가리키는 포인터 레지스터 ( instruction pointer ). 바로 다음 주소의 명령어를 가리키고 있습니다.

 

범용 레지스터

 

ax : 주 누산기(primary accumulator)인 ax는 주로 입출력과 대부분의 산술 연산에 쓰입니다. eax(extended ax)의 하위 16비트. ax의 상위 8비트를 ah, 하위 8비트를 al 이라고도 합니다.

 

cx : 카운트 레지스터. 루프 반복의 횟수 혹은 쉬프트 연산 횟수를 저장하는 데에 쓰입니다. ecx(extended cx)의 하위 16비트. cx의 상위 8비트를 ch, 하위 8비트를 cl 이라고도 합니다.

 

세그먼트 레지스터

세그먼트 예시

 

세그먼트는 메모리에 영역을 지정해 용도를 분류하기 위해 등장한 개념입니다. 코드 세그먼트와 데이터 세그먼트로 나뉩니다. 데이터 세그먼트 중 스택으로 쓰이는 데이터 세그먼트를 스택 세그먼트라고 합니다.

 

cs : 코드 세그먼트 레지스터. 코드 세그먼트의 시작 주소를 가리킵니다.

 

ds : 데이터 세그먼트 레지스터. 데이터 세그먼트의 시작 주소를 가리킵니다.

 

es : 데이터 세그먼트 레지스터. 데이터 세그먼트의 시작 주소를 가리킵니다.

 

ss : 스택 세그먼트 레지스터. 스택 세그먼트의 시작 주소를 가리킵니다.

 

인덱스 레지스터

di : es와 함께 문자(String) 조작 연산 시 사용됩니다. 주소를 가리키는 오프셋(offset)으로 쓰입니다.

 


코드를 이해하기 위한 어셈블리어

어셈블리어 기본 구조

어셈블리어는 기본적으로 다음과 같은 구조를 가지고 있습니다.

 

레이블( Label ) : 레이블의 바로 다음 명령어의 메모리 주소를 쉽게 사용하기 위한 장치입니다. 이 장치가 없다면, 해당 명령어에 접근하기 위해 사람이 직접 명령어의 개수와 바이트 수를 계산해서 입력해주어야 합니다. 레이블을 오퍼랜드( Operand )로 사용하면 어셈블러에 의해 레이블 바로 다음에 위치한 명령어의 주소 값으로 치환됩니다. 레이블은 모든 어셈블리 코드에 존재하는 것이 아니라, 프로그래머가 필요한 부분에만 작성합니다.

 

오피 코드( Opcode ) : CPU가 어떤 동작을 해야 하는지 결정하는 명령어 부분입니다. 모든 어셈블리 코드에 반드시 존재해야 합니다.

 

오퍼랜드( Operand ) : 명령어의 입력값에 해당하는 부분입니다. 같은 오피 코드의 명령어도 오퍼랜드에 따라 다른 결과를 산출합니다. 오퍼랜드는 오피 코드에 따라 요구하는 개수가 달라집니다.

 

주석( Comment ) : 어셈블리 코드를 부가 설명하는 부분입니다. 어셈블리어는 CPU의 연산 과정을 매우 구체적으로 서술하는 언어이기 때문에, 사람이 해당 명령어가 무엇을 의미하고 있는지 파악하기가 상당히 어렵습니다. 그러므로, 주석을 이용하여 코드를 이해하기 쉽게 설명하는 것이 중요합니다. 

 

지시어

지시어는 CPU에게 직접 명령을 내리는 코드가 아닌, 어셈블러에게 프로그램의 상태를 알려주는 어셈블리 코드입니다. 어셈블러에 의해 기계어로 번역되지 않고, 지시어에 따라 어셈블러가 특정 행동을 합니다.

 

[org n] : 어셈블러에게 '이 코드는 n번지에서부터 시작할 거야'라고 알려주는 지시어입니다.

 

[bits 16] : 16비트로 동작하는 프로그램으로 만들라고 어셈블러에게 알려주는 지시어입니다.

 

[bits 32] : 32비트로 동작하는 프로그램으로 만들라고 어셈블러에게 알려주는 지시어입니다.

 

db 오퍼랜드 : 오퍼랜드를 해당 위치의 메모리에 바이트( byte ) 단위로 저장하는 지시어. 명령어와 동일하게 레이블을 지정할 수 있습니다. 다만 형식이 조금 다릅니다.

 

레이블을 msgBack이라 지정하고, 바이트 단위로 0x12, 0x34를 저장하려면, 다음과 같이 작성하면 됩니다.

 

dw 오퍼랜드 : db와 같은 기능을 합니다. 다만, 워드( word, 2바이트) 단위로 오퍼랜드를 기록합니다.

 

times n '무언가' : '무언가'를 n번 반복시키는 지시어입니다. 

 

명령어

명령어는 CPU에게 직접적으로 '어떻게 행동해라'라는 명령을 내리는 어셈블리 코드입니다. 어셈블러에 의해 기계어로 번역됩니다.

 

mov 오퍼랜드 1 오퍼랜드 2 : 오퍼랜드 2를 오퍼랜드 1로 대입하는 명령어입니다.

 

add 오퍼랜드1 오퍼랜드 2: 오퍼랜드 1과 오퍼랜드 2를 더하는 명령어입니다. 결괏값은 오퍼랜드 1에 저장됩니다.

 

inc 오퍼랜드 : 오퍼랜드를 1 증가시키는 명령어입니다.

 

dec 오퍼랜드 : 오퍼랜드를 1 감소시키는 명령어입니다.

 

jmp 오퍼랜드 : 오퍼랜드로 프로그램의 실행 흐름을 이동시키는 명령어입니다. (= ip 값을 오퍼랜드로 바꿔라.)

 

jcc : 조건( EFLAGS 레지스터 )에 따라 연산 결과가 달라지는 점프 명령어입니다.

 

 

명령어의 결괏값에 따라 EFLAGS 레지스터의 각 비트 값이 달라집니다. 자주 쓰이는 플래그만 살펴보면,

 

ZF : 명령어의 결괏값이 0이면 1로 변경되고, 0이 아니면 0으로 변경됩니다.

 

jcc ex) jnz 오퍼랜드 : ZF가 0이 아니면 오퍼랜드로 프로그램의 실행 흐름을 이동시키는 명령어. 이전 명령어의 결괏값이 0이 아니면 오퍼랜드로 프로그램의 흐름을 이동시키고 0이면 프로그램의 실행 흐름을 이동시키지 않고 다음 명령어를 실행합니다.( jump not zero )

 

주소 및 숫자

52 : 10진수 52.

 

52h : 16진수 52. 맨 앞 숫자가 a~f 라면 컴파일 에러가 발생합니다. 이 경우엔 아래 방식을 사용해주세요.

 

0x52 : 16진수 52.

 

effecfive address : 메모리의 주소값을 대괄호 [ ] 사이에 넣어 메모리를 참조하여 오퍼랜드로 사용하는 것을 말합니다. 대괄호 사이에는 값뿐만 아니라 계산식도 들어갈 수 있습니다.

 

[0x52] : 메모리의 52번지에 있는 값.

 

[bx+cx] : 메모리의 bx와 cx의 합에 해당하는 값의 번지에 있는 값.

 

byte [0x52] : 52번지부터 1 바이트에 해당하는 값.

 

word [0x52] : 52번지부터 1 워드(52, 53 번지)에 해당하는 값. 1 워드는 2 바이트입니다.

 

세그먼트:오프셋 = 세그먼트*16 + 오프셋 = 메모리 물리 주소

ex) 0x07c0 : 0x52 = 0x07c0 * 16 + 0x52 = 0x7c00 + 0x52 = 0x7c52

      es : di = es * 16 + di

 

심볼

$ : 해당 명령어를 실행할 때, 자기 자신의 주소

 

$$ : 현재 세그먼트의 시작 주소.

 


어셈블리 튜토리얼

https://www.tutorialspoint.com/assembly_programming/assembly_variables.htm

 

레지스터 참고

https://karfn84.tistory.com/entry/%EC%96%B4%EC%85%88%EB%B8%94%EB%A6%AC-%EB%A0%88%EC%A7%80%EC%8A%A4%ED%84%B0%EC%9D%98-%EA%B8%B0%EB%8A%A5

 

어셈블리에서 $$의 의미

https://stackoverflow.com/questions/14928741/whats-the-real-meaning-of-in-nasm

 

세그먼트 예시 사진 출처

https://upload.wikimedia.org/wikipedia/commons/d/db/Overlapping_realmode_segments.svg

 

effective address

http://www.tortall.net/projects/yasm/manual/html/nasm-effaddr.html

 


이번 글에서는 다음 글에서 작성할 코드를 이해하기 위해 꼭 알아야 할 것들에 대해 알아보았습니다.

제가 최대한 간결하면서, 자세히 설명드리려고 노력했는데, 잘했나 모르겠네요 ㅜ.ㅜ

다음 글에서는 간단한 어셈블리 프로그램을 작성해서, 부트스트랩 과정을 직접 해보겠습니다.

 

감사합니다.