컴퓨터 시스템

컴퓨터 시스템 정리 (CSAPP) Chapter 3 Machine-Level Reperesentation of Programs

jamie-lee 2022. 11. 30. 22:49
  • 개인적으로 공부하면서 지속적으로 정보를 추가, 수정, 삭제합니다.
  • 정확하지 않은 부분 피드백 주시면 감사합니다.
  • 본 포스팅은 컴퓨터 시스템 (줄여서 CSAPP) 교재를 강의와 함께 노트한 것입니다.
  • 노란색 하이라이트는 블로그 주인의 생각 + 이해가 더 필요한 부분을 개인적으로 표시한 것입니다.

참고: Computer System A Programmers Perspective (3rd), Randal E. Bryan, David R. O’Hallaron, 김형신 번역, 피어슨에듀케이션코리아, 2016
CSAPP CMU 저자 직강
홍구의 개발 블로그

Chapter 3: Machine-Level Reperesentation of Programs

학습목표

  • GCC 컴파일러에 의해 생성된 어셈블리어 코드를 살펴볼 것
  • 우리가 쓴 자료 구조, 프로시저가 저수준 언어에서 어떻게 구현되는가?

3.1 역사적 관점

  1. 인텔 x86-64 프로세서 기준으로 설명할 거고, x86-64만 다룰 것
  2. ARM (Acorn RISC Machine)
    1. 저전력 프로세서. x86-64보다 simpler. 휴대폰 등에 사용 됨.
    2. x86-64와 ARM은 서로 설계 다르고, 현재 주요한 프로세서 디자인의 두 가지 축

3.2 프로그램 인코딩 program encoding: C, 어셈블리, 기계어

  1. architecture (ISA, instruction set architecture)란?
    1. 프로세서 설계의 일부로 우리가 기계어를 쓰고 이해하는데 필요함
    2. 컴파일러의 타겟
  2. C를 object code로 변환하기 Pasted image 20221122221506.png
    • %r 어쩌구는 실제로 레지스터의 이름
    • 각각의 한 줄은 명령, instruction 이다!
      • pushq -> 스택에 뭔갈 넣어라
      • mov -> 한 곳에서 다른 곳으로 카피해라
      • call -> procedure(=함수function, method)를 호출해라
      • popq -> pushq의 반대
      • ret -> 지금 이 특정한 함수를 종료하고 결과값을 밖으로 반환해라
    • sum.c 실습
      • gcc -Og -S sum.c : -Og는 최적화된 어셈블리어를 만들으라는 것

3.3 Data Formats

  1. 어셈블리어의 특징
    1. 1, 2, 4, 8 바이트의 integer data
    2. 4, 8, 10 바이트의 floating point
    3. array나 structure같은 aggregate types는 없음 (자료구조 없음!) → 이런 자료구조는 컴파일러에 의해서 구축되는 것
    4. 한 줄 한 줄의 명령은 매우 제한적인 것을 할 수 있음 Pasted image 20221122223914.png
      • 위의 sum.c 파일에서 등장한 sumstore라는 함수는 실제로 위와 같은 14바이트의 코드
      • 0x0400595라는 주소에서 시작
  2. 기계어 명령의 예시 Pasted image 20221122224346.png
    1. local value인 t는 일반적으로 레지스터에 저장 될 것
    2. *dset는 포인터 변수로 주소를 담고 있고, 이 주소 값도 레지스터에 저장 되어 있음 → 그림 상의 %rbx
    3. 그래서 movq %rax, (%rbx)%rax라고 불리는 레지스터에서 값을 복사해 %rbx 레지스터에 저장하라는 것
    4. 이 코드의 목적 코드는 단 3 byte (그림에서 보이는 숫자 세개)
    5. 기계어 수준에서 우리가 지어준 변수 명은 완전히 사라진 것을 볼 수 있다!
  3. disassembling object code
    1. assembler가 소스코드를 bytes로 바꿔 주듯이, 그 반대의 역할을 수행
    2. bytes를 주고 어셈블리어로 바꿔줌 Pasted image 20221122225339.png
      1. 각각의 bytes는 어셈블리어로 오른쪽에 나타남
      2. push 명령어는 1바이트(숫자 하나), 나머지 명령어가 몇 바이트를 차지하는지 알 수 있음
      3. 이러한 디스어셈블링을 돕는 툴 존재 (실습에서는 gdb, objdump를 사용 )
    3. 무엇을 디스어셈블링 할 수 있을까? Pasted image 20221122230648.png
      1. MS Word의 실행 파일을 디스어셈블링 한 코드 신기하다
      2. 실행 가능한 파일이라면 디스어셈블링 할 수 있음

