(Win32 : WindowsProgramming-3) 어셈블리 기본문법

Posted by : at

Category : win32   WindowsProgramming


함수이름은 어떻게 정의 될까?

#include <stdio.h>

int asm_main();     // 어셈블리 파일로 만들예정

int main()
{
    int n = asm_main();
    printf("result : %d\n", n);
}
; asm1.asm

.model flat

public _asm_main

.code
_asm_main:
    mov     eax, 100        ; return 100과 동일
    ret

end     ; 파일의 끝에는 항상 end가 있어야 한다.
  • cpp에서는 함수의 이름을 asm_main()로 선언했는데 어셈블리에선 _asm_main라 정의한 이유??
    cl컴파일러는 함수의 이름 앞에 _를 붙여서 쓴다. cl컴파일러의 규칙을 따르기 위해서 추가함.

.model flat 이란

  • .model에서는 세 가지를 정의할 수 있다.
    • memory model
    • language-type
    • stack-option
  • memory model의 경우 32bit 응용프로그램의 경우 flat으로 정의 하고 16bit는 여러가지가 있는데 16는 쓰일일이 없으니 설명은 생략한다.
  • language-type의 경우 c, stdcall로 정의가능한데 이후에 별도로 설명한다
  • stack-option의 경우 32bit에서는 사용이 안되고 16bit에서는 여러 설정이 있지만 역시 설명은 생략

대략적 사용은 아래와 같이 한다.

.model flat             ;
.model flat, c          ; language-type을 c로
.model float, stdcall   ; language-type을 stdcall로

PE Format이란?

C++로 실행파일(exe)을 만들시 내부메모리 구조는 아래와 같다


Header
-------
Section .text (함수가 들어있다)
Section .data (전역 변수가 들어있다)
...

C++에서는 Section .text, .data를 알아서 컴파일러가 정리해줬다고하지만…
어셈블리에서는 어떻게 정리할까?

.model flat

public _asm_main ; 다른 함수에서도 부르게 해달라는 명령(c에서 부를수 있게 된다.)

.data
; Section .data를 의미한다
; 전역 변수이다

.code   ; .text 를 의미한다.
_asm_main:
    mov     eax, 100    ; eax레지스터는 보통 반환값을 넣는 용도로 사용됨
    ret ; 함수 종료(return)를 의미

end

PE에 관해서는 이후 DLL설명에서 추가설명을 넣음.
일단 PE Format이라는게 바이너리가 Header Section .text(함수), Section .data(변수)로 구분된다고 기억하자


또 다른 어셈블리 표기법

함수를 또 다른 방법으로 표기

.model flat

public _asm_main

.code
_asm_main proc  ; 콜론(:)대신 proc표기가능 
    mov     eax, 100
    ret
_asm_main endp  ; proc가 끝났다고 알린다

end

Section을 또 다른 방법으로 표기

.model flat

public _asm_main

_DATA SEGMENT   ; .data와 동일
_DATA ends

_TEXT SEGMENT    ; .code 대신에 _TEXT SEGMENT 표기가능

_asm_main proc
    mov     eax, 100
    ret
_asm_main endp

_TEXT ends

end

C소스를 어셈블리 파일로 생성하기

# asm1.c를 어셈블리 언어로 표현해주세요
$ cl asm1.c /FAs /c
  • /FAs : 어셈블리를 만들어 달라
  • /c : 컴파일만 해달라

Data section

.model flat
public _asm_main

.data
L1  DWORD   100     ; 4바이트 할당 100을 넣어라
L2  DD      200     ; DD = DWORD와 동일어
L3  DD      ?       ; 초기값을 정의하고 싶지 않다면 ?를 넣자

.code
_asm_main:
    mov     eax, L1 ; 100이 리턴

    ; 메모리 복사
    ; mov     L1, L2  ; 컴파일 에러 : 2개의 오퍼랜드가 모두 메모리일 수 없다.
    mov     ebx, L2
    mov     L1, ebx
    ; 이런식으로 해야함

    ; 메모리 주소값 구하기
    ; ebx = &L1
    mov     ebx, offset L1  ; L1의 메모리주소를 넣어라
    mov     eax, [ebx]      ; []는 C의 *와 동일 *ebx(ebx가 가리키는 값 = L1값)

    ; ebx = 300
    ; mov     [ebx], 300            ; 에러 : 300을 몇 바이트(int, short)로 넣을지 미정
    mov     dword ptr[ebx], 300     ; dword(2바이트)로 넣어주세요

    ret

