본문 바로가기

awesome-c Beginner 번역/Building C Projects

<비공식 번역>awesome-c Beginner 번역 : 7. Compilation and assembly

현재위치


1. Configuration

2. Standard directory dectection

3. Source file dependency calculation

4. Header file location

5. Header precompileation

6. Preprocessing

7. Compliation and assembly

8. Object file dependency calculation

9. Linking

10. Installation

11. Resource linking

12. Package generation

13. Dynamic linking

<주의!!>

nethack4.org의 'Building C Projects'의 공식적인 번역이 아니며 수를 받은 것 역시 아닙니다!!




7: Compilation and assembly (7:컴파일과 어셈블리)


우리는 이제 소스파일에서 전처리기까지 C 레벨에서 일어나는 매우 많은 모든 것을 봤습니다. 이전에 중요한 점을 되풀이해서, 전처리기의 결과물은 일반적으로 약간 표준 라이브러리의 관련된 모든 부분에 대한 선언을 포함하는 C의 연장입니다 (일반적으로 관련이 없는 부분이지만 최적화됩니다.). 빌드 과정의 다음 부분은 전처리기의 결과물을 변환하여 "오브젝트 파일"로 만드는 컴파일과 어셈블리입니다.


전처리기의 결과물은 약간 C의 연장이지만, 오브젝트 파일은 (링커의 입력물)은 약간 기계 코드의 연장입니다. 프로세서가 실제로 실행될 기계어 코드의 차이는 프로세서 명령 뿐만 아니라 링커의 명령 또한 가득차 있다는 것입니다; 일반적인 링커 명령은 라인에 따라 "이 문자열은 읽기 전용 데이터를 다른 문자열과 함께 나를 위해서 배치하고 MMU에게 이것들은 읽기 전용임을 말한다" 또는 "저는 서브루틴으로 제가 모두 0인 주소를 호출했음을 압니다, 이것이 어디에 있던 strcmp주소와 함께 대체할 수 있습니까?" 한 마디로, 오브젝트 파일은 독립적인 파일 하나에서 보여짐에 따라 계산될 수 있는 기계어 코드를 양을 가집니다. 나머지는 빌드에서 나중에 파일을 링커 명령어로 패치합니다.


C를 기계어코드로 변환에 사용할 수 있는 두가지 기본적인 접근법이 있습니다. 하나는 직접 바꾸는 것입니다; 다른 하나는 어셈블리 코드를 통해 진행하는 것입니다(어셈블리 코드는 거의 기계어 코드와 같은 수준의 저레벨입니다), 하지만 더 많은 사람이 읽을 수 있고 기계어 코드가 할 수 없는 다양한 링커 명령어를 표현할 수 있습니다. 어셈블리 코드가 중간단계로 존재할 때(또는 이것이 아닐 수도 있고 - 어떤 컴파일러는 요구에 따라서 이 코드를 생산할 수 있습니다), 컴파일러는 일반적으로 -S 옵션으로 여러분이 이 코드를 볼 수 있도록 지원합니다. 우리의 Hello World 프로그램은 어셈블리 코드 30줄로 확장됩니다. 오브젝트 파일이 "disassemble"도 가능하며 기계어 코드가 어셈블리 코드랑 비슷해 보임을 확인하고 사람이 더 읽기 편하도록 만듭니다. 여기에 C로 만들어진 Hello World 프로그램이 있습니다(복습차원에서):


#include <stdio.h>


int main(void)

{

fputs("Hello world!\n", stdout);

return 0;

}


그리고 여기는 컴파일러에 의해 생성된 어셈블리가 어떻게 보일지와 어떻게 오브젝트 파일이 disassemble 된 이후에 보이는가를 나타냅니다 (공통선이 서로 옆에 있도록 재정렬; 제 디스어셈블러 또한 기계어를 16진수로 출력했지만 저는 그 저장공간을 삭제했습니다, 비록 개행과 NUL이 "Hello World!"의 마지막에 모호하게 나타내더라도) :


    .file   "t.c"

    .section    .rodata             Contents of section .rodata:

.LC0:

    .string "Hello, world!\n"       Hello, world!.. 

    .text                           Disassembly of section .text:

    .globl  main

    .type   main, @function

