2025-1 컴퓨터구조 (Assembly Language and machine code 3)

JayainHUFS·2025년 4월 23일
post-thumbnail

  • 아래의 포스팅은 한국외국어대학교 컴퓨터공학부 Mohsen Ali Alawami 교수님의 [컴퓨터구조] 강의를 기반으로 작성했습니다.
  • 또한, 상업적 의도는 전무하며, 단순 학습 용도의 교재 자료를 첨부함을 알립니다.

Calling Functions

%rsp (stack pointer register)

(1) 함수 호출 전

%rsp는 현재 스택의 top을 가리킴.

main()에서 %rsp는 main의 프레임의 끝(최상단)을 가리킴.

호출 이전 상태를 기억해야 하므로, 이후 변경될 주소를 기준 삼음.

(2) 함수 호출 (예: foo())

호출되면 %rsp는 아래쪽으로 감소(stack grows downward).

새로운 스택 프레임 생성.

함수의 리턴 주소 및 레지스터 저장값 등이 이곳에 저장됨.

(3) 중첩 호출 (예: foo2())

foo() 안에서 foo2() 호출 시,
%rsp는 다시 감소하며 foo2의 프레임을 생성.

스택은 계속 아래로 쌓임.

(4) 함수 종료 시 (return)

함수가 ret 명령어로 종료되면 -> %rsp는 이전 위치로 복원됨.

해당 함수의 스택 프레임은 사라짐.


Push Instruction

(1) 기본 정의 및 역할

핵심 내용
pushq src는 데이터를 스택의 최상단에 저장하며, 동시에 %rsp 값을 감소시킴

스택은 주소가 감소하는 방향(아래 방향) 으로 자람

동작 요약
(1) %rsp를 8 감소시킴 (64비트 기준 → 8바이트 공간 확보)
(2) 확보된 위치(%rsp)에 데이터를 저장

(2) 내부 연산 분해

Effective Operations (실제 실행되는 연산)

단계명령어설명
Step 1subq $8, %rsp스택 공간 확보 (8바이트 감소)
Step 2movq src, (%rsp)확보된 위치에 src 값 저장

즉, pushq src는 위의 두 명령으로 분해됨.

(3) 실제 예시 (값 저장)

스택 동작 (예: pushq $0x3026)

전 상태동작후 상태
%rsp = 0xff20subq $8, %rsp 실행%rsp = 0xff18
movq $0x3026, (%rsp)0xff18 위치에 저장

Pop Instruction

(1) popq Instruction 기본 동작

내용
pop 명령어는 스택 최상단의 데이터를 꺼내어 목적지(dst)에 저장

그리고 %rsp를 8바이트 증가시켜 스택 포인터를 조정

스택에서 값을 “꺼낸다(remove from top of stack)“는 의미

1단계: movq (%rsp), dst   // %rsp가 가리키는 값을 목적지에 복사  
2단계: addq $8, %rsp      // %rsp를 증가시켜 다음 스택 위치로 이동

(2) popq Instruction 예시 (popq %rdx)

예시

popq %rdx 는
(1) %rsp가 가리키는 주소(예: 0x3026)의 값을 %rdx에 복사
(2) 그 후 %rsp를 0xff18 → 0xff20으로 8바이트 증가

결과 흐름

값 이동 : 0x3026 → %rdx
스택 포인터 이동 : %rsp = %rsp + 8


Stack Example


Calling Functions In Assembly

Caller / Callee

Caller: 함수를 호출하는 함수 (ex. main)

Callee: 호출되는 함수 (ex. foo)

하나의 함수는 동시에 caller이자 callee일 수 있음
-> 예: foo()가 foo2()를 호출하면, foo()는 callee이면서 caller.

What does “Remembering Where We Left Off” mean?

어셈블리에서 함수를 호출하면 현재 위치로 다시 돌아와야 함
(C 언어의 result = sum(a, b) 이후에 이어서 실행)

이를 위해 CPU는 프로그램 상태(state)를 저장함

저장 위치: Stack, Registers 사용

Caller 함수가 실행 중 Callee 함수를 호출하고, Callee가 끝난 후 돌아올 위치를 기억해야 함

“Remembering Where We Left Off” – Mechanism

%rip: 다음 실행할 명령어를 가리키는 Instruction Pointer

함수 호출 전 %rip 값을 Stack에 push

Callee 함수 실행 후 pop으로 %rip 복구 → Caller 위치로 복귀

함수 호출 시 기본 절차 (4단계)

(1) push: 호출 이후 돌아올 %rip 주소를 스택에 저장

(2) call: Callee 함수 실행

(3) pop: 완료 후 저장했던 주소를 %rip에 복구

(4) 계속 실행: Caller 함수 이후 명령부터 이어서 실행


Remembering Where We Left Off - Example

(1) 함수 호출 개요

내용
main 함수에서 foo 함수를 호출하는 예시.
호출 당시의 %rip 값(0x3021)은 현재 실행 중인 명령의 주소를 가리킨다.
이 주소는 호출 후 다시 돌아와야 하므로 스택에 저장된다.
%rsp는 스택의 top을 가리킴.

(2) 복귀 주소 저장

