본문으로 바로가기

1. C프로그래밍


- visual studio (컴파일러 아님), 이클립스

- IDE : 통합 개발 활경

- IDE는 compiler + 편집기 + 디버거를 뜻한다.


  리눅스에서의 프로그래밍


- IDE를 따로 사용하지 않고 compiler, editor, debugger를 사용한다.

- compiler : GCC

- editor : VI

- debugger : GDB


hello.c 를 작성해보자.

 

[root@M4RC0 /root]# vi hello.c

 

 

아래와 같이 gcc로 컴파일을 할 수 있다.

단, gcc 컴파일 시 옵션을 주지 않아 새로운 이름을 설정해주지 않으면 기본 파일명은 a.out으로 나온다.

 

 

2. 컴파일 과정

 

gcc -v 소스파일.c : 소스파일 컴파일 과정을 보여준다.

 

[root@M4RC0 /root]# gcc -v hello.c


Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/specs
gcc version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)
 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/cpp -lang-c -v -undef -D__GNUC__=2 -D__GNUC_MINOR__=91 -D__ELF__ -Dunix -Di386 -D__i386__ -Dlinux -D__ELF__ -D__unix__ -D__i386__ -D__i386__ -D__linux__ -D__unix -D__i386 -D__linux -Asystem(posix) -Asystem(unix) -Acpu(i386) -Amachine(i386) -Di386 -D__i386 -D__i386__ -D__tune_i386__ hello.c /tmp/cc2jKsys.i
GNU CPP version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release) (i386 Linux/ELF)
#include "..." search starts here:
#include <...> search starts here:
 /usr/i386-redhat-linux/include
 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include
 /usr/include
End of search list.
 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/cc1 /tmp/cc2jKsys.i -quiet -dumpbase hello.c -version -o /tmp/ccAdZBoJ.s
GNU C version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release) (i386-redhat-linux) compiled by GNU C version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release).
 as -V -Qy -o /tmp/ccI9ZPW0.o /tmp/ccAdZBoJ.s/t
GNU assembler version 2.9.5 (i386-redhat-linux) using BFD version 2.9.5.0.22
 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/collect2 -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/crtbegin.o -L/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66 -L/usr/i386-redhat-linux/lib /tmp/ccI9ZPW0.o -lgcc -lc -lgcc /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/crtend.o /usr/lib/crtn.o

 

실제로 보여지는 소스파일.c의 컴파일 과정이다.

 

전체 컴파일 과정에 대해 알아보자. (gcc --save-temps 소스코드.c -> Do not delete intermediate files)

 

1) 전처리 단계 - cpp( C PrerProcess) 

- hello.c -> hello.i

- 메크로

- 헤더파일 처리

 

2) 컴파일 단계 -cc1

- hello.i -> hello.s

- 어셈블리 언어로 변환한다.

   어셈블리 언어로 변환하는 이유는 바로 기계어로 변환할 수 없기 때문에 기계어로 변환 가능한 어셈블리어로 변환한다.

- 왜 어셈블리어를 사용하는가?

   처음 0과 1을 문자에 일대일 대응한 문자이기 때문이다.

ex) 001010 -> push

      011101 -> pop

- 바이너리(실행파일)은 0, 1로 이루어진 파일이다. 사람이 바이너리 파일을 직접 만드는것은 어렵기 때문에 소스를 작성해 어셈블리어로 변환 후 컴파일러에 의해 자동변환을 하는 것이다.

 

* visual studio에서 컴파일 에러가 발생하면 이 단계(컴파일)에서 에러가 발생한 것이다.

 

3) 어셈블러 - as

- hello.s -> hello.o

- 어셈블리어 -> 기계어

- 목적 파일을 생성한다(바이너리)

- object  파일로만은 라이브러리 파일에 있는 함수를 사용할 수 없다.

 

4) 링크(일반적으로 dynamic-linker 사용) - collect2, ld, gcc

- hello.o -> hello

- 목적 파일을 가지고 여러가지 라이브러리를 합쳐 하나의 바이너리를 만든다.

 

hello1.c를 작성해보자.

 

 

hello1.c를 가지고 컴파일 과정을 좀 더 자세히 확인해보자.

 

1) 전처리 단계

 

[root@M4RC0 /root]# cat hello1.i
매크로 함수가 처리된것을 알 수 있다.

 

 

2) 컴파일 단계

 

[root@M4RC0 /root]# cat hello1.s

C언어가 어셈블리어로 변환된 것을 볼 수 있다.

 

 

 

리눅스에서 바이너리 파일을 ELF 라고 부른다. ELF 임을 확인하는 방법은 PE의 헤더를 보면 알 수 있다.

 

 

 

 

3. 어셈블리어로 작성하기

 

어셈블리어로 작성하면 1), 2) 단계가 사라지고 3), 4) 단계만 이용한다.

 

c언어 -> 어셈 -> 기계어

          x          <-

 

위의 hello.c를 어셈블리어로 작성해보자.

 

(1). C 라이브러리를 이용한 어셈블리 프로그램 작성

 

 

3)번 과정 - nasm -f elf -o hello.o hello.asm

4)번 과정 - gcc -o hello hello.o

 

 

Segmentation fault 는 추후에 설명할 예정이니 잠시만 그냥 넘어가자.

 

hello.asm을 분석해보면 다음과 같다.

 

1) data segment

- 읽기/쓰기 가능한 메모리 영역

- 프로그램 실행에 필요한 데이터

 

2) text segment

- 실행 가능한 메모리 영역 (쓰기불가)

- 주로 실행할 명령어들이 들어있다.

 

(2). C 라이브러리를 사용하지 않고 프로그램 작성하기( original )

 

 

3)번 과정 - nasm -f elf -o hello2.o hello2.asm

4)번 과정 - ld -o hello2 hello2.o

 

 

한 프로세스가 사용하는 전체 크기는 4GB이다.

리눅스 경우 1G + 3G로 나눈다.

1G는 커널 + 3G는 프로세스이다.

3G 프로세스를 크게 text, data, stack 영역으로 나눈다.

 

윈도우는 2G + 2G로 나눈다.

2G는 커널 + 2G는 프로세스

 

segment or pase or section 이라고 부른다.

 

프로세스를 분산해서 메모리에 올린다.(binding)

 

그렇다면 운영체제는 무엇을 보고 segment를 분류하는가?

- 실행파일의 시스템 구조를 보고 분류한다.

         주로 PE를 보고 확인할 수 있다. elf: 리눅스, pe: 윈도우