3. 프로그램의 기계 수준 표현
고급 언어 : 모든 PC에서 가능 -> 어셈블리어 : 특정 PC에서만 가능
But 성능의 극대화와 최적화에 이용하기 위해 기계어 코드의 공부
3.2 프로그램의 인코딩
3.2.1 기계 수준 코드
- ISA Instruction Set Architecture
-기계 수준 프로그램의 형식과 동작을 정의.
-프로세서의 상태, 인스트럭션의 형식, 그리고 그 영향들을 정의.
-기계 수준 프로그램이 사용하는 가상 주소를 메모리가 매우 큰 바이트 배열인 것처럼 보이게 하는 메모리 모델 제공
- Moore의 법칙
-
인텔 설립자 Gordon Moore는 당시 칩 기술로부터 외삽하여 칩당 트랜지스터 수가 향후 10년간 두배씩 증가하리라 예측, 실제로 50년 이상 평균 18개월마다 2배씩 늘어남
-
프로세서의 상태는 일반적으로 감춰짐
- 프로그램 카운터(PC, %rip)는 실행할 다음 인스트럭션의 메모리 주소 가리킴
- 정수 레지스터 : 64비트 값 저장을 위해 16개의 이름을 붙인 위치 가짐, 주소나 정수 데이터 저장, 일부 레지스터는 함수 리턴 값, 프로그램 상태 추적, 프로시저의 지역 변수 같은 임시값 저장.
- 조건코드 레지스터 : 가장 최근에 실행한 산술 or 논리 인스트럭션 상태 저장, if/while 문의 구현에 필요한 제어, 조건에 대한 데이터 흐름 변경 구현에 사용.
- 벡터 레지스터 : 하나 이상의 정수 or 부동소수점 값들을 각각 저장
(기계어 코드는 메모리를 단순히 바이트 지정 가능한 큰 배열로 여김)
3.2.2
- 인스트럭션들은 1~15 바이트 길이, 인코딩은 자주 사용되는 인스트럭션들과 오퍼랜드가 적은 것들이 짧은 길이를 갖도록 함, 반대의 경우는 길게
- 인스트럭션 형식은 디코딩할 수 있는 유일한 바이트 값을 준다.(바이트 값이 겹치지 않음)
3.3 데이터의 형식

3.4 정보 접근하기

- 인스트럭션들은 16개의 레지스터 하위 바이트들에 저장된 다양한 크기의 데이터에 대해 연산 가능
3.4.1 오퍼랜드 식펼자 specifier
-
대부분의 인스트럭션은 하나 이상의 오퍼랜드 가짐
-
오퍼랜드는 연산을 수행할 소스Source 값과 그 결괄르 저장할 목적지 destination의 위치 명시
-
소스 : 상수 or 레지스터나 메모리에서 읽어옴
-
오퍼랜드의 종류
- immediate 상수값
- register 레지스터의 내용
- 메모리 참조 : 유효 주소 effective address라 부르는 계산된 주소에 의해 메모리 위치에 접근.
3.4.2 데이터 이동 인스트럭션
-
데이터를 다른 위치로 복사
-
여러 데이터에 이동 명령 + 수행할 변환과 부수 효과 부여 -> 이것들을 인스트럭션 클래스로 구분
-
인스트럭션들은 같은 동작을 하는 다른 오퍼랜드 크기를 사용하여 수행
ex) mov : 다른 위치로 복사, 서로 다른 크기의 데이터에 대해 계산
-
소스 오퍼랜드 : 상수. 레지스터 저장 값. 메모리 저장 값 표시
-
목적 오퍼랜드 : 레지스터 or 메모리 주소 위치 지정
- 둘 다 메모리 위치엔 올수 없음
-
메모리 안에서 다른 위치로 값을 복사할 때 - 소스 값을 레지스터에 적재하는 인스트럭션 + 레지스터 값을 목적지에 쓰는 인스트럭션
3.4.4 스택 데이터의 저장과 추출 Push, Pop
-
popq, pushq : 한 개의 오퍼랜드 사용(추가할 소스 데이터와 추출을 위한 데이터 목적지)
-
pop : 스택 포인터를 증가, push : 스택 포인터를 감소

