전공/시스템 프로그래밍

[시스템 프로그래밍 12 -1강]advanced processor (Stack Frames)

뜨거운 개발자 2023. 5. 28. 20:39

개요

  • 우리가 지금껏 쓰던 프로시저(PROC)는 그냥 코드의 위치를 변경하는 것 뿐이었다.
  • 사실 high level 언어인 C나 CPP나 모두 함수를 할 때 processor를 쓰는것이다.
  • 하지만 고급 언어에서는 함수로 인자를 넘겨주는 것을 볼 수가 있다. 오늘은 어떻게 인자를 함수에 넘겨주는가에 대해서 배울 것이다.
  • 물론 그 방법을 이해하기 앞서 필요한 개념들을 먼저 설명하도록 하겠다.

Stack Frames

1. Stack Parameters

stack Frame이란 간단히 말해 다음의 것들을 저장하기 위해서 스택의 영역이다.

  • 함수에게 전달된 인수(인자)가 스택에 저장된다.
  • 서브루틴의 반환주소(리턴 주소)
  • 로컬 변수(함수 안에서 사용하는 로컬 변수로 스택에서 지역변수에 대한 메모리 공간이 할당 되는 것이다.)
  • 저장된 레지스터 (이건 옵션적인 것인데, 프로시저 안에서 레지스터를 사용하는 경우 프로시저를 호출하기 전의 레지스터 상태를 푸쉬해준다.
  • 이런 정보들을 저장해주는 것을 Stack Frame이라고 부른다.

스택 프레임 생성 과정 (단계별)

함수를 우리가 구현해보는 것이라고 생각하면된다.

  • 중요 개념!! stack에 뭔가를 push하면 주소값이 감소한다! 스택은 주소를 높은곳에서 낮은 곳으로 올라가는 형식을 취하기 때문이다.
  1. 함수(프로지서)에 전달되는 인자들이 있으면 스택에 집어 넣어준다.
  1. 해당 프로시저가 불린다. → 당연하게도 서브루틴의 리턴 주소가 스택에 들어간다. (프로지서가 실행 후에 다시 원래 코드의 진행 방향으로 돌아오기 위해서 있는 것으로 당연하다고 생각하면 된다.)
  1. 서브루틴(프로시저)가 호출이 되서 실행되면, 일단 EBP를 스택에 집어 넣어준다.
    1. EBP는 Base pointer이고
    1. ESP는 스택포인터로 TOP을 가리기고 있다.
    1. 스택에 뭔가가 푸쉬가 되면 ESP가 감소가 된다.(사실 푸쉬를 하는것인데 감소 시켜주는게 푸쉬한 것이다.) (그림상으로 ESP가 올라간다는 의미)
  1. 다음으로 EBP가 ESP와 동일하게 설정된다.
    1. 이것이 무슨의미인가? 함수안에 들어왔기 때문에 스택의 바닥을 다시 설정해주는 것이다.
  1. 만약 함수 안에서 지역 변수를 만들게 되면, 다시 ESP가 그만큼 감소하게 된다.(ESP가 그림상으로 위로 올라간다)
  1. 함수 안에서 특정 레지스터의 값을 바꾸는 경우 레지스터를 스택에 푸쉬해준다.
  • 스택 프레임의 구조는 메모리 모델과 인수 전달 규칙에 영향을 받는다.
    • 우리는 메모리 Model까지 신경 쓸 필요는 없다.
    • 왜냐하면 우리는 공부하는게 32비트 x86을 기준으로 하는데 32비트 메모리의 경우 protected모드를 사용하고, 무조건 flat모델만을 쓰기 때문에 무시해도 된다.
    • 다만 인수전달 규칙은 조금 다를 수 있다. (이는 조금 뒤에서 다루겠다.)
    • 그동안 우리는 stdcall 컨벤션을 사용했지만, 그것이 조금 다른 종류를 사용할 수가 있다.
  • 우리가 만약 윈도우 32비트 API에서 함수를 사용하려면 스택에 인수를 전달 해줘야 한다.
    • 이걸 언제 써봤는가? ExitProcess 를 사용해봤다.
    • invoke ExitProcess,0
    • ExitProcess가 윈도우 API가 제공하는 함수이다.

2. Disadvantages of Register Parameters

레지스터 매개변수의 단점!!!

  • 우리는 그동안 인자를 전달할 때 레지스터에 그 값을 집어 넣어주고 프로시저를 호출 했었다. (Irvine32에서 그랬듯)
  • 우리는 폰노이만 컴퓨터에서 bottleneck이 Bus인 것을 알고 있다.
    • 그렇기 때문에 당연하게도 레지스터에 넘겨서 주는 방식은 효율적이다.
  • 이렇게 파라미터를 레지스터에 넣어서 전달하겠다는 것을 우리는 Fastcall 방식이라고 부른다.
  • 다만 여기서 문제점이 있다
    • 레지스터의 갯수는 한정적이기 때문에 프로시저 내부에서 레지스터를 사용하려면 stack에 이전값을 push해주고 사용을 해야만 했다.
    • 우리가 레지스터에 전달해주기 위해서 다른 레지스터의 값을 stack에 푸쉬하고 pop을 해야한다는 것이 모순적이다.
    • 이는 우리가 빠르게 하려고 레지스터에 인자를 집어 넣은 행동을 상쇄시켜버린다.
    • 또한 우리가 push와 pop을 할 때 순서가 일치하도록 매우 주의해야 한다는 단점도 생겼다..
  • 이 단점을 극복하기 위해서 레지스터에 넣는게 아니라 매개변수를 스택에 넣는 방식으로 변화했다.

매개변수를 레지스터에 push하는 방법과 stack에 푸쉬하는 방법 사이 비교

  • 스택에 어떤 순서로 꺼내면 어떤 변수가 나올지에 대해서 미리 합의를 했다.(마지막 매개변수를 가장 먼저 집어넣음..!)
  • 따라서 프로그래머가 실수 할 여지가 줄어들고 깔끔해졌다.
  • 이렇게 서브루틴 호출 중에 스택에 푸쉬되는 인자의 유형은 2가지 이다.
    1. Value인자(변수 및 상수의 값)
    1. referance 인자(변수의 주소)

스택 매개변수를 전달하는 방법 2가지

  • 주의할 점!! :: 여기 아래 stack 그림은 위에서 그린 stack을 거꾸로 그려 놓은것 이라고 보면된다.

1. Value를 전달하는 것

  • Passing by Value 방법
  • 이 방법은 값의 복사본을 스택에 푸쉬해주는 방법이다.
  • 두번째 인자가 먼저 들어간다. (C와 C++에서는 그렇게 약속이 되어있다.)
    • 이런 약속을 기반으로 C언어의 코드를 어셈블리어로 변경해서 해석하는 것이다.
  • 위 코드는 C++에서는 : int sum = AddTwo(val1, val2)

2. Reference를 전달하는 것

  • Passing by Reference
  • 이건 전달 되는 인수가 객체의 주소(OFFSET)이다.
  1. 여기서 레퍼런스는 CPP에서 레퍼런스와 살짝 다르다.
  1. 여기서는 포인터에 더 가까운 의미이다.
  • 보면 주소값을 push 해주는 것을 볼 수가 있다.
  • 이 코드는 C++에서는 Swap(&val,&val2); 이다.
  • Passing Arrays
    • 우리는 배열을 전달할 때 배열의 주소를 전달해준다.(상식)
    .data
    array DWORD 50 DUP(?)
    .code
    push OFFSET array
    call ArrayFill
    • 만약 여기서 주소를 전달하지 않고 통째로 집어 넣으면..! 배열의 원소를 하나하나 다 스택에 집어 넣어야 한다. (공간, 시간적 낭비)

3. Accessing Stack Parameters

지금까지는 스택에 집어넣는 방법이었다면 이제 어떻게 프로시저(함수)가 전달받은 인자에 접근하는지 살펴보자.

  1. 프로시저가 실행되면(Fuction 안에서) EBP레지스터가 PUSH된다.(복귀 주소를 알리는 행동이다.)
  1. 이제 새로운 바닥설정. EBP를 ESP로 올린다.
  1. 함수 호출이 끝나면 EBP레지스터를 복구한다. (스택에 넣어준 EBP를 다시 EBP에 올려준다.)
  • 다시 ESP레지스터도 resturn address로 이동한다.
  • 이걸 왜 해주냐?? 프로시저 호출 되기 전에도 stack을 사용하고 있었기 때문에!

CPP함수와 PROC(프로시저)비교

  • 위와 같은 스택의 구조를 볼 수가 있다.
    • 스택은 다시 말하지만 처음의 그림에 거꾸로 뒤집혀 있다고 보면 된다 .(스택은 push하면 주소값이 작아진다.)

위의 코드 간단한 흐름으로 설명

  1. Y가 들어간다
  1. X가 들어간다.
  1. return address들어간다.
  1. ESP들어간다.
  1. EBP를 ESP로 올린다.
  1. 이제 EBP+8의 의미 : 첫번째 인자.
  1. 두번째 인자 : EBP + 12 : 두번째 인자.
  1. return 되면 EIP로 이동한다.(intruction pointer)
  1. 그러면 함수 호출 전으로 돌아간다.

결국 인자 접근은 stack을 이용해서 간접적인 방식으로 참조해서 접근 하는 것이다.

  • 아래 코드는 인자를 간접적 주소를 표현하는 것을 매크로로 변경해서 더 보기 쉽게 만든 코드이다.
y_param EQU [ebp + 12]
x_param EQU [ebp + 8]
AddTwo PROC
	push ebp
	mov ebp, esp
	mov eax, y_param
	add eax, x_param
	pop ebp
	ret
AddTwo ENDP

4. 32-Bit Calling Conventions

  • 윈도우 환경에서 32비트 프로그램할 때 가장 일반적으로 사용하는 2가지 calling convention이 존재한다.

1. The C Calling Convention

Example1 PROC
	push 6
	push 5
	call AddTwo
	add esp,8 ; remove argument from the stack
	ret
Example1 ENDP
  • 이제 우리는 인자를 4바이트를 넣어준 것이고, 그냥 ESP를 8만큼 밀어주게 되면 이제 비로소, 완전히 인자를 넘겨주기 전에 상태로 돌아오는 것이다.
  • 저기서 8의 의미는 스택에 넣어준 녀석이 크기를 8이라고 생각하는 것이다.
  • 이게 C언어에서 사용하는 Calling convention이다.
  • C calling Convention 의 경우는 우리가 직접 넣어준 매개변수를 직접 pop해야하는 것을 의미한다.(ESP레지스터의 주소를 매개변수의 크기만큼 원래대로 이동)

2. STDCALL Calling Convention

AddTwo PROC
	push ebp
	mov ebp, esp
	mov eax,[ebp + 12]
	add eax,[ebp + 8]
	pop ebp
	ret 8
AddTwo ENDP
  • C스타일은 ESP를 함수 밖에서 증가 시켜주는 것이고, STDCALL 스타일은 함수 내부에서 ret을 해주는 것이다.
  • 이건 알아서 처리를 해준다.(내부적으로는 같은 일을 하고 있다.)
    • 쉽게 말해서 ret 8을 해주면 아까 전에 add esp,8 을 직접 하지 않아도 알아서 해준다는 의미이다.
  • 즉 여기서 ret은 ESP + ret리턴값으로 스택에서 사용하는 매개변수의 크기만큼 즉 함수의 인자의 크기만큼 넣어주면 된다.
  • 물론 C와 마찬가지로 STDCALL은 인자를 역순으로 스택에 밀어 넣는다.
  • RET명령에 매개변수를 포함함으로써 STDCALL은 하위루틴 호출을 위해서 생성되는 코드의 양을 줄이고 호출 프로그램이 stack을 정리하는 것을 잊지 않게 할 수 있다.

Saving and Restoring Registers

이 파트는 우리가 특정 레지스터의 값을 변경했을 때 즉 선택적으로 발생하는 상황이다.

MySub PROC
	push ebp              ;save base pointer
	mov ebp, esp          ;base of stack frame (스택 프레임의 base를 위로 올림) 
	push ecx              
	push edx              ;save EDX 
	mov eax,[ebp + 8]     ;get the stack parameter 스택에 
	.
	.
	.
	pop edx               ;restore saved resigsters
	pop ecx               ;
	pop ebp               ;restore base pointer
	ret                   ;clean up the stack
MySub ENDP
  • 당연하게 EBP, ESP를 먼저 push하고 쓰는것이다.

5. Local Variables

이제 함수내부 즉 프로시저 내부에서 사용되는 지역변수에 대해서 알아보자.

  • 지역변수 접근도 EBP-4, EBP-8이렇게 접근도 가능하다.
    MySub PROC
    	push ebp
    	mov ebp,esp
    	sub esp,8                ;create locals
    	mov DWORD PTR [ebp-4],10 ;X
    	mov DWORD PTR [ebp-8],20 ;Y
    	mov esp,ebp              ;remove locals from stack
    	pop ebp
    	ret
    MySub ENDP
  • 코드를 보면 esp,ebp 부분이 있는데 이는 pop해주는 것과 같다. (왜냐면 stack의 top을 현재 bottom으로 주소값을 변경한 것이기 때문에
  • 지역변수들은 스택에 들어가기 때문에 어셈블타임에 default값들이 지정될 수 었다.
    • 따라서 지역변수는 runtime에 초기화가 가능하고 전역변수는 어셈블 타임에 초기화가 가능하다.

6. Reference Parameters

참조 매개변수

배열을 채우는 예제

.data
count = 100
array WORD count DUP(?)

.code 
push OFFSET array
push count
call ArrayFill

ArrayFill PROC
	push ebp
	mov ebp, esp
	pushad          ;save registers
	mov esi,[ebp+12];offset of array
	mov ecx,[ebp+8] ;array length
	cmp ecx,0.      ;ECX == 0?
	je L2           ;yes : skip over loop
L1 :
	mov eax, 10000h ;get random 0 ~ FFFFh
	call RandomRange;form the link libray
	mov [esi],ax    ;insert value in array
	add esi,TYPE WORD;move to next element
	loop L1
L2 : popad         ;restore register
	pop ebp
	ret 8            ;clean up the stack
ArrayFill ENDP
  • 포인터를 전달해준 것 과 같다.
  • 이 예제는 배열에 값을 채워주는 예제이다.
  1. 2바이트가 100개 있고,
  1. 배열의 주소를 push해주고,
  1. 매개변수 푸쉬 해주고
  1. 함수 호출하면 자동으로 ret addr들어가는 거 잊지 말자.
  1. 프로시저에서 레지스터를 쭉 저장해주면 스택에 쭉 푸쉬가 또 되는 것이다.
  1. ecx에 100을 넣어주는 걸 보면 100번 돌 것을 예상 가능
  1. esi를 보면, 배열의 위치가 있으님까 랜덤한 값이 들어가는 것이다.
  1. 이제 빠져나온다.
  1. 원래대로 esp가 복구되서
  1. 이건 standcall 방식인 것을 알 수가 있다.(ret 8이기 때문)

7. LEA (Load Effective Address) Instruction

  • LEA명령은 간접 피연산자의 주소를 리턴해준다.
    • 이전에 OFFSET을 썻었는데 OFFSET은 런타임에서 달라지는 주소를 리턴해주는 게 아니다.
    • OFFSET은 어셈블 타임에 바뀌는 녀석의 주소를 리턴해주는 것이다.
  • 지금까지 우리가 변수를 저장했던 Data segment에 있는 녀석들은 전역변수라고 생각해 볼 수 있다.
  • 여러분이 작성한 프로그램을 뜯어보면 code와 data로 나뉘어서 있는 것을 볼 수가 있다.
  • 전역변수들은 프로그램에 일부로 들어가게 되는 것이다. (전역변수가 크기가 크면 프로그램의 크기가 커진다
  • 프로그램을 실행하면 운영체제가 세그먼트를 할당해준다.
    • 그 세그먼트는 data code stack의 세그먼트로 쪼개진다.
  • OFFSET은 주소라기보다는 얼마나 떨어져 있는가 즉 상대주소의 개념이다.
    • 따라서 어셈블타임에 결정이 된다.
    • 지역변수들은 스택에 들어가기 때문에 어셈블타임에 default값들이 지정될 수 없다.
    • 그래서 우리가 런타임중에 알아야하는 주소를 offset명령어를 쓰면 오류가 발생한다.
    • mov esi,OFFSET [ebp-30] 은 에러가 발생한다.
  • 그럴 때 쓰는게 LEA 명령이다.

cpp코드

void makeArray()
{
	char myString[30];
	for (int i=0; i < 30; i++)
		myString[i] = '*';
}

어셈블리 코드

makeArray PROC
	push ebp
	mov ebp,esp
	sub esp,32                ;myString is at EBP -30
	lea esi,[ebp-30]          ;load address of myString
	mov ecx,30                ;loop counter
L1: mov BYTE PTR [esi], '*' ;fill noe position
	inc esi                   ;move to next
	loop L1                   ;continue untill ECX =0
	add esp,32                ;remove the array (restore ESP)
	pop ebp
	ret
makeArray ENDP
  1. ESP를 32빼줘서 공간 만들고
  1. esidp ebp-30 즉 첫번째 원소의 주소를 넣어주는 것이다.
  1. 여기서 왜 32만큼 공간을 할당했는지 의문이 생길 수 있는데, 단어의 경계가 4바이트 단위로 접근해야 하려고 그것을 맞춰주는 것이다.(X86프로세서의 특징 4바이트씩 읽음) 메모리는 조금 낭비되지만 속도에서 큰 이득이 있기 때문에 이런 방식을 사용한다.

8. ENTER and LEAVE Instructions

ENTER

  • 스택에서 항상 하는 일을 더 간단하게 해주는 명령어이다.
  • 로컬 변수를 위한 스택 공간을 확보하고 스택에 EBP를 Save해주는 것을 자동화 해주는 명령어이다.
  • 즉 프로시저를 들어갈때마다 해야하는 행동을 간단하게 해주기 위해서 등장한 명령이다.
  • 첫번째 매개변수는 예약할 스택의 공간의 바이트수를 지정하는 상수이다.(즉 지역변수의 크기) 항상 4의 배수로 반올림해서 ESP를 double Word 경계에 유지 시켜준다.
  • 두번째 매개변수는 함수안에 몇번째 함수인가 (어휘중첩 수준이라고 하는데 함수안에 함수를 정의하는 언어에서는 중요하다고 한다.) (사실 우리가 쓰는 거의 대부분의 프로그램에서는 다 0으로 준다고 생각해도 된다.)

LEAVE

  • 함수를 빠져나올 때 하는 일을 간단하게 만들어주는 명령어
  • 시험문제는 enter의 사용법이 아니라 과연 이게 실제로 하는 게 무엇일까에 대한 걸 시험에 낼 수 있다.
  • 이런 명령은 어셈블리어를 업으로 삼는 사람에 대해서 편의 기능을 제공하는 것이다.
  • 이것도 enter할 때 ebp를 push 했듯 leave할 때는 원래대로 돌려준다. 즉 코드에서 보듯 esp를 ebp로 바꿔주고, ebp를 pop해주는 것을 볼 수가 있다.
  • ebp에 스택의 최상위 값을 pop해주는 것이기 때문에, 처음 들어올때 push됐던 ebp의 위치로 ebp를 보내주는 것이다.

예제 Recursion

1. Recursively Calculating a Sum

  • 아까 한 행동이 재귀를 사용해도 아무런 문제가 없는지를 보여준 예제이다.
  • 예제 1부터 n까지의 합을 재귀로 구현한다.
  • 우리가 레지스터에 값을 전달한다고 stack을 안 쓰는게 아니라 return주소가 stack에 들어갑니다.

예시코드

INCLUDE Irvine32.inc
.code
main PROC
	mov ecx, 5 ;count = 5
	mov eax, 0 ;holds the sum
	call CalcSum ;calulate sum
L1: call WriteDec ;display EAX
	call Crlf
	exit
main ENDP

CalcSum PROC
	cmp ecx,0 ;check counter value
	jz L2.    ;quit if zero
	add eax,ecx ;otherwise, add to sum
	dec ecx     ;decrement counter
	call CalcSum ;recursive call
L2 :ret
CalcSum ENDP
end Main

2. Calculating a Factorial

INCLUDE Irvine32.inc
.code
main PROC
	push 5        ;calc 5! (5팩토리알)
	call Factorial;calc factorial eax
	call WriteDec ;display
	call Crlf
	exit
main ENDP

Factorial PROC
	push ebp
	mov ebp,esp
	mov eax,[ebp+8] ;get n
	cmp eax,0       ;n > 0?
	ja L1           ;yes continue
	mov eax,1       ;no : return 1 as the value of 0!
	jmp L2          ;and return to the caller
L1 : dec eax
	push eax        ;Factorial(n-1)
	call Factorial 
ReturnFact:
	mov ebx,[ebp+8] ;get n
	mul ebx         ;EDX:EAX = EAX *EBX
L2 : pop ebp
	ret 4
Factorial ENDP
END main
  • 이 코드는 stack을 사용해서 한 예제이다.
  • 5팩토리얼을 구하는 예제
  • 그림은 3팩토리얼이다. (5팩토리얼은 그림이 너무 길어지기 때문에)
  • 이건 스택이 뒤집혀 있음을 알면 됩니다.

INVOKE, ADDR, PROC, and PROTO

1. INVOKE Directive

  • 문법 : INVOKE procedureName [, argumentList]
push TYPE array
push LENGTHOF array
push OFFSET array
call DumpArray

위 코드를 우리는 아래와 같이 바꿀 수 있다.

  • INVOKE DumpArray, OFFSET array, LENGTHOF array, TYPE array
  • 우린 이미 exitprocess 할 때 이미 사용해봤다.
  • 뒤에 오는 인자들은 생략이 가능하고 여러개가 들어갈 수도 있다.
  • 뒤에 들어오는 ,0은 stack에 0을 푸쉬해주고 함수를 실행한다는 의미이다.
  • 아 이건 거꾸로 푸쉬하는게 아니라 정순으로 인자를 넣어줍니다.(이전에는 거꾸로 인자들을 push했지만 이건 순서대로 인자를 넣어줘도 된다.)
  • 한가지 특이사항!! : 스택에 집어넣기 위해서 32비트에 맞춰서 집어넣는 특성이 있다.
    • 알아서 4바이트로 확장해서 집어넣어준다. (4바이트 단위로 읽기 때문)
  • 따라서 이 녀석은 레지스터를 사용하고 그 과정에서 EAX와 EDX를 사용하기 때문에 프로시저 호출 전에 저장해두는 것을 추천합니다.

2. ADDR Operator

  • 이 녀석은 OFFESET과 똑같이 작동하는데 INVOKE 명령을 위해서 사용하는 녀석이라고 보면된다.

에러 예시

INVOKE FillArray, ADDR myArray
INVOKE mySub, ADDR [ebp + 12] ;error
mov esi, ADDR myArray ;error

ADDR 사용예시

.data
Array DWORD 20 DUP(?)
.code 
...
INVOKE Swap,
	ADDR Array,
	ADDR [Array+4]
  • 사실 이것과 같다
    push OFFSET Array +4
    push OFFSET Array
    call Swap

3. PROC Directive

  • 그동안 그냥 PROC를 사용해왔지만 사실 이건 더 많은 기능이 있다.
  • PROC 구문 : label PROC [attributes] [USER reglist], parameter_list

parameter_list

  • PROC 지시문을 사용하면 쉼표로 구분된 명명된 매개변수 목록으로 프로시저를 선언할 수 있다.
  • 코드에서 매개변수를 [ebp +8] 과 같이 계산된 스택 오프셋이 아닌 이름으로 참조할 수 있다.

과거 버전

AddTwo PROC,
	push ebp
	mov ebp, esp
	mov eax, dword ptr [ebp + 8]
	add eax, dword ptr [ebp+0Ch]
	leave
	ret 8
AddTwo ENDP

지시문을 사용해서 더 간단하게 표현하는 방법

AddTwo PROC,
	val1:DWORD,
	val2:DWORD,
	mov eax, val1
	add eax, val2
	ret
AddTwo ENDP
  • 인자 2개를 넣어주는 것이다. 지역변수 아니다.
  • 즉 val1과 val2를 이렇게 쓰겠다는 의미다. (형식 지정)
  • 그동안은 접근방식이 ebp에서 몇만큼 더해주서 push된 지역변수를 사용했지만 그것보다 훨씬 더 심플하게 사용할 수 있다.
  • 교수님께서 Directive의 사용법 보다는 Directive를 쓰는게 아니라 직접 이 코드를 해석하는 걸 시험에 낼 수 있는 것이다.
  • leave도 코드로 직접 만들어보는 것도 좋아요!

프로시저에서 전달 프로토콜을 직접 설정 할 수도 있다.

Example1 PROC c,
	parm1:DWORD, parm2:DWORD
Example1 PROC STDCALL,
	parm1:DWORD, parm2:DWROD
  • CALLING 컨벤션을 바꿔줄 수도 있다. (중요하지는 않습니다~~)

4. PROTO Directive

  • 내가 작성한 코드에 존재하지 않는 프로시저를 실행하라고 명령을 내리면, 어셈블러가 알아들을 수가 없다.
  • INVOKE를 사용하려면 PROTO가 필요하다.
  • 위에서 선언이 되어있다면 굳이 필요없다. (C언어의 함수 전방 선언과 같다.)
  • 4바이트 단위로 들어가기 때문에 저렇게 되는 것이고 2 빈칸 만들고 푸쉬 뒤쪽은 al만 정상 뒤는 의미없는 값 이렇게 들어간다.
MySub PROTO
		.
INVOKE MySub
		.
MySub PROC
		.
		.
MySub ENDP
  • PROTO 지시어는 기존 프로시저의 프로토타입을 생성한다.
  • 프로토타입은 프로시저의 이름과 매개변수 목록을 선언합니다.
Sub1 PROTO, p1:

Uploaded by N2T

728x90