외로운 Nova의 작업실

Assembly 언어 공부 - 17(간접 주소 지정 이론 및 실습) 본문

Programming/Assembly

Assembly 언어 공부 - 17(간접 주소 지정 이론 및 실습)

Nova_ 2022. 8. 1. 23:24

안녕하세요, 이번에는 어셈블리에서 간접 주소 지정 이론 및 실습을 진행해보도록 하겠습니다.

 

직접, 간접 주소 지정

직접 주소 지정의 경우 주소를 직접 써넣는 것입니다. 예를 들어 코드를 보시죠.

include 	c:\assembly\irvine32.inc
includelib  c:\assembly\irvine32.lib
includelib  c:\assembly\kernel32.lib
includelib  c:\assembly\user32.lib

.data
array BYTE 10, 20, 30, 40

.code
main PROC

mov al, [00404000b]		;al = 10
mov ah, [00404001b]		;ah = 20
mov bl, [00404002b]		;bl = 30
mov bh, [00404003b]		;bh = 40

call DumpRegs

    exit

main ENDP
END main

위 코드처럼 배열의 두번째 세번째 값의 주소를 직접 써넣어서 참조하는 것을 직접 주소 지정이라고합니다. 다만, 위 코드는 어셈블러로 컴파일이 되지않습니다. 애초에 배열에 접근할때는 직접 참조 방식으로 못하게 막아놨죠. 따라서 우리는 배열을 참조할때는 간접 주소 지정 방식으로 접근 하게됩니다. 바로 아래 코드처럼요.

include 	c:\assembly\irvine32.inc
includelib  c:\assembly\irvine32.lib
includelib  c:\assembly\kernel32.lib
includelib  c:\assembly\user32.lib

.data
array BYTE 10h, 20h, 30h, 40h

.code
main PROC

mov al, [array]         ;al = 10h
mov ah, [array + 1]     ;ah = 20h
mov bl, [array + 2]     ;bl = 30h
mov bh, [array + 3]     ;bh = 40h

call DumpRegs

    exit

main ENDP
END main

위 코드는 주소를 직접 써넣지 않고 array 변수와 + 기호를 가지고 간접적으로 주소를 지정하고 있습니다. 이는 배열을 사용하기에 편리하고 적합한 주소 지정 방식이며, 어셈블리에서는 이러한 방식으로 배열을 참조하고 있습니다. 또한 간접 참조 당하는 피연산자를 간접 피연산자라고 합니다. 위 코드에서는 array가 되겠죠.

 

인덱스 피연산자

배열의 이름을 사용하여 배열을 다루는 방법은 총 두가지가 있습니다. 

1.[array + index * type]

2. array[index * type]

두가지의 의미는 같으며 실제 사용되는 방법은 실습을 통해 알아보겠습니다.

 

1. array[index * type]

include 	c:\assembly\irvine32.inc
includelib  c:\assembly\irvine32.lib
includelib  c:\assembly\kernel32.lib
includelib  c:\assembly\user32.lib

.data
bArray BYTE 10h, 20h, 30h, 40h
wArray WORD 1000h, 2000h, 3000h, 4000h

.code
main PROC

mov al, [bArray]             ;al = 10h
mov ah, [bArray + 1 * 1]     ;ah = 20h
mov bl, [bArray + 2 * 1]     ;bl = 30h
mov bh, [bArray + 3 * 1]     ;bh = 40h

call DumpRegs

mov ax, [wArray]             ;ax = 1000h
mov bx, [wArray + 1 * 2]     ;bx = 2000h
mov cx, [wArray + 2 * 2]     ;cx = 3000h
mov dx, [wArray + 3 * 2]     ;dx = 4000h

call DumpRegs

    exit

main ENDP
END main

Byte는 1바이트기 때문에 type으로 1을 곱해주고, Word는 2바이트이기때문에 type으로 2를 곱해줍니다. 이 코드를 실행 시켜보면,

잘 실행되는 것을 확인할 수 있습니다.

 

2. array[index * type]

include 	c:\assembly\irvine32.inc
includelib  c:\assembly\irvine32.lib
includelib  c:\assembly\kernel32.lib
includelib  c:\assembly\user32.lib

.data
bArray BYTE 10h, 20h, 30h, 40h
wArray WORD 1000h, 2000h, 3000h, 4000h

.code
main PROC

mov al, bArray            ;al = 10h
mov ah, bArray[1 * 1]     ;ah = 20h
mov bl, bArray[2 * 1]     ;bl = 30h
mov bh, bArray[3 * 1]     ;bh = 40h

call DumpRegs

mov ax, wArray            ;ax = 1000h
mov bx, wArray[1 * 2]     ;bx = 2000h
mov cx, wArray[2 * 2]     ;cx = 3000h
mov dx, wArray[3 * 2]     ;dx = 4000h

call DumpRegs

    exit

main ENDP
END main

위 코드를 실행시켜보면, 

잘 실행이 되는 것을 확인 할 수 있습니다.

 

포인터

