Fundamental of CS/: : CSAPP

[CSAPP] Ch 3. 프로그램의 기계수준 표현 : (4) 정보 접근하기

Jay.P Morgan 2024. 3. 19. 23:23

  3.4  정보 접근하기

 

  x86-64 CPU  64bit 값을 저장할  있는 16개의 범용 레지스터 보유하고 있다 레지스터들은 정수 데이터 포인터를 저장하는데 쓰인다. 64bit로 확장하면서 8개의 새로운 레지스터들이 추가되었으며, %r8 ~ %r15라 칭한다.

 

레지스터 관련 참고 :

x86-64 CPU 레지스터(Register) 종류, 32bit / 64bit 비교 (tistory.com)

 

  중첩된 사각형이 보여주듯, 인스트럭션들은 16개의 레지스터 하위 바이트들에 저장된 다양한 크기의 데이터에 대해 연산할 수 있다. 바이트수준 연산들은 가장 덜 중요한 바이트에 대해 연산을 할 수 있으며, 16비트 연산들은 가장 덜 중요한2바이트에 접근하고, 32비트 연산은 덜 중요한 4바이트에, 64비트 연산은 레지스터 전체에 접근할 수 있다. 인스트럭션들은 16개의 레지스터 하위 바이트들에 저장된 다양한 크기의 데이터에 대해 연산이 가능하다.

(비트에 따라 쓰이는 레지스터가 구획)

 

  인스트럭션들이 레지스터들을 목적지로 할 때에는 8byte보다 작은 byte를 생성하는 인스트럭션들의 레지스터에서 남는 byte를 어떻게 처리해야 하는지 2가지 방법을 결정해야 한다.

      1 또는 2byte를 생성하는 경우에는 나머지 byte들은 변경 없이 그대로 유지

      4byte 길이의 값을생성하는 경우는 상위 4byte를 0으로 설정 

 

 서로 다른 레지스터들은 각각의 목적으로 이용된다. 스택포인터 (%rsp) 런타임 스택의  부분을 가리키기 위해 사용된다 외에 뒤에서도 살펴보겠지만상황 그리고 변수와 인자에따라 쓰이는 레지스터들이 나름대로 정해져있다.

 

 

  3.4.1  오퍼랜드 식별자 specifier

 

  대부분의 인스트럭션들은 하나 이상의 오퍼랜드 가진다.

  오퍼랜드는 연산을 수행할 소스(src) 값과  결과 값을 저장 목적지(dest) 위치를 명시한다.

   소스 값은 상수로 주어지거나 레지스터나 메모리로부터 읽을 수 있으며, 결과 값은 레지스터나 메모리에 저장된다.

※ 오퍼랜드의 종류. 배율 인자 s는 1, 2, 4, 8 중의 하나가 될 수 있다.

 

오퍼랜드의 종류

     1.  Immediate상수값이다. $기호 다음에 16비트의 정수값으로 표현이 된다.

     2.  Register​: 레지스터의 내용을 나타내며, 하위 일부분인 8, 4, 2, 1byte중 하나의 레지스터를 가리킨다.

           R[r_a]라고 한다면임의의 레지스터a 가리키게 된다. R 레지스터 집합배열이고, r_a 레지스터a 가리킨다.

     3.  Memory: 메모리 참조로, 유효주소라 부르는 연산된 주소로 메모리 위치에 접근한다.

           M_b[Addr] 같이 표시하며, 일반적으로 아래첨자 b 생략보통 괄호에 싸여져있음.

 

  여러 유형의 메모리 참조를 가능하게 하는 많은 주소지정방식 중, 가장 일반적인 형태는 표 마지막에 있는 Imm(r_b, r_i, s)이다. 이 형태의 메모리 참조는 4개의 구성요소를 갖는다. 

  상수 오프셋 Imm, 베이스 레지스터 r_b, 인덱스 레지스터 r_i, 배율 s

  유효주소는 Imm + R[r_b] + R[r_i] • s로 계산하며, 배열의 원소를 참조할 때 종종 볼 수 있다. 

 

 

  3.4.2  데이터 이동 인스트럭션

 

  가장 많이 사용되는 인스트럭션은 데이터를  위치에서 다른 위치로 복사하는 명령이다.

