컴퓨터 시스템 : CSAPP 3장 정리 - 3.2장 프로그램의 인코딩 Part. 2
🧪 3.2.2 Code Examples — 코드 예제
💡 C 코드로 함수 만들기
먼저 아래와 같은 C 코드를 가정해 보자. 파일 이름은 mstore.c라고 한다.
long mult2(long, long);
void multstore(long x, long y, long *dest) {
long t = mult2(x, y);
*dest = t;
}
이 함수는 x와 y를 mult2 함수에 넘겨서 곱한 결과를 dest가 가리키는 곳에 저장한다.
🛠️ 어셈블리 코드로 보기
이 C 코드를 어셈블리로 바꾸려면 이렇게 명령어를 입력한다:
gcc -Og -S mstore.c
multstore:
pushq %rbx ; %rbx 저장 (함수 호출 시 보존해야 함)
movq %rdx, %rbx ; dest를 %rbx로 복사
call mult2 ; mult2(x, y) 호출 (%rdi, %rsi가 인자 전달)
movq %rax, (%rbx) ; 결과(%rax)를 dest가 가리키는 메모리에 저장
popq %rbx ; 저장했던 %rbx 복원
ret ; 함수 종료
각 줄은 다음과 같은 일을 한다:
- pushq %rbx: rbx 레지스터의 내용을 스택에 저장한다.
- movq %rdx, %rbx: dest의 값을 rbx에 복사한다.
- call mult2: mult2 함수를 호출해. 결과는 rax에 들어있음.
- movq %rax, (%rbx): rax에 있는 값을 *dest에 저장한다.
- popq %rbx: 저장해 둔 rbx 값을 스택에서 복원한다.
- ret: 함수 종료
🔍 기계어로 보기
그런데 이 어셈블리도 결국은 숫자(byte)로 바뀌어서 저장된다. mstore.o라는 오브젝트 파일을 만들면, 이 함수는 14바이트 길이의 코드로 바뀌게 된다:
53 48 89 d3 e8 00 00 00 00 48 89 03 5b c3
이 숫자들이 실제 컴퓨터가 실행하는 진짜 기계어다.
🔧 레지스터란?
📦 레지스터는 컴퓨터의 초고속 메모리!
CPU 안에는 데이터를 저장하는 특별한 장소들이 있다. 이걸 레지스터(register)라고 부른다. 컴퓨터가 계산을 하거나 명령을 수행할 때, 데이터를 바로 꺼내 쓰려면 빠른 접근이 가능한 이 레지스터를 사용한다.
🏷️ 대표적인 x86-64 레지스터들
이름 | 설명 |
---|---|
%rax | 결과값을 저장하는 기본 레지스터. 함수 반환값도 여기에 담김 |
%rdx, %rsi, %rdi | 함수 인자를 전달할 때 사용 |
%rbx, %rcx, %rsp, %rbp 등 | 다양한 계산이나 메모리 접근에 사용됨 |
각 레지스터는 특정 역할이 있고, 어떤 레지스터는 호출 전/후에 보존해야 하는 규칙도 있다.
🎯 레지스터의 예시와 의미
%rdi, %rsi, %rdx:
이들은 보통 함수의 입력 인자를 담는 데 사용된다.
- %rdi: 첫 번째 인자
- %rsi: 두 번째 인자
- %rdx: 세 번째 인자
예를 들어, multstore(2, 3, &d)를 호출하면,
- x=2는 %rdi
- y=3는 %rsi
- &d는 %rdx에 담긴다
main.c
#include <stdio.h>
void mulstore(long, long, long *);
int main() {
long d;
mulstore(2,3,&d);
printf("2 * 3 --> %ld\n", d);
return 0;
}
long mult2(long a, long b) {
long s = a * b;
return s;
}
🔧 어셈블리 코드 (objdump -d 결과)
0000000000400540 <multstore>:
400540: 53 push %rbx
400541: 48 89 d3 mov %rdx,%rbx
400544: e8 42 00 00 00 callq 40058b <mult2>
400549: 48 89 03 mov %rax,(%rbx)
40054c: 5b pop %rbx
40054d: c3 retq
📦 한 줄씩 분석하기
🔹 push %rbx
→ rbx 상자에 있는 값을 스택에 저장해 둔다.
(나중에 다시 쓰기 위해 잠깐 보관)
🔹 mov %rdx, %rbx
→ rdx에는 &d라는 주소가 있었다.
그걸 rbx라는 상자에 복사한다.
즉, “결과 저장할 장소 기억해 둬”
🔹 callq 40058b <mult2>
→ mult2(2, 3) 함수를 호출한다
그 결과는 rax에 들어온다.
🔹 mov %rax, (%rbx)
→ rax에 있던 곱한 결과를, rbx가 가리키는 메모리 위치에 저장한다.
즉, “결과를 d에 저장한다”
🔹 pop %rbx
→ 아까 push로 저장했던 값을 다시 rbx로 되돌려준다.
🔹 retq
→ 함수 실행을 끝내고 다시 main으로 돌아간다.
📞 callq는 “전화 걸기” 같은 것이다.
C에서 mult2(2, 3) 이렇게 함수를 부르면,
어셈블리에서는 callq 명령어를 써서 그 함수에 점프한다.
callq 40058b <mult2>
이건 "주소가 40058b인 곳으로 가서, 거기에 있는 mult2 함수를 실행해!"라는 뜻이다.
📍 그럼 주소는 무엇인가?
컴퓨터는 함수가 어디에 있는지 숫자로 기억한다.
- 40058b ← 함수 mult2가 시작되는 정확한 위치(주소)이다..
- callq는 그 주소로 이동해서 거기부터 명령어를 읽기 시작한다.
📦 마치 “요리 책의 58쪽에 가서 요리법을 읽어!” 하는 것처럼, 주소는 “이 코드가 있는 곳”을 나타내는 책갈피이다.
🔙 함수 끝나면 어떻게 돌아오는가?
callq는 똑똑해서 함수 부르기 전에 돌아올 주소도 기억해 둔다.
- callq가 실행되면,
- “나 이 함수 끝나면 여기로 돌아와야 해!” 하고
- 현재 위치를 스택에 저장해 둔다
- 그리고 함수 주소로 이동해서 실행한다.
- 함수 안에서 마지막에 retq를 호출하면
- 아까 기억해 둔 주소로 돌아간다
3.2.3 형식에 대한 설명
💾 어셈블리 파일은 어떤 모습일까?
C 프로그램을 컴파일할 때 -S 옵션을 주면 어셈블리 코드 파일(.s)이 생성된다.
예를 들어
gcc -Og -S mstore.c
그러면 mstore.s라는 파일이 생기는데, 안을 보면 다음과 같이 생겼다:
.file "010-mstore.c"
.text
.globl multstore
.type multstore, @function
multstore:
pushq %rbx
movq %rdx, %rbx
call mult2
movq %rax, (%rbx)
popq %rbx
ret
.size multstore, .-multstore
.ident "GCC: (Ubuntu 4.8.1...)"
.section .note.GNU-stack,"",@progbits
- .file, .text, .globl, .type, .ident, .section 같은 것들은 명령이 아니라 지시문이다.
- 컴퓨터(어셈블러나 링커)는 이걸 보고 코드를 어떻게 배치할지 결정한다.
- 하지만 우리 인간 입장에서는 이 지시문은 너무 많고 복잡해서, 읽기 어렵다
💡 그래서! 이 책에서는 이렇게 바꿔서 보여준다
주석 달린 친절한 버전:
void multstore(long x, long y, long *dest)
// x in %rdi, y in %rsi, dest in %rdx
1 multstore:
2 pushq %rbx // %rbx 값 보관
3 movq %rdx, %rbx // dest 복사
4 call mult2 // mult2(x, y) 호출
5 movq %rax, (%rbx) // 결과 저장
6 popq %rbx // %rbx 복구
7 ret // 복귀
🧾 요점 정리
구분 | 의미 |
.로 시작하는 줄 | 어셈블리 지시문 (사람은 무시해도 됨) |
어셈블리 명령어 | 진짜 동작하는 부분 (mov, call, ret 등) |
주석 설명 | 코드를 이해하기 쉽게 도와주는 글 |