내용
foo 함수 호출 직전 다음 명령어의 주소인 0x3026을 스택에 push하여 저장함.
이때 %rsp는 8 바이트 감소하고, 해당 주소가 스택 top에 저장됨.

(3) 실제 함수 호출

내용
%rip가 0x4058로 바뀌며 foo() 함수 실행 시작.
이전 복귀 주소 0x3026은 여전히 스택에 남아 있고 %rsp는 이를 가리키고 있음.

(4) 함수 종료와 복귀 주소 복원

내용
foo()가 종료되면 스택에 있던 0x3026을 pop하여 %rip에 복원함. 이는 원래 main() 함수의 다음 실행 지점.

(5) 복귀 주소로 점프

내용
%rip가 0x3026으로 복원되어 main()의 이어지는 명령어를 수행하게 됨. 함수 복귀가 정상적으로 이루어진 것.

(6) 이후 명령어 실행

내용
%rip는 이제 0x302B를 가리키며 함수 호출 이후의 main() 코드가 실행됨. 이는 함수 호출 전체 흐름이 정상적으로 완료되었음을 뜻함.


What does "call" instruction do?

call 명령어는 다음 명령어의 주소를 스택에 push하고,

%rip을 호출할 함수의 시작 주소로 설정해 해당 함수로 점프한다.

즉, 현재 위치 저장 + 함수 진입이라는 두 단계를 수행.


What does "ret" instruction do?

ret 명령어는 스택의 최상단에서 주소를 pop하여

%rip에 넣고, 저장된 주소로 점프함으로써

호출자 함수로 복귀한다.

즉, 함수 실행을 마치고 호출했던 위치로 돌아감.


Practice : Call and ret

설명
%rip에는 401156 주소가 들어감 → 이는 foo() 함수의 첫 번째 명령어 주소

스택에는 401138 주소가 push 됨 → 이는 callq 다음 명령어(mov %eax, %edx)의 주소

결과
call 명령어는 복귀할 주소(다음 명령)를 스택에 저장하고,

%rip 값을 호출된 함수의 첫 명령어 주소로 바꾼다.

이후 retq가 실행되면 스택에서 주소(0x401138)를 pop하여 %rip에 복원한다.

따라서 foo() 실행 후 다시 mov %eax, %edx로 복귀하게 된다.


Arrays : Example 1

(1) C 코드: n = a[i] (C 코드에서 배열 접근)

배열 a[100], 정수형 변수 i, n이 선언되어 있고, i = 3, n = a[i]가 실행됨.

이는 n = a[3]을 의미하며, 배열 인덱스 접근의 의미를 어셈블리로 구현하려는 목적

(2) movl $3, i → movslq i, %rax (i 값을 레지스터로 이동)

변수 i에 3을 저장하고, 이를 movslq 명령어로 %rax에 sign-extension을 포함해 64비트로 이동시킴.

즉 %rax = 3이 된다.

(3) salq $2, %rax (배열 오프셋 계산을 위한 shift)

배열 요소의 크기인 4바이트를 고려하여, %rax 값을 왼쪽으로 2비트 시프트.

이는 3 * 4 = 12 바이트 offset이 계산된 것이다.
-> %rax = 12

(4) addq $a, %rax (배열의 base address 더하기)

배열 a의 시작 주소 1000에 offset 12를 더해 a[3]의 주소인 1012를 계산.
-> %rax = 1012

(5) movl (%rax), %r10d (간접 주소지정 방식으로 메모리 접근)

메모리 주소 1012 (a[3])에 저장된 값을 가져와 %r10d에 저장.
→ %r10d = a[3] = 123

(6) movl %r10d, n (레지스터 값을 변수 n에 저장)

%r10d에 있는 값을 메모리 상의 변수 n의 주소에 저장.
→ 메모리 주소 1404 (n)에 123 저장

(7) C 코드 n = a[i]의 전체 어셈블리 동작(전체 흐름 요약)

%r10d에 있는 값을 메모리 상의 변수 n의 주소에 저장.
→ 메모리 주소 1404 (n)에 123 저장

Indirect Access mode


Arrays : Example 2

(1) i 값을 레지스터로 이동

movl i, %eax

변수 i의 값을 %eax에 저장함

이 예제에서 i = 3이므로 %eax = 3

(2) 배열 인덱스 연산

sall $2, %eax

%eax 값을 왼쪽으로 2비트 시프트함 → 곱하기 4

즉, i 4 = 3 4 = 12가 되어, 배열 인덱스 바이트 주소 계산됨

%eax = 12

(3) a 배열 주소 기준으로 값 접근

movl a(,%eax), %r10d

a는 배열의 시작 주소 (0x1000)

a(,%eax)는 a + %eax → 1000 + 12 = 1012

a[3]의 값인 123을 %r10d에 저장

%r10d = 123

(4) 값 저장

movl %r10d, n

%r10d 값을 변수 n에 저장함

n = 123

(5) 메모리 상태 반영

메모리 주소 1404 (변수 n)에 123이 저장됨

전체 흐름: n = a[i] 수행 완료됨


일단, 중간고사 범위 끝...

profile
"REAL" 밑바닥부터 시작하는 데이터분석

0개의 댓글