본문 바로가기
TIL & WIL

[TIL] 크래프톤 정글 3주차 CS:app 4(메모리에서의 정보 접근)

by 적용1 2024. 4. 14.
728x90

3.4 정보 접근하기

- x86-64 주처리장치(CPU)는 64비트 값을 저장할 수 있는 16개의 범용 레지스터를 보유하고 있다.

x86-64에서 사용하는 16개의 범용 레지스터

 

- 위 그림에서 볼 수 있듯이 instruction들은 16개의 레지스터의 하위 바이트들에 저장된 다양한 크기에 대해 작동할 수 있다.

 

3.4.1 Operand Specifiers(오퍼랜드 식별자)

- 대부분의 instruction들은 하나 이상의 오퍼랜드를 갖는다.

  오퍼랜드는 연산을 수행할 source값과 그 결과를 저장할 destination의 위치를 명시한다.

 

다양한 오퍼랜드의 형태

 

- x86-64는 위와 같이 다양한 오퍼랜드 형태들을 지원한다.

 오퍼랜드는 세 가지 타입으로 구분할 수 있다.

 

1) immediate(상수 값을 말한다.)

ATT 형식의 어셈블리 코드에서 상수는 $ 기호 다음에 C 표준 서식을 사용하는 정수로, $-577, $0x1F와 같이 나타낸다.

 

2) register

· 레지스터의 내용을 나타내며, 각각 16개의 64bit, 32bit, 16bit, 8bit의 레지스터들이 하위 부분인 8, 4, 2, 1byte 중 하나의 레지스터를 가리킨다.

· 위 그림에서 r_a 는 레지스터 a를 나타내며, 해당 값은 R[r_a]를 참조하여 나타내며, 레지스터 집합을 r_a와 같은 레지스터 식별자를 index로 사용하는 array R로 본다.

 

3) 메모리 참조(memory reference)

· effective address라 불리는 계산된 주소에 의해 메모리 위치에 접근한다.

· 메모리를 거대한 바이트의 배열로 생각할 수 있으므로 M_b[Addr] 과 같이 표시한다.

(M_b[Addr] : 메모리 주소 Addr부터 저장된 b 바이트를 참조한다는 의미)

 

- 메모리 참조를 가능하게 하는 많은 주소 지정 방식 중 가장 일반적으로 Imm(r_b, r_i, s)와 같은 형태를 사용한다.

Imm : 상수 오프셋, r_b : 베이스 레지스터, r_s : 인덱스 레지스터, s : 배율(1, 2, 4, 8의 값을 가짐)

(레지스터는 64bit register이다.)

 

여기서 effective address는 Imm + R[r_b] + R[r_i] * s로 계산된다.

 

3.4.2 데이터 이동 instruction

- 가장 많이 사용되는 instruction은 데이터를 다른 위치로 복사시키는 것이다.

다양한 MOV insturction

 

- 위 그림은 MOV class라 부르는 데이터 이동 instruction들을 나타낸 것이다.

 

- 이 클래스는 4개의 instruction들로 구성된다.(movb, movw, movl, movq)

4개의 instruction들은 서로 다른 크기의 데이터(왼쪽부터 1, 2, 4, 8byte)에 대해 작동한다는 것을 제외하면 유사한 효과를 갖는다.

 

- source 오퍼랜드는 immediate(상수), 레지스터, 메모리에 저장된 값을 나타내고,

destination 오퍼랜드는 레지스터 또는 메모리 주소의 위치를 나타낸다.

x86-64에서는 데이터 이동 instruction에서 두 개의 오퍼랜드 모두가 메모리 위치에 올 수 없게 한다.

 

- 따라서 어떤 값을 메모리로부터 다른 메모리 위치로 복사하기 위해서는 2개의 instruction이 필요하다.

1) source 값을 레지스터에 적재하는 instruction

2) 레지스터의 값을 destination에 쓰기 위한 instruction

 

- 그림 3.2를 보면 이 instruction들을 위한 레지스터 오퍼랜드들은 16개의 레지스터 중 이름이 붙은 부분이 될 수 있다.

(레지스터의 크기는 instruction의 마지막 문자 b, w, l, q와 같아야한다.)

 

- 대부분의 경우 MOV instruction들은 특정 레지스터 바이트들이나 destination 오퍼랜드로 지정된 메모리 위치만을 업데이트하지만, movl의 경우 레지스터를 목적지로 갖는 경우, 레지스터의 상위 4byte도 0으로 설정한다.

 

instruction source destination의 순서로 MOV instruction이 구성된다.

 

- 그림 3.4의 마지막 instruction movabsq는 64bit 상수를 다루기 위한 것으로, 오직 32bit 2의 보수로 나타낼 수 있는 immediate source 오퍼랜드들만을 갖는다.

 

이 값은 그 후 부호 확장되어서 destination을 위해 64bit 값으로 만들어진다.

 

movabsq instruction은 임의의 64bit 상수 값을 source 오퍼랜드로 가질 수 있고, destination으로는 레지스터 만을 가질 수 있다.

 

