x86-64 CPU는 다음과 같이 64비트 값 저장 가능한 범용 레지스터를 16개 가지고 있다.
- 맨 앞의 붙은 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;
}
breakpoint는 k = 10;을 수행하기 직전 단계로 설정했다.
스택의
끝 부분을 가리키는 `$rsp</code>는
0x7fffffffe4d0
의 주소를 가지고 있고
k
가 저장되어 있는 주소는
0x7fffffffe4c8
의 주소를 가지고 있다.
지역
변수 k
가 스택의 끝 부분보다 낮은 주소를 가지고
있으므로 기대하던 것과 같은 것으로 보인다.
실제
k
의 주소에 저장되어 있는 비트를 참조하면
0x0000000000000030
으로 16 * 3 = 48의 값이 잘
저장되어 있다.
</p>
3.4.1 오퍼랜드 식별자(specifier)
인스트럭션의 경우 인스트럭션을 수행할 오퍼랜드를 한 개 이상
가지게 된다.
오퍼랜드는 값을 참조해서 연산을 수행할
소스(source)가 될 수도 있고, 연산 수행의 결과를 저장할
목적지(destination)가 될 수도 있다.
보통 소스 값은 상수로 직접 주어지거나 레지스터에 있는 값을
참조할 수 있다.
결과값은 레지스터에 저장하거나 메모리에
직접 저장할 수 있다.
이러한 오퍼랜드는 세 가지 유형으로 구분할 수 있다.
-
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비트 데이터를 의미)  - 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 ```  - %rdi와 %rsi는 각각 첫번째 인자와 두번째 인자 주소를 갖는 레지스터. - 이 값은 함수가 실제로 실행되기 전에 설정된다. (함수 caller에 의해) - %rax는 t0값(주소 x), %rdx는 t1값(주소 x)을 가지고 있음      --- ### 주소 계산 인스트럭션 - 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,DestDest = Dest + Src subq
Src,DestDest = Dest - Src imulq
Src,DestDest = Dest*Src salq
Src,DestDest = Dest << Src shlq
로 불리기도 함sarq
Src,DestDest = Dest >> Src 산술 우측 시프트 shrq
Src,DestDest = Dest >> Src 논리 우측 시프트 xorq
Src,DestDest = Dest ^ Src andq
Src,DestDest = Dest & Src orq
Src,DestDest = Dest Src - 형식에서는 소스가 먼저 오고 목적지가 뒤에 오지만 실제 계산은 반대 순서인 것에 주의!
- 비트 수준 연산에서는 signed, unsigned 구분이 없으니까 여기서도 구분 없이 연산
오퍼랜드 한 개짜리 인스트럭션
형식 계산 incq
DestDest = Dest + 1 increment decq
DestDest = Dest - 1 decrement negq
DestDest = -Dest negation notq
DestDest = ~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$ 결과는 의도한 대로 나오지만 실제 기계어 수준 인스트럭션은 우리가 생각하는 대로 이루어지지 않을 수 있다는 점이 요점.`
-
'$'기호 다음에 C표준 형태의 정수로 나온다.
- $-577</code>이나