cs

[크래프톤 정글] hello.s 의 어셈블리 의 구조

하루이2222 2024. 9. 30. 18:40

예시: hello.c 파일

#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}

이 프로그램을 컴파일한 후, 어셈블리 코드로 변환하면 (gcc -S hello.c -o hello.s), hello.s 파일에는 다음과 같은 어셈블리 명령어가 생성된다.


hello.s 파일 예시 (x86-64 아키텍처 기반):

    .section    .rodata
.LC0:
    .string     "Hello, World!"

    .text
    .globl  main
    .type   main, @function
main:
    pushq   %rbp
    movq    %rsp, %rbp
    leaq    .LC0(%rip), %rdi
    call    printf
    movl    $0, %eax
    popq    %rbp
    ret

어셈블리 명령어의 구조

어셈블리 명령어는 일반적으로 연산 코드 (opcode)피연산자 (operands)로 구성된다.

  1. 연산 코드 (Opcode): CPU가 수행할 작업을 지시하는 명령어의 이름이다.
  2. 피연산자 (Operands): 연산의 대상이 되는 데이터나 레지스터를 나타낸다.

예시:

  • movq %rsp, %rbp에서 movq연산 코드(opcode)이고, %rsp%rbp피연산자(operands)다. 이 명령어는 레지스터 %rsp의 값을 %rbp에 복사하는 작업을 수행한다.

어셈블리 명령어의 역할 분석 (예시)

1. .section .rodata

  • 역할: 읽기 전용 데이터 섹션을 정의한다. 이 섹션에는 변경되지 않는 상수문자열 리터럴이 저장된다.
  • 이 예에서는 "Hello, World!" 문자열이 .rodata 섹션에 저장된다.

2. .string "Hello, World!"

  • 역할: "Hello, World!"라는 문자열을 메모리에 저장한다. 어셈블리 코드에서 이 문자열을 사용할 때는 이 위치를 참조한다.

3. .text

  • 역할: 실행할 코드(명령어)가 저장될 코드 섹션을 정의한다. 이후에 오는 명령어들이 이 섹션에 배치된다.

4. .globl main

  • 역할: main 함수가 글로벌 심볼임을 선언한다. 이 함수는 외부에서 참조할 수 있으며, 링커가 이를 인식하여 실행의 시작점으로 사용한다.

5. pushq %rbp

  • 역할: 현재의 베이스 포인터(%rbp) 값을 스택에 저장한다.
  • 이 명령어는 함수의 호출 직후 스택 프레임을 설정하는 작업의 일부이다. 함수가 호출되면 현재 스택 프레임을 저장하고 새로운 프레임을 설정해야 한다.

6. movq %rsp, %rbp

  • 역할: 현재의 스택 포인터(%rsp) 값을 베이스 포인터(%rbp)에 복사한다.
  • 이 명령어는 스택 프레임을 설정하는 작업으로, 새로운 스택 프레임의 기준점을 설정하기 위한 것이다.

7. leaq .LC0(%rip), %rdi

  • 역할: .LC0에 저장된 "Hello, World!" 문자열의 주소를 계산하고, 이를 레지스터 %rdi에 저장한다.
  • leaq는 메모리 주소를 계산하는 명령어로, printf와 같은 함수에 문자열의 주소를 전달할 때 사용된다. 여기서 %rip는 현재 명령어 포인터를 기준으로 오프셋을 계산하는 방식이다.

8. call printf

  • 역할: printf 함수를 호출한다.
  • call 명령어는 함수 호출을 수행하며, 함수가 끝난 후에 돌아올 주소를 스택에 저장한 다음, 함수의 첫 번째 명령어로 제어를 넘긴다.

9. movl $0, %eax

  • 역할: 리턴 값 0%eax 레지스터에 저장한다.
  • main 함수가 종료될 때 반환하는 값(0)을 레지스터에 저장하여 운영체제에 전달한다.

10. popq %rbp

  • 역할: 스택에서 이전의 베이스 포인터 값을 복원한다.
  • 함수가 종료될 때, 스택에 저장된 이전의 스택 프레임을 복원하여 호출된 함수의 실행을 마무리한다.

11. ret

  • 역할: 함수가 호출된 위치로 돌아간다.
  • ret 명령어는 스택에 저장된 복귀 주소를 읽어와, 호출된 함수가 끝난 후 호출된 위치로 제어를 넘긴다.

요약: 어셈블리 명령어의 구조와 역할

  1. 구조: 어셈블리 명령어는 연산 코드피연산자로 구성된다.
    • 연산 코드(opcode): CPU가 수행할 작업을 정의하는 명령어.
    • 피연산자(operands): 연산의 대상이 되는 값, 레지스터 또는 메모리 주소.
  2. 역할:
    • 어셈블리 명령어는 고수준 C 코드의 기능을 CPU가 이해할 수 있는 기계어로 변환하는 중간 단계이다.
    • 예를 들어, printf와 같은 C 함수 호출은 어셈블리 코드에서 call printf로 변환되며, 이 명령어는 CPU가 해당 함수의 시작 주소로 제어를 넘기는 역할을 한다.
  3. 실행 과정: 어셈블리 명령어는 CPU가 각 명령어를 순차적으로 실행하여 프로그램이 예상한 동작을 수행하도록 한다.