C 프로그램을 어셈블리로 변환

Abdul Mateen 2023년10월12일
  1. 어셈블리 언어
  2. C 언어
  3. C 프로그램을 어셈블리 언어로 변환
C 프로그램을 어셈블리로 변환

이 자습서에서는 C 언어 프로그램을 어셈블리 언어 코드로 변환하는 방법에 대해 설명합니다.

어셈블리 및 C 언어의 기본 사항에 대해 간략하게 설명합니다. 나중에 우리는 C 프로그램을 어셈블리 코드로 변환하고 어셈블리 코드를 분해하는 것을 볼 것입니다.

어셈블리 언어

어셈블리는 저수준 해석 언어입니다. 일반적으로 어셈블리 언어로 작성된 명령문은 단일 기계 수준 명령으로 변환됩니다.

그러나 니모닉을 사용하기 때문에 기계어보다 훨씬 읽기 쉽습니다. 니모닉은 영어와 유사한 명령어 또는 연산 코드입니다.

예를 들어, 니모닉 ADD는 두 개의 숫자를 더하는 데 사용됩니다. 마찬가지로 MOV는 데이터 이동을 수행하는 데 사용됩니다.

마찬가지로 CMP는 두 표현식을 비교하고 JMP는 실행 제어를 특정 레이블 또는 위치 마커로 이동합니다.

어셈블리 언어는 기계(하드웨어)에 매우 가깝습니다. 따라서 어셈블리 언어로 작성된 명령어는 매우 빠릅니다. 그러나 프로그래머는 고급 언어의 개발자보다 훨씬 더 많은 하드웨어 지식이 필요합니다.

어셈블리 언어는 일반적으로 장치 드라이버, 바이러스/안티바이러스 프로그램, 임베디드 시스템 소프트웨어 및 TSR(종료되고 상주하는 프로그램)과 같은 효율적인 시스템 프로그램을 작성하는 데 사용됩니다.

어셈블러는 어셈블리 언어 프로그램을 시스템에서 실행 가능한 기계어 프로그램으로 어셈블해야 합니다.

C 언어

C는 기계 독립적인 고급 프로그래밍 언어입니다. 일반적으로 C 프로그램에는 하드웨어 지식이 필요하지 않습니다(약간의 지식만 있으면 됨).

C에는 고급 문이 있으며 C 언어의 각 문을 하나 이상의 어셈블리 언어 문으로 변환하는 컴파일러 프로그램이 필요합니다. 예를 들어, C 언어의 간단한 명령어 c = a + b는 다음 어셈블리 언어 문으로 변환됩니다.

mov edx, DWORD PTR - 12 [rbp] mov eax, DWORD PTR - 8 [rbp] add eax,
    edx mov DWORD PTR - 4 [rbp], eax

여기에서 메모리의 변수의 첫 번째 및 두 번째 명령문 값이 레지스터로 이동됩니다. 추가 명령은 두 개의 레지스터 값을 추가합니다.

네 번째 문에서는 레지스터의 값이 메모리의 변수로 이동됩니다.

게다가 컴파일러는 많은 일을 해야 하지만 프로그래머의 삶은 C 언어로 일하는 것이 단순하다. C 언어는 높은 수준의 비즈니스 응용 프로그램에서 낮은 수준의 유틸리티 프로그램에 이르기까지 광범위한 응용 프로그램을 가지고 있습니다.

C 프로그램을 어셈블리 언어로 변환

일반적으로 사람들은 정교한 통합 환경을 사용하여 C 언어 프로그램을 작성, 편집, 컴파일, 실행, 수정 및 디버그하거나 gcc 명령을 사용하여 C 언어 프로그램을 실행 가능한 프로그램으로 변환합니다.

이러한 도구는 C와 같은 일부 고급 언어로 작성된 소스 코드를 기계 실행 가능 코드로 변환하는 것과 관련된 단계를 사용자가 인식하지 못하도록 합니다. 일반적으로 다음 단계는 중간에 수행됩니다.

  1. 전처리 - 전처리 프로그램은 세 가지 작업을 수행합니다. 첫 번째 작업은 헤더 파일 포함, 두 번째 작업은 매크로 교체, 세 번째 작업은 소스 프로그램에서 주석 제거
  2. 컴파일러 - 두 번째 단계에서 컴파일러는 고급 언어 프로그램을 어셈블리 언어 프로그램으로 변환합니다.
  3. 어셈블러 - 세 번째 단계에서 어셈블러 프로그램은 어셈블리 언어 프로그램(컴파일러에 의해 번역됨)을 취하여 오브젝트 코드라는 기계 실행 가능 형식으로 어셈블합니다.
  4. 링커 - 네 번째 단계에서 링커 프로그램은 컴파일된 라이브러리 파일과 목적 코드를 첨부하여 이 프로그램을 독립적으로 실행합니다.