end
; 배열 만들어 보기
.model flat
public _asm_main

.data
; L1   DD   100
L1   DD   100, 200, 300, 400
L2   DD   4 dup (100) ; 100, 100, 100, 100
; 4개의 100을 넣어달라

.code
_asm_main:
    ; mov eax, L1
    mov esi, offset L1
    mov eax, dword ptr[esi+4]
    ret

end

여기서 궁금한 점 레지스터는 어디까지 있나??

eax, ebx … 어디까지 있는거지??

  • 참고사이트 : 알파벳 순이 아니라 용도에 따라 사용되는 레지스터가 다르다

함수 호출의 원리

jump 함수호출

.model flat

public _asm_main
public _foo

.code
_asm_main:
    mov eax, 100
    ; 여기서 _foo를 호출하고 싶다면?
    ret

_foo:   ; 참고로 c에서도 호출하게 하고 싶어서 _를 앞에 붙임

end
  • 함수를 호출하는 방법
    • jmp
    • call

jmp

_asm_main:
    mov eax, 100
    mov ebx, POS_A      ; 함수를 호출 후 POS_A로 돌아오게 해주세요
    ; mov ebx, 돌아올 주소
    jmp _add ; 함수 호출
POS_A:
    ret

_add:
    mov eax, 300
    jmp ebx             ; ebx에 리턴 후 돌아갈 주소가 남겨져 있기에 jmp하면 돌아감

ebx에 돌아갈 주소를 넣어둘 경우 ebx는 함수가 종료될때까지 사용하지 못하게된다.
ebx를 써야할 필요가 발생한다면???
해결해보자

_asm_main:
    mov eax, 100
    ; mov ebx, 돌아올 주소
    push POS_A
    jmp _add ; 함수 호출
POS_A:
    ret

_add:
    mov eax, 300
    pop ebx ; ebx에 pop해달라
    jmp ebx

call 함수호출

_asm_main:
    mov eax, 100
    call _add   ; 스택에 넣고 돌아올 주소까지 할당
    ret

_add:
    mov eax, 300
    ret         ; 스택에 꼭대기에 돌아갈 주소가 있다고 가정
  • 결론 : call, ret을 쓰자

C를 어셈블리로 만들어서 call을 어떻게 사용하나 보자

int add()
{
    return 300;
}

int main()
{
    add();
}

이걸 어셈블리로 만들어 보자.

cl call.c /FAs

결론은 call, ret을 VS에서도 그대로 쓴다는 것을 확인하는 것이다.

; Listing generated by Microsoft (R) Optimizing Compiler Version 19.28.29914.0 

include listing.inc

INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES

PUBLIC	add
PUBLIC	main
pdata	SEGMENT
$pdata$main DD	imagerel $LN3
	DD	imagerel $LN3+16
	DD	imagerel $unwind$main
pdata	ENDS
xdata	SEGMENT
$unwind$main DD	010401H
	DD	04204H
xdata	ENDS
; Function compile flags: /Odtp
; File C:\Git\Example\Example\call.c
_TEXT	SEGMENT
main	PROC

; 7    : {

$LN3:
	sub	rsp, 40					; 00000028H

; 8    :     add();

	call	add

; 9    : }

	xor	eax, eax
	add	rsp, 40					; 00000028H
	ret	0
main	ENDP
_TEXT	ENDS
; Function compile flags: /Odtp
; File C:\Git\Example\Example\call.c
_TEXT	SEGMENT
add	PROC

; 3    :     return 300;

	mov	eax, 300				; 0000012cH

; 4    : }

	ret	0
add	ENDP
_TEXT	ENDS
END


About Taehyung Kim

안녕하세요? 8년차 현업 C++ 개발자 김태형이라고 합니다. 😁 C/C++을 사랑하며 다양한 사람과의 협업을 즐깁니다. ☕ 꾸준한 자기개발을 미덕이라 생각하며 노력중이며, 제가 얻은 지식을 홈페이지에 정리 중입니다. 좀 더 상세한 제 이력서 혹은 Private 프로젝트 접근 권한을 원하신다면 메일주세요. 😎

Star
Useful Links