3.4 정보에 접근하기; 어셈블리 기초: 레지스터, 오퍼랜드, move

  1. x86-64 integer registers Pasted image 20221122231110.png
    1. x86-64 프로세서는 그림과 같이 16개의 레지스터를 가짐
    2. 이 레지스터들은 integer와 포인터(주소 값)을 담을 수 있음
    3. 레지스터 이름의 %r은 64비트를 담을 수 있다는 뜻
    4. %e는 32비트를 담을 수 있다는 뜻 (그림 상에서 절반으로 표현되어 있음 )
    5. 만약 long 자료형을 사용한다면 %r 레지스터를 사용하는 것, 그 외 32비트 자료형을 사용한다면 %e 레지스터를 이용
    6. 핑크색으로 칠해진 레지스터는 "스택 포인터"라고 불리고 특별한 용도로 쓰임
  2. 데이터 옮기기 Pasted image 20221122232539.png
    1. 이 부분은 %%컴퓨터 운영체제 큰 그림 노트%%에서 다룬 내용을 더 디테일하게 다룸
    2. immediate는 즉시 주소 지정 방식에서 다루는 것. 상수 데이터 밸류를 말함.
  3. movq 명령어 이해하기: 어디서 어느 곳으로 복사하는가? Pasted image 20221122232928.png
    1. movq <src> <dst> 모습으로 나타난다 !
    2. 소스와 dst가 될 수 있는 경우의 수
      1. immedate -> 레지스터 or 메모리
      2. 레지스터 -> 레지스터 or 메모리
      3. 메모리 -> 레지스터
    3. 메모리에서 메모리로 바로 자료를 복사할 수 없음 -> 레지스터를 거쳐가야 함. (하드웨어 디자이너의 편의를 위해서라고 한다.)
  4. 주소 지정 방식 Pasted image 20221122233823.png
    1. 레지스터 이름에 괄호를 친 것의 의미; 이 레지스터를 사용해라, 거기 안에는 주소가 들어있다! 그리고 그 주소는 특정한 메모리 위치로 이어져 있다.
    2. 아래 displacement D는 변위 주소 지정 방식에 대한 얘기
    3. swap() 함수 이해하기 Pasted image 20221122234922.png
      1. 교수 왈 이 부분 이해하는 것 중요! 포인터 이해 안 될때도 도움 될 듯
      2. 네 개의 레지스터를 이용하는 것을 볼 수있다.
      3. *xp, *yp와 같은 포인터 변수는 특정 메모리 주소를 의미함
      4. 첫번째 줄 Pasted image 20221122235338.png
        1. %rax라는 레지스터에 값을 저장할건데, %rdi 레지스터로 가라, 괄호가 쳐져 있으므로 그곳에는 주소가 있음을 나타내고 그 주소에 가서 값을 복사해라
        2. movq의 q는 quad word를 의미하고 8바이트를 뜻함
      5. 두 번째 줄 Pasted image 20221122235838.png
        1. 첫번째 줄과 동일한데, 사용하는 레지스터랑 주소만 다름
      6. 세 번째 줄 Pasted image 20221123000030.png
        1. %rdx로 가서 값을 복사한 뒤, %rdi 레지스터로 가는데, 거기에는 주소가 있을 거고 그 주소로 가서 값을 저장하여라
      7. 마지막 줄 Pasted image 20221123000326.png
        1. 위와 동일한데 레지스터와 주소만 다름.
    4. 변위 주소 지정 방식 이해하기 Pasted image 20221123001629.png
      1. 배열 참조를 만드는 자연스러운 방법이라고 함.
      2. 베이스 레지스터(시작 주소), 인덱스 레지스터(변위 참조 주소)가 존재
      3. scale: 1,2,4,8 라는 숫자를 의미하고 해당 숫자를 주소에 곱하라는 뜻임!
      4. 실제 주소 계산하는 예시 Pasted image 20221123001317.png
  5. (책 3.4.4) 스택 데이터의 저장과 추출 (push, pop)
    1. 스택
      1. 프로시저 호출(함수 호출)을 처리하는데 중요한 역할
      2. push 연산으로 스택에 데이터 추가, pop 연산으로 제거됨
      3. 제거하는 값은 가장 최근에 추가된 값이며, 그 값은 스택에 여전히 남아 있다
      4. 원소가 나가고 들어올 수 있는 이 한쪽 끝을 top 이라고 함
      5. 스택은 아래로 자라는 형태이므로, 스택의 top 원소가 모든 스택 중 가장 낮은 주소를 가짐
      6. 스택 포인터 %rsp는 top의 주소를 저장함
    2. 기계어 인스트럭션 Computer Systems A Programmers Perspective (3rd)-227 1.jpg
      1. pushq <추가할 소스 데이터>
        1. 데이터를 스택에 추가
        2. 스택 포인터의 주소가 감소
      2. popq <추출을 위한 데이터 목적지>
        1. 스택의 top 데이터를 추출
        2. 추출한 데이터를 목적지에 저장
        3. 스택 포인터의 주소가 증가