-
스택 탑은 %rsp 가 가리키는 곳이고 열에서 그 위에 저장된 값은 모두 무효.
3.5 산술 연산과 논리 연산
3.5.1 유효 주소 적재 load Effective Address
- leaq : movq의 변형. 메모리에서 레지스터로 읽어들이나 메모리는 참조하지 않음
3.5.2 단항 및 이항연산
3.5.3 쉬프트 연산
3.6 제어문
3.6.1 조건 코드
3.6.2 조건 코드 사용하기
3.6.3 점프 Jump
- 프로그램이 완전히 새로운 위치로 실행을 전환하게 함
3.6.4 점프 인스트럭션 인코딩
- pc 상대적relative 방법 : 대상 인스트럭션과 점프 인스트럭션 바로 뒤에 오는 인스트럭션 주소와의 차이를 인코딩
- 3.6 절은 나중에 더 보자
3.7 프로시저
- 좋은 소프트웨어 시스템 : 계산된 값과 프로시저가 프로그램 상태에 미치는 효과를 정의 내려 보여주고, 일부 동작의 구체적 구현은 감춰주는 방식으로 프로시저를 추상화 매커니즘으로 이용
- 프로시저는 여러 프로그래밍 언어에서 다르게 사용되지만, 일반적 특징을 공유한다.
- 함수, 메소드, 핸들러 등
ex) 프로시저 P가 프로시저 Q 호출, Q 실행 후 다시 P로 리턴
- 제어권 전달 : 프로그램 카운터는 진입 시 Q에 대한 코드 시작 주소로 설정. 리턴 시엔 P에서 Q를 호출하는 인스트럭션 다음의 인스트럭션으로 설정.
- 데이터 전달 : P는 하나 이상의 매개 변수를 Q에 제공할 수 있어야 하고, Q는 다시 P로 하나의 값을 리턴할 수 있어야 함.
- 메모리 할당과 반납 : Q는 시작할 때 지역 변수들을 위한 공간을 할당할 수도 있고, 리턴할 때 이 저장소를 반납할 수도 있다.
3.7.1 런타임 스택
- 프로시저 호출 동작 방식 : 스택 자료 구조의 후입 선출 활용 가능

