본문 바로가기
CSE/system programing

[시스템 프로그래밍 4강] 어셈블리 언어 문법 및 구성요소

by 뜨거운 개발자 2023. 3. 30.

예제

1. 숫자 2개를 더하는 프로그램

main PROC ; main 프로그램의 시작점
	mov eax,5 ; cpu안에 있는 eax 레지스터 안에 숫자 5 배치
	add eax ,6 ; 숫자 6과 eax를 더해서 11이라는 새로운 값 젝ㅇ
	
	INVOKE ExitProcess ,0 ; INVOKE 는 함수호출의 개념. 
;운영체제에서 제공(윈도우 서비스)하는 함수인데, 프로그램 종료할 때 필요.
;인자로 0을 줬다는 의미이다.
main ENDP ;주 절차의 종료 마커이다.

숫자 5와 6을 더해서 eax에 저장하는 프로그램

2. 변수 저장하기

.data  ; data segment 라는 의미
sum DWORD 0 ; sum이라는 변수에 0이 들어갔다. 4바이트 공간을 sum이라 부르고 0이 들어감
;sum은 주소 즉 오프셋이다.(메모리의 시작부분에서 얼마나 떨어져있는가라는 의미
;DWORD는 단순히 크기만 4로 이야기하고 있다.

.code;코드 세크먼트
main PROC
	mov eax,5 ; cpu안에 있는 eax 레지스터 안에 숫자 5를 넣어라
	add eax ,6 ; 숫자 6과 eax를 더해라
	mov sum, eax ; EAX레지스터에 11이 있었는데 이걸 다시 sum위치로 보냈다.
	INVOKE ExitProcess ,0 ; INVOKE 는 함수호출의 개념. 
;운영체제에서 제공하는 함수인데, 프로그램 종료할 때 필요.
;인자로 0을 줬다는 의미이다.
main ENDP
  • 오프셋과 주소와 혼용해서 사용한다.
  • 우리가 변수를 쓰면 실제로 컴파일러는 cpu에게 주소를 전달 할 뿐이다.

과거 16비트 세그먼트는 층수와 오프셋이었고,

여기서 나오는 세그먼트는 용도에 맞게 3가지로 나누어져 있다.

메모리 어딘가에 이런 명령어들이 있었는데 그걸 가져오거나 다시 메모리에 값을 보내줄 때 시간이 오래걸린다.

어셈블리 문법

1. 정수로 된 리터럴 (Integer Literals)

정수 리터럴은 선택적 선행 기호, 하나 이상의 자릿수, 숫자의 기수를 나타내는 기수문자로 구성된다.

기본형 : [ { + | - } ] digit [ radix ]

[ ] 는 옵션을 의미

예시 : 26, 26h, 1101, 1101b : 비슷해보여도 뒤에 오는 거에 따라서 다르다.

16진수가 알파벳으로 시작하지 않다면 0을 붙혀준다.

0A3h : 이래야만 16진수로 해석 할 수 있다

2. 상수 정수 표현 (Constant Integer)

  • 정수 리터럴과 산술 연산자를 포함하는 수학 표현식
  • 각 표현식은 정수여야하고 32비트에 저장할 수 있다.
  • 그것들은 어셈쁠리 타임에 정해진다.
  • 우리 코드에서 산술연산자를 사용하면 컴파일 타임에 이미 어셈블러가 그 값을 계산해서 넣어버린다.
  • 산술 연산자

3. 실수 리터럴 Real Number literal (주로 플로팅 포인트)

주로 10진수로 표시하거나 16진수로 표시한다.

decimal reals or encoded (hexadecimal) reals

Decimal real (일반적 소수점 표현)

  • 표현법 : [sign] integer.[integer][exponent]
  • 최소 한자리와 소수점이 필요하다.

E가 들어가 있으면 floating point로 처리를 한다.

어떻게 E가 들어가 있으면 floating point 로 처리 하냐면 flaoting 포인터가 컴퓨터에서 처리할 때 지수부 소수부를 나누기 떄문이다.

플로팅 포인트(리얼넘버)의 내부적 처리방식

개념상 유효숫자와 지수부를 다르게 저장하는것이 개념

  • 1개의 부호비트(MSB), 8개의 지수부, 23개의 유효숫자 비트
  • 실제로 저장 방식은 지수부에 +127을 더하게 되고 유효숫자는 1을 빼고 들어간다.
  • 따라서 32비트 float은 불정확하니까 왠만하면 사용하지 말자. 대신에 double 사용 권장 - 64비트

Encoded real

  • 4개씩 끊어서 읽으면 그것이 Encoded real 이다.
  • 41820000r 방식으로 인코딩이 된다. ( 컴퓨터한테 - 다만 잘 쓰지는 않고 이런 방법이 있다는 것만 알자.)
  • 짧은 실수에 IEEE 부동 소수점 혁식을 사용하여 16진수로 실수를 나타낸다.
  • 이런게 있구나 정도만 알자.

4. 문자 리터럴 / 문자열 리터럴

문자 리터럴 :작은 따옴표 또는 큰 따옴표로 묶인 단일 문자이다.

  • 어셈브러는 이 값을 문자의 바이너리 아스키코드로 메모리에 저장한다. 예를 들어 A의 경우 65 또는 hex 41이 온다.

문자열 리터럴 : 정수바이트 값으로 순서대로 쭉 저장이 된다. (공백 포함)

5. 예약어(Reserved Words)

  • 특별한 의미가 있으며 올바른 문맥에서만 사용할 수 있다.
  • 어셈블리어는 대소문자를 구분하지 않는다.

예약어 종류

  • MOV, ADD, MUL등 명령 니모닉
  • 레지스터이름 사용 불가
  • 어셈블리 함수나 변수 상수이름 등 미리 지정해둔 이름 (BYTE, WORD)
  • 상수 표현식에 사용되는 연산자.
  • 어셈블리시 상수 정수 값을 반환하는 @data 같은 이름

6. 식별자(Identifiers) (변수)

  • 프로그래머가 선택한 이름

형성 규칙 (안 중요)

  • 1부터 247까지 문자를 포함할 수 있다.
  • 대소문자 구분 x
  • 첫번째 문자는 문자, 밑줄(_), @ , ? $ 여야 한다.
    • 첫문자 숫자 불가능
  • 예약어와 동일할 수 없다.

7. 지시어 (Directives)

  • 어셈블러가 읽고 사용한다. 즉 어셈블러에 의해 실행된다. 따라서 런타임에 실행되는 게 아니다.
  • 주로 변수나 매크로 프로시저를 디파인 하는데 사용
  • 메모리 세그먼트에 이름을 할당하는데 여기서 세그먼트는 그냥 블록이라고 생각하면 된다.

지시어와 명령문의 차이점을 보여주는 예시

myVar DWORD 26
mov eax,myVar 
  • myVar DWORD 26 : 이건 CPU가 사용하지 않고 CPU가 하는 일이 아니다.
    • 이 녀석은 이미 프로그램이 만들어 질때 이미 크기를 잡아뒀기 때문에 CPU는 이 과정에서 한 일이 없다. 따라서 변수를 만드는 것은 어셈블러의 역활인 것이다.
    • DWORD 지시어는 어셈블러에게 공간을 예약하도록 한다. (이건 CPU가 하는일은 아니다.)
  • mov eax,myVar : 이건 Cpu가 해석해서 일한다.
    • MOV 명령은 런타임에 실행되서 myVar의 내용을 EAX레지스터에 복사했다.
  • 모든 어셈블러는 명령어 (instruction set)는 동일하지만 지시어는 공유하지 않는다. (set of directives)

명령어는 거의 비슷하지만 Directive는 명령어가 많이 달라질 수 있다.(문법을 공유하지 않기 때문에)

세그먼트 정의 - 이것도 지시어이다.

The .DATA directive identifies a segment which can be used to define variables – The .CODE directive identifies the area of a program containing executable instructions – The .STACK directive identifies the area of a program holding the runtime stack, setting its size: » .stack 100h

8. 명령어 Instructions

  • 프로그램이 어셈블될 때 실행 가능하게 되는 명령문
  • 기계어로 번역되어야 하는 부분이다.
  • 어셈블러에 의해 기계어 바이트로 변환되어 런타임에 cpu에 의해서 로드 및 실행된다.
  • 명령어는 4가지 기본부분으로 구성된다.
    • Label (선택)
    • Instruction mnemonic ( 필수)
    • Operand (피연산자) (보통 필수)
    • 주석(;)

8 -1 . label

  • 명령어나 데이터에 대해서 이름을 붙히는 식별자.
  • 레이블에는 2가지 종류가 있다. 데이터 레이블과 코드 레이블
    • 인스트럭션 바로 앞에 배치된 레이블은 인스트럭션의 주소를 의미한다.
    • 변수 바로 앞에 배치 된 레이블은 변수의 주소를 의미한다.

A. 데이터 레이블

data label 은 사실상 변수 명과 같다.

값이 하나당 하나의 이름만 붙힐 수도 있지만 하나의 변수에 여러가지 값을 넣을 수도 있다.

변수의 이름표는 주소기 때문에 4바이트 떨어져있는 위치에 접근하면 접근 할 수있어서 이름이 가장 앞 값에만 붙어도 되는 것이다. (배열의 예시)

B. 코드 레이블

  • 프로그램의 코드 영역의 레이블은 콜론(:) 문자로 끝나야 한다.
  • 코드 레이블은 점프 및 반복 명령의 대상으로 사용된다.
  • 특정 명령어 앞에 이름표를 붙혀주는것
  • JMP 명령은 레이블 대상의 위치로 제어를 전송해서 루프를 생성한다.
  • 코드 레이블은 인스틀럭션과 같은 줄을 공유하거나 단독으로 한 줄에 있을 수 있다.
  • 레이블의 이름 규칙은 식별자와 동일한 규칙을 따른다.
  • 레이블은 각각 고유하다면 레이블 명이 같을 수도 있다.

즉 레이블은 주소를 의미한다고 보면 된다. 코드건 데이터거나..!

8 -2 명령어 니모닉

  • 명령을 식별하는 짧은 단어.
  • mov, add, sub와 같은 어셈블리 언어 명령어 니모닉은 해당 명령어가 수행하는 연산 유형에 대한 힌트를 제공합니다

8 -3 피연산자 (Operand)

  • 명령의 입력 또는 출력에 사용되는 값이다.
  • 4가지 종류가 있다. 어셈블리어는 피연산자를 (0~3개를 가질 수 있는 것이다.)
    • 레지스터 ,메모리 피연산자, 정수 표현식, 입출력 포트
    • 메모리 피연산자를 만드는 방법에는 변수이름, 괄호로 둘러싸인 레지스터 사용 등 방법이 있다.
    • 변수 이름은 벼수의 주소를 의미하며, 컴퓨터가 주어진 주소의 메모리 내용을 참조하도록 지시한다.
  • 예시
  • 일반적으로 피연산자가 여러개면 뒤가 소스 피연산자 앞에께 대상 피연산자.

8 -4 Comment (주석)

프로그램 제일 위에 정보들을 써주면 좋다정도 알고만 있자..

; 이건 한줄 주석이다.

COMMENT ! 
	이렇게 코멘트 가능
!
COMMENT &
	막써라
&

8 - 5 NOP (No Operation) 명령어

  • 가장 안전하지만 가장 쓸모없는 명령어이다.
  • 1바이트의 저장공간을 차지하지만 아무런 작업도 수행하지 않는다.
  • 컴퓨터는 명령어마다 길이가 다르기 때문에 사용
  • 컴파일러와 어셈블러 코드를 효율적으로 주소경계에 맞추기 위해 사용된다.
  • MOV명령은 3바이트 모신코드 바이트를 만든다
  • NOP 명령은 세번째 명령을 4의 배수의 경계인D doubleword boundary에 정렬한다.

위의 예시에서는 명령어가 3바이트 2바이트로 나뉘어져 있다.

명령어를 CPU는 4byte개씩 끊어서 가져온다.

이렇게 가져올 때 명령어가 적재되어있는 메모리를 4의 배수만큼 늘려서 4바이트 단위로 메모리에 접근을 하는데..! 그러면 명령어에 해당하지 않는 다음 명령을 침범한다.

이것을 막기위해 1바이트씩 더 넣어주는 것이다.접근을 한다.

c에서 바이트 패딩 예시

C나 C++에서 구조체를 만들 때, int char int 이런식으로 데이터를 선언한다.

그러면 우리는 4바이트 인트 캐릭터 1 , int 4 이렇게 넣는다.

컴파일러는 똑똑해서 char 1바이트를 3바이트 패딩을 넣어준다.

왜냐면 컴퓨터는 4바이트 단위로 메모리를 관리하기 떄문!!

X86 프로세서의 똑똑한 특징이다.

완성된 프로그램 예시

1. .386 의미 :

이 프로그램에서 80386 명령어를 쓰겠다는 뜻이다.

그중에서 우리는 80386을 쓸 생각입니다. 여러개를 지원하기 때문

.Modle 지시문

2. . model flat stdcall

우리 프로그램이 사용할 메모리 모델을 선택한 것이다. ( flat한 모델을 쓰겠다) 만약 우리가 프로시저(펑션) 을 호출하면 인자를 전달할텐데 그 인자를 어떻게 처리할지를 이야기 할지를 말하는 것이다. (이건 인자를 스택에 넣어서 전달한다는 뜻)

  • 어셈블러에 어떤 메모리 모델을 사용할지 알려준다.
    • 과거에 세그먼트와 오프셋 개념으로 있어서 특성 세그먼트에 특정 오프셋으로 접근하는 식으로 관리를 했었다.
    • 현대에서는 이제 그렇게 나눌 필요가 없어져서 flat 모델을 사용하는 것이다.
    • stdcall이라는 녀석은 프로시저의 호출을 할 때 인자를 넘겨줄 수 있다.
    • 어셈블리어에서는 프로시저에게 어떤 인자를 전달하고 싶으면 stack에 역순으로 집어넣는다.

.STACK 지시어(.Stack 4096)

메모리 상에서 스택의 크기를 4096으로 디파인 하겠다는 의미인데

4096이라는게 왜 나왔는지 말하자면(이 수업에서는 안 중요하지만 운영체제에서는 중요한 개녕)

과거에는 물리 주소에 바로 접근을 했었다. 이제 보호모드가 되면서 구조가 약간 바뀌게 된다.

물리주소와 가상메모리로 나뉘게 되는데, 가상 메모리가 있고 그 가상메모리 하나의 공간을 페이지라고 부르는데 그 페이지들이 물리주소의 연속되어있지는 않다.

즉 가상메모리의 주소는 연속적으로 보이지만 실제로 물리주소에서는 여기저기 파편화 되어있다.

운영체제가 그렇게 매핑을 해둔것이다.

실제로 물리주소와 가상메모리의 크기는 같지도 않다.

가상 메모리를 사용하면 실제 메모리보다 더 크게 보이긴한다. 급하게 쓰지 않는 것들은 HDD에 넣어둔다.

가상 메모리의 단위 크기가 페이지인데 그 페이지의 단위가 4096 바이트이기 때문에 그것보다 작아봤다 의미가 없기 때문에 4096을 사용을 했다.

3. .statck 4096

이건 우리 컴퓨터에서 스택 세그먼트의 크기를 4096 byte를 쓰겠다는 의미이다.

4. ExitProcess PROTO, dwExitCode :DWORD

ExitProcess 를 사용해서 프로세스를 종료하는데 이건 마이크로소프트가 지원하는 함수이다.

여기서 어셈블러(컴파일러)에게 프로토타입을 제공해야한다.

이건 인자로써 dwExitCode라는 인자로 DWORD라는 자료형으로 전달 할 것이라는 프로토 타입을 선언 하는 것 과 같다고 생각하면 된다.

C언어의 함수 프로토타입 선언과 비슷하다고 보면 된다.

즉 ExitProcess함수라는게 있다는 것을 알려주는 거야!! 즉 어셈블리가 링킹할 때 그 함수로 점프할 것이다.

5. 마지막줄 엔드뒤에 메인은

우리 함수 시작지점이 메인이라는 의미이다.

Visual studio 예시 만들기

어셈블 링크 실행 사이클

오프젝트 파일로만은 실행 불가

링커는 연결해주는 역활..

리스팅 파일

소스코드를 링킹할 때 리스팅 파일이 선택적으로 생성되는데 그 파일이 생기면

이게 리스팅 파일인데 명령어로 생긴 녀석들이 생기면 이런 파일이 나오게 된다.

즉 명령어들을 대치한 것이다.


Uploaded by N2T

728x90