3.5 Arithmetic and Logical Operations

  1. 산술, 논리 연산
    1. 유효 주소 적재 명령 이해하기
      1. 이해하기 쉽지 않은 부분
      2. leaq src, dst
      3. dst는 레지스터가 되어야 함. 소스는 메모리 주소

3.6 Control

3.7 Procedures

intro

  • 프로시저 호출 메커니즘이 기계어 수준에서 어떻게 구현되는가
  • 프로시저 호출 시 데이터, 인자, 매개변수, 리턴 밸류 등은 어떻게 다뤄지는가
  • 재귀 함수 콜 메커니즘

1. 프로시저의 메커니즘

  1. 프로시저란
    • 함수, function, 메소드, 핸들링 등 우리가 그렇게 부르는 것들.
  2. 3가지 메커니즘: 함수 P가 함수 Q를 호출한 뒤, Q가 실행한 후 다시 P로 리턴할 때
    1. 제어권 전달 passing control
      1. 프로시저에 진입하면서 레지스터에 Q에 대한 코드의 시작 주소로 설정
      2. 리턴시 P에서 Q를 호출하는 명령 다음의 명령으로 설정되어야 함
    2. 데이터 전달 passing data
      1. P는 하나 이상의 매개변수를 Q에 제공
      2. Q는 다시 P로 하나의 값을 리턴
    3. 메모리 할당과 반납 memory management
      1. Q가 시작할 때, 자신의 지역변수들을 위한 공간을 할당할 수 있어야 함
      2. 리턴할 때 이 공간들을 반납할 수 있음

2. 런타임 스택

  1. 프로시저 호출은 스택 자료구조의 후입선출 방식으로 메모리를 관리함
  2. 스택에 대해서는 위에서 언급한 스택 데이터의 저장과 추출 항목에서 더 자세히 보기
    • 작은 주소 방향으로 성장함
    • 스택 포인터 %rsp는 스택의 top 원소를 가리킴
    • pushq, popq 명령어를 이용
  3. 스택 프레임 Computer Systems A Programmers Perspective (3rd)-277 1 1.jpg
    1. 프로시저가 레지스터에 저장할 수 있는 개수 이상의 저장공간을 필요로 할 때, 스택에 할당해주는 공간
      • 모든 지역 변수를 레지스터에 보관할 수 있거나, 이 함수가 다른 함수를 하나도 호출하지 않을 때는 스택 프레임을 요청하지 않음
    2. 현재 실행 중인 프레임은은 스택 구조의 top에 위치
    3. 프로시저 P가 Q를 호출 -> 돌아와야 할 주소(return address)를 푸시 -> Q의 실행이 끝나고 리턴할 때, P에서 재시작해야 하는 위치를 가리킴
    4. 이때 리턴 주소는 P에 관계된 상태를 저장하므로, 리턴 주소는 P의 스택 프레임에 속함
    5. 스택 프레임에서는 다음과 같은 것을 할 수 있음.
      1. 레지스터 값들을 저장
      2. 지역변수들을 위한 공간을 할당
      3. 자신이 호출하는 프로시저들을 위한 인자들을 설정

