본문 바로가기
코스웨어/12년 내장형하드웨어

[Assembly] 9월 5일 업무일지 By.정철

by 알 수 없는 사용자 2012. 9. 6.
728x90
반응형

업무일지 입니다.

오늘 어셈블리 수업내용은 좀 헷갈리는 부분이 있어

그림을 첨부합니다. (발로 그렸음)

그림이 다소 애매모호 하고 틀린부분이 있을수 있으니,

나머지는 여러분의 상상력에 맏기도록 하겠습니다. (just imagine!!)


*어셈블리 함수 모양

.386                      ;16비트 호환가능 표시
.MODEL    FLAT    ;
PUBLIC    _Emb     ; 외부에서 함수에 접근이 가능하도록
.CODE                 ; 여기서 부터 시작
_Emb    PROC    NEAR32   ;함수의 시작
   
    mov    eax, 1234   
    ret                              ;리턴
_Emb    ENDP                   ;함수의 끝

END    ;프로그램 끝 (.CODE와 한쌍)


위와 같이 함수를 만들고

C프로그램에서 함수를 호출하도록 한다.


#include <stdio.h>

int Emb();

int main()
{
  int iNum;

  iNum = Emb();

  printf("return value :: %d\n");

  return 0;
}


함수에서 리턴값으로 1234를 넣었고,

우리가 만든 프로그램은 리턴값을 변수에 저장 하고, 이를 화면에 출력한다.


* 어셈블리는 인자를 인식하지 않아!


위의 c코드에서 Emb하수 선언시에 인자를 넣고,

호출시에도 인자를 넣었다.


어셈블리 코드에 아무것도 수정하지 않았지만 오류가 나지 않았다.


* 인자를 활용해 보자


#include <stdio.h>

int Emb(
int, int);

int main()
{
  int iNum;

  iNum = Emb(3, 4);

  printf("return value :: %d\n", iNum);

  return 0;
}


이번에는 Emb함수에 정수 2개를 인자로 실어서 보내보기로 한다.

우리가 원하는 값은 인자 둘이 더해진 값이 나오는 것이다.


.386
.MODEL    FLAT
PUBLIC    _Emb
.CODE
_Emb    PROC    NEAR32

    push    ebp            ;base pointer 값을 스택에 밀어 넣는다. Entry Code
    mov    ebp, esp      ;esp와 ebp를 같은 위치에 자리시킨다. Entry Code

    ;sub    esp, 8
    ;mov    [ebp - 4], 50   
    ;mov    [ebp - 8], 25    ;변수를 쓸 경우 필요한 코드이다.

    mov    eax, [ebp + 8]    ;첫번째 인자 땡겨옴 eax = 3
    add    eax, [ebp + 12]   ;두번째 인자 더함 eax = eax + 4
   
    mov    esp, ebp    ;Exit Code
    pop    ebp           ;Exit Code
    ret                      ;Exit Code
_Emb    ENDP

END


인자가 위치한곳을 Proc의 ebp를 기준으로 잡아준다.

(어셈블리는 주소연산없고 4바이트 단위임)

인자의 위치는 함수호출 규약에 따른다. (stdcall, cdecl ....)


어셈블리 코드에서 함수를 만들 때, entry code 2줄과 exit code 3줄이 필요하다.

프록시저에서 스택을 새로 만들기 위해 ebp와 esp를

재설정 해줘야 하는데, 이 레지스터들은 하나만 존재한다.

하지만 함수가 끝나면 원래의 값 (즉, main :: stack의 앞뒤)으로 돌아가야 한다.

그래서 ebp값을 스택에 백업해야 하는 과정에서 생겨나는 코드이다.

entry code는 ebp 값을 push한 후에 ebp값의 재설정

exit code는 esp값을 재설정 한 후에 ebp값 pop 그리고 return까지

즉, 레지스터를 함수 시작전의 값으로 되돌리는 과정이다.


* 함수호출시 약속


함수호출 전후로 레지스터값이 변하면 안된다.

(단, eax는 변해도 상관이 없다)


그래서 eax를 제외한 나머지 레지스터를 사용해야 할 경우

push를 이용하여 stack에 적재하고 난뒤 (백업) 사용하고,

그 값을 pop을 이용해서 값을 복원한다.


.386
.MODEL    FLAT
PUBLIC    _Emb  
.CODE     
_Emb    PROC    NEAR32

    push    ebp        ;Entry Code
    mov    ebp, esp    ;Entry Code

    push    ebx       
    push    ecx        ;사용할 레지스터 값을 push한다.
   
    mov    ebx, [ebp + 8]
    mov    ecx, [ebp + 12]
    mov    eax, [ebp + 16]

    add    ebx, ecx
    add    eax, ebx
   
    pop    ecx
    pop    ebx        ;사용한 레지스터를 pop한다.

    mov    esp, ebp    ;Exit Code
    pop    ebp        ;Exit Code
    ret            ;Exit Code
