Assembly2010. 1. 5. 01:36



.model large
.386
.stack 100h
.data
    num  word 0f3f2h
.code
L_start:
    mov ax, @data
    mov ds, ax
   
    mov dx, num
    call printDecimal
   
    mov ah, 4ch
    int 21h

;---------------------------------
; 헥스값을 10진수로 변환
; 입력값 dx : 16-bit unsigned
;
; F3F2라는 숫자를 10진수로 출력하려면 F3F2라는 숫자를 10으로 나누어
; 나머지를 스택에 차례로 저장했다가 복원하면서 하나씩 아스키문자로
; 변환해서 출력해 주면 됩니다.
; F3F2 / 10 = 1865....0
; 1865 / 10 = 270.....5
; 270 / 10  = 3E......4
; 3E / 10   = 6.......2
; 6 / 10    = 0.......6
; 각 자리를 푸쉬하는 이유는 각 연산의 결과 05426이 됩니다. 그렇기
; 때문에 자리를 뒤집어서 찍어줘야 합니다. 이런 경우 스택을 사용하면
; 간단히 해결할 수 있습니다. 스택의 구조는 후입선출로서 나중에 들어
; 간 것이 먼저 나오므로 각 연산의 결과를 스택에 푸쉬해 두고 차례로
; 팝하면서 찍어주면 62450이 정상적으로 찍히게 됩니다.
;
; 각 자리를 10으로 나누기 때문에 9이상의 숫자가 나머지로 나올 수
; 없습니다. 10이 나온다면 10으로 나누어 떨어져 결국 나머지는 0이기
; 때문입니다.
; 0~9는 10진수와 16진수가 공통으로 가지고 있는 부분이기
; 때문에 다른 변환없이 바로 사용할 수 있습니다. 때문에 16진수를
; 출력하는 프로시져로 출력해도 정상적인 10 진수를 출력할 수 있습니다.
printDecimal proc
        pusha                  ; 레지스터 보호
        mov ax, dx             ; 피젯수를 ax에 저장
        mov si, 10             ; 젯수를 si에 저장
        xor cx, cx             ; cx를 0으로 초기화
NONE_ZERO:
        xor dx, dx             ; dx를 0으로 초기화
        div si                 ; ax의 내용을 si의 값으로 나눔
        push dx                ; 나머지 dx를 스택에 저장
        inc cx                 ; cx 증가시킴
        or ax, ax              ; ax 가 0인가 ?
        jne NONE_ZERO          ; 0이 아니면 NONE_ZERO로 이동
WRITE_DIGIT_LOOP:
        pop dx                 ; 푸쉬한 수만큼 스택에서 꺼내면서 출력하기
        call printHex             ; 16진수 출력 루틴
        loop WRITE_DIGIT_LOOP
END_DECIMAL:
        popa                   ; 레지스터 보호 해제

        ret                    ; 프로시져 리턴
printDecimal endp

;--------------------------------
; 헥스값이나 10진수 값을 화면에 출력
; 입력값 dl : 8-bit 의 출력하고자하는 니블의 값
printHex proc
        push dx         ; 레지스터 보호
        push ax        

        cmp dl, 10              ; dl을 10과 비교  
        jae HEX_LETTER    ; 10보다 크거나 같으면 16진수 A~F에 해당하는 아스키 출력
        add dl, '0'              ; 10보다 작으면 '0'의 아스키코드 30h를 더해 실제 아스키코드 만듦
                        ; 가령 dl이 9이면 30+9=39 39는 아스키코드 '9'임                                      
                   
        jmp short WRITE_DIGIT   ; 아스키코드 출력 루틴으로 이동
HEX_LETTER:
        add dl, 'A'-10         ; 10이상인 경우 10~15까지의 숫자가 A~F로 할당된다. dl이 11이라면
                    ; 'A'에 해당하는 아스키코드 41h + 0Bh = 4Ch가 되어 'L'에 해당하는
                    ; 아스키코드가 되어 버린다. 여기서 변환하는 목적은 실제 수를 변환
                    ; 하거나 연산하는 것이 아니고, 단순히 그 숫자에 해당하는 아스키
                    ; 코드를 찍어주는 것이다. 그러므로 10~15의 숫자중 1의 자리에 해당
                    ; 하는 0~5만 있으면 A~F의 아스키코드에 짝지울 수 있다.
                    ; 그래서 아스키코드'A'에서 10을 빼주는 것이다. dl이 11이면 10을
                    ; 빼서 1로 만들고 거기에 아스키코드'A'를 더해주면 아스키코드'B'
                    ; 가 나와서 정확한 수치표현을 할 수 있다. 여기서 sub dl, 0Ah를
                    ; 하고 add dl, 'A'를 하지 않는 이유는 불필요한 코드를 줄이기 위해서
                    ; 이다. 'A'-10은 컴파일시에 자동으로 41h-0Ah=37h 하여 37h값으로
                    ; 대치하여 컴파일 해준다. 어째됐건 우리의 목표인 10을 빼는데 문제
                    ; 가 없으므로 필요없는 2줄의 코드를 한줄로 줄여주는 효과가 있다.
