본 글은 코드누리의 Windows Programming 강좌를 개인 학습 목적으로 정리한 글 입니다.
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
목차
- asm1.asm 분석
- 같은 의미의 다른 표기법
- C 소스 코드를 어셈블리 파일로 생성하는 법
- 데이터 영역 사용하기
- 메모리 주소 구하는 방법
- 메모리 주소 역참조하기
- 배열 만들기
- 배열에 접근하기
- 어셈블리 내에서 함수 호출하기 - jmp를 이용한 방법
- 어셈블리 내에서 함수 호출하기 - 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
- memory model, language-type, stack option
- 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
- 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 소스 코드를 어셈블리 파일로 생성하는 법
- 개발자 명령 프롬프트에서 소스 파일이 있는 폴더로 이동
- cl 소스이름.c /FAs /c 입력
- 해당 경로에 소스이름.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를 이용한 방법
; 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 |
댓글