~1/30 TIL (CSAPP 3장 총 정리)

김주민·2024년 1월 30일

csapp

목록 보기
6/8

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 데이터의 형식

  • mov : 데이터 이동 인스트럭션

3.4 정보 접근하기

  • 인스트럭션들은 16개의 레지스터 하위 바이트들에 저장된 다양한 크기의 데이터에 대해 연산 가능

3.4.1 오퍼랜드 식펼자 specifier

  • 대부분의 인스트럭션은 하나 이상의 오퍼랜드 가짐

  • 오퍼랜드는 연산을 수행할 소스Source 값과 그 결괄르 저장할 목적지 destination의 위치 명시

  • 소스 : 상수 or 레지스터나 메모리에서 읽어옴

  • 오퍼랜드의 종류

  1. immediate 상수값
  2. register 레지스터의 내용
  3. 메모리 참조 : 유효 주소 effective address라 부르는 계산된 주소에 의해 메모리 위치에 접근.

3.4.2 데이터 이동 인스트럭션

  • 데이터를 다른 위치로 복사

  • 여러 데이터에 이동 명령 + 수행할 변환과 부수 효과 부여 -> 이것들을 인스트럭션 클래스로 구분

  • 인스트럭션들은 같은 동작을 하는 다른 오퍼랜드 크기를 사용하여 수행
    ex) mov : 다른 위치로 복사, 서로 다른 크기의 데이터에 대해 계산

  • 소스 오퍼랜드 : 상수. 레지스터 저장 값. 메모리 저장 값 표시

  • 목적 오퍼랜드 : 레지스터 or 메모리 주소 위치 지정
    - 둘 다 메모리 위치엔 올수 없음

  • 메모리 안에서 다른 위치로 값을 복사할 때 - 소스 값을 레지스터에 적재하는 인스트럭션 + 레지스터 값을 목적지에 쓰는 인스트럭션

3.4.4 스택 데이터의 저장과 추출 Push, Pop

  • popq, pushq : 한 개의 오퍼랜드 사용(추가할 소스 데이터와 추출을 위한 데이터 목적지)

  • pop : 스택 포인터를 증가, push : 스택 포인터를 감소

  • 스택 탑은 %rsp 가 가리키는 곳이고 열에서 그 위에 저장된 값은 모두 무효.

3.5 산술 연산과 논리 연산

  • add : 덧셈 인스트럭션

3.5.1 유효 주소 적재 load Effective Address

  • leaq : movq의 변형. 메모리에서 레지스터로 읽어들이나 메모리는 참조하지 않음

3.5.2 단항 및 이항연산

  • inc, dec

3.5.3 쉬프트 연산

  • 쉬프트할 크기를 주고 쉬프트 할 값을 준다.

3.6 제어문

3.6.1 조건 코드

  • cmp, test

3.6.2 조건 코드 사용하기

  • set

3.6.3 점프 Jump

  • 프로그램이 완전히 새로운 위치로 실행을 전환하게 함

3.6.4 점프 인스트럭션 인코딩

  • pc 상대적relative 방법 : 대상 인스트럭션과 점프 인스트럭션 바로 뒤에 오는 인스트럭션 주소와의 차이를 인코딩
  • 3.6 절은 나중에 더 보자

3.7 프로시저

  • 좋은 소프트웨어 시스템 : 계산된 값과 프로시저가 프로그램 상태에 미치는 효과를 정의 내려 보여주고, 일부 동작의 구체적 구현은 감춰주는 방식으로 프로시저를 추상화 매커니즘으로 이용
  • 프로시저는 여러 프로그래밍 언어에서 다르게 사용되지만, 일반적 특징을 공유한다.
    - 함수, 메소드, 핸들러 등
    ex) 프로시저 P가 프로시저 Q 호출, Q 실행 후 다시 P로 리턴
  1. 제어권 전달 : 프로그램 카운터는 진입 시 Q에 대한 코드 시작 주소로 설정. 리턴 시엔 P에서 Q를 호출하는 인스트럭션 다음의 인스트럭션으로 설정.
  2. 데이터 전달 : P는 하나 이상의 매개 변수를 Q에 제공할 수 있어야 하고, Q는 다시 P로 하나의 값을 리턴할 수 있어야 함.
  3. 메모리 할당과 반납 : 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 표시.
  1. 자료형 T의 크기 : L. L*N 바이트의 연속적 공간을 메모리에 할당
  2. 새로운 식별자 A를 통해 배열이 시작하는 위치의 포인터로 사용(Xa)
  • 배열의 원소 i는 주소 Xa+L*i 에 저장

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 가변 크기 스택 프레임 지원

0개의 댓글