<System Call의 이해/ 어셈블리어(PC버전)>
■ 기계 명령의 존재를 확인해 보자
■ 프로그램의 실체를 파헤쳐보자
■ 내가 만든 프로그램은 어떻게 실행되는지 확인해 보자.
■ low level 처리가 가능한 high level 엔지니어가 되어 보자.
■ 어셈블리 프로그래밍
-> 8051/AVR 어셈블리 프로그래밍 (8비트/16비트)
-> 임베디드 보드용 ARM 어셈블리 프로그래밍 (32비트)
-> PC용 어셈블리 프로그래밍 (16비트/32비트) : 짧은시간에 가장 간단하고 유용하게 익혀볼 수 있는 환경
<컴파일 & 실행 파일 만드는 법>
1. DOS Box 설치 / MASM 폴더 복사 / 폴더 Mount
2. 윈도우 환경의 Editor로 편집한다.
3. Dos Box에서 Masm sample.asm
4. Link ascii.obj
5. 혹은 3. 4. 과정을 줄여서 ml sample.asm
6. 실행
7. 다음 페이지를 참조
//f:\masm경로에 파일이 있으면 x라는 곳에 마운트 시켜 간단하게 사용가능하다.
//x 디렉토리에 dtype.asm을 저장한다.
//dir은 dtype의 파일 형식을 보여준다.
//cv /s dtype은 디버그 창을 띄워준다.
<개발 과정 설명>
■ 에디터로 source 파일을 작성한다. ex) example.asm
■ DOS 창에서 아래 작업을 수행한다.
1. C:\MASM> masm example;
- 에러가 발생하면 에러 메시지와 라인 번호를 참조하여 에디터로 수정 (Assemble error)
- 에러가 없으면 Object 파일(example.obj) 생성
2. C:\MASM>link example;
- 에러가 발생하면 에러 메시지를 참조하여 에디터로 수정 (Link error)
- 에러가 없으면 실행 파일(example.exe) 생성
3. C:\MASM>example
- 제대로 수행되지 않으면 디버깅 필요 (Run-time error)
4. C:\MASM>debug example.exe
<Register>
■ Register란?
-> Data 저장, 연산 등을 위한 임시 기억 장치
-> 각 register는 자체의 특수한 용도와 제한점이 있다.
-> 장점 : Memory(변수)보다 access 속도가 빠르다.
-> 단점 : 개수가 한정되어 있고, 용도가 제한적이다.
■ 종류
-> 범용 register : 8개
-> 세그먼트 register : 4개
-> 프로세서 컨트롤 register : 2개
<Register의 종류>
//AX,BX,CX,DX는 범용 레지스터, 특별한 용도가 있긴 하다.
//AH 8비트, AL 8비트는 AX 16비트를 반으로 나눈 것이다.
//SP,BP,SI,DI는 위치나 주소를 알려주는데, 즉 인덱스로 사용한다.
//우리가 만든 Code는 CS에 들어간다. Data는 DS에 들어간다. CS DS 둘이 묶여서 사용될 때도 있다.
//SS는 Stack 관리, ES는 여분 공간을 위해 만듬
//IP는 어떤 위치에 명령어를 가르킬지
//FL은 어떤 상태가 어떤 이유를 알려주는지 FLAG를 알려준다.
<Register의 기능>
■ 범용 register : +, -, *, / 등의 사칙 연산과 memory address 지정
■ 연산
-> AX register
- 입출력, 거의 모든 산술연산에 사용
- 곱셈, 나눗셈, 변환(translate) 명령에서 반드시 사용
-> BX register
- 주소 지정을 확대하기 위해 index로 사용
- 일반적인 계산에 사용
-> CX register
- 루프(loop)의 반복 횟수, 쉬프트(shift) 비트 수를 기억
- 일반적인 계산에 사용
-> DX register
- 몇몇 입출력 동작에서 반드시 사용
- 큰 수의 곱셈과 나눗셈에는 DX와 AX를 쌍으로 사용
■ 번지 계산
-> SP, BP register : 스택 조작에 사용
-> SI, DI register : 메모리 번지의 간접 지정에 사용
■ Segment register //전체 메모리를 segment 단위로 구분
-> CPU가 명령을 fetch할 때 - CS:IF로부터 명령을 fetch
//메모리로부터 읽어 오는 동작
-> Data 전송 - DS : offset address
-> Stack 동작 - SS : SP //PUSH, POP
※ Segment들은 서로 중복 가능
-> Code Segment : 프로그램 code 저장
-> Data Segment : 변수, 상수 저장
-> Stack Segment : 돌아갈 주소 등 저장
■ Processor control register
-> IP (Instruction Pointer) : 다음에 실행할 명령어의 번지를 가리키는 register
-> FL (Flag Register)
- CPU의 현재 상태 표시
- 비교, 연산 결과를 보존
<Flag register>
<8086의 주소 지정> //16비트 2개를 가지고, 20비트를 만들어 낸다.
■ 8086 프로세서는 1MB까지의 memory를 취급(00000H ~ FFFFFH)
■ 1MB = 2^20bit = address data로 20bit 필요
■ 8086의 register는 16bit 크기뿐 → 2개의 register를 조합
■ Segment register의 역할
//segment + offset의 조합
■ Segment 배치의 예
<명령의 구성>
■ 일반적인 명령의 구성
-> Label : 명령행이 존재하는 번지를 지정 (의사 명령), 생략 가능
-> OP code (Operation code) : 연산자, 명령의 종류 //여러 개 있을 수도, 없을 수도 있다.
-> Operand : 피연산자, 명령에 따라 0개 ~ 2개, Operand 2에서 Operand 1 방향으로 조작이 이루어짐
-> Comment : 설명, 비실행문
-> 조작의 방향 : 모든 경우에 뒤에서 앞으로 동작이 이루어짐
■ 일반적인 명령의 구성에 대한 부연 설명
-> OP code(연산자)와 Operand(피연산자)는 반드시 필요
-> Label은 대부분의 경우 필요가 없고, 특별한 경우에 붙임
-> Comment는 명령에 대한 간단한 설명을 붙이는 것으로 실제 어셈블하면 기계어 code로는 변환되지 않고, 단지 설명일 뿐임
ex) MOV AX, BX //C언어로는 AX = BX; 와 같이 구현
→ BX register에 들어 있는 값을 AX register로 전송하라는 (MOVe)명령, 결과적으로 AX register와 BX register는 같은 값을 갖게 된다.
ex) ADD AX, BX //C언어로는 AX += BX; 와 같이 구현
→ BX register의 값을 AX register의 값과 더해서 (ADDition) 결과 값을 다시 AX register에 저장하라는 명령
<의사 명령어(Pseudo Code)>
■ 명령어 : 어셈블하면 기계어 코드로 변환되며, CPU에게 데이터 처리를 내리는 명령 ex) MOV, ADD, SUB, ...
■ 의사 명령어 : 어셈블 할 때 기계어 코드로 변환되지 않으며, 어셈블러(컴파일러) 자체에게 작업을 지시하는 명령
ex) ASSUME, SEGMENT, ENDS, DB, Label, ...
<프로그램의 초기 작성>
■ 세그먼트 선언 //세그먼트는 영역을 나눠준다.
-> Code Segment, Data Segment, Stack Segment 중 필요한 Segment를 선언
-> Code Segment는 반드시 선언
■ 세그먼트 선언 형식
<세그먼트 선언>
//CODE SEGMENT는 시작을 알리고 CODE ENDS는 끝을 알려준다.
//DATA SEGMENT가 양이 너무 적으면 CODE SEGMENT 안으로 들어갈 수도 있다.
<ASSUME 의사 명령어>
■ Segment 이름과 Segment register의 대응 관계를 선언
1. Code Segment만 존재(Data 없음) - ASSUME CS:CODE
2. Code Segment와 Data Segment가 분리 존재 - ASSUME CS:CODE, DS:DATA
3. Code Segment 내에 Data 저장 - ASSUME CS:CODE, DS:CODE
//ASSUME은 가정을 하여 컴파일러에게 알려주는 것,//실제 프로그램 할때는 ASSUME ~ 과정을 다시 코드를 써야 한다.
//ENDS는 세그먼트가 끝난거고, END는 프로그램 전체가 끝난 것이다.
<예제 (sample1.asm)>
//CODE SEGMENT 하나만 있다.
//CS는 자동으로 잡히는데, DS는 잡히지 않는다.
//여기부터가 실제 코드
//프로그램을 종료하는 인터럽트 시스템 콜(4CH)
<인텔 8086 명령어 세트 찾기>
■ https://www.gabrielececchetti.it/Teaching/CalcolatoriElettronici/Docs/i8086_instruction_set.pdf
//MOV는 복사한다는 뜻
//imm은 숫자
//MOV imm,imm은 안된다. 어떤 숫자를 어떤 숫자로 복사하지 못한다.
//MOV mem,mem도 안된다. 어떤 메모리 번지의 값을 어떤 메모리 값을 복사할 수 없다. 못하게 한다.
//윗줄은 레지스터를 거쳐야 가능하다.
//명령에 따른 필요한 클락 수
//명령어가 실제로 이렇게 저장된다
//모드에 따라 처리되는게 다르다
<MOV AL, BL>
■ MOV Reg, Reg 100010dw oorrrmmm
■ opcode : 100010 (MOV)
■ d : 1 or 0 (1로 해 보자)
: (from R/M to REG, 즉 BL -> AL)
: 즉, R/M = BL, REG = AL
■ w : 0 (byte)
■ mod : 11 (two register)
■ reg : 000 (REG = AL)
■ r/m : 011 (R/M = BL)
■ 100010 1 0 11 000 011
■ 1000 1010 1100 0011 = 8AC3
<MOV AL, BL (ALT)>
■ MOV Reg, Reg 100010dw oorrrmmm
■ opcode : 100010 (MOV)
■ d : 1 or 0 (0으로 해 보자)
: (from REG to R/M)
: 즉, R/M = AL, REG = BL
■ w : 0 (byte)
■ mod : 11 (two register)
■ reg : 011 (REG = BL)
■ r/m : 000 (R/M = AL)
■ 100010 0 0 11 011 000
■ 1000 1000 1101 1000 = 88D8
<간단한 어셈블리 프로그래밍>
■ Code Segment만 존재하는 예
//위쪽 코드는 그냥 프로그램 부분이다.
//아래쪽 빨간 박스 코드 부분이 시스템 콜 부분이다.
//4C와 INT를 합쳐 인터럽트를 호출하는 시스템 콜이고, 21H는 십진수로 33, 즉 TRAP을 의미한다.
<데이터 정의 의사 명령어>
■ 데이터를 기억 장소(memory)에 할당하기 위해 Data type을 결정
■ 형식 -> Variable_name 지시자 초기값
- ex) SUM DW 0 cf) int sum = 0;
//여러 개 쓰기 가능하다. 예를 들면 SUM DW 0, SUB DW 0에서 SUM SUB가 (시작점) 이름
//초기 값 없으면 ? 써주면 된다.
■ Data type
//D는 Define의 준말
<Data type의 저장 방법>
ex) 문자 출력 프로그램 → dtype.asm으로 저장
//코드, 데이터 세그먼트 각각 1개씩이다.
//ASSUME은 컴파일러에게만 알려주므로, DS:DATA가 제대로 전달되도록 //MOV AX, DATA //MOV DS, AX로 위치를 잘 가르키게 한다. (AX 레지스터를 경유)
//DATA에서 바로 DS를 접근하는 명령어는 없다. DS는 중요한 영역이기 때문이다.
//MOV AH,2 = SubFunc
//INT 21H = Interrupt System Call
//INT 21H 함수의 FUN이 2가 들어갔구나
//Function code는 2번은 아스키한문자를 출력하는 System Call
<주소 지정 방식(Addressing mode)>
■ Operand의 내용이나 장소를 지정하는 방식
(1) 즉치 주소 지정 방식(Immediate addressing)
ex) MOV AX, 1234H //직접 수치 대입
ex) DAT EQU 1234H//EQU는 C언어에서 DEFINE과 같다.
MOV AX, DAT
//CS,DS,ES,SS/IP,FL에 직접 수치 전송 불가능
(2) 직접 방식(Register addressing; Direct addressing)
- Register의 내용을 직접 전송 //값이 Register 내에
ex) MOV AH, DH //8bit
MOV DS, AX //16bit
ex) MOV AX, DH (X) //사이즈가 같아야 함
//Segment register간 전송 불가능
//IP, FL register 사용 불가
//CS register로 전송 불가
※ Register의 크기에 따라 전송할 데이터 크기 결정
ex) MOV DX, [BX] //16비트 전송
MOV DL, [BX] //8비트 전송
(3) 간접 방식(Memory reference addressing; Indirect addressing)
- 전송하는 값이 저장되어 있는 번지를 지정 //값이 메모리 내에
ex) MOV AX, DAT //변수이름 = 데이터가 들어있는 번지
MOV AX, [BX+DI+4] //번지의 내용 - Pointer 개념
*간접 방식에서의 번지 지정법
- 허용되지 않는 조합
(1) 3개 모두 없으면 안된다.
(2) 8bit 숫자만 사용해야 한다. //번지는 16bit
(3) [BP] --> [BP + 0]으로 사용해야 한다. //(3x3x3=27)-3 = 24(기계어 코드 가지 수)
ex) MOV DL, [BX+DI]
ex) MOV AX, [1234H] (X) //직접 memory 번지 지정 불가능 → 다른 register 거쳐서 ??16비트 숫자 안됨??
→ BP 포함 : 데이터는 stack segment 내에
→ 그 외 : 데이터는 data segment 내에
//EQU는 C언어에서 DEFINE과 같다.
//OFFSET BUFFER는 BUFFER라는 놈의 어떤 SEGMENT안에 상대적인 주소를 나타내라는 말
//OFFSET은 'example'에서 'e'를 가리킨다. [BX+SI]는 ‘a’를 가리킨다.
//CR, LF쪽 코드는 줄띄움 기능을 한다.
■ Memory로 숫자를 move할 때 크기 지정 필요
ex) MOV [BX], 12H
(1)Byte 데이터 (2)Word 데이터
//MOV DX,12H ;0012H
//MOV DX,1234H
ex) MOV BX, OFFSET DATA1
MOV BYTE PTR [BX], 12H
MOV WORD PTR[BX], 3456H
DATA1 DB ?
<역워드 형식(참고)>
■ 16bit register 상의 데이터를 memory에 전송할 때 상하위 바이트가 바뀐다.
- 저장할 때 바꿔서 저장하고, 읽어올 때 바꿔서 읽어오기 때문에 관계는 없음
ex) DATA SEGMENT
SCR_1 DB 'A'
SCR_2 DW 4243H
DATA ENDS
//앞에 43이 먼저 저장되고 42가 그다음 저장된다.
<덧셈 명령>
■ ADD(ADDition)
-> 형식 : ADD OP1, OP2 //Size 같아야
reg imm
reg reg
reg mem
mem imm
mem reg
-> 기능 : OP1 ← OP1 + OP2 //Memory끼리는 안됨
mem mem(x)
■ ADC(ADdition with Carry)
-> 기능 : OP1 ← OP1 + OP2 + Carry Flag //앞 연산 결과를 반영
//NC : +0
//CY : +1
//Carry Flag가 0인 경우 : ADC =ADD
<어셈블리 프로그램 예제 실행>
1. 현재 나의 masm파일 위치로 용이한 접근을 위해 경로를 x에 마운트 시킨다.
2. 예제 1번을 C:\masm경로에 저장 → ml 1dtype.asm → 1dtype 실행
'운영체제' 카테고리의 다른 글
운영체제)#12 Process Synchronization (0) | 2020.06.01 |
---|---|
운영체제)#11 System Call의 이해 : 어셈블리어 (PC버전) (2) (0) | 2020.05.28 |