본문 바로가기

CS/System programming

Data Transfers Addressing and Arithmetic

728x90

Data Transfer Instructions

1. Intro

  • CPU 단으로 보내기 전에 컴파일러가 type checking을 해준다.
  • CPU는 자기가 할 수 있는 연산이면 뭐든지 다 하기 때문에 프로그래머가 엄격하게 체크해주어야 한다.

2. Operand Types

  • 피연산자에는 기본적으로 세 가지 유형이 있다.
    • Immediate: 숫자 리터럴 표현
    • 레지스터: CPU의 이름이 지정된 레지스터
    • 메모리: 메모리 위치를 참조

3. Direct Memory Operands

  • 변수 이름은 메모리에서 오프셋을 참조
.data
var1 BYTE 10h
mov al var1
  • var1의 사이즈는 1바이트이고 10h를 포함하고 있다.
  • 마지막 명령어는 al 레지스터에 값을 복사한다.
  • var이 offset 10400h에 위치한다고 가정하면
    • 마지막 문장은 A0 00010400의 기계 명령어로 바뀐다.
  • bracket을 사용해 메모리를 참조할 수 있다.
    • mov a1, [var1]

4. MOV Instruction

  • 소스 연산자에서 대상 피연산자로 데이터를 복사한다.

  • MOV destination source

  • 대상 피연산자는 값이 변경되지만, 소스 피연산자는 변경되지 않는다.

  • 다음 규칙만 지킨다면 MOV를 매우 유연하게 쓸 수 있다.

    • 두 피연산자의 크기가 같아야한다.
    • 두 피연산자 모두 메모리 피연산자일 수 없다.
    • instruction pointer register는 대상 피연산자가 될 수 없다.
  • 메모리간 데이터 이동

    • 소스 피연산자의 값을 레지스터로 옮겨서, 이후 메모리 피연산자에 값을 할당해야 한다.

      .data
      var1 WORD ?
      var2 WORD ?
      .code
      mov ax, var1
      mov var2, ax
  • 정수 상수를 변수나 레지스터에 복사할 때, 상수의 바이트 수를 고려해야 한다.

  • Overlapping Values

      .data
      oneByte BYTE 78h
      oneWord WORD 1234h
      oneDword DWORD 12345678h
      .code
      mov eax, 0          ; EAX = 00000000h
      mov al, oneByte     ; EAX = 00000078h
      mov ax, oneWord     ; EAX = 00001234h
      mov eax, oneDword   ; EAX = 12345678h
      mov ax, 0           ; EAX = 12340000h

5. Zero/sign Extension of Integers

  • 작은 크기의 피연산자에서 큰 크기의 피연산자로 복사

    • MOV 명령어는 작은 피연산자에서 큰 피연산자로 직접 데이터를 복사할 수 없다.

    • 예를 들어 16비트의 unsigned 변수인 count를 ECX로 이동해야 한다면, EXC를 0으로 설정한 후, count를 CX로 이동시킬 수 있다.

      .data
      count WORD 1
      .code
      mov exc, 0
      mov cx, count
    • 만약, -16을 같은 방법으로 적용한다면?!

      .data
      count SWORD -16     ; FFF0h
      .code
      mov ecx, 0
      mov cx, count       ; ECX = 0000FFF0h(+65520)
    • 완전히 다른 수가 ECX에 저장된다.

      mov ecx, 0FFFFFFFFh
      mov cx, count       ; ECX = FFFFFFF0h(-16)
    • 다행히도, 인텔은 MOVZX와 MOVSX 명령어를 통해 부호 없는 정수, 부호 있는 정수 모두 처리할 수 있다.

  • MOVZX Instruction

    • 소스 피연산자의 내용을 대상 피연산자에 복사하고 16비트 또는 32비트로 값을 zero확장한다.

    • 부호 없는 정수와 함께 사용

    • 예시

      mov bx, 0A69Bh
      movzx eax, bx       ; EAX = 0000A69Bh
      movzx edx, bl       ; EDX = 0000009Bh
      movzx cx, bl        ; CX = 009Bh
      ========================================
      .data
      byte1 BYTE 9Bh
      word1 WORD 0A69Bh
      .code
      movzx eax, word1    ; EAX = 0000A69Bh
      movzx edx, byte1    ; EDX = 0000009Bh
      movzx cx, byte1     ; CX = 009Bh
  • MOVSX Instruction

    • signed integer에서 사용

      mov bx, 0A69Bh
      movsx eax, bx       ; EAX = FFFFA69Bh
      movsx edx, bl       ; EDX = FFFFFF9Bh
      movsx cx, bl        ; CX = FF9Bh