C 코드를 동등한 어셈블리로 변환하는 명령

일반적으로 명령줄 사용자는 실행 파일을 생성하는 gcc program_name.c를 입력합니다(오류가 없는 경우). 대상 파일 이름이 지정되지 않은 경우 UNIX 운영 체제 제품군의 a.out 또는 Windows 운영 체제의 program_name.exe로 사용할 수 있습니다.

그럼에도 불구하고 gcc 명령에는 특정 작업을 수행하기 위한 방대한 매개변수 목록이 있습니다. 이 자습서에서는 -s-C 플래그에 대해서만 설명합니다.

-S 플래그는 C 소스 코드에서 어셈블리 언어 프로그램을 생성합니다. 소스 파일로 test.c가 있는 다음 예제를 사용하여 이 플래그를 이해해 보겠습니다.

// test.c
int main() {
  int a = 2, b = 3, c;
  c = a + b;
  return 0;
}

다음 명령은 확장명이 .S인 대상 어셈블리 언어 코드를 생성합니다.

$ gcc -S test.c
$ ls
test.c test.s

이 명령은 기계어 코드를 작성하지 않았습니다. 어셈블리 언어 코드만 생성됩니다. Bash에서 cat 명령을 사용하여 이 생성된 어셈블리 코드의 내용을 표시해 보겠습니다.

$ cat test.s
    .file   "Test.c"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    endbr64
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $2, -12(%rbp)
    movl    $3, -8(%rbp)
    movl    -12(%rbp), %edx
    movl    -8(%rbp), %eax
    addl    %edx, %eax
    movl    %eax, -4(%rbp)
    ...

생성된 Assembly 코드는 Intel x86 아키텍처용 Assembly 코드 작성 경험이 있는 많은 프로그래머에게 익숙하지 않을 수 있습니다.

Intel x86 아키텍처에 대한 대상 어셈블리 코드가 필요한 경우 다음 명령이 이를 수행합니다.

$ gcc -S -masm=intel  Test.c

다시, Bash 터미널에서 cat 명령을 사용하여 볼 수 있는 Test.s 파일에 출력이 생성됩니다. Windows에서는 메모장이나 더 나은 편집기와 같은 일부 편집기에서 열 수 있습니다.

어쨌든 위의 명령으로 생성된 Assembly 코드의 내용을 살펴보겠습니다.

 cat Test.s
    .file   "Test.c"
    .intel_syntax noprefix
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    endbr64
    push    rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    mov rbp, rsp
    .cfi_def_cfa_register 6
    mov DWORD PTR -12[rbp], 2
    mov DWORD PTR -8[rbp], 3
    mov edx, DWORD PTR -12[rbp]
    mov eax, DWORD PTR -8[rbp]
    add eax, edx
    mov DWORD PTR -4[rbp], eax
    ...

출력이 약간 다릅니다. movadd 명령은 매우 명확합니다.

개체 코드 분해

C 언어 프로그램을 어셈블리 언어로 변환하는 것 외에도 동등한 어셈블리 언어 코드를 보기 위해 바이너리 코드(기계 코드)를 디스어셈블할 수 있습니다. 이를 위해 Linux에서 objdump 유틸리티를 사용할 수 있습니다.

예:

gcc -c Test.c 명령을 실행하여 Bash 터미널에서 Test.c 파일을 컴파일한다고 가정합니다. Test.o라는 이름의 개체 파일(기계어 코드)을 생성합니다.

이제 이 개체 코드를 동등한 어셈블리 코드로 다시 변환/분해하려면 다음 Bash 명령을 사용하여 수행할 수 있습니다.

$ objdump -d Test.o

Test.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0:   f3 0f 1e fa             endbr64
   4:   55                      push   %rbp
   5   48 89 e5                 mov    %rsp,%rbp
   8:   c7 45 f4 02 00 00 00    movl   $0x2,-0xc(%rbp)
   f:   c7 45 f8 03 00 00 00    movl   $0x3,-0x8(%rbp)
  16:   8b 55 f4                mov    -0xc(%rbp),%edx
  19:   8b 45 f8                mov    -0x8(%rbp),%eax
  1c:   01 d0                   add    %edx,%eax
  1e:   89 45 fc                mov    %eax,-0x4(%rbp)
  21:   b8 00 00 00 00          mov    $0x0,%eax
  26:   5d                      pop    %rbp

이 출력에서 왼쪽에 있는 코드는 16진법의 이진 코드입니다. 오른쪽에는 읽을 수 있는 형식의 어셈블리 언어 코드가 표시됩니다.