※ 간단한 데이터 이동 인스트럭션

 

  위 그림은 가장 간단한 형태의 데이터 이동 인스트럭션이다.

  이 인스트럭션들은 데이터를 소스 위치에서 목적지 위치로 그대로 복사한다.

  이 MOV 클래스는 movb, movw, movl, movq (1, 2, 4, 8byte) 네가지 크기의 버전으로 구성된.

 

 • 소스 오퍼랜드 상수, 레지스터 저장값, 메모리 저장값을 표시하며, 목적 오퍼랜드 레지스터 혹은 메모리 주소의 위치를 지정한.

 • x86-64는 데이터 이동 인스트럭션에서 두 개의 오퍼랜드 모두가 메모리 위치에 올 수 없도록 제한한다.

 • 한 메모리 위치에서 다른 위치로 값을 복사하기 위해서는, 두 개의 인스트럭션이 필요하다. (레지스터 적재, 메모리 쓰기)

 • 이 인스트럭션들에 레지스터가 오퍼랜드로 쓰일때, 레지스터의 크기와 mov접미사(b, w, l, q)의 크기가 일치해야한다.

 

 

 

  참고로 movq인스트럭션은 32비트 2 보수 숫자로 나타낼 있는 숫자만 소스 오퍼랜드로 가질 있고, 아래 나와있는 movabsq unsigned int 64비트 숫자를 소스 오퍼랜드로 가져갈 있다.

  Note:    mov연산에서 src dest 모두 메모리값이 없다!

메모리 -> 메모리 mov연산을 하고 싶으면, 레지스터에 적재하는 과정을 거치고, 레지스터값을 메모리로 mov해야한다.

위의 예시를 이해해보자. 처음에 movabsq 8바이트 단위의 값이 %rax 옮겨졌다.

그다음에 분명히 movb 통해 -1 옮겼는데 왠걸 %rax = 00112233445566FF 됐네?

마찬가지로 movw 연산 후에 %rax = 001122334455FFFF 됐네?

근데 movl 연산 후에는 %rax = 00000000FFFFFFFF 됐네?

부분이 mov중에 movl 조심해야 하는 예외적인 케이스이다.

l이전의 바이트에따라 두부분 혹은 네부분만 갱신이 된다. 레지스터 구조 자체가 %rax안에 %eax있고, 안에 %ax있고 그안에 %al 있는 구조라 그런 것이다. 같은 논리라면 movl이후에소 00112233FFFFFFFF 돼야할것 같은데 l 상위 4바이트를 0으로 채우고 값을 가져온다.

 

CASE1: 작은바이트 소스에서 큰바이트 목적지로 값을 옮기려면? → "확장" 필요

작은소스에서 목적지로 가기 때문에 무조건 끝에 접미사가 () + () 이다! (ex: movzbq)

1. unsigned 소스에서 어떤 목적지로 옮길때

movzlq 없으며, movl 자체역할을 한다.

2. signed 소스에서 어떤 목적지로 옮길때

여기서는 movz 다르게 l q 가능하다!

아래 cltq 무조건 movslq %eax, %rax 동치이다.

CASE2: 큰바이트 소스에서 작은바이트 목적지로 값을 옮기려면 확장 필요 없음.

 

 

  3.4.3  데이터 이동 예

 

 

%rax 리턴 value 담당이다.

프로시저가 시작되면 매개변수 xp y 레지스터 각각 %rdi, %rsi 저장이 된다.

인스트럭션에서 %rax 메모리에서 읽은 x 저장하고 %rax (%rdi) 저장 -> x = *xp

, *xp = y 구현한 모습이다.

여기서 있는점은 C언어에서 포인터라고 부르는 것이 어셈블리어에서는 단순히 주소라는 것이다.

  3.4.4  스택 데이터의 저장과 추출 push & pop

 

  push pop mov 비슷하지만 다른 이동연산중 하나이다. 이는 스택에 데이터를 저장(push) 하거나 추출(pop)하기 위해 사용된다. 스택에서는 (top)원소가 모든 스택 원소중에서 가장 낮은 주소를 갖는 형태이다.보통 자료구조 생각하면 스택의 top 위에 있고 위로 자라는 구조를 떠올릴텐데, 여기서는 반대이다.

     •  스택의 top 가장 아래있으며, 아래로 성장하는 구조이다.

     •  스택 포인터 %rsp 스택 원소의 주소를 저장하는 레지스터이다.

push pop 모두 오퍼랜드는 위처럼 하나만 사용한다.

push연산을 해석해보면, %rsp 8만큼 아래로 이동시키고, %rsp %rbp 저장한다.

%rbp값을 스택에 저장한다.

pop 연산을 해석해보자면 %rax 스택 맨위의값 %rsp값을 읽어오고 (스택에 있는 걸 )

%rsp 8만큼 위로 올린다. (원상복구)