'공부합시다/어셈블리어'에 해당되는 글 7건

  1. 2009.11.03 STST와 LDST
  2. 2009.05.14 [어셈블리어] push와 pop의 구분동작
  3. 2009.05.14 [어셈블리어] PUSHA와 POPA
  4. 2009.05.11 [어셈블리어] 데이터 전송 명령어 mov, movzx, movsx
  5. 2009.05.08 [어셈블리어] 곱셈 - MUL, IMUL
  6. 2009.05.06 [어셈블리어] 나눗셈
  7. 2009.05.06 [어셈블리어] data 세그먼트와 bss 세그먼트
2009. 11. 3. 12:31

STST와 LDST

STST 와 LDST 구현하기

file : main.c
typedef struct _Context
{

    int   efl;
    int   eip;
    int   edi;
    int   esi;
    int   ebp;
    int   esp;
    int   ebx;
    int   edx;
    int   ecx;
    int   eax;
}Context;

void LDST(Context *);
void STST(Context *);

int main()
{
    Context stat;
    int i;
    memset(&stat, 0,sizeof(Context));
    printf("시작\n");
    printf("efl = [%08X]\n", stat.efl);
    printf("eip = [%08X]\n", stat.eip);
    printf("edi = [%08X]\n", stat.edi);
    printf("esi = [%08X]\n", stat.esi);
    printf("ebp = [%08X]\n", stat.ebp);
    printf("esp = [%08X]\n", stat.esp);
    printf("ebx = [%08X]\n", stat.ebx);
    printf("edx = [%08X]\n", stat.edx); 
    printf("ecx = [%08X]\n", stat.ecx);
    printf("eax = [%08X]\n", stat.eax); 

    STST(&stat);

    printf("\n");
    printf("여기는 과거\n");

    printf("efl = [%08X]\n", stat.efl);
    printf("eip = [%08X]\n", stat.eip);
    printf("edi = [%08X]\n", stat.edi);
    printf("esi = [%08X]\n", stat.esi);
    printf("ebp = [%08X]\n", stat.ebp);
    printf("esp = [%08X]\n", stat.esp);
    printf("ebx = [%08X]\n", stat.ebx);
    printf("edx = [%08X]\n", stat.edx); 
    printf("ecx = [%08X]\n", stat.ecx);
    printf("eax = [%08X]\n", stat.eax); 

    printf("0 == Exit, 1 == First\n");
    scanf("%d", &i);

    if(1 == i)
    {
        LDST(&stat);
        printf("실패!!!");
    }

    return 0;


file : asm.asm

segment .text
    global    _STST
    global    _LDST

_STST:
    push     ebp
    mov     ebp, esp

    pushf
    mov    esp, [ebp+8]    ;context 의 시작 주소
    add    esp, 40        ;context 의 끝 주소
    pusha        ; ax, cx, dx, bx, sp, bp, si, di

    mov    eax, [ebp+4]    ;eax = eip(return address)
    push    eax        ;context.eip = eip

    mov    eax, [ebp-4]    ;eax = efl
    push    eax        ;context.efl = efl
    mov    eax, [ebp]    ;eax = before ebp
    mov    [esp+16], eax    ;context.ebp = ebp(before)
    mov    eax, ebp   
    add    eax, 8        ;eax = before esp
    mov    [esp+20], eax    ;context.esp = esp(before)

    mov     esp, ebp
    pop     ebp

    ret

_LDST:
    mov    esp, [esp+4]    ;esp = address of context
    popf                ;efl = context.efl

    pop    eax            ;eax = context.eip
    mov    ebx, esp        ;ebx = esp
    mov    esp, [esp+12]    ;esp = context.esp
    push     eax            ;esp(before) = eip

    mov    esp, ebx        ;esp(before) = esp(now)

    popa                ;di, si, bp, bx, dx, cx, ax

    mov    esp, [esp-20]    ;esp = context.esp(before)
    sub    esp, 4        ;esp(before) = eip(before)

    ret

Trackback 0 Comment 0
2009. 5. 14. 17:27

[어셈블리어] push와 pop의 구분동작

push eax
 1. --esp
 2. mov [esp], eax

스택 포인터를 하나 감소 시키고(즉, 스택포인터를 올리고) 값을 넣는다.(스택은 아래서부터 저장되므로 스택이 쌓일수록 스택 포인터가 가르키는 주소의 숫자는 감소한다.)


pop eax
 1. mov eax, [esp]
 2. ++esp

 스택 포인터가 가르키는 메모리 주소의 값을 레지스터에 저장하고, 스택포인터를 증가 시킨다.(즉, 스택포인터를 내린다.)
Trackback 0 Comment 0
2009. 5. 14. 14:07

[어셈블리어] PUSHA와 POPA

PUSHA : Push All
스택에 EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI 순으로 모두 저장한다.

POPA: Pop All
스택에서 EDI, ESI, EBP, EBX, EDX, ECX, EAX 순으로 꺼낸다.
(ESP는 복원하지 않는다.)
Trackback 0 Comment 0
2009. 5. 11. 16:10

[어셈블리어] 데이터 전송 명령어 mov, movzx, movsx

MOV 명령

소스 피연산자로부터 도착점 피연산자로 데이터를 이동시킨다.

데이터 전송 명령으로 알려진 이 명령은 실질적으로 모든 프로그램에서 사용된다.

이 명령의 기본 포멧은 첫 번째 피연산자가 도착점이고 두 번째 피연산자가 소스이다.

MOV destination,source

오른쪽에서 왼쪽으로의 데이터 이동은 C++ 이나 자바에서의 할당 문과 유사하다

dest=source;

[거의 모든 어셈블리 언어 명령에서,

왼쪽 피연산자는 도착점이고,

오른쪽 피연산자는 소스이다.]

MOV는 피연산자의 사용에 있어서 다음의 규칙만을 따르면 된다.

1. 피연산자는 같은 크기여야 한다.

2. 피연산자는 모두 메모리 피연산자일 수 없다.

3. CS, EIP, IP는 도착점 피연산자가 될 수 없다.

4. 즉시값은 세그먼트 레지스터로 이동될 수 없다.

 

세그먼트 레지스터는 프로그램이 실제 주소 모드로 동작될 때만 사용할 수 있다.

CS가 도착점 피연산자로 사용될 수 없다는 것을 제외하고 다음과 같은 옵션이 가능하다.

MOV r/m16, sreg

MOV sreg,r/m16

 

메모리에서 메모리로 MOV 명령은 하나의 메모리 위치에서 다른 위치로 데이터를 이동시키는 데 사용할 수 없다.

대신에 도착점 피연산자로 이동하기 전에 소스 피연산자를 레지스터로 이동시킬 수 있다.

.data

var1 WORD ?

var2 WORD ?

.code

mov ax,var1

mov var2,ax

 

변수나 레지스터로 이동될 때 정수 상수가 몇 바이트가 필요한지를 먼저 고려해야만 한다.

 

작은 피연산자에서 큰 피연산자로 정수를 복사하기 위하여 MOV 명령을 사용하는 것은 오류를 발생시킨다.

그러나 때때로 그렇게 할 필요성이 있을 때가 있다.

count(부호가 없는 16비트)를 ECX(32비트)로 옮겨야 한다고 가정하자. 간단한 해결방법은 ECX를 0으로 지정하고 난 후 count를 CX로

옮기는 것이다.

.date

count WORD 1

.code

MOV ecx, 0

MOV cx, count

 

만약 부호가 있는 정수 -16을 같은 방법으로 시도한다면 어떤일이 일어나는가?

.data

signedVal SWORD -16                          ; FFF0h(-16)

.code

MOV ecx, 0

MOV cx, signedVal                              ; ECX = 0000FFF0h (+65520)

 

ECX(+65520)값은 -16 과 완전히 다른 값이다. 반면에, ECX를 FFFFFFFFh로 채운 후 signedVal을 CX에 복사한다면 마지막 값은 올바를 것이다.

 

MOV ecx, 0FFFFFFFFh                         //0FFFFFFFFh

MOV cx, sigendVal                              ; ECX=FFFFFFF0h

 

이것은 부호가 있는 정수를 다룰 때 문제를 야기한다. 도착점 피연산자를 어떻게 채울지 결정하기 위하여 그 수가 양수인지 음수인지를 점검해야 하는 것은 불편하다. 다행스럽게도 인텔 공학자들은 인텔 386 프로세서를 설계할 때 이 문제를 고려하였고 부호가 없는 정수와 부호가 있는 정수를 다루기 위한 명령어로 <OVZX와 MOVSX 명령어를 도입하였다.

예제)  aaa.asm 파일