포인터는 변수의 주소값을 담고있는 변수입니다. 태생이 주소값을 담기위해 만들어진 녀석입니다. 아까전 코드에서 bArray 변수는 배열을 생성하기 위해 만들어진 녀석으로 주소 지정 방식에 사용하기엔 기능적으론 괜찮지만 의미적으로 적합하지않습니다. 우리는 주소 지정만을 위해 만들어진 녀석이 필요합니다. 바로 포인터죠. 포인터로 배열을 다루는 것이 기능적으로, 의미적으로 적합합니다.

실습을 통해 실제로 어떻게 포인터를 만들고 사용하는지 알아봅시다.

include 	c:\assembly\irvine32.inc
includelib  c:\assembly\irvine32.lib
includelib  c:\assembly\kernel32.lib
includelib  c:\assembly\user32.lib

.data
bArray BYTE 10h, 20h, 30h, 40h
bPtr DWORD OFFSET bArray              


.code
main PROC

mov esi, bPtr       ;esi = OFFSET bArray
mov ebx, [bPtr]     ;eax = 00404000 no, [bPtr]
mov al, [esi]       ;al = 10h Yes, [esi(reg)]

call DumpRegs

    exit

main ENDP
END main

코드를 실행시키면,

여기서 중요한점은 [mem] 은 mem 과 같지만 [reg] 는 reg와 다르다는 것입니다. masm32 컴파일러는 [mem와 mem을 똑같이 해석하지만 [reg]는 reg의 값을 역참조해라, reg는 reg의 값 으로 해석합니다. 다음 코드를 보시죠.

include 	c:\assembly\irvine32.inc
includelib  c:\assembly\irvine32.lib
includelib  c:\assembly\kernel32.lib
includelib  c:\assembly\user32.lib

.data
bArray BYTE 10h, 20h, 30h, 40h
bPtr DWORD OFFSET bArray              


.code
main PROC

mov esi, bPtr       ;esi = OFFSET bArray
mov eax, [bPtr]     ;eax = 00404000
mov ebx, esi        ;ebx = 00404000
mov ecx, [esi]      ;ecx = 40302010


call DumpRegs

    exit

main ENDP
END main

이렇듯 [mem] = mem 이고, [reg]는 reg와 다르게 해석됩니다. 이러한 사실에서 우리는 알 수 있습니다. 포인터로 역참조를 할때는 레지스터로 복사하여 실행해야된다는 것 을요. 또한 중요한 개념을 하나 집고 넘어가겠습니다.

우리는 데이터에 접근할때 2가지 방법을 사용하여 접근할 수 있습니다.

1. 변수의 이름으로 데이터에 접근하기 ex) mov eax, bVal

2. 데이터의 주소로 데이터에 접근하기 ex) mov eax, [esi] ; esi = OFFSET bVal

여기서 참조와 역참조에 대해서 자세히 알아봅시다.

1. 참조 : 참조는 고급언어에서 일어납니다. 보통 a = b 일때 a의 주소가 b의 주소와 같아질때 생깁니다. b의 값을 변경시키면 a의 값도 변경되는 상황이 참조했다고 말하는 거죠. 즉, a변수의 주소를 직접 가지고 다룰때 참조했다고합니다.

2. 역참조 : 역참조는 참조가 아니라는 뜻입니다. a의 주소를 b라는 변수가 가지고 있고, b라는 변수를 통해 a의 주소에 접근하여 a가 가지고있는 데이터를 다룰때 역참조가 일어난다고 합니다. 간접 참조라고도 합니다.

참조는 직접적으로 데이터에 접근 및 다뤄지고 역참조는 간접적으로 데이터에 접근 할 수 있습니다. 이것이 참조와 역참조의 다른점입니다.

 

TYPEDEF 연산자 사용

TYPEDEF 연산자는 사용자 정의 자료형을 만들 수 있게하는 연산자입니다. 이 연산자를 이용하여 포인터 변수를 만드는데 이상적입니다. 

pByte TYPEDEF PTR BYTE : BYTE 크기의 새로운 pByte 자료형

위 선언은 대개 프로그램의 데이터 세그먼트 앞에 놓입니다. 실습을 통해 알아보겠습니다.

include 	c:\assembly\irvine32.inc
includelib  c:\assembly\irvine32.lib
includelib  c:\assembly\kernel32.lib
includelib  c:\assembly\user32.lib

PBYTE TYPEDEF PTR BYTE
PWORD TYPEDEF PTR WORD
PDWORD TYPEDEF PTR DWORD

.data
bArray BYTE 10h, 20h, 30h
wArray WORD 1000h, 2000h, 3000h
dArray DWORD 1h, 2h, 3h
ptrb PBYTE bArray
ptrw PWORD wArray
ptrd PDWORD dArray            


.code
main PROC

mov esi, ptrb           ;esi = OFFSET bArray
mov al, [esi]           ;al = 10h
mov esi, ptrw           ;esi = OFFSET wArray
mov bx, [esi]           ;bx = 1000h
mov esi, ptrd           ;esi = OFFSET dArray
mov ecx, [esi]          ;ecx = 1h

call DumpRegs

    exit

main ENDP
END main

위 코드를 실행하면,

잘 실행되는 것을 볼 수 있습니다.

Comments