태초의 프로그래밍 언어 어셈블리

assembly, 8086, x86

변수

변수는 그냥 메모리의 위치입니다. 그런데 메모리의 위치는 숫자이고 숫자는 사람이 기억하기에 어렵지요. 그래서 어셈블러에서도 변수를 만들어서 쓸 수 있도록 해줍니다. 가만 생각해보면 이전 글에서 0b800h:0h 위치에 데이터를 썼는데요 이 메모리 번지를 변수로 만들어 놓으면 간편해지지 않을까요?(주1)

일단 간단한 변수를 만들어보겠습니다. 백문이 불여일코라 백번 들어도 한번 코딩해보는게 더 잘 이해되지요. 다음 코드를 실행해보겠습니다.

ORG 100h
 
MOV AL, var1
MOV BX, var2
 
RET    ; stops the program.
 
VAR1 DB 7
var2 DW 1234h

이전에도 말씀드렸듯이 대소문자 구분은 하지 않습니다. VAR1이나 var1이나 같습니다. db는 define byte 혹은 data byte라고 읽을 수 있습니다. 한바이트의 변수를 선언하는 것입니다. dw는 한 워드의 변수를 선언하는 것입니다.

에물레이터를 실행해서 프로그램을 실행해보면 AL과 BX 값이 7과 1234h로 변하는 것을 확인할 수 있습니다.

간단하지요? 그런데 여기에 더 중요하게 봐야할 것이 있습니다. 변수는 메모리의 위치라고 말씀드렸습니다. 우리가 만든 var1, var2의 메모리 위치는 어디는 어디일까요? 우리는 메모리의 어디를 변수로 이름지은 걸까요?

해답은 에물레이터에 있습니다. 우리가 만든 변수의 값이 7과 1234h입니다. 에물레이터 어딘가에 7과 1234가 보이면 그게 우리가 만든 변수의 위치입니다.

ret 명령어에 해당하는 숫자는 C3입니다. C3 명령은 메모리 7107h번지에 저장되어 있습니다. ret 명령이 실행되면 프로그램은 끝나게 되는데요 바로 ret 명령 다음에 07과 3412가 보입니다.

그리고 에물레이터 화면 오른쪽을 보면 mov al, [00108h] 라고 써진게 보입니다. [] 표시안에는 메모리의 주소가 들어간다고 말씀드렸습니다. 즉 al에 저장할 변수 var1의 주소가 ds:108h라는 것입니다. ds를 깜박하고 주소가 108h라고 생각할 수 있습니다. 항상 세그먼트 레지스터를 생각해야 합니다. ds의 값이 700h 이므로 최종 주소값은 7108h입니다. 

바로 우리가 선언한 변수입니다. 우리가 변수들을 ret 명령 바로 다음에 선언했는데 그게 실제 프로그램에서도 ret 명령 바로 다음에 저장된 것입니다.

이상한건 1234h를 저장했는데 왜 3412로 보인다는 것입니다. 이것은 little endian 이라는 규약입니다. 작은 인디언이 아니라 endian입니다. 반대로 big endian도 있습니다. little endian은 프로그램에서 메모리에 쓴 값을 반대 순서로 저장하는 것입니다. 16비트 1234h를 쓰면 34, 12 순서로 저장되고 32비트 12345678h을 저장하면 78, 56, 34, 12 로 저장됩니다. 메모리의 단위가 바이트이므로 87654321로 저장되는게 아니라 바이트 단위로 나눠져서 순서가 바뀝니다. 여러가지 이유가 있지만 자세한 것은 위키피디어등을 참고하시고 이 글에서는 일단 그렇다는 것만 알고 넘어가겠습니다. (주2) 두꺼운 전공책도 아니고 새로 나오는 개념들을 모두 설명하려고하면 끝이 없습니다. 핵심 줄기에 집중하겠습니다.

우리가 소스 파일에 변수를 선언하면 프로그램에도 그 위치대로 변수가 생겨날까요? 이렇게 해보면 알겠지요.

