CSAPP 3.4장 요약

낚시하는 곰·2025년 4월 6일

krafton jungle

목록 보기
46/52

3.4.1 Operand Specifiers (피연산자 지정자)

요점

  • **피연산자(Operand)**는 명령어가 사용할 데이터의 위치나 값을 지정해.
  • x86-64는 다양한 피연산자 형태를 지원해. 총 3가지 유형이 있어:
    • Immediate: 상수값 (예: $0x10)
    • Register: 레지스터 (예: %rax)
    • Memory: 메모리 주소 기반 (예: (%rax))

메모리 주소 지정 방식

  • 절대 주소: ImmM[Imm]
  • 간접 주소: (ra)M[R[ra]]
  • 베이스 + 오프셋: Imm(rb)M[Imm + R[rb]]
  • 스케일 인덱스 주소: Imm(rb, ri, s)M[Imm + R[rb] + R[ri] * s]
    👉 스케일 s1, 2, 4, 8만 가능해

연습문제 3.1

레지스터 값:
%rax = 0x100, %rdx = 0xC, %rcx = 0x4

오퍼랜드의미 설명계산된 주소해당 메모리 값
%rax레지스터 자체 값-0x100
0x104절대 주소0x1040xAB
$0x108즉시 값 (Immediate)-0x108
(%rax)간접 주소 - rax가 가리키는 주소0x1000xFF
4(%rax)rax + 40x1040xAB
9(%rax,%rdx)rax + rdx + 90x10C0x11
260(%rcx,%rdx)260 + rcx + rdx = 0x104 + 0x4 + 0xC0x1080x13
0xFC(,%rcx,4)0xFC + rcx × 4 = 0xFC + 0x100x10C0xFF
(%rax,%rdx,4)rax + rdx × 4 = 0x100 + 0x300x1300x11

3.4.2 Data Movement Instructions (데이터 이동 명령어)

요점

  • 가장 자주 쓰이는 명령어는 mov 계열로, 데이터를 복사해.
  • 명령어 형식: mov[bwlq] S, DD ← S
    • b, w, l, q는 각각 1, 2, 4, 8 바이트
    • movl은 레지스터 대상이면 상위 4바이트를 0으로 초기화

예시

movl $0x4050, %eax        ; 즉시값 → 레지스터
movw %bp, %sp             ; 레지스터 → 레지스터
movb (%rdi,%rcx), %al     ; 메모리 → 레지스터
movb $-17, (%esp)         ; 즉시값 → 메모리
movq %rax, -12(%rbp)      ; 레지스터 → 메모리

※ 메모리↔메모리는 직접 복사가 안 되고, 반드시 레지스터를 거쳐야 함


연습문제 3.2

📌 목적: 다양한 mov 명령어를 보고 오퍼랜드 형식과 데이터 크기를 파악하는 문제
📍 %rsp, %rax, %rdx, %eax, %dx, %dl, %bl 등은 전부 64비트 기반 레지스터들

명령어동작 설명
movl %eax, (%rsp)%eax의 4바이트 값을 %rsp가 가리키는 메모리에 저장
movw (%rax), %dx%rax가 가리키는 메모리 2바이트를 %dx에 복사
movb $0xFF, %bl즉시값 0xFF를 1바이트 레지스터 %bl에 저장
movb (%rsp,%rdx,4), %dl주소 계산: %rsp + 4 × %rdx의 1바이트 값을 %dl에 저장
movq (%rdx), %rax%rdx가 가리키는 메모리 8바이트를 %rax에 복사
movw %dx, (%rax)%dx의 2바이트 값을 %rax가 가리키는 메모리에 저장

연습문제 3.3