TITLE move
INCLUDE Irvine32.inc
.data
             signedVal SWORD -16
.code
main PROC
             mov ecx,0FFFFFFFFh
             mov cx,signedVal
             call DumpRegs
             exit
main ENDP
END main

 

MOVZX 명령 (부호가 없는)

소스 피연산자를 도착점 피연산자로 복사하고 그 값을 16비트나 32비트로 확장한다. 이 명령은 부호가 없는 정수에만 사용된다.

3개의 변형이 가능하다.

 

MOVZX      r32, r/m8                                    //[32비트 범용 레지스터], [8비트 피연산자,8비트 범용레지스터나 메모리 바이트]

MOVZX      r32,r/m16

MOVZX      r16,r/m8

 

첫 번째 피연산자는 도착점이고 두 번째가 소스이다. 도착점은 레지스터여야만 한다.

다음 그림은 8비트 소스 피연산자가 제로 확장되어 16비트 도착점으로 이동하는 모습을 보여 준다.

 

 


다음 예는 피연산자가 모두 레지스터일 때 크기의 변형을 보여 준다.

MOV bx,0A69Bh

MOVZX EAX, BX                                     ; EAX = 0000A69Bh          //16비트 BX의 값이 이동

MOVZX EDX, BL                                     ; EDX = 0000009Bh           //8비트 BL(BX의 하위 8비트)값이 이동

