minhui study

3부 10장 - 2. 함수 호출 관점에서의 컴퓨터 구조의 이해 본문

컴퓨터 구조/Windows System Programming

3부 10장 - 2. 함수 호출 관점에서의 컴퓨터 구조의 이해

minhui 2020. 1. 24. 17:34

Section 02. 함수 호출 인자의 전달과 PUSH & POP 명령어 디자인

 

> PUSH & POP

 

STORE 명령어를 이용해서 첫 번째 전달인자인 숫자 7을 현재 sp레지스터가 가리키는 메모리 영역에 저장하고자 한다. 

참고로 STORE 명령어는 앞 장에서 배웠듯이 STORE  대상(레지스터),  목적지(메모리 주소)형태이어야 한다.

첫 번째로 7을 임의의 레지스터에 저장한다. (MOV 명령어도 있지만 우리는 사칙연산 명령어만 만들어 놓았기 때문에 일단 ADD로 진행)

 

ADD  r1,  7,  0

 

두 번째로는 sp레지스터에 7을 넣어야 하지만 목적지에는 메모리주소가 와야 하므로 Indirect모드 연산을 해야 한다.

일단 sp가 지니고 있는 값을 0x40번지(다른 주소의 메모리도 상관없음)에 저장한다.

 

STORE  r1,  0x40

 

이제 Indirect 모드로 0x40번지를 참조해서 데이터를 저장한다. 

 

STORE  r1,  [0x40]

 

숫자 7을 sp가 참조하는 메모리 영역에 저장하는 명령어 조합을 다시 한번 정리하면 다음과 같다.

ADD  r1,  7,  0

STORE  sp,  0x40

STORE  r1,  [0x40]

 

스택에 데이터를 저장하는 과정

위의 그림과 같이 스택에 데이터를 저장한 후에는 다음에 들어오는 데이터를 저장하기 위해서 반드시 sp레지스터값을 증가시켜야 한다. 따라서 최종적으로 명령어들을 조합하면 다음과 같다.

 

ADD  r1,  7,  0

STORE  sp,  0x40

STORE  r1,  [0x40]

ADD  sp,  sp,  4

PUSH & POP

이제 두 명령어 PUSH와 POP에 대해서 알아보자

    -  "PUSH 0x02"   or   "PUSH  r1 " 

         : 현재 sp값을 참조하여 해당 위치에 데이터 0x02(혹은 레지스터 r1의 값)을 저장하고 sp의 값 또한 자동으로 증가 하는 명령어

    - "POP" = "ADD  sp,  sp,  -4"  or  "SUB  sp,  sp,  4"

         : 스택에 가장 마지막에 들어간 데이터를 꺼낸다는 의미를 지닌다.

 

내용을 정리하는 차원에서 C언어 코드와 함수 호출이 진행된기 전 후의 메모리 구조를 살펴보자

먼저 이전의 메모리 구조이다.(fp가 0x40번지를 가리킨다는 것은 어디까지나 가정이다.)

function함수 호출 이전의 상황

function함수가 호출되면서 인자가 전달되고 나면 다음과 같이 변경된다.

function 함수 호출 이후의 상황

함수가 호출되면 현재 fp에 저장된 주소 정보를 스택에 쌓는 일이 제일 먼저 진행이 된다. sp에 저장된 값을 fp에 저장해야 하기 때문에 fp를 비워야 한다. PUSH  fp

fp 레지스터에 저장된 값을 스택으로 옮겨 놓았으면 sp값을 fp에 저장할 차례이다. ADD  fp, sp, -4

여기서 왜 4를 뺀 것일까?

앞서 fp의 값을 PUSH 했기 때문에 이미 sp값이 4만큼 증가한 상황이므로 현재 sp값에서 다시 4만큼 뺀 값을 fp에 저장해야만 하는 것이다. 만약 4만큼 빼지 않는다면 fp는 숫자 7이 저장된 위치를 가리키게 되어 함수 반환이 끝났을 때 올바른 위치로 돌아오지 않게 된다. 

그 다음 이제 7,8을 스택에 쌓으면 된다.     PUSH 7 / PUSH 8

 

 


Section 03. 함수 호출(Procedure Call)에 의한 실행의 이동

 

> 다시 살펴보는 메모리 구조와 프로그램 카운터(Program Counter)

 