어셈블리 코드오류 내용 설명
movb $0xF, (%ebx)%ebx는 주소 지정용 레지스터로 사용할 수 없어. (주소 계산에 사용할 수 없는 32비트 레지스터)
movl %rax, (%rsp)❌ 레지스터 크기와 명령어 접미사가 맞지 않아. movl은 32비트인데 %rax는 64비트야.
movw (%rax), 4(%rsp)❌ 메모리 → 메모리 복사는 불가능해! (레지스터를 거쳐야 함)
movb %al, %sl%sl이라는 레지스터는 존재하지 않아. 오타이거나 잘못된 이름이야.
movl %eax, $0x123❌ 즉시값은 **목적지(오른쪽)**에 올 수 없어. 항상 소스에서만 사용 가능!
movl %eax, %dx❌ 대상 레지스터의 크기가 다름. %eax는 32비트, %dx는 16비트 → 크기 불일치!
movb %si, 8(%rbp)%si는 16비트인데 movb는 1바이트 → 크기 불일치!

3.4.3 Data Movement Example

요점

  • 간단한 C 함수 exchange(long *xp, long *yp)를 예시로, 어떻게 데이터가 이동하는지 보여줘.

C 코드

long exchange(long *xp, long *yp) {
    long x = *xp;
    long y = *yp;
    *xp = y;
    *yp = x;
    return x;
}

어셈블리 코드

movq (%rdi), %rax   ; *xp → %rax
movq (%rsi), %rdx   ; *yp → %rdx
movq %rdx, (%rdi)   ; %rdx → *xp
movq %rax, (%rsi)   ; %rax → *yp
ret

※ 함수 인자는 %rdi, %rsi, 반환값은 %rax

CSAPP 3.4 연습문제

📌 변수 선언:
src_t *sp;
dest_t *dp;

원하는 연산:
*dp = (dest_t) *sp;

C의 형변환과 어셈블리에서의 데이터 이동/확장 방식 연결!

src_tdest_tInstruction설명
longlongmovq (%rdi), %rax -> movq %rax, (%rsi)8바이트 → 8바이트 그대로 복사
charintmovsbl (%rdi), %eax -> movl %eax, (%rsi)1바이트 → 4바이트 부호 확장
charunsignedmovsbl (%rdi), %eax -> movl %eax, (%rsi)GCC가 char를 부호형으로 간주하여 movsbl 사용
unsigned charlongmovzbl (%rdi), %eax -> movq %rax, (%rsi)1바이트 → 4바이트 0 확장 → 8바이트로 저장
intcharmovl (%rdi), %eax -> movb %al, (%rsi)4바이트에서 하위 1바이트만 복사
unsigned intunsigned charmovl (%rdi), %eax -> movb %al, (%rsi)하위 1바이트만 저장
charshortmovsbw (%rdi), %ax -> movw %ax, (%rsi)1바이트 부호 확장 → 2바이트로 저장

주요 개념 요약

  • movs* : sign extension (부호 유지 확장)
  • movz* : zero extension (상위 0으로 채움)
  • movb/movw/movl/movq : 복사 크기 (1/2/4/8 바이트)
  • 작은 타입 → 큰 타입 : movs* 또는 movz* 사용
  • 큰 타입 → 작은 타입 : movb, movw로 하위 일부만 저장

연습문제 3.5번

void decode1(long *xp, long *yp, long *zp){
    *xp = *yp;
    *yp = *zp;
    *zp = *xp;
}

위는 정확한 답은 아니다. 착각을 한 부분이 있는데

movq (%rdi), %r8
movq (%rsi), %rcx
movq (%rdx), %rax

이 부분을 함수 호출 시에 각 매개 변수 값을 임시로 복사하여 저장하는 로직이라고 줄 착각했다. 함수 호출 시에 매개 변수의 주소값을 rbp가 가지고 있다가 rsp에 임시로 저장한다고 책에서 읽은 부분이랑 헷깔린 것 같다. 애초에 C코드를 이렇게 짜면 xp의 데이터 값이 변경되기 때문에 zp에 값이 제대로 저장되지 않는 문제가 발생한다.


3.4.4 Pushing and Popping Stack Data (스택 데이터 푸시 & 팝)

