x86-64 CPU는 다음과 같이 64비트 값 저장 가능한 범용 레지스터를 16개 가지고 있다.

registers

  • 맨 앞의 붙은 r은 register의 의미로 64비트를 뜻한다
  • 맨 앞에 e가 있는 경우 32비트를 의미
  • 기본적으로 그림에서 보듯이 인스트럭션은 16개의 레지스터에 있는 여러 크기의 하위 바이트 데이터에 대해 연산할 수 있다.
  • 64비트 연산시 레지스터 전체에, 32비트 연산시 하위 4바이트에, (위 그림에는 없지만) 16비트 연산시 하위 2바이트에, 8비트 연산시 하위 1바이트에 접근.

레지스터에서 1,2,4,8바이트를 사용할 수 있는데 8바이트를 사용하는 경우를 제외하고 전체 레지스터를 사용하지 않는다. 그러한 경우 레지스터의 남는 바이트들에 대해서 다음과 같이 처리한다.

  • 1 or 2바이트 생성 : 나머지 바이트 변경 없이 유지
  • 4바이트 생성 : 나머지 상위 4바이틀 0으로 설정

이 중 %rsp는 스택 포인터로 런타임 스택의 끝 부분을 가리킨다. 따라서 특정 인스트럭션들 위졸 이 레지스터를 읽거나 기록한다.
나머지 레지스터는 이보다는 조금 더 자유롭다.

cf) gdb를 통해 메인 함수의 지역 변수가 실제 스택에 저장되어 있는지 확인해 보았다.

int main(){
    long long k = 48 // 16 * 3
    k = 10;
}

gdb_rsp