2의 보수 : 컴퓨터가 음수를 저장하기 위한 방법 중 하나(맨 앞 비트가 0이면 양수, 1이면 음수)

└> 원래 수를 2진수로 바꾼 후, 1 -> 0, 0 -> 1로 바꾸는 NOT 연산을 한 뒤, 1을 더하면 2의 보수가 완성됨

 

 

movz instruction(zero extend를 하는 MOV instruction)
movs instruction(부호 확장을 하는 MOV instruction)

 

위의 두 그림은 작은 source 값을 더 큰 destination으로 복사하는 class 2개를 나타낸 것이다.

 

- MOVZ 클래스의 instruction들은 destination의 남는 byte들을 모두 0으로 채워주며,

MOVS 클래스는 가장 중요한 비트를 반복해서 복사하는 부호 확장으로 채운다.

 

- 이 instruction들은 마지막 2개 문자로 사이즈를 나타내는 것을 갖는다.

  첫 번째 : source의 크기, 두 번째 : destination의 크기

 

각 class에 1, 2 byte의 source 크기, 2, 4 byte의 destination 크기를 모두 다루는 3개의 instruction들이 포함되어 있다.(destination이 source보다 더 긴 경우만 다룸)

 

- 그림 3.5에 4 byte source를 8 byte destination으로 zero-extend instruction이 없다.

논리상으로는 해당 instruction은 movzlq여야 하지만, 존재하지 않는다.

따라서 레지스터를 destination으로 하는 movl을 이용해서 구현할 수 있다.(4byte를 생성하는 instruction이 상위 4byte를 0으로 채울 수 있다는 성질을 활용)

 

64bit destination의 경우, 부호 확장은 세 종류 source 모두 지원하고, 0으로 확장하는 이동은 2개의 더 작은 source 타입에 대해 지원한다.

 

- 그림 3.6은 cltq instruction에 대해서도 소개한다.

이 instruction은 오퍼랜드가 없이, 항상 레지스터 %eax를 source로, %rax를 destination으로 사용해서 부호 확장 결과를 만든다.

movslq %eax, %rax와 같은 효과를 내지만 더 압축적인 인코딩을 가짐

 

3.4.3 데이터 이동 예제

 

 

- (b)에서 리턴 값을 레지스터 %rax에 저장해서 함수가 값을 리턴하거나, 이 레지스터의 하위 부분 중의 하나로 리턴한다.

 

- (b)에서 주목해야 할 특징 2가지

1) C언어에서 포인터라 부르는 것이 어셈블리어에서는 단순히 주소임

2) x 같은 지역 변수들은 메모리에 저장되기 보다 종종 레지스터에 저장된다.(레지스터 접근 속도 >>> 메모리 접근 속도)

3.4.4 스택 데이터의 저장과 추출(push, pop)

 

 

- 마지막 2개의 데이터 이동 연산은 프로그램 스택에 데이터를 push하거나 pop하기 위해 사용된다.

 

- x86-64에서 프로그램 스택은 메모리 특정 영역에 위치한다.

 

 

- 위 그림처럼 stack의 top에는 stack 원소 중 가장 낮은 주소를 갖는다.

 

- 스택 포인터 %rsp는 스택의 맨 위(top) 원소의 주소를 저장한다.

 

- pushq와 popq는 1개의 오퍼랜드를 사용한다.

(pushq : 추가할 source 데이터, popq : 추출을 위한 데이터 destination)

 

- 쿼드워드 값을 스택에 추가하려면, 스택 포인터를 8 감소시키고, 추가할 값을 스택 주소의 새로운 top에 기록하는 것으로 구현된다.

 

- 위의 2개의 instruction은 총 8byte가 필요하지만, pushq는 1byte 기계어 코드로 인코딩된다.

 

- 그림 3.9에서 왼쪽 2개의 그림은 pushq %rax를 실행한 결과를 보여준다.

 

- 쿼드워를 pop하는 것은 스택 top 위치에서의 읽기 작업 후에 스택 포인터를 8 증가시키는 것으로 구현된다.

 

- 그림 3.9의 세 번째 그림은 popq %rdx 를 실행한 결과를 보여준다.

보이는 것처럼 %rsp가 0x108로 복구되기만 할 뿐, 0x123 값은 다른 값으로 덮어써질 때까지 메모리 주소 0x100에 남아있다.

 

Stack top : %rsp 가 가리키는 곳(이 위의 주소 값들은 stack에 포함되지 않은 무효인 값들)

 

- stack이 프로그램 코드와 다른 형태의 프로그램 데이터와 같은 메모리에 저장되기 때문에 프로그램들은 표준 메모리 주소 지정 방법을 통해 스택 내 임의의 위치에 접근이 가능하다.

ex) stack top이 쿼드워드라 할 때, movq 8(%rsp), %rdx instruction은 스택의 두 번째 쿼드워드를 레지스터 %rdx에 복사해준다.

728x90