요점

  • 스택은 LIFO 구조로, 데이터를 마지막에 넣은 순서로 꺼내.
  • 스택은 메모리의 낮은 주소 방향으로 자람

명령어

명령어동작 설명
pushq Srsp -= 8; M[rsp] = S
popq DD = M[rsp]; rsp += 8

스택 예시

pushq %rax   ; %rax 값을 스택에 저장 → rsp는 8 감소
popq %rdx    ; 스택 최상단 값을 %rdx에 저장 → rsp는 8 증가

참고

  • pushq %rsp는 특별한 경우니까 처리 순서 유의해야 해 (스택 포인터를 저장할 때 주의 필요!)

궁금한 내용 알고 가기

1. 레지스터와 메모리 주소

  • 레지스터는 CPU 내부의 저장소로, 데이터를 직접 저장하고 처리할 수 있다.
  • 메모리는 주소 기반으로 접근하며, 레지스터를 포인터처럼 사용하여 간접 주소 지정 가능:
    • 예: (%rbp), 8(%rbp), -8(%rbp)
  • 레지스터 자체에는 데이터를 저장하지 않고, 해당 주소를 통해 메모리에 접근한다.

2. mov 명령어와 레지스터 구조

  • movb, movw, movl, movq로 각각 1, 2, 4, 8 바이트 데이터를 옮긴다.
  • %rax는 64비트, %eax는 하위 32비트, %ax는 하위 16비트, %al은 하위 8비트.
  • movl은 상위 32비트를 자동으로 0으로 초기화하여 예측 가능성을 높인다.

3. 스택과 push/pop

  • 스택은 LIFO 구조로, 낮은 주소 방향으로 자란다.
  • %rsp는 스택 최상단 주소를 가리키며, push 시 8바이트 감소, pop 시 8바이트 증가:
    • pushq %raxrsp -= 8; M[rsp] = %rax
    • popq %rax%rax = M[rsp]; rsp += 8
  • %rbp는 현재 함수의 기준점이며, 함수 호출 시 %rsp의 값을 저장해서 기준으로 사용한다.

4. 주소 지정 방식

  • offset(reg) 형태는 메모리 주소 지정 방식:
    • 예: 12(%rbp)%rbp + 12 바이트 떨어진 주소를 참조
  • 이 방식 자체는 위험하지 않으며, 컴파일러가 충돌이 없도록 안전하게 오프셋을 배치한다.

5. 스택의 크기와 제한

  • 운영체제가 프로세스마다 사용하는 스택 크기를 제한한다 (보통 리눅스는 8MB).
  • 스택에 저장하는 모든 데이터는 해당 범위 안에서만 가능.
  • 너무 많은 지역 변수를 선언하면 Stack Overflow가 발생하여 프로그램이 종료됨.
  • 예: int arr[2100000]; 는 8MB를 초과하므로 Segmentation Fault 발생.

6. 32비트와 64비트 스택 포인터

  • %esp: 32비트 스택 포인터 (레거시)
  • %rsp: 64비트 스택 포인터 (현대 x86-64에서 사용)
  • %esp%rsp의 하위 32비트이며, 32비트 연산 시 상위 32비트가 0으로 초기화된다.

7. 메모리 정렬과 낭비

  • CPU는 정렬된 주소에서 데이터를 읽을 때 더 빠르기 때문에, 컴파일러는 타입 크기에 따라 **정렬(padding)**을 삽입한다.
  • 예: int는 4바이트 정렬, long/pointer는 8바이트 정렬 필요
  • char는 1바이트만 사용하지만 정렬을 위해 나머지를 padding으로 둘 수 있다.

8. 실전 스택 오버플로우 계산 예시

  • 스택 크기: 8MB = 8,388,608 bytes
  • int는 4바이트 → 2,097,152개 이상 선언 시 스택 초과
  • 예: int big[2100000]; → Stack Overflow 발생

profile
취업 준비생 낚곰입니다!! 반갑습니다!!

0개의 댓글