전공/시스템 프로그래밍

[시스템 프로그래밍 6-2] 어셈블리어 데이터 관련 연산자와 간접 주소방식

뜨거운 개발자 2023. 4. 6. 13:27

데이터와 관련된 연산자(operators)와 Directives

  • 데이터와 관련된 연산자와 directives들은 자체적으로 기계어와 1대1로 대응되는 명령어는 아니다. - 즉 실행 가능한 명령어가 아니다.
    • 대신 어셈블러에 의해 해석된다.
  • 데이터의 크기에 대한 정보를 제공하거나 주소에 대한 정보를 제공한다.
    • 이것 자체로는 기계어로는 번역이 되지는 않는다.
    • 다른 구문들과 함께 사용이 됨으로써 기계어로 번역이 되는 것이다.

1. OFFSET Operator

offset이란 해당하는 데이터가 들어있는 데이터 세그먼트의 시작 부분부터 레이블(label)까지의 거리(byte)를 나타낸다.

이 그림의 의미는 myByte는 사실 Offset에 불과하고, 우리는 offset값에 이름을 붙힌 것 뿐이다.

1-1 예시 코드1

.data
bVal BYTE ?
wVal WORD ?
dVal DWORD ?
dVal2 DWORD ?

mov esi, OFFSET bVal  ; ESI = 00404000h 
mov esi, OFFSET wVal  ; ESI = 00404001h
mov esi, OFFSET dVal  ; ESI = 00404003h
mov esi, OFFSET dVal2 ; ESI = 00404007h
  • ESI(32bit register)에 offset 값들이 저장된다.
  • 여기서 mov esi bVal을 하면 오류가 났을텐데, bVal 은 1바이트 esi는 32bit(4byte)이기 때문에 원래는 오류가 나지만 OFFSET명령어를 사용했기 때문에 주소가 들어가서 오류가 발생하지 않는다.

1-2 direct-offset operand에 적용 가능

.data
myArray WORD 1,2,3,4,5
.code
mov esi, OFFSET myArray + 4
  • 4를 늘렸지만 배열에서 값 3의 주소값을 본다는 것을 이해하자.

1-3 OFFSET을 사용해서 포인터를 만들 수 있다

.data
myArray WORD 1,2,3,4,5
.code
mov esi, OFFSET myArray + 4
  • bigArray라는 이름의 배열이 DWORD의 크기로 500개 복제되어있다.(초기화X)
  • mov명령을 사용하면 값을 가져오지만 위의 방식대로 선언을 하면서 사용하면 OFFSET값을 가져온다. (mov pArray, bigArraypArray DWORD bigArray의 차이)
  • 따라서 이 코드에서 pArray 는 배열의 시작주소를 가리킨다.

2. ALIGN Directive

  • 변수들을 정렬시키는 역활을 한다. (byte, word, doubleword, or paragraph(16byte) boundary)
  • 범위는 1, 2, 4, 8, or 16이 가능하다.
    • 1: 다음 변수가 1바이트 경계(1의 배수)에 정렬된다.(the default)
    • 2: 다음 변수는 짝수 주소에 정렬된다.
    • 4: 다음주소는 4의 배수주소에 정렬된다.
    • 16: 다음 주소는 16배수의 주소이다.

!!!메모리를 배치할 때 빈공간을 추가하는 역활!!!

  • 사용이유 : 우리가 32비트 프로세서를 사용하면 4바이트 단위로 메모리에 접근하게 된다.
  • 따라서 메모리 공간을 조금 낭비하더라도, 4바이트씩 읽기 때문에 두번 읽는 것보다는 빈 공간으로 두는게 더 속도 측면에서 효율이 좋다.

3. PTR Operator

  • operand(피연산자)의 원래 사이즈를 override해주는 역활을 한다.
  • 원래 정해진 사이즈가 아니라 다른 사이즈로 해당 operand에 접근하고 싶을 때 사용을 한다.
.data
myDouble DWORD 12345678h
.code
mov ax,myDouble ;error 16bit만 가지고 오고 싶은상황이지만 이건 불가.
mov ax,WORD PTR myDouble

마지막 줄의 의미 WORD, PTR, myDouble myDouble에 접근할 때 이것이 WORD라고 가정하고 접근하라는 의미

  • 죽 우리는 5678h를 AX로 가져올 수 있다.
  • X86프로세서는 변수의 시작 수소에 낮은 바이트가 저장되는 리틀앤디안 형식을 사용한다.