main:

.LFB0:

    .cfi_startproc

    pushq   %rbp                    push   %rbp

    .cfi_def_cfa_offset 16

    .cfi_offset 6, -16

    movq    %rsp, %rbp              mov    %rsp,%rbp

    .cfi_def_cfa_register 6

    movq    stdout(%rip), %rax      mov    0x0(%rip),%rax

    movq    %rax, %rcx              mov    %rax,%rcx

    movl    $14, %edx               mov    $0xe,%edx

    movl    $1, %esi                mov    $0x1,%esi

    movl    $.LC0, %edi             mov    $0x0,%edi

    call    fwrite                  callq  22 <main+0x22>

    movl    $0, %eax                mov    $0x0,%eax

    popq    %rbp                    pop    %rbp

    .cfi_def_cfa 7, 8

    ret

    .cfi_endproc

.LFE0:

    .size   main, .-main

    .ident  "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"

.section    .note.GNU-stack,"",@progbits


위는 Hello World 프로그램의 어셈블리 출력입니다; 우리는 링커를 위해서 많은 주의할 점과(. 으로 시작하는 라인), 기계어 코드에는 존재하지 않는 다양한 구조를 볼 수 있습니다 (예를 들어 fwrite를 호출, stdout 언급, 심지어 fwritestdout은 메모리 어디있는지도 모릅니다.). 오브젝트 파일을 디스어셈블하여 생성된 기계어 코드와 비교하면, 우리는 기본적으로 동일한 많은 라인을 볼 수 있습니다, 그러나 일부는 0으로 비트가 대체되어 있습니다; 예를 들어 movq stdout(%rip), %raxmov 0x0(%rip), %rax 로 해석하기는 어렵습니다. 마찬가지로 fwrite 호출은 현재 명령어 포인터 위치를 호출로 대체되었습니다 (여기 또한 기계어 코드에서 0으로 변환되었습니다).


이것에 주목할 필요가 있습니다, 비록 프로그램은 최적화되지 않고 컴파일 되었으나 컴파일러는 어떤방법으로든 작은 최적화를 수행합니다; 오리지널 Hello World 프로그램은 fputs를 호출하지만컴파일러는 fwrite를 호출합니다. 이 두개의 함수는 C 표준에 따라서 출력을 저장하기위해 버퍼의 사용이 필요합니다; fputsstrncpy 혹은 동일한 함수처럼 버퍼를 채우는 일련의 작업을 수행하는 반면에 (strcpy 대신 strncpy인 이유는 출력의 결과물이 너무 길어 버퍼를 오버플로우 할 가능성이 있기때문입니다), fwrite 함수는 동작하기 전에 길이를 알고있고 (이 같이 짧은 문자열에서) 버퍼메모리에 직접 memcpy 할 수 있습니다. memcpy는 동일한 시간에 더 많은 문자를 복사할 수 있기 때문에 strncpy보다 빠릅니다. (strncpy는 문자열을 끝을 지나서 읽지 않도록 주의해야하고 문자열의 끝이 어디있는지도 사전에 알지 못합니다.) 그래서 fwritefput에 비해 빠른 것입니다. (우리는 원래 프로그램에는 없는 어셈블리 코드의 "14"를 볼 수 있습니다. 위의 프로그램에서 포함하고 있는 문자열 "Hello world!\b"의 길이입니다. 문자열 마지막의 NUL을 출력에서 카운트하지 않았기 때문이죠.) (번역자 : Hello world! 는 총 13글자 입니다 띄어쓰기까지 포함해서죠. 즉 여기에 NUL까지 합하면 14가 됩니다.)


오브젝트 파일은 링커가 지시(결국은 OS 커널)한 데이터 저장의 목적에 따라 여러 섹션으로 출력을 배치합니다. 우리의 main 함수는, 혼란스럽겠지만, .text 라고 불리는 섹션으로 이동됩니다; 이 섹션은 우리가 만든 프로그램의 실제 코드를 가지고 있습니다. (리눅스에서는 그렇게 부르지만 다른 운영체제에서는 이름이 변화합니다, 그러나 당신이 얻을 수 있는 내용은 바이너리거나 텍스트 임에도 거의 항상 역사적인 이유로 "text"라고 부릅니다.) "Hello world!\n"문자열은 읽기 전용으로 데이터를 저장하는 분리된 섹션 .rodata에 있습니다.