6.LAHF and SAHF Instructions

  • LAHF: EFLAGS 레지스터의 하위 바이트를 AH 레지스터에 복사한다.
  • Sign, Zero, Auxiliary Carry, Parity, Carry
.data
saveflags BYTE ?
.code
lahf                ; load flags into AH
mov saveflags, ah   ; AH를 변수에 저장
  • SAHF: AH 레지스터에 저장된 플래그 값을 EFLAGS 레지스터의 하위 8비트에 저장
mov ah, saveflags   ; 플래그를 AH에 로드
sahf                ; 플래그 레지스터에 복사

7. XCHG Instruction

  • 두 피연산자끼리 교환
  • XCHG는 immediate operands를 사용하지 못하는 것을 제외한 MOV와 규칙 똑같다
swap 예시
mov ax, var1
xchg ax, var2
mov var1, ax

8. Direct-Offset Operands

  • 명시적인 label 없을 수 있는 메모리 위치에 엑세스 가능
arrayB BYTE 10h, 20h, 30h, 40h, 50h
mov al, arrayB      ; AL = 10h
mov al, [arrayB+1]  ; AL = 20h
mov al, [arrayB+2]  ; AL = 30h
.data
arrayW WORD 100h, 200h, 300h
.code
mov ax, arrayW          ; AX = 100h
mov ax, [arrayW + 2]    ; AX = 200h
.data 
arrayD DWORD 10000h, 20000h
.code
mov eax, arrayD             ; EAX = 10000h
mov eax, [arrayD+4]         ; EAX = 20000h

Addition and Subtraction

1. INC and DEC Instructions

  • INC: 피연산자에 1 더함
  • DEC: 피연산자에 1뺌
  • INC와 DEC는 Carry flag에 영향을 주지 않는다.

2. ADD Instruction

  • 목적지 연산자에 소스 연산자를 더한다. 같은 사이즈이어야 한다.
  • 목적지 연산자에 따라서 플래그가 변경될 수 있다.
    • 예를 들어, 덧셈 결과가 0이면 Zero flag

3. SUB Instruction

  • 목적지 피연산자에서 소스 피연산자를 뺀다.
  • 목적지 피연산자에 따라 플래그 변경

4. NEG Instruction (negate)

  • 어떤 수의 표현 방법을 2의 보수법을 통해 부호를 반대로 만드는 명령어

5. Implementing Arithmetic Expressions

  • Rval = -Xval + (Yval - Zval);
Rval SDWORD ?
Xval SDWORD 26
Yval SDWORD 30
Zval SDWORD 40

; -Xval
mov eax, Xval
neg eax

; (Yval - Zval)
mov ebx, Yval
sub ebx, Zval

; add terms and store
add eax, ebx
mov rVal, eax

6. Flags Affected by Addition and Subtraction

  • 산술 명령어를 실행하고 결과물에 대한 정보를 알고싶다
    • 음수인지, 0인지 등
    • 목적지 피연산자에 비해 너무 큰 or 작은 값인지
  • CPU 상태 플래그 값을 이용해 산술 연산의 결과를 확인한다.
  • 상태 플래그들에 대한 개요
    • Carry flag: unsigned 정수 오버플로우 나타낸다.
    • Overflow flag: signed 정수 오버플로우 나타낸다.
    • Zero flag: 연산 결과가 0
    • Sign flag: 연산 결과가 음수임을 나타낸다.
    • Parity flag: 산술 or 불리언 명령어 실행 직후 대상 연산자의 가장 작은 바이트에서 1비트가 짝수인지 홀수인지
    • Auxiliary Carry flag: 대상 연산자의 가장 작은 바이트에서 3번째 비트 자리에서 1비트가 올라갈 때 설정됨
  • Unsigned Operations: Zero, Carry and Auxiliary Carry
    • Zero flag: 연산결과 0일때 설정
    • 덧셈과 Carry flag: 부호 없는 정수를 더할 때, 대상 피연산자의 MSB에서의 캐리 값
      • CF = 1은 합이 대상 피연산자의 저장 크기를 초과할 때
    • 뺼셈과 Carry flag: 더 작은 부호 없는 정수에서 더 큰 부호 없는 정수를 뺄 때 설정
    • Auxiliary Carry Flag (AC): 목적지 피연산자의 3번 비트에서 캐리가 발생하거나 빌림이 발생할 때 설정
    • Parity flag: 목적지 피연산자의 가장 작은 바이트에서 1비트가 홀수 개 있을 경우, 이 플래그 설정
  • Signed Operations
    • Sign Flag: 결과 음수
    • Overflow Flag: 부호 있는 정수의 산술 연산의 결과가 오버플로우 or 언더플로우일 때 설정
    • 음수 피연산자 두 개를 더했는데 양수 합이 생성되면 오버플로우 발생
    • 영수 피연산자 두 개를 더했는데 음수 합이 생성되면 오버플로우 발생
    • MSB에서 carry in과 carry out을 XOR하여 참이 나오면 Overflow
    • NEG Instruction의 경우 [AL] = -128일때, neg al하면 OF = 1
    • CPU는 signed인지 unsigned인지 구분 없이 그냥 산술하므로 프로그래머가 플래그를 적절히 이용하여 결정하여야 한다.