PTR 연산자는 반드시 표준 어셈블러 데이터 유형인 BYTE, SBYTE, WORD, SWORD, DWORD, SDWORD, FWORD, QWORD, or TBYTE중 하나와 조합하여 사용해야 한다.

.data
myDouble DWORD 12345678h
.code
mov ax,myDouble ;error 16bit만 가지고 오고 싶은상황이지만 이건 불가.
mov ax,WORD PTR myDouble
작은 값을 큰값으로 이동하는것도 가능하다
.data
wordList WORD 5678h,1234h
.code
mov eax,DWORD PTR wordList ;EAX = 12345678h
  • 사용 응용 예시 : 데이터 타입을 바꾸는게 상당히 편리하다. (리틀앤디안의 장점!!)

4. TYPE Operator

  • 이 변수는 크기가 몇인 타입인가
.data
var1 BYTE ?
var2 WORD ?
var3 DWORD ?
var4 QWORD ?

5. LENGTHOF Operator

  • 배열의 한줄에 몇개의 원소가 들어있는지 보여주는 연산자
.data
var1 BYTE ?
var2 WORD ?
var3 DWORD ?
var4 QWORD ?

 

.data
var1 BYTE ?
var2 WORD ?
var3 DWORD ?
var4 QWORD ?
  • 첫번째는 LENGTHOF myArray return the value 5
  • 두번째는 LENGTHOF myArray would return the value 10
  • 궁금증…! digitStr BYTE "12345678", 0 이게 8인 이유가 따옴표로 8개라서 8인지 아니면 주소라서 8인지 확인해보기

6. SIZEOF Operator

  • LENGTHOF 와 TYPE의 곱을 리턴한다.
.data
intArray WORD 32 DUP (0)
.code
mov eax, SIZEOF int Array ;EAX = 64

7. LABEL Directive

  • 저장공간을 사용하지 않고 레이블을 삽입하고 크기 속성을 지정할 수 있다.
  • 레이블의 일반적인 용도는 데이터 세그먼트에서 다음에 선언된 변수에 대한 대체이름과 크기 속성을 제공하는 것이다.
.data
val16 LABEL WORD
val32 DWORD 12345678h
.code
mov ax, val16 ;AX = 5678h;
mov dx,[val16+2] ; DX = 1234h
.data
LongValue LABEL DWORD
val1 WORD 5678h
val2 WORD 1234h
.code
mov eax,LongValue ;EAX = 12345678h

 

 

간접 주소 (Indirect addressing)

[var1 +1] : 이런 방식이 direct Addressing 이라고 불린다.

  • Direct addressing은 배열 처리에 거의 사용되지 않는데, 상수 오프셋을 사용하여 몇 개 이상의 배열 요소를 주소 지정하는 것은 비현실적이기 때문입니다.
  • 대신 레지스터를 포인터로 사용하고(간접 주소 지정이라고 함) 레지스터의 값을 조작합니다.
  • 피연산자가 간접 주소 지정을 사용하는 경우, 이를 간접 피연산자라고 합니다

1. Indirect Operands

Protected Mode

  • indirect operand는 이무 32-bit general-purpose register가 brackets으로 둘러싸여 있으면 사용가능하다.
  • 레지스터에는 일부 데이터의 주소가 포함되어 있다고 가정한다.
    .data
    byteVal BYTE 10h
    .code
    mov esi, OFFSET byteVal
    mov al, [esi]    ;AL = 10h
    • 이 코드에서 MOV명령은 간접 피연산자를 소스로 사용해서 ESI 값이 역참조 되어 바이트가 AL로 이동한다.
  • destination operand가 간접 주소지정을 사용하는 경우 레지스터가 가리키는 위치에 새 값이 메모리에 배치된다.
    mov [esi] , bl
    • BL레지스터 내용은 ESI가 지정한 메모리 위치로 복사된다. * p = bl이런식으로 이해

Using PTR with Indirect Operands

  • 피연산자의 크기는 명령에서 명확하지 않다.
inc [esi] ;error operand must have size 
inc BYTE PTR [esi]
  • PTR명령은 피연산자의 크기를 확인한다.

2. Arrays

  • 간접 피연산자는 배열을 단계적으로 처리하는 데 이상적인 도구입니다
.data
arrayB BYTE 10h,20h,30h
.code
mov esi, OFFSET arrayB
mov al ,[esi] arrayB ;AL = 10h
inc esi
mov al, [esi] ; AL = 20h
inc esi
mov al,[esi] ; AL = 30h
  • 이 코드에서 inc하나만 했을 때 작동을 하는 이유는 원소들이 byte이기 때문이다.