WRITE_DIGIT:
        mov ah, 2         ; 도스기능 호출 2번으로 dl에 저장된 하나의 문자를 출력
        int 21h
                
        pop ax             ; 레지스터 복원
        pop dx

        ret
printHex endp

          end L_start   

Posted by houdinist
Assembly2010. 1. 5. 00:47
1. 클래스의 등장 배경
        
1) 초기에는 순차적으로 쭉 코드를 써내려갔습니다.
        
2) 하지만 같은 코드가 계속 반복되어 비효율적입니다. 그래서 같은 코드는 한번만 코딩해 주고 필요할때 불러서 쓰는 서브루틴 개념이 생겼습니다. call 과 ret 개념이 생긴거지요.        
        
3) 역시나 서브루틴은 너무 하나의 프로그램에 종속적이기 때문에 다른 프로그램에서 재사용하기 힘듭니다. 그래서 생겨난 것이 함수입니다. 각종 인자를 넘기고 리턴값을 받을 수 있습니다. 이로서 프로그램간에 코드를 공유하기 쉬워졌습니다.
        
4) 하지만 여러 프로그램에서 같이 쓰기 위해서는 너무 많은 인자를 넘겨줘야 하는 경우가 생겼습니다. 그래서 생각한것이 필요한 인자를 구조체로 만들어서 구조체주소를 넘겨 주는거지요.
        
5) 그러나, 문제가 있었습니다. 이 구조체란게 너무 함수에 밀접하게 관계가 있어서 다른 곳에서는 쓰이지 않습니다. 게다가 이 함수를 쓰기 해서는 어떤 구조체를 쓰는지도 알아야 합니다. 반대로 이 구조체가 쓰는 함수를 알아내는 것도 쉽지 않습니다.
        
6) 그래서, 클래스가 등장하게 된것입니다. 위에서 본 것과 같이 클래스란 단순히 함수가 쓰는 구조체와 함수를 함께 묶어주는 역활을 하는것입니다. 이로써 구조체와 관련함수가 하나로 묶여서 코드의 재사용성이 증가합니다.

2. 클래스의 구조        
        
1) 그럼 클래스의 구조를 보도록 하죠.        
        
class CTest        
{       
private:                
WORD        wFirst;        
public:                
WORD        Add(WORD wValue);        
};
        
위와 같이 클래스가 있다고 합시다. 여기서 주의해야할 점이 있습니다.        
private: public: 같은 키워드에 의해 데이터가 보호되는 것은 단지 컴파일러에서 지원해 주는 기능일 뿐이지 바이너리코드 상의 차이점은 아니라는 점입니다. private: 이나 public:은 바이너리 코드상에서는 모두 같다는 겁니다. 다시 한번 말씀드리지만, 이 키워드들은 단지 컴파일 과정에서 컴파일러가 에러를 내주는 것이라는 겁니다.
        
이제 이 클래스를 어셈블리로 변환해 보죠..
        
CTest struct                
wFirst                word        ?                
AddTest             dword      AddFunc        
CTest ends
        
이게 전부입니다. 간단하지요 ?  단지 함수선언은 함수포인터로 대치되는게 차이점이라면 차이점입니다. 이 함수 포인터가 바로 계승에 절대적인 도움을 주는 점입니다.
        
이 클래스를 계승한 자식클래스를 만든다면 다음과 같이 하면 됩니다.
        
CTestChild struct                
CTest { ?,AddNewFunc }        
CTestChild ends
        
단순히 자식클래스 상단에 부모구조체를 써주면 됩니다. 그리고 이 예제에서는 메서드를 자식함수로 오버라이드하고 있습니다. 자식클래스에 프로퍼티와 메소드를 추가한다면 다음과 같겠지요.
        
CTestChild struct                
CTest { ?,AddNewFunc }                
wSecond      word         ?                
Multiply        dword        MultiplyFunc        
CTestChild ends
        
이로써 자식클래스만의 프로퍼티와 메소드를 정의하였습니다. 간단하지요 ?
        
하지만 주의해야 할 점이 있습니다. 부모구조체가 항상 자식구조체 시작위치에 있어야 한다는 점입니다. 이렇게 하야만 계승된 필드가 같은 옵셋에 치해 있다는것을 보장할 수 있습니다.