Data-Related Operators and Directives

  • 데이터 관련 연산자와 지시어는 실행 가능한 명령어가 아니다.

1. OFFSET Operator

  • data label의 offset을 반환한다.
  • offset은 데이터 세그먼트의 시작부터 해당 라벨까지의 바이트 거리를 나타낸다.
  • 예를 들어, bVal이 offset 00404000에 위치한다고 하면
.data
bVal BYTE ?
wVal WORD ?
dVal DWORD ?
dVal DWORD ?
mov esi, OFFSET bVal        ; ESI = 00404000h
mov sei, OFFSET wVal        ; ESI = 00404001h
mov sei, OFFSET dVal        ; ESI = 00404003h
mov sei, OFFSET dVal2       ; ESI = 00404007h
  • 변수의 offset으로 DWORD 변수를 초기화하여 포인터를 만들 수 있다.
.data
bigArray DWORD 500 DPU(?)
pArray DWORD bigArray

mov esi, pArray
  • pArray는 bigArray의 시작 부분을 가리킨다.
  • 포인터의 값이 ESI에 로드되므로 레지스터는 배열의 시작 부분을 가리킬 수 있다.

2. ALIGN Directive

  • 변수를 byte, word, doubleword 경계에 맞춰 정렬한다.
  • ALIGN bound
  • Bound는 1, 2, 4, 8 또는 16일 수 있다.
    • 1: 다음 변수는 1바이트 경계에 맞춰 정렬된다.
    • 2: 다음 변수는 짝수 주소에 맞춰 정렬된다.
    • 4: 다음 주소는 4의 배수
    • 16: 16배수
  • 정렬을 맞추기 위해 변수 앞에 빈 바이트를 삽입할 수도 있다.
  • CPU는 짝수 주소에 저장된 데이터를 홀수 주소에 저장된 데이터보다 더 빠르게 처리할 수 있다.
  • 즉, 메모리를 낭비하더라도 데이터 버스를 빠르게 하기 위함이다.
bVal BYTE ?         ; 00404000h
ALIGN 2
wVal WORD ?         ; 00404002h
bVal BYTE ?         ; 00404004h
ALIGN 4
dVal DWORD ?        ; 00404008h
dval2 DWORD ?       ; 0040400Ch

3. PTR Operator

  • PTR은 표준 어셈블러 데이터 유형과 조합하여 사용해야 한다.
.data
myDouble DWORD 12345678h
.code
mov ax, myDouble
mov ax, WORD PTR myDouble

mov ax, WORD PTR [myDouble+2]       ; 1234h
mov bl, BYTE PTR myDouble           ; 78h
  • 작은 값들을 큰 대상으로 옮기기

      .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 ?
    • TYPE var1 -> 1
    • TYPE var2 -> 2
    • TYPE var3 -> 4
    • TYPE var4 -> 8

5. LENGTHOF Operator

  • 배열에서 element의 개수를 세준다. 동일한 줄에서
.data
byte1 BYTE 10, 20, 30
array1 WORD 30 DUP(?), 0, 0
array2 WORD 5 DUP(3 DUP(?))
array3 DWORD 1, 2, 3, 4
digitStr BYTE "12345678", 0
  • LENGTHOF byte1 -> 3
  • LENGTHOF array1 -> 30 + 2
  • LENGTHOF array2 -> 5 * 3
  • LENGTHOF array3 -> 4
  • LENGTHOF digitStr -> 9
myArray BYTE 10, 20, 30, 40, 50
        BYTE 60, 70, 80, 90, 100    ; LENGTHOF myArray 5
myArray1 BYTE 10, 20, 30, 40, 50,
            60, 70, 80, 90, 100     ; LENGTHOF myArray 10

6. SIZEOF Operator

  • LENGTHOF 와 TYPE의 곱을 반환
.data
intArray WORD 32 DUP(0)
.code
mov eax, SIZEOF intArray    ; EAX = 64