_Emb    ENDP   

END  

* 어셈블리 코드에서 포인터를 사용해보자


Emb(3, &iNum);


.386
.MODEL    FLAT
PUBLIC    _Emb   
.CODE       
_Emb    PROC    NEAR32

    push    ebp        ;Entry Code
    mov    ebp, esp    ;Entry Code

    push    ebx        ;ebx쓸꺼니까 백업해둔다


    mov    eax, [ebp + 8]
    inc    eax
    mov    ebx, [ebp + 12]    ;iNum의 주소를 레지스터로 이동
    mov    [ebx], eax        ;포인터 사용이다.


    pop    ebx        ;ebx를 불러낸다
    mov    eax, 0        ;안전할수 있다.

    mov    esp, ebp    ;Exit Code
    pop    ebp        ;Exit Code
    ret            ;Exit Code
_Emb    ENDP  

END  



역시나 인자는 ebp를 기준으로 컨트롤한다

ebx에 iNum의 주소를 넣고, 포인터를 이용한다.

C에서는 *를 이용하지만, 어셈블리에서는 []를 이용한다.


* 어셈블리로 더블 포인터


.386
.MODEL    FLAT
PUBLIC    _Emb   
.CODE       
_Emb    PROC    NEAR32

    push    ebp        ;Entry Code
    mov    ebp, esp    ;Entry Code

    push    ebx        ;ebx쓸꺼니까 백업해둔다


    mov    eax, [ebp + 8]
    dec    eax
    mov    ebx, [ebp + 12]    ;ebx -> p
    mov    ebx, [ebx]    ;p -> iNum (더블 포인터 처리)
    mov    [ebx], eax    ;iNum = 2


    pop    ebx        ;ebx를 불러낸다
    mov    eax, 0        ;안전할수 있다.

    mov    esp, ebp    ;Exit Code
    pop    ebp        ;Exit Code
    ret            ;Exit Code
_Emb    ENDP  

END  



특별한 점은 없다.

C언어에서 처럼 더블포인터 써주면 된다.

mov    ebx, [ebx]

가 좀 신기하긴 했음.


* 어셈블리 코드에서 구조체 컨트롤


Emb(&st);    //st는 구조체이다.


.386
.MODEL    FLAT
PUBLIC    _Emb
.CODE      
_Emb    PROC    NEAR32

    push    ebp        ;Entry Code
    mov    ebp, esp    ;Entry Code

    push    eax
    push    ebx
   
    mov    eax, [ebp + 8]    ;eax가 구조체의 처음을 가르킨다.

    mov    ebx, [esp + 4]    ;ebx에 esp + 4(old eax)값을 대입
    mov    [eax], ebx    ;구조체의 처음에 old eax 넣음

    mov    ebx, [esp]    ;ebx값이 변해서 esp(old ebx)값 복구
    mov    [eax + 4], ebx    ;구조체에서 4만큼 떨어진곳에 ebx 대입
   
    mov    [eax + 8], ecx    ;구조체에서 8만큼 떨어진곳에 ecx 대입

    mov    [eax + 12], edx    ;구조체에서 8만큼 떨어진곳에 ecx 대입
   
    mov    eax, 0        ;안전할수 있다.
   
    pop    ebx
    pop    eax       
   
    mov    esp, ebp    ;Exit Code
    pop    ebp        ;Exit Code
    ret            ;Exit Code
_Emb    ENDP    ;ENDP 함수의 끝 }

END    ;.code END가 한쌍


(그림그리다가 표현의 한계점에...........)

우선 구조체를 컨트롤하기 위해서는 최소 2개의 레지스터가 필요하기 때문에

eax, ebx를 푸쉬합니다.


다음 eax가 구조체 st를 가리키게 합니다.

mov    eax, [ebp + 8]


이제 eax값을 st.eax에 넣어야 하는데 우리가 필요한 값은 old eax이기 때문에

[esp + 8]값을 [eax]에 넣어야 합니다.

그런데 memory to memory가 불가능 하기 때문에

일단 ebx에 [esp + 8]값을 저장한 다음 [eax]에 ebp값을 넣어야 합니다.

mov    ebx, [esp + 4]
mov    [eax], ebx 


이제 ebx값을 st.ebx에 넣어야 하는데 ebx는 앞에서 사용했고,

우리가 필요한 값은 old ebx가 됩니다.

그래서 ebx의 값을 복구 시키고 [eax + 4]의 자리에 넣어줘야 합니다.

mov    ebx, [esp]
mov    [eax + 4], ebx


이제는 ecx와 edx값을 구조체에 넣어줘야 하는데

ecx와 edx는 건드린적이 없고 memory to memory가 아니기 때문에

그냥 넣어주면 됩니다.

mov    [eax + 8], ecx   
mov    [eax + 12], edx


이 후 레지스터값을 원상복구 시키고 리턴하면 됩니다.


728x90