breakpoint는 k = 10;을 수행하기 직전 단계로 설정했다.
스택의 끝 부분을 가리키는 `$rsp</code>는 0x7fffffffe4d0의 주소를 가지고 있고 k가 저장되어 있는 주소는 0x7fffffffe4c8의 주소를 가지고 있다.
지역 변수 k가 스택의 끝 부분보다 낮은 주소를 가지고 있으므로 기대하던 것과 같은 것으로 보인다.
실제 k의 주소에 저장되어 있는 비트를 참조하면 0x0000000000000030으로 16 * 3 = 48의 값이 잘 저장되어 있다. </p>


3.4.1 오퍼랜드 식별자(specifier)

인스트럭션의 경우 인스트럭션을 수행할 오퍼랜드를 한 개 이상 가지게 된다.
오퍼랜드는 값을 참조해서 연산을 수행할 소스(source)가 될 수도 있고, 연산 수행의 결과를 저장할 목적지(destination)가 될 수도 있다.

보통 소스 값은 상수로 직접 주어지거나 레지스터에 있는 값을 참조할 수 있다.
결과값은 레지스터에 저장하거나 메모리에 직접 저장할 수 있다.

이러한 오퍼랜드는 세 가지 유형으로 구분할 수 있다.

  1. Immediate : 상수값을 의미
    • '$'기호 다음에 C표준 형태의 정수로 나온다. - $-577</code>이나 $0x1F같은 형태. - 1,2,4바이트 중 하나로 인코딩된다. - Register : 레지스터의 내용을 의미 - 16개의 레지스터들의 하위 8바이트(전체), 4바이트, 2바이트, 1바이트를 의미한다. - 보통 %rax, %r13처럼 작성 - Memory : 특정 메모리 주소가 주어졌을 때 해당 메모리 주소에 들어있는 8개의 연속적인 값을 의미 - (%rax)와 같은 형태 : 괄호가 값 참조와 비슷한 의미 ex) movq srcs,dest인스트럭션을 사용하는 경우(srcs에서 dest로 데이터 이동; q는 8바이트=64비트 데이터를 의미) ![movq_operands](https://github.com/tbonelee/for_images/raw/master/movq_operands.png) - immediate값은 상수이므로 dest가 될 수 없다. - 하드웨어 기능 구현의 편의상 메모리에서 메모리로 직접 값 복사하는 것은 불가능. (mem -> reg && reg -> mem해야 함) ### 메모리 주소 지정 방식 - Normal : 표기 - (R) -> 의미 - Mem[Reg[R]] - 여기서 레지스터 R은 메모리 주소를 의미. - 뒤에 Mem[Reg[R]]은 메모리에서 레지스터 R이 담고 있는 주소를 가지고 참조해서 값을 반환하는 것. - C의 포인터 역참조를 생각하면 된다. - movq (%rcx), %rax - Displacement(이동) : 표기 - D(R) -> 의미 - Mem[Reg[R]+D] - R은 위와 같은 의미 - 주소가 D의 오프셋만큼 이동(양수든 음수든) - movq 8(%rbp), %rdx - Most General Form : 표기 - D(Rb,Ri,S) -> 의미 - Mem[Reg[Rb]+S*Reg[Ri]+D] - D: 상수항으로 1,2,4바이트가 올 수 있음 <- 오프셋의 의미 - Rb: Base register: 16개의 정수 레지스터 중 아무 거나 - Ri: Index register: %rsp을 제외한 모든 레지스터 - S: Scale: 1,2,4,8이 올 수 있음(인덱스를 하나씩 셀 때 데이터 크기만큼 넘겨야 하는데 그 데이터 크기가 보통 1,2,4,8이 오니까..) ### - 메모리 주소 계산 예시 다음의 주소값을 가정 | %rdx | 0xf000 | | %rcx | 0x0100 | 그러면 다음에서 명령과 계산 결과를 알 수 있다. | Expression | 주소 계산 | 주소 | | --- | --- | --- | | 0x8 (%rdx) | 0xf000 + 0x8 | 0xf008 | | (%rdx, %rcx) | 0xf000 + 0x100 | 0xf100 | | (%rdx, %rcx, 4) | 0xf000 + 4*0x100 | 0xf400 | | 0x80(,%rdx,2) | 2*0xf000 + 0x80 | 0x1e080 | | cf) base register도 생략 가능(다른 것도 마찬가지지만..) | | | ### movq 인스트럭션 예시를 통해 메모리 주소 지정 방식 살펴보기 ```False void swap(long *xp, long *yp) { long t0 = *xp; long t1 = *yp; *xp = t1; *yp = t0; } ``` ```False swap: movq (%rdi), %rax movq (%rsi), %rdx movq %rdx, (%rdi) movq %rax, (%rsi) ret ``` ![swap](https://github.com/tbonelee/for_images/raw/master/swap.png) - %rdi와 %rsi는 각각 첫번째 인자와 두번째 인자 주소를 갖는 레지스터. - 이 값은 함수가 실제로 실행되기 전에 설정된다. (함수 caller에 의해) - %rax는 t0값(주소 x), %rdx는 t1값(주소 x)을 가지고 있음 ![swapex1](https://github.com/tbonelee/for_images/raw/master/swap_ex1.png) ![swapex1](https://github.com/tbonelee/for_images/raw/master/swap_ex2.png) ![swapex1](https://github.com/tbonelee/for_images/raw/master/swap_ex3.png) ![swapex1](https://github.com/tbonelee/for_images/raw/master/swap_ex4.png) ![swapex1](https://github.com/tbonelee/for_images/raw/master/swap_ex5.png) --- ### 주소 계산 인스트럭션 - leaqSrc, Dst - Src는 주소 지정 방식으로 되어 있는 표현 - Dst는 레지스터여야 함. Dst를 Src가 나타내는 주소로 설정하는 인스트럭션. - 어디에 쓰이는지? - 메모리 참조하지 않고 주소를 계산하기 - ex) p = &x[i];의 어셈블리어 버전 - x+k*y형태의 산술 표현을 계산 - k = 1,2,4,8 - 주소를 할당하는 것이지 값을 할당하는 것이 아님에 유의! - ex) 다음 예시에서 leaq를 사용.(아마 같은 8바이트 데이터라 주소 연산을 활용해서 최적화하는 느낌?) (lea인스트럭션을 쿼드워드에 수행)컴파일러가 다음의 어셈블리어로 변환 - leaq (%rdi,%rdi,2), %rax # t <= x+x*2 salq $2, %rax # return t<<2
    • long m12(long x) { return x*12; }
    • %rdi에 있는 주소값(정수)에 (%rdi값*2)한 값을 더하면 3만큼 곱한 값이 구해지고
    • salq를 통해 2만큼 좌측 비트 시프트(2^{2} = 4만큼 곱한 효과)
    • 12만큼 곱한 효과

    산술 계산 오퍼레이션

    오퍼랜드 두 개짜리 인스트럭션

    형식 계산 특이사항
    addq Src,Dest Dest = Dest + Src
    subq Src,Dest Dest = Dest - Src
    imulq Src,Dest Dest = Dest*Src
    salq Src,Dest Dest = Dest << Src shlq로 불리기도 함
    sarq Src,Dest Dest = Dest >> Src 산술 우측 시프트
    shrq Src,Dest Dest = Dest >> Src 논리 우측 시프트
    xorq Src,Dest Dest = Dest ^ Src
    andq Src,Dest Dest = Dest & Src
    orq Src,Dest Dest = Dest Src
    • 형식에서는 소스가 먼저 오고 목적지가 뒤에 오지만 실제 계산은 반대 순서인 것에 주의!
    • 비트 수준 연산에서는 signed, unsigned 구분이 없으니까 여기서도 구분 없이 연산

    오퍼랜드 한 개짜리 인스트럭션

    형식 계산
    incq Dest Dest = Dest + 1 increment
    decq Dest Dest = Dest - 1 decrement
    negq Dest Dest = -Dest negation
    notq Dest Dest = ~Dest not

    산술 표현 예시

    long arith(long x, long y, long z)
    {
        long t1 = x+y;
        long t2 = z+t1;
        long t3 = x+4;
        long t4 = y * 48;
        long t5 = t3 + t4;
        long rval = t2 * t5;
        return rval;
    }

    어셈블리어로 변환하면 다음과 같다.

    arith:
        leaq    (%rdi,%rsi), %rax   # t1
        addq    %rdx, %rax       # t2
        leaq    (%rsi,%rsi,2), %rdx
        salq    $4, %rdx # t4
     leaq 4(%rdi,%rdx), %rcx # t5
     imulq %rcx, %rax # rval
     ret
    
    | Register | 어떻게 쓰였는지 |
    | --- | --- |
    | %rdi | 인자 x |
    | %rsi | 인자 y |
    | %rdx | 인자 z |
    | %rax | t1, t2, rval |
    | %rdx | t4 |
    | %rcx | t5 |
    
    
    	- leaq : 주소 계산
    	- salq : 비트 좌측 시프트
    	- imulq : 곱셈
    	- 우리가 작성한 코드와 실제 컴파일된 어셈블리어는 딱
    	 들어맞지 않는다. (심지어 직접적인 t3연산은 찾을 수 없음)
    	- 컴파일러가 최적화 과정을 거치기 때문
    	- $\therefore$ 결과는 의도한 대로 나오지만 실제 기계어 수준
    	 인스트럭션은 우리가 생각하는 대로 이루어지지 않을 수
    	 있다는 점이 요점.`