- 프로시저가 레지스터 이상의 저장공간을 필요로 할 땐 공간을 스택에 할당(프로시저의 스택 프레임)
- P의 return address : Q가 리턴할 때 P가 재시작할 위치 가리킴
- 대부분의 프로시저의 스택 프레임은 시작할 때 할당되는 고정 크기 가짐.
3.7.2 제어의 이동
- Q리턴 & P재실행 (인스트럭션 call Q 로 프로시저 Q를 호출하여 기록)
- call Q는 주소 A를 스택에 푸시 -> pc를 Q의 시작으로 설정
- A : 리턴 주소, call 인스트럭션 바로 다음 인스트럭션 주소로 계산. 이에 대응되는 인스트럭션 ret는 주소 A를 스택에서 팝 해오고 pc를 A로 세팅
call Label Procedure call
call Operand Procedure call
ret Return from call
3.7.3 데이터 전송
- 프로시저로부터의 데이터 전달은 레지스터를 통해 일어남
3.7.4 스택에서의 지역 저장 공간
- 지역 데이터가 메모리에 저장되어야 하는 경우
- 지역 데이터 모두를 저장하기에 레지스터 수 부족
- 지역 변수에 연산자 '&'가 사용도고, 이 변수의 주소를 생성할 수 있음
- 일부 지역 변수들이 배열 or 구조체. 이들이 참조로 접근되어야 한다.
3.7.5 레지스터를 이용하는 지역 저장소
- 프로그램 레지스터 : 모든 프로시저들이 공유하는 단일 자원의 역할
- P가 Q호출 시 P의 데이터들은 레지스터에 저장되어 있거나 스택에 저장되어 있어야 함.
3.7.6 재귀 프로시저
- 각 프로시저 콜은 스택 상에 자신만의 사적 공간을 가지고, 각 지역 변수들은 서로 간섭하지 않는다.
- 재귀 시 함수의 호출-리턴 과 스택의 할당-반환 은 일치
3.8 배열의 할당과 접근
-
배열 원소들에 대한 포인터 생성, 포인터 간의 연산 가능
-
Python 과의 차이
- python은 배열 자체를 언어가 가짐. 그러나 C는 메모리에 할당된 배열의 주소만을 저장하여 효율적. 주소를 포인터로 나타내어 배열 원소의 인덱스를 찾는다.
3.8.1 기본 원리
- 자료형 T, 정수형 상수 N
T A[N];
- 시작 위치 Xa 표시.
- 자료형 T의 크기 : L. L*N 바이트의 연속적 공간을 메모리에 할당
- 새로운 식별자 A를 통해 배열이 시작하는 위치의 포인터로 사용(Xa)
3.8.2 포인터 연산
- P(T의 포인터), Xp(P의 값), p+i = Xp + L*i
- 인덱스에 접근하기 위해서 ex ) Xp + 4i (4바이트 int 자료형 T)
3.8.3 다중 배열
- 사람들의 추상화와 다르게 실제론 [[arr1], [arr2] ...] 의 형태로 1-D 배열로 메모리에 저장
- 읽는 방식이 1-D 포인터와 거의 유사
- T D[R][C]; &D[i][j] = Xd + L(C*i + j)
3.8.4 고정 크기의 배열
- 배열의 시작 위치를 ptr, 끝을 end로 설정.
3.8.5 가변 크기의 배열
- long n , int A[n][n], long i, longj
- Xa + 4(ni) + 4j = Xa + 4(ni + j)
3.9 이기종 Heterogeneous 자료 구조
- struct 구조체 : 다수의 객체를 한 단위로 연결
- union 공용체 : 하나의 객체를 여러 자료형으로 참조되게
3.9.1 구조체
3.9.2 공용체
- 구조체와 유사하나 메모리 할당이 효율적
- But 버그 발생의 위험
3.9.3 데이터의 정렬
- 할당된 메모리에서 낭비되지 않게 활용
- 구조체의 경우 정렬 시 메모리의 추가 할당이 필요할 수도
3.10 기계 수준 프로그램에서 제어와 데이터의 결합
3.10.1 포인터 이해하기
- 포인터 : C의 핵심 특징
- 포인터는 연관된 자료형을 갖는다.
- 모든 포인터는 특정 값을 갖는다. (특정 자료형을 갖는 어떤 객체의 주소)
- 포인터는 & 연산자로 만든다.
- 포인터는 * 연산자를 사용하여 역참조
- 배열과 포인터는 밀접한 관계를 가짐
- 포인터에서 다른 종류로의 자료형 변환은, 종류는 바뀌지만 값은 바뀌지 않는다.
- 포인터는 함수를 가리킬 수도 있음.
3.10.3 범위를 벗어난 메모리 참조와 버퍼 오버플로우
-
스택에 저장된 상태 정보가 범위를 벗어난 배열의 원소에 대한 쓰기 작업에 의해 변경됨
-
이 후 레지스터에 재적재 or ret인스트럭션 실행 시 오류 발생
-
버퍼 오버플로우 : 어떤 문자 배열이 스택에 스트링을 가지고 할당되어 있으나, 스트링의 크기는 배열에 할당된 공간을 초과시 발생
-
치명적인 버퍼 오버플로우 사용 : 프로그램이 하지 않은 기능을 실행
3.10.4 버퍼 오버플로우 공격 대응 기법
-
스택 랜덤화 : 스택 주소 예측을 어렵게 하기 위해서 프로그램의 실행마다 스택 위치를 다르게
- but 공격 시도가 많으면 결국 뚫린다. 완벽한 대책은 아님
-
스택 손상 검출 : 배열의 경계를 넘는 쓰기 작업 같은 해로운 효과를 감지. 스택 보호 코드를 추가 카나리Canary
-
실행코드 영역 제한 : 공격자의 실행 코드를 추가할 가능성 제거
3.10.5 가변 크기 스택 프레임 지원