- 개인적으로 공부하면서 지속적으로 정보를 추가, 수정, 삭제합니다.
- 정확하지 않은 부분 피드백 주시면 감사합니다.
- 본 포스팅은 컴퓨터 시스템 (줄여서 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 역사적 관점
- 인텔 x86-64 프로세서 기준으로 설명할 거고, x86-64만 다룰 것
- ARM (Acorn RISC Machine)
- 저전력 프로세서. x86-64보다 simpler. 휴대폰 등에 사용 됨.
- x86-64와 ARM은 서로 설계 다르고, 현재 주요한 프로세서 디자인의 두 가지 축
3.2 프로그램 인코딩 program encoding: C, 어셈블리, 기계어
- architecture (ISA, instruction set architecture)란?
- 프로세서 설계의 일부로 우리가 기계어를 쓰고 이해하는데 필요함
- 컴파일러의 타겟
- C를 object code로 변환하기
- %r 어쩌구는 실제로 레지스터의 이름
- 각각의 한 줄은 명령, instruction 이다!
- pushq -> 스택에 뭔갈 넣어라
- mov -> 한 곳에서 다른 곳으로 카피해라
- call -> procedure(=함수function, method)를 호출해라
- popq -> pushq의 반대
- ret -> 지금 이 특정한 함수를 종료하고 결과값을 밖으로 반환해라
sum.c
실습gcc -Og -S sum.c
:-Og
는 최적화된 어셈블리어를 만들으라는 것
3.3 Data Formats
- 어셈블리어의 특징
- 1, 2, 4, 8 바이트의 integer data
- 4, 8, 10 바이트의 floating point
- array나 structure같은 aggregate types는 없음 (자료구조 없음!) → 이런 자료구조는 컴파일러에 의해서 구축되는 것
- 한 줄 한 줄의 명령은 매우 제한적인 것을 할 수 있음
- 위의
sum.c
파일에서 등장한sumstore
라는 함수는 실제로 위와 같은 14바이트의 코드 - 0x0400595라는 주소에서 시작
- 위의
- 기계어 명령의 예시
- local value인
t
는 일반적으로 레지스터에 저장 될 것 *dset
는 포인터 변수로 주소를 담고 있고, 이 주소 값도 레지스터에 저장 되어 있음 → 그림 상의%rbx
- 그래서
movq %rax, (%rbx)
는%rax
라고 불리는 레지스터에서 값을 복사해%rbx
레지스터에 저장하라는 것 - 이 코드의 목적 코드는 단 3 byte (그림에서 보이는 숫자 세개)
- 기계어 수준에서 우리가 지어준 변수 명은 완전히 사라진 것을 볼 수 있다!
- local value인
- disassembling object code
- assembler가 소스코드를 bytes로 바꿔 주듯이, 그 반대의 역할을 수행
- bytes를 주고 어셈블리어로 바꿔줌
- 각각의 bytes는 어셈블리어로 오른쪽에 나타남
- push 명령어는 1바이트(숫자 하나), 나머지 명령어가 몇 바이트를 차지하는지 알 수 있음
- 이러한 디스어셈블링을 돕는 툴 존재 (실습에서는 gdb, objdump를 사용 )
- 무엇을 디스어셈블링 할 수 있을까?
- MS Word의 실행 파일을 디스어셈블링 한 코드 신기하다
- 실행 가능한 파일이라면 디스어셈블링 할 수 있음
3.4 정보에 접근하기; 어셈블리 기초: 레지스터, 오퍼랜드, move
- x86-64 integer registers
- x86-64 프로세서는 그림과 같이 16개의 레지스터를 가짐
- 이 레지스터들은 integer와 포인터(주소 값)을 담을 수 있음
- 레지스터 이름의 %r은 64비트를 담을 수 있다는 뜻
- %e는 32비트를 담을 수 있다는 뜻 (그림 상에서 절반으로 표현되어 있음 )
- 만약 long 자료형을 사용한다면 %r 레지스터를 사용하는 것, 그 외 32비트 자료형을 사용한다면 %e 레지스터를 이용
- 핑크색으로 칠해진 레지스터는 "스택 포인터"라고 불리고 특별한 용도로 쓰임
- 데이터 옮기기
- 이 부분은 %%컴퓨터 운영체제 큰 그림 노트%%에서 다룬 내용을 더 디테일하게 다룸
- immediate는 즉시 주소 지정 방식에서 다루는 것. 상수 데이터 밸류를 말함.
movq
명령어 이해하기: 어디서 어느 곳으로 복사하는가?movq <src> <dst>
모습으로 나타난다 !- 소스와 dst가 될 수 있는 경우의 수
- immedate -> 레지스터 or 메모리
- 레지스터 -> 레지스터 or 메모리
- 메모리 -> 레지스터
- 메모리에서 메모리로 바로 자료를 복사할 수 없음 -> 레지스터를 거쳐가야 함. (하드웨어 디자이너의 편의를 위해서라고 한다.)
- 주소 지정 방식
- 레지스터 이름에 괄호를 친 것의 의미; 이 레지스터를 사용해라, 거기 안에는 주소가 들어있다! 그리고 그 주소는 특정한 메모리 위치로 이어져 있다.
- 아래 displacement D는 변위 주소 지정 방식에 대한 얘기
swap()
함수 이해하기- 교수 왈 이 부분 이해하는 것 중요! 포인터 이해 안 될때도 도움 될 듯
- 네 개의 레지스터를 이용하는 것을 볼 수있다.
*xp
,*yp
와 같은 포인터 변수는 특정 메모리 주소를 의미함- 첫번째 줄
- %rax라는 레지스터에 값을 저장할건데, %rdi 레지스터로 가라, 괄호가 쳐져 있으므로 그곳에는 주소가 있음을 나타내고 그 주소에 가서 값을 복사해라
movq
의 q는 quad word를 의미하고 8바이트를 뜻함
- 두 번째 줄
- 첫번째 줄과 동일한데, 사용하는 레지스터랑 주소만 다름
- 세 번째 줄
- %rdx로 가서 값을 복사한 뒤, %rdi 레지스터로 가는데, 거기에는 주소가 있을 거고 그 주소로 가서 값을 저장하여라
- 마지막 줄
- 위와 동일한데 레지스터와 주소만 다름.
- 변위 주소 지정 방식 이해하기
- 배열 참조를 만드는 자연스러운 방법이라고 함.
- 베이스 레지스터(시작 주소), 인덱스 레지스터(변위 참조 주소)가 존재
- scale: 1,2,4,8 라는 숫자를 의미하고 해당 숫자를 주소에 곱하라는 뜻임!
- 실제 주소 계산하는 예시
- (책 3.4.4) 스택 데이터의 저장과 추출 (push, pop)
- 스택
- 프로시저 호출(함수 호출)을 처리하는데 중요한 역할
- push 연산으로 스택에 데이터 추가, pop 연산으로 제거됨
- 제거하는 값은 가장 최근에 추가된 값이며, 그 값은 스택에 여전히 남아 있다
- 원소가 나가고 들어올 수 있는 이 한쪽 끝을 top 이라고 함
- 스택은 아래로 자라는 형태이므로, 스택의 top 원소가 모든 스택 중 가장 낮은 주소를 가짐
- 스택 포인터 %rsp는 top의 주소를 저장함
- 기계어 인스트럭션
pushq <추가할 소스 데이터>
- 데이터를 스택에 추가
- 스택 포인터의 주소가 감소
popq <추출을 위한 데이터 목적지>
- 스택의 top 데이터를 추출
- 추출한 데이터를 목적지에 저장
- 스택 포인터의 주소가 증가
- 스택
3.5 Arithmetic and Logical Operations
- 산술, 논리 연산
- 유효 주소 적재 명령 이해하기
- 이해하기 쉽지 않은 부분
leaq src, dst
- dst는 레지스터가 되어야 함. 소스는 메모리 주소
- 유효 주소 적재 명령 이해하기
3.6 Control
3.7 Procedures
intro
- 프로시저 호출 메커니즘이 기계어 수준에서 어떻게 구현되는가
- 프로시저 호출 시 데이터, 인자, 매개변수, 리턴 밸류 등은 어떻게 다뤄지는가
- 재귀 함수 콜 메커니즘
1. 프로시저의 메커니즘
- 프로시저란
- 함수, function, 메소드, 핸들링 등 우리가 그렇게 부르는 것들.
- 3가지 메커니즘: 함수
P
가 함수Q
를 호출한 뒤,Q
가 실행한 후 다시P
로 리턴할 때- 제어권 전달 passing control
- 프로시저에 진입하면서 레지스터에
Q
에 대한 코드의 시작 주소로 설정 - 리턴시
P
에서Q
를 호출하는 명령 다음의 명령으로 설정되어야 함
- 프로시저에 진입하면서 레지스터에
- 데이터 전달 passing data
P
는 하나 이상의 매개변수를Q
에 제공Q
는 다시P
로 하나의 값을 리턴
- 메모리 할당과 반납 memory management
Q
가 시작할 때, 자신의 지역변수들을 위한 공간을 할당할 수 있어야 함- 리턴할 때 이 공간들을 반납할 수 있음
- 제어권 전달 passing control
2. 런타임 스택
- 프로시저 호출은 스택 자료구조의 후입선출 방식으로 메모리를 관리함
- 스택에 대해서는 위에서 언급한 스택 데이터의 저장과 추출 항목에서 더 자세히 보기
- 작은 주소 방향으로 성장함
- 스택 포인터 %rsp는 스택의 top 원소를 가리킴
pushq
,popq
명령어를 이용
- 스택 프레임
- 프로시저가 레지스터에 저장할 수 있는 개수 이상의 저장공간을 필요로 할 때, 스택에 할당해주는 공간
- 모든 지역 변수를 레지스터에 보관할 수 있거나, 이 함수가 다른 함수를 하나도 호출하지 않을 때는 스택 프레임을 요청하지 않음
- 현재 실행 중인 프레임은은 스택 구조의 top에 위치
- 프로시저 P가 Q를 호출 -> 돌아와야 할 주소(return address)를 푸시 -> Q의 실행이 끝나고 리턴할 때, P에서 재시작해야 하는 위치를 가리킴
- 이때 리턴 주소는 P에 관계된 상태를 저장하므로, 리턴 주소는 P의 스택 프레임에 속함
- 스택 프레임에서는 다음과 같은 것을 할 수 있음.
- 레지스터 값들을 저장
- 지역변수들을 위한 공간을 할당
- 자신이 호출하는 프로시저들을 위한 인자들을 설정
- 프로시저가 레지스터에 저장할 수 있는 개수 이상의 저장공간을 필요로 할 때, 스택에 할당해주는 공간
3. 제어권 전달 passing control
call <호출할 프로시저>
- 리턴 주소를 스택에 푸시 → PC를 Q의 시작 주소로 세팅
- return address: 호출 이후의 다음 명령의 주소 (=
call
명령 다음 명령어의 주소)
ret
- 리턴 주소를 스택에서 팝 → PC를 리턴주소로 세팅
4. 데이터 전달 passing data
- 프로시저 콜에서의 데이터 전달
- 데이터를 인자로 전달
- 리턴 값의 전달
- 레지스터로 인자 전달
- x86-64는 최대 여섯개의 정수형(정수와 포인터) 인자가 레지스터로 전달 됨
- 인자의 크기에 따라, 정해진 순서로 레지스터에 할당
- 여섯 개 이상의 정수형 인자를 가진 함수의 경우, 나머지 인자가 스택으로 전달
- 인자 배치 후
call
명령어 실행 - %rax: 리턴 값이 저장되는 레지스터 왜 굳이 정해놨을까?
5. 함수 내에서 사용되는 데이터 관리하기
- 스택의 지역저장공간local storage
- 필요한 경우, 다음과 같은 이유로 local data가 메모리(스택 공간)에 저장될 때가 있음
- local data 전부를 저장하기에 레지스터 수가 모자람
- 지역변수에 연산자
&
이 사용되었고, 이 변수의 주소를 생성할 수 있어야 함 - 일부 지역변수가 배열 or 구조체라서, 참조로 접근되어야 함
- 스택 포인터를 감소시켜 스택 프레임에 공간 할당(그림 3.25의 local variables를 위한 공간) → 함수가 완료되었을 때 주소를 증가시켜 반환
- 필요한 경우, 다음과 같은 이유로 local data가 메모리(스택 공간)에 저장될 때가 있음
- 레지스터의 지역저장공간local storage
- 프로그램 레지스터들은 모든 프로시저들이 공유하는 단일 자원의 역할 → 서로의 값을 덮어씌우면 문제!
- 레지스터 저장 컨벤션(register saving conventions)
- 함수를 호출하고, 리턴하는 과정에서 레지스터의 값을 보존하기 위함
- 호출자를 위한 레지스터, 피호출자를 위한 레지스터
- callee-saved
- P가 Q를 호출할 때, 나중에 리턴했을때 P의 값이 깨지지 않고 보존되도록 함
- 레지스터 값을 전혀 건드리지 않거나, 원래 값을 스택에 푸시해서 저장(그림 3.25의 saved registers 공간)
- caller-saved
- 로컬 데이터를 레지스터에 보관하고, 다음 함수 Q를 호출하는 함수 P의 입장
- Q가 이 레지스터를 맘껏 변경할 수 있기 때문에 P가 Q를 부르기 전에 먼저 저장함
- callee-saved
6. 재귀 함수 설명하기
- 위에서 설명한 대로 레지스터, 스택 운영 방식으로 다뤄짐! (재귀 함수라고 특별한 무언가는 없다)
- 프로시저 콜은 스택상에 자신만의 사적인 공간을 가지며, 따라서 다수의 별도의 호출들의 지역변수들은 서로 간섭 안 함
- 스택 구조는 프로시저가 호출될 때 지역저장소를 할당하고, 리턴하기 전에 이것을 적절히 반환
- 마지막에 호출된 함수가 먼저 끝남!
- 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
'컴퓨터 시스템' 카테고리의 다른 글
컴퓨터 시스템 정리 (CSAPP) Chapter 11 네트워크 프로그래밍 (0) | 2022.12.10 |
---|---|
컴퓨터 시스템 정리 (CSAPP) Chapter 9 가상메모리 (0) | 2022.12.03 |
컴퓨터 시스템 정리 (CSAPP) Chapter 6 (0) | 2022.12.03 |
컴퓨터 시스템 정리 (CSAPP) Chapter 1 A tour of Computer Systems (1) | 2022.11.09 |