본문 바로가기
시스템프로그램

[시스템프로그램]03-1-1 Program encodings(1)

by 케찹이 2020. 5. 2.

3장부터는 프로그램이 어떻게 binary로 표현되는지 알아보고 C코드 기준으로 살펴보도록 할것이고 하드웨어는 x86-64 그리고 필요할때에는 IA32를 살펴볼 것이다. 운영체제의 환경은 Linux가 되고 gcc컴파일러를 사용할 것이다. 이 장에서 우리들은 multicore artichetectuer, hyperthreading, SSE, AVX등과 같은 개념들에대해 알게 될 것이다. 일반적으로 우리가 기계어코드를 알어보기 힘들기 때문에 사실상 주로 assembly code들을 살펴본다. 

우리가 assembly code를 보면 할 수 있는 일이 크게 두가지가 있다. 첫번째는 컴파일러가 optimization을 한다. 그래서 optimization을 컴파일러가 어떤식으로 하는지 살펴볼수 있다. 그전에 optimization이란 한글로 최적화라는 뜻인데 이는 만약에 코드 작성자가 코드를 뒤죽박죽 썼다면 순서를 정렬하고 코드가 효율적으로 돌아가게 바꾼다는 식이다. 그러므로 이 컴파일러가optimization를 적게 할수록 그 코드는 굉장히 그 자체만으로 좋은 코드인 것이다. 정리해서 말하자면 코드의 작성자가 코드를 이상하게 작성하면 컴파일러가 프로그래머를 욕한다는 것이다. 그래서 쪽팔리지 않으려면 애초에 코드를 잘 작성하도록 하자. 두번째로는 코드의 취약성이 있는지 알수 있다. 그리고 시스템 자체에서 몇가지 코드를 공격할 수 있는 구멍들을 보호하는 역활도 하는데 어쨌든 어셈블리어로 바꾸게 될 경우 코드의 취약성을 나름 쉽게 관찰 할 수 있다. 코드의 가장 대표적인 취약성은 Buffer Overflow같은 것이 있는데 이에 대해서도 3장  막바지에서 설명이 있다. 

위 표는 Intel사의 CPU의 발전?을 보여주는 표인데 거의 5에서 10년마다 CPU가 새로 나오는 것을 볼 수 있는데 지금은 i7에서 멈춰있는 것을 볼 수 있다. 물론 지금 i9가 나왔다. 옆에 Transistors라고 써져 있는데 저게 뭐냐면 CPU에 들어가는 일종의 반도체인데 정말 시간이 지날수록 기하급수적으로 CPU에 들어가는 트렌지스터의 양이 늘어나는 것을 확인 할 수 있다. 

정말 시대에 걸쳐서 많은 CPU들이 나왔는데 우리가 주로 이 장에서 살펴보는 CPU는 2004년에 나온 Pentium 4E이다. 왜냐하면 이때부터 x86-64를 공식적으로 지원했기 때문이다. 그리고 가끔씩 IA32를 사용하기 위해 1985에 출시된 i386도 살펴보도록 한다. 

 

IA32는 32비트 머신이며 총 4GB의 메모리까지 서포트한다. x86-64는 64비트 머신이고 대부분 256TB만 사용한다.(256TB는 2^45Byte이다). 물론 16EB(2^64B)의 메모리도 사용이 가능하다. 

 

linux> gcc -Og -o p p1.c p2.c

위 코드는 이제 1장에서 살펴본 프로그램을 실행하는 명령어인데 -o p는 그때도 설명했던 것처럼 C파일을 p라는 결과물로 전달하라는 의미 였고 오늘 살펴볼 놈은 -Og인데 이녀식이 바로 optimization에 관한 명령어다. -Og는 제일 간단하게 optimization을 하라는 명령어인데 간단하게라는 뜻은 원래의 코드 즉 original source code와 optimization한 것과의 구조가 비슷한 정도로 optimization하라는 것이다. 대부분 각 줄 하나 당 원래의 한 줄 명령어를 뜻하는 식으로 지금 정도에서 이해하면 될 것이다. -Og말고도 -O1, -O2, -O3까지 있으며 각각 레벨1, 레벨2, 레벨3 optimization을 하라는 의미이고 숫자가 높아질수록 optimization하는 정도가 높아진다. Optimization level 3정도가 되면 도대체 내 코드가 왜이렇게 작동하는지 어려울 정도로 보기 힘들다고 한다. 

Optimization Level이 올라가면 최종 실행 파일이 더 빠르게 실행이 된다. 하지만 앞서 말한것 처럼 소스코드와 기계어 코드의 relation을 이해하기 어렵다. 그리고 컴파일 하는데 시간이 오래 걸린고 디버깅하기에도 쉽지 않다.

 

 

우리가 시스템을 배울때 컴퓨터 자체는 굉장히 많은 부분이 있는데 예를 들자면 HW와 SW의 경계에서 어디까지 배워야 시스템프로그램을 배웠다 해야할까의 경계선이 바로 ISA(Instruction Set Architecture)이다. 그럼 ISA란 무엇이냐. 

ISA 밑에 굉장히 많은 하드웨어 부분이 있는데 그것 하나하나를 모두 개발자에게 알려줄 필요는 없고 어느정도 선에서만 보여주겠다. 밑에 기계가 기계어 종류가 뭐가 있는지 기계가 제공하는 CPU register는 뭐가 있는지 memory addressing은 어떻게 하는지 데이터 표현 에를 들자면 int와 float는 어떻게 표현하는지 이런것들을 위에다 보여주는게 ISA. ISA까지만 알면 기본적으로 그 정보만 가지고 밑에 있는 디테일은 볼 필요없이 우리의 소스코드가 기계어코드로 어떻게 바뀌는 지 볼 수 있다.  

그래서 먼저 ISA를 보고 기계어코드를 공부하도록 하겠다. 그럼 바로 CPU State를 보자. 

 

PC(Program Counter)는 다음 실행될 기계어명령의 주소를 저장하는 레지스타이다. 이게 x86-64에서 %rip라는 레지스터가 역활을 수행한다. 

Integer register, FP register도 있다. 

Condition Code register는 ALU가 어떤 연산을 했을 때 더하기 던 빼기 던 그 연산의 결과가 양수인지 음수인지 overflow인지 carry를 했는지 저장하는 역활을 해서 conditional jump를 하게 한다. 그래서 그에대한 conditional code들을 배우게 될것이다. 

 

기계어코드에서는 data type이란 것이 없다. 모든 변수들은 그저 상수에 불과하다. 기계어코드에선 결국 0과1로 이루어져있는데 메모리에서 그 수들을 뽑아 왔을 때 이것이 정수다, 실수다 이런것을 명확하게 지정해서 알 수는 없다. 그리고 아주 기본적인 operation들만 수행한다. +,-,*,/, data move, conditional branch, function call같은 기능만 수행 가능하다. 메모리는 virtual adressing한다. 기본적으로 기계어에선 virtual addressing을 하지만 CPU 하드웨어인 MMU가 virtual address를 physical address로 변환해서 메모리로 접근한다. 

 

 

댓글