7. LABEL Directive

  • 이 지시어를 사용하면 공간을 할당하지 않고 크기 속성을 지정하면서 레이블을 삽입할 수 있다.
.data
val16 LABEL WORD
val32 DWORD 12345678h
.code
mov ax, val16           ; AX = 5678h
mov dx, [val16+2]       ; DX = 1234h
  • val16과 val32는 동일한 곳을 가리킨다.
  • LABEL의 일반적인 사용법은 데이터 세그먼트에서 다음으로 선언된 변수의 대체 이름과 크기 속성을 제공

Indirect Addressing

  • [var1 + 1]과 같이 상수적인 오프셋을 사용해서 주소 지정하는 것은 비실용적.
  • 대신, 레지스터를 포인터로 사용해 레지스터의 값을 조작한다.

1. Indirect Operands

  • Protected Mode

    • 간접 피연산자는 bracket으로 둘러싸인 32비트 범용 레지스터가 될 수 있다.

      .data
      byteVal BYTE 10h
      .code
      mov esi, OFFSET byteVal
      mov al, [esi]           ; AL = 10h
    • MOV 명령은 간접 피연산자를 소스로 사용하여, ESI의 오프셋을 참조한 바이트를 AL에 넣는다.

    • 대상 피연산자가 간접 주소 지정을 사용하는 경우, 새 값을 레지스터가 가리키는 위치의 메모리에 넣는다.

    • "mov [esi], bl"의 경우, BL 레지스터의 내용이 ESI가 가리키는 메모리 위치로 복사된다.

  • Using PTR with Indirect Operands

      inc [esi]       ; error: 연산자의 사이즈가 정해져야 한다.
      inc BYTE PTR [esi]
    • PTR을 통해 피연산자의 크기를 확인할 수 있다.

2. Arrays

.data
arrayB BYTE 10h, 20h, 30h
.code
mov esi, OFFSET arrayB
mov al, [esi]               ; AL = 10h
inc esi
mov al, [esi]               ; AL = 20h
inc esi
mov al, [esi]               ; AL = 30h
  • 16비트 정수 배열을 사용한다면, ESI에 2를 더해주어야 한다.
.data
arrayW WORD 1000h, 2000h, 3000h
.code
mov esi, OFFSET arrayW
mov ax, [esi]               ; AX = 1000h
add esi, 2
mov ax, [esi]               ; AX = 2000h
add esi, 2
mov ax, [esi]               ; AX = 3000h

3. Indexed Operands

  • 인덱스 피연산자는 레지스터에 상수를 더하여 유효한 주소 생성, 따라서 32비트 범용 레지스터 사용

  • constant[reg], [constant + reg]

    • 첫 번째 표기 형식은 변수 이름과 레지스터를 결합
  • 인덱스 피연산자는 배열 처리에 이상적이다.

      .data
      arrayB BYTE 10h, 20h, 30h
      .code
      mov esi, 0
      mov al, arrayB[esi]         ; AL = 10h
    • 인덱스 레지스터는 첫 번째 배열 요소에 접근하기 전에 0으로 초기화되어야 한다.
    • 마지막 문장은 ESI를 arrayB의 오프셋에 추가한다
    • [arrayB + esi] 식으로 생성된 주소가 참조되고 메모리의 바이트가 AL로 복사된다.
  • Adding Displacements

    • 두 번째 유형의 인덱스 지정은 레지스터와 상수 오프셋 결합

      .data
      arrayW 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
  • Scale Factors in Indexed Operands

      .data
      arrayD DWORD 100h, 200h, 300h, 400h
      .code
      mov esi, 3 * TYPE arrayD
      mov eax, arrayD[esi]
    • 일일이 계산하면 귀찮으므로 scale factor라는 방법을 통해 편하게 계산

      .data
      arrayD DWORD 1, 2, 3, 4
      .code
      mov esi, 3
      mov eax, arrayD[esi*4]
      
      mov esi, 3
      mov eax, arrayD[esi*TYPE arrayD]

4. Pointers

  • 다른 변수의 주소를 포함하는 변수
  • 포인터는 보유하는 주소를 런타임에서 수정할 수 있기 때문에 배열과 데이터 구조를 조작하는 데에 매우 유용한 도구이다.
.data
arrayB byte 10h, 20h, 30h, 40h
ptrB dword arrayB
  • ptrB는 arrayB의 오프셋을 포함

  • prtB dword OFFSET arrayB

    • 선택적으로 OFFSET 연산자로 선언하여 더 명확하게 할 수 있다
  • TYPEDEF Operator

    • TYPEDEF 연산자는 이미 있는 built-in type으로 새로운 user-defined type을 만들어준다.

      PBYTE TYPEDEF PTR BYTE
      .data
      arrayB BYTE 10h, 20h, 30h, 40h
      ptr1 PBYTE ?
      ptr2 PBYTE arrayB
    • 위 선언을 통해 바이트에 대한 포인터인 새 데이터 타입 PBYTE를 생성

