![](https://velog.velcdn.com/images/kts5927/post/9ccfaced-2130-402e-bfea-873e9d762968/image.png)
책은 읽어도 뭔소리인지 모르겠다.
하지만 짤의 왼쪽처럼 포기할건가?
난 포기하지 않고 동료의 til 과 인터넷의 일명 '해석본'을 참고해서 글을 적을 것이다.
근데 적는다 해놓고 그렇게 자세하게 쓰지 않을것이다.
![](https://velog.velcdn.com/images/kts5927/post/d39fda5e-06b1-48cd-b51f-6f969d5f1aa2/image.png)
둔기는 이 돼지도감 하나면 충분하다.
3-1장 역사적 관점
별 다른 내용은 없다.
간단하게 요약하자면
![](https://velog.velcdn.com/images/kts5927/post/d12e94d9-a046-4ffa-b840-2319f6a122ae/image.png)
이친구가
![](https://velog.velcdn.com/images/kts5927/post/9f922371-bce1-4cd9-8f48-619558c765f1/image.png)
이런 멋진 코어가 될때까지의 과정을 적어놓은거다.
물론 책에서는 13년에 나온 i7 haswell 까지 적어놨지만
- 대충 32비트가 64비트가 되었네
- 몇년에 어떤게 나왔네
이런 이야기가 적혀있다.
1장에서 중요하게 봐야할 것은
앞으로 적힐 아키텍쳐는 x86-64를 기준으로 적는다는 것이다.
3-2 프로그램의 인코딩
c 파일이 인코딩 될때 컴파일러, 어셈블리어, 링커를 거쳐 실행파일 p를 만든다는 것은 앞에서 나와 있을 것이다. 이것 이외에도 중요하게 봐야할 것은 기계어 수준의 코드 이다.
기계어 수준 코드
- 기계수준 프로그램의 형식과 동작은 인스트럭션 집합구조 ISA에 의해 정의된다.
- 기계수준 프로그램이 사용하는 주소는 가상주소이며, 메모리가 매우 큰 바이트 배열인것처럼 보이게 하는 메모리 모델을 제공한다.
이후 x86-64 기계어 코드에 대한 설명이 나온다.
X86-64 기계어 코드
- 프로그램 카운터(%rip) : 실행할 다음 인스트럭션의 주소를 가르킴
- 정수 레지스터 파일 : 64비트 값을 저장하기 위해 16개의 이름을 붙인 위치.
- 조건코드 레지스터 : 가장 최근에 실행한 산술/논리 인스트럭션에 관해 상태정보를 저장.
- 벡터 레지스터 : 하나 이상의 정수나 부동소수값 저장
3-3 데이터 형식
그저 간단하게 인터넷에서 볼 수 있는 자료형이다.
![](https://velog.velcdn.com/images/kts5927/post/8ab91de7-ff9a-422a-899e-443f7198eec0/image.png)
이거 하나면 설명이 된다.
3-4 정보 접근하기
![](https://velog.velcdn.com/images/kts5927/post/aa693cc2-eddf-45d0-8fe0-4e966e448a2c/image.png)
위 그림은 레지스터에 해당하는 이름을 나타내는 것이다.
인스트럭션은 각각 다른 이름을 가진 16개의 레지스터로 연산이 가능하다.
또한 특이한 점이 각각의 레지스터들은 저마다 잘하는 연산이 따로 있다는 점이다.
예를 들면 %rsp는 스택 포인터로 런타임 스택의 끝 부분을 가리키기 위해 사용되며
%rip는 프로그램카운터라고 하며 실행할 다음 인스트럭션의 메모리 주소를 가리킨다.
오퍼랜드 식별자
![](https://velog.velcdn.com/images/kts5927/post/1de92476-9156-419c-aff6-9e1ef09a961c/image.png)
정말 간단하게 설명하면 '계산 형식' 이다.
한가지 생각을 해 보자.
우리가 사칙연산을 어떻게 하는가?
- +기호는 양옆의 숫자를 더한다.
- -기호는 양옆의 숫자를 뺀다.
- *기호는 우선적으로 계산되며 양옆의 숫자를 곱한다.
- %기호는 우선적으로 계산되며 왼쪽의 숫자를 오른쪽 숫자로 나눈다.
- 이때, +,-/,% 의 우선순위가 같다. 즉 와 %는 계산순서가 바뀌어도 된다.
라고 하지 않는가?
이와 마찬가지로 그냥 '계산 방법'이라고 알면 된다.
이 오퍼랜드는 3가지 형식이 있다.
- immediate : 상수값이다. ATT 어셈블리 코드에서 $ 기호를 앞에 붙인다.
- register : 레지스터의 내용을 가르킨다. 각각 16개의 64,32,16,8 비트 레지스터들의 하위 부분인 8,4,2,1 바이트 중 하나의 레지스터를 가리킨다.
- 메모리 참조 : 유효참조라고 부르는 계산된 주소에 의해 메모리 위치에 접근한다.
데이터 이동 인스트럭션
가장 많이 사용되는 인스트럭션은 데이터를 한 위치에서 다른 위치로 복사하는 명령이다.
이 복사 인스트럭션은 크게 4가지가 있는데
- movb Move byte
- movw Move word
- movl Move double word
- movq Move quad word
가 있다.
이름에서 짐작할수 있듯이 각각 byte,word,double word, quad word의 크기를 가진다.
이것 이외에도 move 명령어의 두 피연산자 모두 메로리를 가리키면 안된다는 규칙이 있기 때문에 메모리를 한 위치에서 다른쪽으로 복사하려면 2개의 명령어가 필요하다.
대부분 mov는 destination 오퍼랜드가 가르키는 특정한 레지스터 바이트 혹은 메모리 위치만을 업데이트 한다.
스택 데이터의 저장과 추출
스텍 데이터는 다음과 같은 특징을 갖는다.
- 자료구조의 '스택'과 마찬가지로 LIFO 형식을 가진다.
- X86-64는 TOP 원소가 낮은 주소값을 가지고, BOTTOM 원소가 높은 주소값을 가진다. 스택은 탑에서 바텀 방향으로 커지고, 탑에서 원소가 pop된다.
- 스택이 프로그램 코드와 다른 형태의 프로그램 데이터와 동일한 메머리에 저장되기 때문에 프로그램들은 표준 메모리 주소지정 방법을 사용해서 스택 내 임의의 위치에 접글 할 수 있다.
산술연산과 논리연산
연산들은 다음과 같은 그룹으로 나뉜다.
- 유효주소 적재
- 단항(Unary)
- 이항(Binary)
- 쉬프트(Shift)
+여기에 추가로 이항연산은 두 개의 오퍼랜드를 가지는 반면, 단항 연산은 한 개의 오퍼랜드를 갖는다.
유효주소 적재
![](https://velog.velcdn.com/images/kts5927/post/e6c91471-115a-46b1-9308-9817d9b480fd/image.png)
- 유효주소 적재 인스트럭션 leaq는 movq 인스트럭션의 변형이다.
- leaq는 메모리에서 레지스터로 읽어들이는 인스트럭션의 형태를 갖지만, 메모리를 전혀 참조하지 않는다.
- leaq는 인스트럭션의 첫 번째 오퍼랜드는 일종의 메모리 참조처럼 보이지만, 가리키는 위치에서 읽기를 수행하는 대신에 유효주소를 목적지에 복사한다.
- leaq는 나중의 메모리 참조에 사용하게 되는 포인터를 생성하기 위해 사용한다.
연산
![](https://velog.velcdn.com/images/kts5927/post/ef5c1015-57aa-4dc5-95cf-59ae9f736a46/image.png)
이걸로 설명을끝낸다.
- 참고로 16진수라서 4*4 하면 10이다.
그러니까 110이 맞다.
3-7 프로시저
-
프로시저 호출은 소프트웨어에서의 주요 추상화, 지정 된 인자들과 리턴 값으로 특정 기능을 구현하는 코드를 감싸주는 방법을 제공한다.
-
잘 설계 된 소프트웨어는 무슨 값이 계산되고, 이 프로시저가 프로그램 상태에 무슨 효과를 갖는지에 대한 명쾌하고 간결한 인터페이스 정의를 제공하는 한편, 일부 동작의 구체적인 구현은 감춰주는 방식으로 프로시저를 추상화 매커니즘으로 이용한다. 프로시저는 서로 다른 프로그래밍 언어에서 여러가지 다른 모습-함수, 메소드, 서브루틴, 핸들러- 등으로 사용된다. 그러나 이 모두는 일반적은 특징들을 공유한다.
-
프로시저에 대한 기계어 수준 지원을 제공할 때 처리되어야 하는 여러 많은 특성
-
- 제어권 전달 : 프로그램 카운터는 진입할 때 Q에 대한 코드의 시작주소로 설정되고 리턴할 때는 P에서 Q를 호출하는 인스트럭션 다음의 인스트럭션으로 설정 되어야 한다.
-
- 데이터 전달 : P는 하나 이상의 매개변수를 Q에 제공할 수 있어야 하며 Q는 다시 P로 하나의 값을 리턴할 수 있어야한다.
메모리 할당과 반납 : Q는 시작할 때 지역변수들을 위한 공간을 할당할 수도 있고, 리턴할 때 이 저장소를 반납할 수도 있다.
런타임 스택
- 대부분 언어에서 프로시저 호출 동작 방식의 주요 특징은 스택 자료 구조가 제공하는 후입선출 메모리 관리 방식을 활용할 수 있다는 점이다. 프로그램은 스택을 사용해서 프로시저들이 요구하는 저장장소를 관리할 수 있으며, 여기서 스택과 프로그램 레지스터들은 제어와 데이터를 전송하기 위해, 메모리를 할당하기 위해 필요한 정보를 저장한다.
- x86-64 프로시저가 레지스터들에 저장할 수 있는 개수 이상의 저장공간을 필요로 할때 공간을 스택에 할당한다. 이 영역은 프로시저의 스택프레임 이라고 부른다.
- 현재 실행중인 프로시저에 대한 프레임은 항상 스택의 맨 위에 위치한다.
- 프로시저P가 프로시저Q를 호출할 때 리턴주소 return address를 스택에 푸시해서 Q가 리턴할 때 P에서 프로그램이 실행을 재시작 해야하는 위치를 가리킨다. 리턴 주소가 P에 관계된 상태를 저장하기 때문에 리턴 주소는 P에 스택 프레임에 속하는 것으로 간주한다. Q에 대한 코드는 현재 스택 경계를 확장해서 자신의 스택프레임을 위한 공간을 할당한다. 이 공간 내에서 레지스터 값들을 저장하고, 지역변수들을 위한 공간을 할당하며, 자신이 호출하는 프로시저들을 위한 인자들을 설정할 수 있다. 대부분의 프로시저의 스택프레임들은 프로시저가 시작될 때 할당되는 고정 크기를 갖는다 그러나 일부 프로시저는 가변 크기 프레임을 필요로 한다.
- 시간과 공간 효율성을 위해 x86-64 프로시저는 요청받은 스택프레임의 부분만을 할당한다. 사실 많은 함수들은 심지어 스택 프레임을 요청하지도 않는다. 이런 경우는 모든 지역변수들을 레지스터에 보관할 수 있고, 이 함수가 다른 함수 하나를 하나도 호출하지 않을 때 발생한다.
제어의 이동
- 제어를 함수 P에서 함수 Q로 전달하는 것은 단순히 프로그램 카운터 PC를 Q를 위한 코드의 시작주소로 설정하는 것과 관련된다.
- x86-64에서 call인스트럭션 Q로 프로시저 Q를 호출해서 기록한다. call인스트럭션은 호출된 프로시저가 시작하는 인스트럭션 주소를 목적지로 갖는다. 점프와 유사하게 명령은 직접 혹은 간접 형태를 갖는다 어셈블리 코드에서의 직접 호출의 목적지는 레이블로 주어지는 반면, 간접호출의 목적지는 ‘*’ 와 그 뒤에 따라오는 식별자에 의해 주어진다.
- 리턴 주소를 스택에 푸시하는 방법을 사용해서 함수가 나중에 프로그램의 적절한 위치로 리턴 가능하게 된다는 것을 알 수 있다.
데이터 전송
- 프로시저로 부터의 데이터 전달은 레지스터를 통해 일어난다.
- x86-64에서는 최대 여섯개의 정수형(정수와 포인터) 인자가 레지스터로 전달될 수 있다. 이 레지스터들은 전달되는 데이터 형의 길이에 따라 레지스터 이름을 이용해서 정해진 순서로 이용된다.
- 인자들은 인자 리스트에서 각자의 순서에 따라 이들 레지스터에 할당된다.
- 함수가 여섯개 이상의 정수형 인자를 가질 때, 다른 인자들은 스택으로 전달된다.
- 매개변수들은 스택으로 전달할 때, 모든 데이터 길이는 8의 배수로 반올림 된다.
- 인자들이 배치되면, 프로그램은 프로시저로 제어를 전달하기 위해 call인스트럭션을 실행할 수 있다. 프로시저는 레지스터와 스택을 통해 자신의 인자들에 접근할 수 있다. 프로시저가 6개가 넘는 인자를 호출하려면 자신의 스택프레임에 argument build area 라고 이름 붙인 영역으로 이들을 위한 공간을 할당할 수 있다.
스택에서의 지역 저장 공간
- 지역데이터가 메모리에 저장 되어야 하는 경우
- 지역데이터 모두를 저장하기에는 레지스터수가 부족하다.
- 지역 변수에 연산자 ‘&’가 사용되었으며, 이 변수의 주소를 생성할 수 있어야 한다.
- 일부 지역 변수들이 배열 또는 구조체여서 이들이 배열이나 구조체 참조로 접근 되어야 하는 경우
레지스터를 이용하는 지역저장소
- 프로그램 레지스터들은 모든 프로시저들이 공유하는 단일 자원의 역할을 한다.
- 레지스터 %rbx, %rbp, %r12-%r15는 피호출자 - 저장 레지스터로 구분한다.
- 프로시저P가 프로시저 Q를 호출할 때, Q는 Q가 P로 리턴될 때 Q가 호출되었을 때의 값들과 동일하도록 보장할 수 있게 이 레지스터들의 값을 보존해야한다. 프로시저 Q는 이 값을 전혀 변경하지 않거나 원래의 값을 스택에 푸시해두고 이 값을 변경하며, 리턴하기 전에 스택에서 이전 값을 POP 해오는 방식으로 레지스터를 보존한다.
- 안전하게 값을 저장할 수 있으며 이 값이 위험 없이 레지스터에서 사용할 수 있다. 스택포인터 %rsp 를 제외한 다른 모든 레지스터들은 호출자 저장 레지스터로 구분된다. 이것은 이들이 함수에 의해 변경될 수 있다는 것을 의미한다.
재귀 프로시저
- 함수를 재귀적으로 호출 하는것도 다른 함수의 호출과 마찬가지로 진행된다.
- 스택 기법을 사용해서 함수의 각 호출시 상태정보(리턴 위치의 저장된 값, 피호출자-저장 레지스터를 위한 자신만의 저장공간을 제공한다.) 필요한 경우 지역변수를 위한 저장 공간도 제공할 수 있다.