혼자 정리
[CSAPP] 3.4 정보 접근하기 ~ 3.5 산술연산과 논리연산 본문
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
는 0x7fffffffe4d0
의 주소를 가지고 있고 k
가 저장되어 있는 주소는 0x7fffffffe4c8
의 주소를 가지고 있다.
지역 변수 k
가 스택의 끝 부분보다 낮은 주소를 가지고 있으므로 기대하던 것과 같은 것으로 보인다.
실제 k
의 주소에 저장되어 있는 비트를 참조하면 0x0000000000000030
으로 16 * 3 = 48의 값이 잘 저장되어 있다.
3.4.1 오퍼랜드 식별자(specifier)
인스트럭션의 경우 인스트럭션을 수행할 오퍼랜드를 한 개 이상 가지게 된다.
오퍼랜드는 값을 참조해서 연산을 수행할 소스(source)가 될 수도 있고, 연산 수행의 결과를 저장할 목적지(destination)가 될 수도 있다.
보통 소스 값은 상수로 직접 주어지거나 레지스터에 있는 값을 참조할 수 있다.
결과값은 레지스터에 저장하거나 메모리에 직접 저장할 수 있다.
이러한 오퍼랜드는 세 가지 유형으로 구분할 수 있다.
- Immediate : 상수값을 의미
- '$'기호 다음에 C표준 형태의 정수로 나온다.
$-577
이나$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 인스트럭션 예시를 통해 메모리 주소 지정 방식 살펴보기
void swap(long *xp, long *yp)
{
long t0 = *xp;
long t1 = *yp;
*xp = t1;
*yp = t0;
}
swap:
movq (%rdi), %rax
movq (%rsi), %rdx
movq %rdx, (%rdi)
movq %rax, (%rsi)
ret
%rdi
와%rsi
는 각각 첫번째 인자와 두번째 인자 주소를 갖는 레지스터.- 이 값은 함수가 실제로 실행되기 전에 설정된다. (함수 caller에 의해)
%rax
는t0
값(주소 x),%rdx
는t1
값(주소 x)을 가지고 있음
주소 계산 인스트럭션
leaq
Src, Dst- Src는 주소 지정 방식으로 되어 있는 표현
- Dst는 레지스터여야 함. Dst를 Src가 나타내는 주소로 설정하는 인스트럭션.
- 어디에 쓰이는지?
- 메모리 참조하지 않고 주소를 계산하기
- ex)
p = &x[i];
의 어셈블리어 버전
- ex)
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$ 결과는 의도한 대로 나오지만 실제 기계어 수준 인스트럭션은 우리가 생각하는 대로 이루어지지 않을 수 있다는 점이 요점.
'CSAPP정리' 카테고리의 다른 글
[CSAPP] 3.3 데이터의 형식 (0) | 2021.06.22 |
---|---|
[CSAPP] 3.2 프로그램의 인코딩(Program Encodings) (0) | 2021.06.22 |
[CSAPP] 3. Machine-Level Representation of Programs ~ 3.1 A Historical Perspective (0) | 2021.06.21 |
[CSAPP] 2.2 정수의 표시(Ch. 2 정보의 표현과 처리) (0) | 2021.04.04 |
[CSAPP] 2.1 정보의 저장(Ch. 2 정보의 표현과 처리) (0) | 2021.04.03 |