3. 제어권 전달 passing control

  • call <호출할 프로시저>
    • 리턴 주소를 스택에 푸시 → PC를 Q의 시작 주소로 세팅
    • return address: 호출 이후의 다음 명령의 주소 (= call 명령 다음 명령어의 주소)
  • ret
    • 리턴 주소를 스택에서 → PC를 리턴주소로 세팅

4. 데이터 전달 passing data

  1. 프로시저 콜에서의 데이터 전달
    1. 데이터를 인자로 전달
    2. 리턴 값의 전달
  2. 레지스터로 인자 전달
    1. x86-64는 최대 여섯개의 정수형(정수와 포인터) 인자가 레지스터로 전달 됨
    2. 인자의 크기에 따라, 정해진 순서로 레지스터에 할당
    3. 여섯 개 이상의 정수형 인자를 가진 함수의 경우, 나머지 인자가 스택으로 전달
    4. 인자 배치 후 call 명령어 실행
    5. %rax: 리턴 값이 저장되는 레지스터 왜 굳이 정해놨을까?

5. 함수 내에서 사용되는 데이터 관리하기

  1. 스택의 지역저장공간local storage
    1. 필요한 경우, 다음과 같은 이유로 local data가 메모리(스택 공간)에 저장될 때가 있음
      1. local data 전부를 저장하기에 레지스터 수가 모자람
      2. 지역변수에 연산자 &이 사용되었고, 이 변수의 주소를 생성할 수 있어야 함
      3. 일부 지역변수가 배열 or 구조체라서, 참조로 접근되어야 함
    2. 스택 포인터를 감소시켜 스택 프레임에 공간 할당(그림 3.25의 local variables를 위한 공간) → 함수가 완료되었을 때 주소를 증가시켜 반환
  2. 레지스터의 지역저장공간local storage
    1. 프로그램 레지스터들은 모든 프로시저들이 공유하는 단일 자원의 역할 → 서로의 값을 덮어씌우면 문제!
    2. 레지스터 저장 컨벤션(register saving conventions)
      1. 함수를 호출하고, 리턴하는 과정에서 레지스터의 값을 보존하기 위함
      2. 호출자를 위한 레지스터, 피호출자를 위한 레지스터
        1. callee-saved
          1. P가 Q를 호출할 때, 나중에 리턴했을때 P의 값이 깨지지 않고 보존되도록 함
          2. 레지스터 값을 전혀 건드리지 않거나, 원래 값을 스택에 푸시해서 저장(그림 3.25의 saved registers 공간)
        2. caller-saved
          1. 로컬 데이터를 레지스터에 보관하고, 다음 함수 Q를 호출하는 함수 P의 입장
          2. Q가 이 레지스터를 맘껏 변경할 수 있기 때문에 P가 Q를 부르기 전에 먼저 저장함

6. 재귀 함수 설명하기

  1. 위에서 설명한 대로 레지스터, 스택 운영 방식으로 다뤄짐! (재귀 함수라고 특별한 무언가는 없다)
  2. 프로시저 콜은 스택상에 자신만의 사적인 공간을 가지며, 따라서 다수의 별도의 호출들의 지역변수들은 서로 간섭 안 함
  3. 스택 구조는 프로시저가 호출될 때 지역저장소를 할당하고, 리턴하기 전에 이것을 적절히 반환
  4. 마지막에 호출된 함수가 먼저 끝남!
  5. P와 Q의 상호 재귀 함수도 마찬가지

3.8 배열 할당과 접근

3.9 Heterogeneous Data Structures

3.10 Combining Control and Data in Machine-Level Programs

3.11 Floating-Point Code

3.12 Summary