MOVZX CX, BL                                       ; CX = 009Bh                   //8비트 BL값이 이동

 

다음의 에는 소스를 메모리 피연산자를 사용한 경우로서 같은 결과를 만든다.

 

.data

byte1 BYTE 9Bh

word1 WORD 0A69Bh

.code

MOVZX EAX, word1                       ; EAX = 0000A69Bh

MOVZX EDX, byte1                        ; EDX = 0000009Bh

MOVZX CX, byte1                          ; CX = 009Bh

 

MOVSX 명령 (부호가 있는)

 

소스 피연산자의 내용을 도착점 피연산자로 복사 후 16비트 혹은 32비트로 값을 부호 확장한다.

이 명령어는 부호가 있는 정수에만 사용된다. 3개의 변형이 있다.

 

MOVSX r32, r/m8

MOVSX r32, r/m16

MOVSX r16, r/m8

 

작은 피연산자의 최상위 비트를 취하고 이를 도착점 피연산자의 확장 비트에 반복적으로 복사하여 부호 확장을 한다.

예를 들어, 8비트 값 10001111b 가 16비트 도착점으로 이동한다면, 하위 8비트는 그대로 복사된다. 다음에 다음 그림에서

보는 것처럼 소스의 최상위 비트는 도착점의 상위 8비트에 복사된다.

 


다음의 예는 피연산자가 모두 레지스터일 때의 경우이다.

 

MOV BX, 0A69Bh

MOVSX EAX, BX                                       ; EAX = FFFFA69Bh

MOVSX EDX, BL                                       ; EDX = FFFFFF9Bh

MOVSX CX, BL                                         ; CX = FF9Bh

 

예제)  bbb.asm 파일

TITLE move
INCLUDE Irvine32.inc
.code
main PROC 
             MOV BX, 0A69Bh
             MOVSX EAX, BX
             MOVSX EDX, BL
             MOVSX CX, BL
             call DumpRegs 
             exit
main ENDP
END main

Trackback 0 Comment 0
2009. 5. 8. 15:01

[어셈블리어] 곱셈 - MUL, IMUL

mul source

mul(부호없는 곱셈)
source 자리에는 레지스터나 주소가 올 수 있으며, 즉시값(상수)은 올 수 없다.
source에 1바이트가 오면 AL(1바이트)과 곱해서 AX(2바이트)에 저장된다. source에 2바이트가 오면 AX와 곱해서 DX:AX에 저장한다. source가 4바이트라면, EAX와 곱해서 EDX:EAX에 저장된다.
(1자리의 숫자 두개를 곱하면 최고 2자리의 숫자가 나올 수 있다. 따라서 1바이트를 2개 곱하면 2바이트의 저장공간이 필요하다.)



