본문 바로가기
운영체제/[ecourse] Windows Programming

1-2. MASM 기본 문법

by 헛둘이 2022. 9. 1.

본 글은 코드누리의 Windows Programming 강좌를 개인 학습 목적으로 정리한 글 입니다.

https://www.ecourse.co.kr/

 

ecourse 온라인 강의 – S/W 교육의 새로운 시도

개강 예정 과정입니다. {"title":"\uac1c\uac15 \uc608\uc815 \uacfc\uc815\uc785\ub2c8\ub2e4.","show_title":"1","post_type":"course","taxonomy":"course-cat","term":"COMMINGSOON","post_ids":"","course_style":"popular","featured_style":"course","masonry

www.ecourse.co.kr

 

 

 

 

목차
  1. asm1.asm 분석
  2. 같은 의미의 다른 표기법
  3. C 소스 코드를 어셈블리 파일로 생성하는 법
  4. 데이터 영역 사용하기
  5. 메모리 주소 구하는 방법
  6. 메모리 주소 역참조하기
  7. 배열 만들기
  8. 배열에 접근하기
  9. 어셈블리 내에서 함수 호출하기 - jmp를 이용한 방법
  10. 어셈블리 내에서 함수 호출하기 - call를 이용한 방법

 


 

 

 

 

 

 

 

 

 

#include <stdio.h>

int asm_main();

void foo();

int main()
{
    foo(); //(1)
    
    int n = asm_main();
    
    printf("result : %d\n", n);
}
  • Visual Studio에서 asm_main() 이라는 함수를 만들면 어셈블리에서는 _asm_main()으로 만들어짐 (함수 이름 앞에 언더바)
  • 그래서 아까 어셈블리 파일에서 _asm_main 이라고 했구나..

 

 

 

 


 

asm1.asm 분석
; asm1.asm

.model flat 	 // (1)

public _asm_main // (4)

.code			 // (2)

_asm_main:		 // (3)
	mov eax, 100
	ret

end
  • masm 어셈블리 언어는 대소문자를 구별하지 않는다.
  • 파일의 끝에는 end를 적어줘야 한다.

 

(1) MASM Directive
  1. memory model, language-type, stack option
  2. 16, 32bit에서만 사용한다. (64bit에서는 사용하지 않음)
  32bit 16bit
memory model FLAT TINY, SMALL, COMPACT, MEDIUM,
LARGE, HUGE, FLAT
language type C, STDCALL C, BASIC, FORTRAN, PASCAL, SYSCALL, STDCALL
Stack option NOT USED NEARSTACK, FARSTACK

 

(2) PE 파일 구조

 

출처: 랄라라님의 tistory

https://unabated.tistory.com/entry/%EA%B0%9C%EB%85%90-%EC%9D%B4%ED%95%B4-PE-%ED%8C%8C%EC%9D%BC%EC%9D%98-%EA%B5%AC%EC%84%B1

 

  • C언어에서는 함수는 .text 영역, 전역, 정적 변수는 .data 영역에 자동으로 들어간다
  • 하지만 어셈블리어는 아래처럼 별도로 지시하여야 한다.
  • .code 라고 적으면 그 밑에 있는 함수나 코드들은 .text 영역으로 들어가게 된다.
  • .data 라고 적으면 그 밑에 있는 것들이 .data 영역으로 들어간다.

 

(3) 함수를 만드는 방법
  • 함수명: 으로 함수를 만든다 (C언어의 goto문의 label을 만드는 것과 비슷하다.)
  • ret를 쓰면 함수를 종료한다
  • eax 레지스터에 넣은 값이 반환값이다.
(4) public의 의미
  • 함수를 다른 파일(C언어)에서 호출하기 위해서 붙여 주는 지시어 (extern과 비슷한?)

 

 

 

 


같은 의미의 다른 표기법 (.TEXT SEGMENG, _asm_main proc ...)
; asm1.asm

.model flat

public _asm_main

.DATA SEGMENT
// 전역 변수 만드는 곳
.DATA ends

.TEXT SEGMENT // (1)

_asm_main proc // (2)

	mov eax, 100
	ret
    
_asm_main endp

.TEXT ends

end
  • .code 대신 .TEXT SEGMENT라고 적어주어도 된다. 대신 .TEXT ends로 닫아주어야 한다.
  • _asm_main: 대신 _asm_main proc으로 적어주어도 된다. 대신 _asm_main endp로 닫아주어야 한다.

 

 


 

C 소스 코드를 어셈블리 파일로 생성하는 법
  1. 개발자 명령 프롬프트에서 소스 파일이 있는 폴더로 이동
  2. cl 소스이름.c /FAs /c 입력
  3. 해당 경로에 소스이름.asm으로 어셈블리파일 생성

 4. notepad 소스이름.asm 으로 실행

 

 


데이터 영역 사용하기

 

명칭 크기 (Byte)
BYTE 1
WORD 2
DWORD 4
QWORD 8
; asm2.asm

.model flat

public _asm_main

.data
L1 DWORD 100	// 4바이트 변수 L1을 100으로 초기화
L2 DD 200       // DD == DWORD
L3 DD ?

.code
_asm_main:
	mov L1, 300  // L1에 300 집어넣기
    
    mov L1, L2   // ERROR! mov의 두 오퍼랜드가 모두 메모리일 수 없다! 아래와 같이 해야 함.
    mov ebx, L2  // 메모리(L2) -> 레지스터(ebx)
    mov L1, ebx  // 레지스터(ebx) -> 메모리(L1)
    
    mov eax, L1
	ret

end
  • 위 예제처럼 메모리에서 메모리를 복사하는 행위는 불가능하다!
그 이유는 현재의 메모리의 구조상 메모리 유닛 자체가 Read/Write를 동시에 할 수 없기 때문이다.

 

 

 

 

 

 


메모리 주소 구하는 방법

 

; asm2.asm

.model flat

public _asm_main

.data
L1 DWORD 100
L2 DD 200
L3 DD ?

.code
_asm_main:
	mov eax, offset L1 // eax = &L1과 동일한 의미
	ret

end

 

  • "offset 메모리" 와 같이 사용되며, offset 우측에 레지스터는 올 수 없다.

 


메모리 주소 역참조하기

 

; asm2.asm

.model flat

public _asm_main

.data
L1 DWORD 100
L2 DD 200
L3 DD ?

.code
_asm_main:
	mov [ebx], 300    // ERROR! 300을 몇 Byte로 집어넣을지 정해지지 않음.
	mov eax, [ebx]    // eax가 4바이트인지 알 수 있기 때문에 에러를 뱉지 않는다.
	ret

end
  • mov "???" [ebx], 300 에서 "???"에 몇 Byte 포인터인지 정해주면 된다.
  • 1Byte 포인터라면 byte ptr
  • 2Byte 포인터라면 word ptr
  • 4Byte 포인터라면 dword ptr
  • 예를 들어 byte ptr인데 300을 집어넣으면 에러 발생!
; asm3.asm

.model flat

public _asm_main

.data
L1 DWORD 100
L2 DD 200
L3 DD ?

.code
_asm_main:
	mov dword ptr[ebx], 300 // 문제 해결!
	mov eax, [ebx]
	ret

end
  • ebx를 역참조 한 후에 그 앞에 dword ptr을 붙여주어 300을 몇 바이트로 볼 것인지 정한다.
  • 2Byte의 300일 수 있고, 4Byte의 300일 수 있기 때문

 

 

 


 

배열 만들기

 

; asm4.asm

.model flat

public _asm_main

.data
L1 DWORD 100, 200, 300, 400
L2 DWORD 4 dup (100) 

.code
_asm_main:
	mov eax, L1
	ret

end
  • L1 DWORD 100, 200, 300, 400 ;   DWORD L1 = [100, 200, 300, 400];
  • L1 DWORD 4 dup (100)             ;   DWORD L1 = [100, 100, 100, 100];

 


배열에 접근하기
; asm5.asm

.model flat

public _asm_main

.data
L1 DWORD 100, 200, 300, 400
L2 DWORD 4 dup (100) 

.code
_asm_main:
	mov ebx, offset L1
	mov eax, dword ptr [ebx + 4] // 배열의 요소에 접근 (L1의 2번째 요소)
	ret

end
  • dword ptr [ebx+4]는 ebx[1]과 동일한 의미이다.

 

 

 


어셈블리 내에서 함수 호출하기 - jmp를 이용한 방법

 

; asm6.asm

.model flat

public _asm_main

.data
L1 DWORD 500

.code
_asm_main:
	mov ebx, _back
	jmp _func


_back:
	ret


_func:
	mov eax, L1
	jmp ebx


end
  • 함수를 호출하는 방법은 jmp를 이용하는 방법과 call을 이용하는 방법이 있다.
  • 위는 jmp를 이용하는 방법인데 돌아오는 지점을 ebx에 저장하고 _func 내에서 jmp ebx로 이동하는 것을 볼 수 있다.
  • 함수 내에서 돌아갈 주소가 담긴 레지스터를 쓸 수 없다는 것은 단점으로 꼽힌다.
  • C언어의 goto문과 상당히 흡사한 모습.. 왜 goto문을 쓰지 말라고 하는지 알 것 같다..

 

 

; asm7.asm

.model flat

public _asm_main

.data
L1 DWORD 500

.code
_asm_main:
	push _back
	jmp _func


_back:
	ret


_func:
	mov eax, L1
	pop ebx
	jmp ebx


end
  • 레지스터를 쓸 수 없는 단점을 보완한 스택을 사용한 버전
  • 마지막에 ebx 레지스터를 사용하지만 일시적이므로 유의미한 개선임

 

 

 


어셈블리 내에서 함수 호출하기 - call를 이용한 방법

 

call과 ret의 의미

; asm8.asm

.model flat

public _asm_main

.data
L1 DWORD 500

.code
_asm_main:

	call _func
	ret

_func:
	mov eax, L1
	ret

end
  • call 명령은 현재 위치 바로 다음 위치를 스택에 저장하고 해당 함수로 jmp 한다
  • ret 명령은 스택의 맨 위에 돌아갈 위치가 있다고 가정하고 pop을 하여 jmp 한다.

'운영체제 > [ecourse] Windows Programming' 카테고리의 다른 글

1-5. C++과 MASM  (0) 2022.09.05
1-4. Stack Frame  (0) 2022.09.04
1-3. Calling Convention  (0) 2022.09.03
1-1-2. 어셈블리 빌드 방법  (0) 2022.09.01
1-1-1. 인라인 어셈블리와 MASM  (0) 2022.09.01

댓글