ORG 100h
 
MOV AL, var1
VAR1 DB 7
MOV BX, var2
 
RET    ; stops the program.
 
var2 DW 1234h
 
에물레이터로 돌려보세요. mov al, var1 다음에 7 값이 보이나요? 그리고 al에 저장하려는 메모리의 주소값이 몇인가요?

다시 한번 말씀드리지만 컴퓨터는 변수 이름이 뭔지 모릅니다. 오직 숫자만 압니다. 103h라는 숫자를 주소값으로 넘겨주면 해당 메모리의 위치를 읽어서 레지스터로 가져오는 것입니다.

그럼 변수 값 자체가 주소값이면 어떻게 될까요? 즉 포인터 변수를 흉내내보면 어떨까요?

ORG 100h
 
MOV word ptr [addr], 1234h
mov bx, addr
mov word ptr [bx], 1234h
 
RET    ; stops the program.
 
addr DW 120h

소스를 보면 addr 변수가 있는데 120h 입니다. 즉 우리는 ds:[120h] 위치에 1234h 값을 저장하려는 것입니다. 에물레이터를 실행해보세요.  mov word ptr [addr], 1234h 명령이 우리가 생각했던대로 mov word ptr [120h], 1234h 로 어셈블링 되었나요? 아니니까 제가 물어본 것이겠지요.

어셈블링된 코드를 보면 mov word ptr [10fh], 1234h로 나타납니다. 왜 그럴까요? 제가 바로 위에서 말씀드렸듯이 변수란 메모리 위치이기 때문입니다. addr이 위치한 메모리 주소가 몇인가요? 값 120h를 찾아보세요. little endian 규약에 따라 20, 01 이라고 써있겠지요. 바로 10fh 입니다. 변수는 값이 아니라 메모리 위치라는 것은 몇번을 강조해도 지나치지 않습니다.

mov al, var1 의 어셈블링 코드는 mov al, byte ptr [var1] 이었습니다. 변수를 메모리 위치로 생각하는 것입니다. mov al, var1이 마치 var1 이라는 문자를 변수 값 7로 바꾸는 것처럼 보이지만 사실은 var1의 메모리 주소를 계산하고 그 다음 메모리에서 해당 주소값에 있는 데이터를 읽어오는 것입니다.

mov al, var1 과 mov al, byte ptr [var1] 이 같은 명령이라는게 이상하게 느껴질 수도 있습니다. 하지만 mov al, var1은 어셈블러가 인간이 보기 편하도록 제공하는 기능이라고 생각하고 원래 기계가 이해하는 문법은 mov al, byte ptr [var1]이라고 생각하셔야 합니다.

그럼 변수에 주소 값일 저장하는 것은 어떻게 해야할까요? 소스 코드에도 써놓았듯이 bx레지스터에 주소 값을 저장하고 [bx] 명령으로 접근하면 됩니다.

mov word ptr [addr], 1234h 명령이 실행되면 addr 변수에 1234h가 저장됩니다. 그리고 mov bx, addr 명령을 실행하면 bx 레지스터에 1234h 값이 저장됩니다. 그리고 마지막으로 ds:[bx] 위치에 1234h 값을 저장합니다.

에물레이터의 aux 버튼을 눌러서 메모리 창을 열고 0700:1234 주소의 메모리를 확인해보세요. 34 12 라는 값이 나타날 것입니다.

잠시 C 언어에 대한 이야기를 하겠습니다. C 언어에 관심없으신 분은 넘기셔도 좋습니다.

C 언어에서 포인터 변수를 읽어서 특정한 메모리 위치에 값을 쓰는 것을 간접 참조라고 부릅니다. 왜 간접일까요? 우리가 어셈블리 언어로 직접 만들었듯이 포인터 변수에 저장된 주소값에 접근하러면 우선 변수에 저장된 주소 값을 레지스터로 읽어와야 합니다. 그리고 레지스터 값을 이용해서 메모리에 접근할 수 있지요.