imul  dest, source1
imul  dest, source1, source2

imul(부호있는 곱셈)
dest
source1
source2
Action

reg/mem8

AX = AL*source1

reg/mem16

DX:AX = AX*source1

reg/mem32

EDX:EAX=EAX*source1
reg16
reg/mem16

dest *= source1
reg32
reg/mem32

dest *= source1
reg16
immed8

dest *= immed8
reg32
immed8

dest *= immed8
reg16 immed16

dest *= immed16
reg32 immed32

dest *= immed32
reg16 reg/mem16
immed8
dest=source1*source2
reg32 reg/mem32 immed8 dest=source1*source2
reg16 reg/mem16 immed16 dest=source1*source2
reg32 reg/mem32 immed32 dest=source1*source2

reg = 레지스터, mem = 메모리 주소내의 값, immed = 즉시값(상수)
Trackback 0 Comment 0
2009. 5. 6. 17:28

[어셈블리어] 나눗셈

1. 나눗셈 연산의 피젯수는(32bit의 나눗셈을 가정) 항상 edx:eax 이다.
2. cdq 는 나눗셈을 위해 피젯수의 사이즈를 확장하는 것이다.


나눗셈연산(div, idiv)은 eax와 edx에 의해서만 이루어집니다.
- 피젯수(나눔을 당하는 수) 는 eax, edx에만 들어갈 수 있다는 얘기에요
16 / 5 연산을 한다고 가정해 봅시다.

16과 5 둘다 32bit data라고 가정하구요

그럼 일단 eax에 16을 넣습니다. 그 다음 ebx(다른레지스터나 메모리도 상관없음)에

5를 넣습니다. 그 다음 div 연산을 하면.........될것 같지만 안됩니다..

일반적으로 제수(여기서는 5)가 32bit이면 피젯수(여기서는 16) 는 64bit가 되어야

32bit 값을 가지는 몫을 얻을 수 있습니다.

그래서 피젯수의 bit를 확장 시켜주는것이 바로 cdq 연산입니다

32bit 크기의 eax의 값을 64bit의 값인 edx:eax로 만들어줍니다.

여기서 edx는 상위자리가되고 eax는 하위 자리가 되죠

자..그럼 cdq 연산까지 끝났으면 edx:eax에 16이 들어가있고 ebx에 5가 들어있겠네요

그럼 idiv연산을 해봅시다(div는 부호가없는 나눗셈 idiv 부호가 있는 나눗셈)

그럼 몫과 나머지가 나와야 하겠죠? 그 결과는 다시 eax와 edx로 들어가는데

eax에는 몫이, edx에는 나머지 부분이 들어갑니다.


출처: 내 심장이 0hz가 되는 순간까지...  (http://zerohz.tistory.com/61)

Trackback 0 Comment 0
2009. 5. 6. 17:15

[어셈블리어] data 세그먼트와 bss 세그먼트

segment .data
msg    db    "Hello world", 0

segmen .bss
input    resd    1

위의 어셈블리어 코드를 보면, 두가지의 세그먼트를 선언하는 것을 볼 수 있다.

data segment에는 initialized data segment(초기화 된 데이터 세그먼트)와 uninitialized data segment(초기화 되지 않은 데이터 세그먼트)가 있는데 segment .data는 전자, segment .bss는 후자를 가르킨다.

즉, segment .data 에서는 초기값이 필요한 전역변수를 선언하고, segment .bss에서는 초기화가 필요없는 전역변수를 선언한다.

segment .bss에서는 RAM에 해당 영역만큼의 공간만을 잡아준다. 하지만 segment .data에서는 RAM을 할당할 뿐만 아니라 해당데이터의 초기 값을 지니고 있을 ROM또한 할당되어야 한다.

따라서, bss영역은 초기화 과정에서 컴파일러에서 지정한 초기화 방식에 의하여 초기화되며, data부분은 ROM에 있는 초기화된 데이터를 해당 data영역으로 복사함으로서 수행하게 된다.

즉, 위의 소스를 해석하면

msg라는 변수에 "Hello world\0"

Trackback 0 Comment 0