.data
arrayW WORD 1000h,2000h,3000h
.code
mov esi, OFFSET arrayW
mov ax ,[esi] arrayW ;AX = 1000h
add esi,2
mov ax, [esi] ; AL = 2000h
add esi,2
mov ax,[esi] ; AL = 3000h

 

  • 32bit int 예시

3. Indexed Operands

  • indexed operand 는 레지스터에 상수를 더해서 유효한 주소를 생성한다.
  • 32비트 범용 레지스터 중 하나를 인덱스 레지스터로 사용할 수있다.
constant[reg] 
[constant + reg]
  • 첫번째 표기형식은 변수 이름에 레지스터를 결합한 것이다.
    • 변수 이름은 어셈블러에 의해 변수의 오프셋을 나타내는 상수로 변환된다.
  • 인덱스 레지스터는 첫번째 배열요소에 엑세스 하기 전에 0으로 초기화 해야한다.
    .data
    arrayB BYTE 10h,20h,30h
    .code
    mov esi,0
    mov al,arrayB[esi] ; AL = 10h
    • 마지막 문은 arrayB의 오프셋에 ESI를 추가한다.
    • [arrayB + ESI] 표현식에 의해 생성된 주소가 역참조되고 메모리에 있는 바이트가 AL로 복사된다.

또 다른 사용방식 Adding Displacements

  • 두 번째 유형의 인덱싱 주소 지정은 레지스터와 상수 오프셋을 결합합니다.
  • 인덱스 레지스터는 배열 또는 구조체의 기본 주소를 보관하고 상수는 다양한 배열 요소의 오프셋을 식별 합니다
.data
arraytW WORD 1000h,2000h,3000h
.code
mov esi,OFFSET arrayW
mov ax, [esi]     ; AX = 1000h
mov ax, [esi + 2] ; AX = 2000h
mov ax, [esi + 4] ; AX = 3000h

16비트 레지스터 사용

  • real모드에서는 이런 방식을 써야하지만 따로 보지 않고 넘어간다.

Scale Factors in Indexed Operands

  • HIGH LEVEL의 인덱스와 다르다. (포인터의 덧셈을 생각해보기)
  • 어셈블리어는 알아서 배열의 크기를 고려해서 더해줘야 한다.
.data
arrayD DWORD 100h, 200h, 300h,400h
.code
mov esi, 3*TYPE arrayD ;offset of arrayD[3]
mov eax, arrayD[esi]   ;EAX = 400h
  • 다행히도 인텔 설계자들은 컴파일러 작성자들이 일반적인 작업을 더 쉽게 할 수 있도록scale factor를 사용해서 오프셋을 계산하는 방법을 제공하였다.
.data
arrayD DWORD 1,2,3,4
.code
mov esi,3 ;subscript
mov eax,arrayD[esi * 4] ;EAX = 4

mov esi,3 ;subscript
mov eax,arrayD[esi*TYPE arrayD] ;EAX = 4

4. Pointers

  • 다른 변수의 주소를 포함하는 변수를 포인터라고 합니다.
  • 포인터는 배열과 데이터 구조를 조작하는 데 유용한 도구로, 포인터가 보유한 주소를 런타임에 수정 할 수 있기 때문입니다
  • 포인터의 크기는 프로세서의 현재 모드(32비트 또는 64비트)에 따라 영향을 받습니다
.data
arrayD DWORD 1,2,3,4
.code
mov esi,3 ;subscript
mov eax,arrayD[esi * 4] ;EAX = 4

mov esi,3 ;subscript
mov eax,arrayD[esi*TYPE arrayD] ;EAX = 4

관계를 더 명확하게 만들고 싶다면

ptrB dword OFFSET arrayB 가 된다.

  • 우리는 32bit 이기 때문데 dobuleword변수에 저장된다는 사실만 기억하자.

Using the TYPEDEF Operator

  • 빌트인 타입을 사용자 정의 유형으로 만들 수 있다. (c와 c++에서도 typedef사용 하듯 비슷한 개념)
  • 포인터 변수를 만드는 데 이상적이다.
.data
arrayD DWORD 1,2,3,4
.code
mov esi,3 ;subscript
mov eax,arrayD[esi * 4] ;EAX = 4

mov esi,3 ;subscript
mov eax,arrayD[esi*TYPE arrayD] ;EAX = 4
  • 일반적으로 이런 선언은 프로그램 시작부분 데이터 세그먼트 앞에 배치됩니다.

 


Uploaded by

N2T
728x90