.cfi_ 는 어셈블러에게 정보를 편히 생성하라고 말하는 라인입니다. 현재는 대부분 무관한 내용입니다. (이것은 현재코드의 위치의 대한 연관된 블록을 잡아내기 위한 C++의 예외처리로서 사용되었습니다. 그리고 디버거에 의해 스택역추적을 수행합니다.); 그들은 또한 오브젝트 파일에서 (.eh_frame) 섹션으로 컴파일합니다.


섹션 이외에, 우리 오브젝트 파일은 링커에 대한 명령을 포함하는 헤더를 포함하고 있습니다; main과 stdout과 같은 이름의 정보는 사라질 수 없습니다. 그러나 기계어 코드로 표현할 수 없기 때문에 이들은 다른방법으로 표현해야 했습니다. 리눅스에서는 우리는 이를 objdump -x 를 사용해서 헤더를 볼 수 있습니다. 실행해보면 한 화면을 가득채울 정도로 텍스트가 나오지만 이 중에 흥미로운 발췌를 아래에 싣겠습니다:


SYMBOL TABLE:

[...]

0000000000000000 g     F .text  0000000000000029 main

0000000000000000         *UND*  0000000000000000 stdout

0000000000000000         *UND*  0000000000000000 fwrite


RELOCATION RECORDS FOR [.text]:

OFFSET           TYPE              VALUE 

0000000000000007 R_X86_64_PC32     stdout-0x0000000000000004

0000000000000019 R_X86_64_32       .rodata

000000000000001e R_X86_64_PC32     fwrite-0x0000000000000004


이 "심볼 테이블"은 소스코드의 모든 식별자에 대해 링커에게 파일 간의 일관된 의미를 부여할 필요가 있다고 말합니다. 이 경우에는 main.text 섹션을 시작으로부터 0x29 byte입니다. 그리고 컴파일러는 stdoutfwrite의 위치를 모릅니다. 그 동안에 "재배치" 기계어 코드의 일부분은 패치가 필요하다고 링커에게 말합니다; fwrite 이전에, stdout 이전에 적절한 값을 대체하기 위해 그리고 파일의 .rodata 섹션의 부분에서 시작하기 위해 링커에게 요청합니다 (이곳에 "Hello world!\n"가 저장되어 있습니다.). 컴파일러는 이들의 위치를 계산할 수 없습니다; .rodata 섹션은 .text 섹션에 상대적이란걸 알 수 없습니다. 그 부분이 얼마나 큰 섹션인지 알 수 없기 때문입니다. 그리고 표준라이브러리의 stdout과 fwrite는 Hello World 프로그램에 자체에는 없습니다. 왼쪽 열의 16진수는 링커에게 .text 섹션에 새로운 값을 교체하도록 전달합니다 (그리고 중앙의 열은 교체를 위해서 링커가 사용해야하는 포멧을 설명합니다.).


컴파일러를 위해 전통적인 이름은 cc였고 어셈블러를 위해서는 as입니다. 그러나 이것이 수행된 이후에 전통적으로 컴파일러는 링커를 실행합니다; 비록 매우작은 프로그램에 적합하지만, 이는 보통 큰 프로젝트가 원하는 것은 아닐 것 입니다 (여러분은 변화된 파일 소스만 재컴파일하길 원하지만  변경된 파일을 의존하는 모든 실행파일을 재링크합니다.). 따라서 컴파일러에게 오직 컴파일만 할 것을 이야기해야합니다.(만약 필요하다면 어셈블러도) 일반적으로 -c 커맨드 라인 옵션을 사용합니다. 대부분의 툴체인과 함께 오브젝트 파일은 확장자가 .o입니다. 비록 몇몇 윈도우 툴체인은 .obj를 사용합니다.



출처1 : https://github.com/aleksandar-todorovic/awesome-c

출처2 : http://nethack4.org/blog/building-c.html