일반 변수는 한번에 접근됩니다. 변수의 주소를 알기때문이지요. 포인터 변수는 변수를 한번 레지스터로 읽어오고 그 다음에야 최종 접근하려는 위치에 접근됩니다. 이렇게 한번 더 메모리를 읽는 절차가 있기 때문에 간접 접근이라고 부르는 것입니다.

몇번째 말씀드리는건지 모르겠네요. C는 어셈블리의 확장판입니다!!

 

 


주1: unsigned short *p = 0xb8000; 이렇게 만들면 일일이 주소값을 쓰는 것보다 변수로 주소를 지정할 수 있겠지요.

주2: little endian이라는 것을 모른다는 사실을 알았네요? 한걸음 걸었으니 little endian에 대해 알아봅시다. 그러면 모른다는 것도 모르는 상태에서 모른다는 것을 아는 상태가 되고, 또 뭔가를 아는 상태가 되는 것입니다.

 

댓글

댓글 본문
작성자
비밀번호
  1. gurugio
    http://opentutorials.org......662
    마지막 챕터에 참고 자료 링크 남겼습니다.
    대화보기
    • starlee3
      2000년전후에 나온 옜날에 매크로 어셈블책 이나,터보 어셈블책을 보고 있으니
      안맞는것같습니다.요점은 어셈블 마다 문법이 다 틀린것같군요.

      요즘에는 nasm 많이 하고 emu8086도 어셈블 문법도 거기에 따르는것같군요. nasm관련책은 시중에 없으니,
      답답하군요.좋은책있으면 소개좀 부탁드립니다 .?
    • gurugio
      code segment, assume cs:code, ds:data 이런 지시어들을 emu8086이 인식하지 못하는것 같습니다. 원래 안되는건지 버그인지는 모르겠습니다만 mov ax, data를 해보면 ax에 2가 들어갑니다. 잘못된 값입니다.
      word ptr [aaa]도 주소로 따져보면 0002:0000 가 됩니다. 변수 aaa의 주소와 다른 메모리에 AB를 저장했다가 읽고 있습니다.

      code segment 등의 지시어는 빼고 실험하시는게 좋을듯합니다.
      대화보기
      • starlee3
        code segment
        assume cs:code,ds:data
        mov ax,data
        mov ds,ax
        mov bx,offset aaa

        mov word ptr[aaa],'AB'
        mov cx,word ptr [aaa]

        mov dl,ch
        mov ah,2
        int 21h

        mov dl,cl
        mov ah,2
        int 21h




        code ends



        data segment

        aaa dw 'XY'

        data ends
        end


        화면에 AB를 출현하는 코드인데요...잘 작동하는데 ~궁금한점이 있습니다.

        에뮬레이터에서 왜???
        mov word ptr[aaa],'AB'할때

        메모리 aaa번지에 'AB'를 넣어라 명령인데

        그럼 에뮬레이터상 AB가 들어가는것이 보여야 하는데 도통 안보이네요
        그냥 aaa번지에 XY만 유지하고 있습니다?? 이유가 뭐죠?

        정상적으로 작동은 하는데 ?궁금하네요?데이타 세크멘트지역 값들은 프로그램 끝날때가지 유지해서 그런가요?
      • starlee3
        친절한 답변에 항상 감사합니다 ^^; 모르다 보니 자꾸 질문만하네요..

        질문1)
        org 100h
        mov ax, 0b800h
        mov ds, ax
        mov bx, 0
        mov cl, 'A'
        mov ch, 'B'
        mov ds:[bx], cx
        mov [bx+3], cx
        ret

        만약 이렇게 되었을때요

        어셈블책 많은책에서 16비트는 물리번지 생성은 짝수,홀수로 된다고하던데요...
        0,2,4,6 이는 BYTE단위에 붙은 주소를 2바이트씩 Read Write할목적에서 그런다고하던데요
        32비트는 0,4,8,12로 물리 주소가 생성된다고하던데요


        상기 예제의 경우

        move ds:[bx] 는
        DS:BX가 물리 번지를 0b800+0000=0b8000에 생성해서 메모리에 A,B를 넣고

        mov [bx+3], cx 할경우
        DS:BX가 물리번지를 0b800+0002=0b8002에 생성해서 메모리에 null,A를넣고
        DS:BX가 물리번지를 0b800+0004=Ob8004를 생성해서 메모리에 B,null를 넣는다고
        보면 되겠나요? 요점은 물리번지를 두번 생성하냐? 질문입니당??


        간접번지지정 명령코드로 생성하는 (물리번지)가 8비트는 짝수홀수 상관없이 아무렇게 생성하면
        되는데 16비트일경우 2바이트 단위로 한꺼번에 처리하니까 좀 아리송해서 질문입니당?!!

        질문2)

        ORG 100h
        mov ax,00700h
        mov ds,ax
        mov bx,offset addr


        MOV word ptr [addr],'AB'


        addr DW 'XY'

        햇을때,잘 작동하는데,

        MOV word ptr [addr],1234h 해도
        addr DW 'XY'
        문자공간에 숫자도 잘들어가는데

        크기는 word ptr 정의하고,
        데이타형은 DW 메모리공간에 정의된 아스키 코드가 있으면 문자형,숫자가 있으면 숫자형으로
        되는것 아닌가요? 정의된 실수가 있으면 실수형 아닌가 질문입니다?


        어셈블러가 123는 문자로 코드를 생성하면 문자형
        123는 숫자로 기계어 코드를 생성하면 숫자형
        어셈블러가 'ABC'를 아스키코드로 번역하면 문자형.. ?? 질문이죠?
      • gurugio
        1. 8086은 리틀 엔디언으로만 동작합니다. 빅엔디언으로는 동작하지 않습니다. mov [bx+3], cx를 하시면 [cx+3]에 cl, [cx+4]에 ch가 저장될겁니다. [bx+3]으로 주소를 지정했는데 [bx+2]같이 지정한 주소보다 낮은 주소에 값을 쓰지 않습니다. 빅엔디언으로 동작하는 프로세서에서는 [bx+3]에 ch, [bx+4]에 cl이 저장됩니다.

        2. 변수가 타입이 뭔지는 상관없고 크기가 얼마냐만 따집니다. 변수에 들어가는 데이터가 문자인지 정수인지는 상관없고 1바이트냐 2바이트냐만 생각합니다. 실수는 취급이 달라집니다만 8086은 실수를 취급하지 않으므로 언급하지 않겠습니다.
        대화보기
        • starlee3
          앞 변수사용법이랑 변수 종합 질문입니당? ^^;

          8086 16비트이니까 원래 주소 지정은 0 2, 4, .. 즉 짝수로 나가는데

          mov [bx+2], cx 경우 [bx+2] 메모리에 낮은번지에 'A' 가고 높은 번지에 '11011111b' 가는데
          첫번째는 리틀 엔디언 방식이라고할수있고,,

          이렇게 지정할경우
          move [bx+3],cx경우 [bx+2] 메모리에 낮은 번지에 '11011111b'가고 높은 번지에 'A'가는데
          그럼 두번째를 빅 엔디언 방식이라고하나요?

          질문2)

          ORG 100h

          MOV word ptr [addr], 1234h
          mov bx, addr
          mov word ptr [bx], 1234h

          RET ; stops the program.

          addr DW 120h

          기계어만들때
          어셈블러에게 지시사항(의사명령)으로써는 DS에 있는 addr번지를, byte ptr [ addr] 연결해주고
          dw,db는 일경우 값을 넣을경우,데이타형을 만들어 준다고 생각하면 될까요?

          즉 문자인지,숫자인지,실수인지? 변수형태 지정은 어떻게 하냐 ? 질문이군요???
        버전 관리
        gurugio
        현재 버전
        선택 버전
        graphittie 자세히 보기