Posted by houdinist
Assembly2010. 1. 5. 00:37
1. 세그먼트 정의
    세그먼트이름 segment 옵션
    세그먼트이름 ends

    옵션 :
    1) alignment type
       BYTE, WORD, DWORD, PARA, PAGE으로 1,2,4,16,256의 배수에 시작
       지정하지 않으면 디폴트로 PARA
  
    2) combine type
       다른 세그먼트와 결합시키는 방식으로, PUBLIC, COMMON, STACK,
       PRIVATE, AT

    3) class type
       ''사이에 넣으며 세그먼트를 하나로 합치는게 아니라 독립적으로 존재
       하며, 단지 기억장소가 인접해 있음.

2. 프로시져 정의
    프로시져이름 proc 옵션
    프로시져이름 endp

    옵션 :
    1) NEAR : 옵션주소만 저장한다.
    2) FAR  : 세그먼트와 옵셋주소를 저장한다.

3. 지역변수와 매개변수의 사용

.model huge, stdcall
option casemap:none
.286

dseg segment
     char db "1234567890",'$'
dseg ends

sseg segment stack
     byte 100h dup('stack')
sseg ends

cseg segment
main proc near
     assume cs:cseg, ds:dseg, ss:sseg
     mov ax, dseg
     mov ds, ax

     mov ax, offset char
     push ax ; 매개변수로 주소를 넘김 (2바이트)
     call TTT

     mov ah, 4Ch
     int 21h
     main endp

TTT proc near

     push bp     ; bp를 저장
     mov bp, sp  ; 스택포인터를 저장
     sub sp, 2   ; 지역변수를 위해 2바이트 확보

     mov ah, 9
     mov dx, [bp+4] ; near 방식이므로 주소값과 bp크기만큼 더해 4
                          ; far 방식일때는 세그먼트까지 [bp+6]이 된다.
     int 21h

     mov byte ptr [bp], 'A' ; 확보한 첫바이트에 'A' 저장
     mov ah,2
     mov dl, [bp]
     int 21h

     mov byte ptr [bp+1], 'B' ; 확보한 두번째 바이트에 'B' 저장
     mov ah,2
     mov dl, [bp+1]
     int 21h

     add sp, 2 ; 지역변수를 해제, 즉 할당한 만큼스택크기를 줄인다
     pop bp     ; bp 복구
     ret
TTT endp
cseg ends

     end main

invoke 사용시는 local 변수이름:크기 를 사용해서 지역변수를 사용할 수 있
습니다. 하지만 결국은 위과 같은 방식으로 바뀌므로 기본적인 방법에 익숙해 지는 것이 좋겠지요.

4. 매크로의 정의
   매크로이름 macro 인자
   endm

   1) 매크로를 지정하면 매크로이름을 쓴곳에 정의한 부분으로 치환하여
      컴파일해 줍니다.

   2) 인자 역시 매크로 안에서 해당값으로 치환되어 들어갑니다.

   3) 인자:req 로 지정하면 항상 인자값을 넣어주어야 합니다. 반드시
      필요한 인자인데 넣지 않는 버그를 없애기 위해 컴파일 에러를 내줍
      니다.

   4) 소스에 내용이 치환되는 관계로 라벨이름이 중복되어 사용될 수 있습
      니다. 그래서 LOCAL 라벨이름을 이용하여 컴파일러가 중복되지 않는
      라벨을 자동으로 생성하게 합니다.
      여기서 중요한게 프로시져 내부에서도 local 을 사용하는데, 프로시져에
      서 local은 지역변수 선언을 의미하며, 스택에 지정한만큼 메모리를 잡아
      줍니다. 매크로의 local과 프로시져의 local을 구별해 두세요.


5. EXTERN, PUBLIC의 사용
   1) PUBLIC : 다른 파일에서 변수나 프로시져를 사용할 수 있게 이름을 외부
                로 노출시킵니다. 프로시져에서 PROTO를 사용할 경우 따로
                PUBLIC을 선언할 필요는 없습니다.

   2) EXTERN : 다른 파일에서 변수나 프로시져를 참조할때 사용합니다.


6. proto, invoke의 사용
    invoke문을 사용하기 위해서는 프로그램 선두에 사용할 프로시져의 인자
    를 미리 선언해야 합니다.

    프로시져이름 proto :byte, :byte  <- 바이트 2개를 받는 프로시져 선언

    invoke 프로시져이름 인자이름:byte, 인자이름:byte
    인자이름을 바로 사용해서 인자를 참조할 수 있습니다. invoke문은
    push 인자
    call 프로시져
    를 단순화한 명령이라고 할 수 있습니다. 결국은 위에서 설명한 방식으로
    매개변수와 지역변수를 사용합니다. 이 방식이 좋은 이유는 간단하면서
    인자를 크기에 맞게 자동캐스팅해줍니다. 그밖에 여러가지 잇점이 있습니
    다.
Posted by houdinist