JMP and LOOP Instructions

  • 어셈블리에서도 conditional한 명령들 제공
  • status flag에 따라 현재 명령어 실행할지 다른 명령어 실행할지
  • 특정 메모리 주소로 현재의 control을 이동할 수 있다. jump할 수 있다.
  • Unconditional Transfer: EIP에 새 주소가 로드된다.
  • Conditional Transfer: 특정 플래그에 따라서 분기할지 말지 결정, ECX 값에 따라서도 결정

1. JMP Instruction

  • JMP destination

    • JMP 명령어는 코드 레이블로 식별된 목적지로 무조건적인 이동을 유발
    • 목적지의 오프셋이 EIP로 이동되어 새 위치에서 실행이 계속된다.
  • 루프 생성

    • JMP 명령은 루프 상단의 레이블로 점프하여 루프 생성

      top:
        .
        .
        jmp top         ; repeat the endless loop
    • JMP는 무조건적이므로 루프가 끝나는 방법을 찾을 때까지 루프가 무한히 반복

2. LOOP Instruction

  • ECX 카운터에 따른 루프

  • 이명령어는 일정한 횟수만큼 반복

  • ECX는 자동으로 카운터로 사용되며 루프가 반복될 때마다 1씩 감소한다.

  • LOOP destination

    • 루프 대상은 현재 위치 카운터로부터 -128바이트에서 +127바이트 이내에 있어야 한다.
  • 동작 방식: ECX에서 1을 빼고 ECX를 0과 비교

  • ECX가 0이 아니라면, 대상으로 지정된 라벨로 점프

      mov ax, 0
      mov ecx, 5
      L1:
          inc ax
          loop L1
    • AX=5, ECX=0
  • Loop 사용 시 흔한 오류

    • 루프 시작 전 ECX를 0으로 초기화

    • 이러한 경우 LOOP 명령은 ECX를 FFFFFFFFh로 감소시키고 루프가 매우 많이 반복됨

    • CX가 루프 카운터인 경우에는 6만번

    • 루프 내에서 ECX를 수정해야 하는 경우, 루프의 시작 부분에서 변수에 저장 후, LOOP 명령 직전에 복원할 수 있다.

      .data
        count DWORD ?
      .code
        mov ecx, 100
      top:
        mov count, ecx
        .
        mov ecx, 20
        .
        mov ecx, count
        loop top
  • Nested Loops

.data
count DWORD ?
.code
    mov ecx, 100
L1:
    mov count, ecx
    mov ecx, 20
L2: 
    .
    .
    loop L2
    mov ecx count
    loop L1
  • 일반적으로, 2레벨 이상으로 중첩된 루프는 작성하기 어렵다.
  • 만약 깊은 중첩이 요구된다면, 일부 내부 루프를 서브루틴으로 옮기자.

3. Summing an Integer Array

  • 배열의 주소를 인덱스 용도로 사용할 레지스터에 할당
  • 루프 카운터를 배열 길이로 초기화
  • 합을 누적할 레지스터에 0을 할당
  • 루프의 시작을 표시하는 레이블 만든다.
  • 루프 몸체에서 합에 배열 요소 하나를 더한다.
  • 다음 배열 요소를 가리키도록 한다.
  • LOOP 반복
.data
intarray DWORD 10000h, 20000h, 30000h, 40000h
.code
main PROC
    mov edi, OFFSET intarray
    mov ecx, LENGTHOF intarray
    mov eax, 0
L1: 
    add eax, [edi]
    add edi, TYPE intarray
    loop L1
    invoke ExitProcess, 0
main ENDP
END main

4. Copying a String

.data
source BYTE "This is the source string", 0
target BYTE SIZEOF source DUP(0)
.code
main PROC
    mov esi, 0
    mov ecx, SIZEOF source
L1:
    mov al, source[esi]
    mov target[sei], al
    inc esi
    loop L1
    invoke ExitProcess, 0
main ENDP
END main
  • 메모리끼리 MOV 불가하므로 레지스터 사용
728x90

'CS > System programming' 카테고리의 다른 글

Procedures  (0) 2023.04.09
Assembly Language Fundamentals  (0) 2023.03.20
X86 Processor Architecture  (0) 2023.03.02
Basic concept  (0) 2023.02.23