다음 그림은 메모리 구조에 대한 것이다.

메모리 구조에서 이번에 관심을 가질 영역은 "코드(code) 영역"이다.

  -> 프로그램이 동작하기 위한 프로그램 코드가 올라가는 위치

 

1장에서 명령어의 실행이 Fetch, Decode, Execution이렇게 세단계로 구분되어 진행됨을 설명하였다. 

명령어의 길이가 4바이트라고 한다면 그리고 실행 주인 ㅍ로그램이 현재 1036번지에 있는 명령이라면 다음 번에는 1040번지에 있는 명령어가 Fetch되어야 한다. 이때 어느 위치에 있는 명령어까지 가져와 실행했는지 기억하여 명령어를 순차적으로 Fetch하기 위해서 필요한 레지스터가 바로 "pc 레지스터"이다.

 

프로그램 카운터(pc)

CPU는 Fetch, Decode, Execution 과정을 계속해서 진행하도록 구현되어 있기 때문에 Fetch 연산이 일어날 때마다 자동적으로 pc값이 증가한다. 옆에 그림은 pc의 동작을 설명하고 있다.

 

 

 

> 함수 호출과 함수 종료

 

코드 영역에서의 함수 호출 구조

그림의 오른쪽 부분은 프로그램 실행을 위해 컴파일된 바이너리 코드가 올라가 있는 코드 영역이다. 

함수 호출이 가능하기 위해서는 순차적인 실행 외에 호출된 함수에서 복귀할 때 특정 위치로의 이동이 필요하다.

그래서 Program Counter(pc)이 필요한 것이다. 즉, pc를 조작하면 순자척으로 실행되고 있던 명령어의 실행을 멈추고 함수 호출이 발생한 위치로 이동해서 명령어의 실행을 이어갈 수 있고 또한 함수 호출이 완료된 다음에 이동이 발생하기 전 위치로 옮겨가 실행을 이어갈 수 있다. 그런데 pc에 이동해야 할 주소값을 저장하기 전에 반드시 스택에 현재 pc값을 백업해야 한다.


 

Section 04. 함수 호출규약(Calling Convention)

 

> 함수 호출규약이란?

  : 함수 호출 시 인자를 전달하는 방식과 스택 프레임을 반환하는 방식을 약속해 놓은 것

 

> 호출규약의 종류와 의미

Calling Convention

32비트 기반 함수 호출규약부터 설명을 하자면 일단 _cdecl은 C/C++의 디폴트 호출규약으로 C언어 스타일을 따르는데 이는 오른쪽에 전달되는 인자가 먼저 스택에 쌓이는 방식을 의미한다. 반환 시에는 함수를 호출하는 호출자가 스택 프레임을 반환하도록 정의되어 있다. 

_stdcall와 _cdecl의 차이점은 스택 프레임을 반환하는 주체이다. _stdcall은 호출된 함수 내에서 스택 프레임을 반환하도록 정의되어 있다.

(*호출자 : 스택 프레임의 반환의 주체는 호출자가 될 수도 있고 호출이 된 함수가 될 수도 있는데 A함수가 B함수를 호출하는 프로그램 코드가 있다고 한다면 A함수가 호출자가 된다.)

_fastcall은 말 그래도 함수 호출을 빠르게 처리하기 위한 호출규약이다. 위 표에서 "Parameters in registers"부분은 전달되는 인자를 저장할 때 레지스터의 사용유무를 설명한다. 예를 들어 첫 번째 전달인자와 두 번째 전달인자는 레지스터 ecx와 edx를 통해 저장된다. 이 호출규약에서 함수 호출이 빨라지는 이유는 레지스터를 사용하고 있기 때문이다. 

 

64비트 시스템에서는 함수 호출규약이 운영체제에 따라서 나뉘게 된다. Windows 기반에서는 총 8개의 레지스터를 활용해서 전달되는 인자를 저장하지만 Linux나 BSD 계열의 시스템에서는 훨씬 많은 수의 레지스터를 전달되는 인자에 할당하고 있음을 볼 수 있다.

 

* Callback 함수 : Windows 시스템에 의해 자동으로 호출되는 함수

 

 

출처 : 윈도우즈시스템프로그래밍(한빛미디어)


 

Comments