728x90
CSAPP
- 어셈블리 코드를 짤때 저급 인스트럭션을 명시해야 하는데, 대개의 경우 고급 언어가 제공하는 높은 수준의 추상화를 사용하는 것이 보다 더 생산적이고 안정적이다.
그렇다면 왜 WHY 기계어를 사용하고 공부할까?
- 컴파일러를 적절한 커맨드라인 인자와 함께 호출하면 컴파일러는 어셈블리 코드 형태의 파일로 출력을 생성한다.
- 이 코드를 이해하면 컴파일러의 최적화 성능을 알 수 있고, 코드에 내재된 비효율성을 분석할 수 있다.
- 프로그램이 얼마나 효율적으로 실행될지 이해하기 위해서 생성된 어셈블리 코드를 컴파일하고 분석해 보곤한다.
- 더욱이 고급언어에서 제공하는 추상화 계층 때문에 이해 가능한 프로그램의 런타임 동작이 감춰지는 경우도 종종 있다.
- 또 다른 예로 악성 프로그램이 시스템을 감염 시킬 수 있도록 프로그램을 공격하는 방법 중 상당수가 프로그램이 런타임 제어정보를 저장하는 방식의 미묘한 차이와 관련이 있다. 이런 취약성이 어디서 발생하는지, 이런 공격을 어떻게 막을 수 있는지 이해하려면 프로그램의 기계수준 표현에 대한 지식이 필요하다.
상세한 내용을 완벽히 이해하면 보다 심오하고 보다 근본적인 개념들을 이해할 수 있게 된다. ‘ 일반적인 원리는 이해해도 자세한 내용은 배우고 싶지 않다.’ 고 말하는 사람들은 자신을 속이는 것이다.
- 좀 많이 찔릴 수 있는 문장. 나태해질 때마다 자주 보자.
프로그램 인코딩
- 일반적으로 최적화 수준을 올리게 되면 최종 프로그램은 더 빨리 동작하게 되지만, 컴파일 시간이 증가하고 디버깅 도구를 실행하기가 어려워질 위험이 있다.
- 높은 수준 최적화를 적용하면 만들어진 코드가 너무 많이 변경되어서 본래의 코드와 생성된 기계어 코드 간 관계를 이해하기 어렵다.
GCC 컴파일러
- GCC명령은 소스코드를 실행코드로 변환하기 위해 일련의 프로그램 호출
- C전처리기 #include 명시된 파일을 코드에 삽입, #define 선언된 매크로 확장
- 컴파일러가 어셈블리로 변환
- 어셈블러가 어셈블리 코드를 바이너리 목적 코드로 변환
- 바이너리 목적코드 - 기계어 코드의 한 유형
- 링커가 목적코드 파일을 라이브러리 함수를 구현한 코드와 합쳐 실행 파일 생성
기계수준코드
- 컴퓨터 시스템은 보다 간단한 추상화 모델을 이용해서 세부 구현 내용을 감추면서 추상화의 여러가지 다른 형태를 사용하고 있다. 이들 중 두가지가 기계 수준 프로그래밍에서 특히 중요하다.
- 기계수준 프로그램의 형식과 동작은 인스트럭션 집합구조(instruction set architecture) 즉 ‘ISA’에 의해 정의된다. 이 ISA는 프로세서의 상태, 인스트럭션의 형식, 프로세서 상태에 대한 각 인스트럭션들의 영향을 정의한다. X86-64를 포함해 대부분의 ISA는 인스트럭션이 순차적인 실행을 하는것처럼 프로그램 동작을 설명한다. 프로세서 하드웨어는 훨씬 정교해서 여러 인스트럭션을 동시에 실행하지만 ISA에 의한 순차적 동작과 일치하는 전체동작을 보이도록 해주는 안전장치를 사용한다.
- 기계수준 프로그램이 사용하는 주소는 가상주소이며, 메모리가 매우 큰 바이트 배열인 것처럼 보이게 하는 메모리 모델을 제공한다.
X86-64 기계어코드
- 프로그램 카운터(X86-64 %rlp) - 실행할 다음 인스트럭션 메모리 주소를 가리킨다
- 정수 레지스터 파일 - 64비트 값을 저장하기 위한 16개의 이름을 붙인 위치를 가진다. 레지스터주소나 정수 데이터를 저장할 수 있다. 일부 레지스터는 프로그램의 중요한 상태를 추적하는데 사용할 수 있으며, 다른 레지스터들은 함수의 리턴 값 뿐만 아니라 프로시저의 지역변수와 인자 같은 임시값을 저장하는데 사용한다.
- 조건코드 레지스터 - 가장 최근에 실행한 산술 또는 논리 인스트럭션에 관해 상태정보 저장. if나 while문 구현할 때 필요한 제어나 조건에 따른 데이터 흐름의 변경을 구현하기 위해 사용한다.
- 벡터 레지스터 - 하나 이상의 정수나 부동소수점 값 저장
- C가 다른 종류의 데이터 타입을 선언하고 메모리에 할당할 수 있는 모델을 제공하는 반면 기계어 코드는 메모리를 단순히 바이트 주소 지정이 가능한 큰 배열로 본다.
- C에서 배열이나 구조체처럼 연결된 데이터 타입은 기계어 코드에서 연속적인 바이트로 표시. 심지어 포인터와 정수형 사이에도 구분을 하지않는다.
- 프로그램 메모리 - 프로그램의 실행 기계어 코드, 운영체제를 위한 일부 정보, 프로시저 호출과 리턴을 관리하는 런타임스택, 사용자에 의해 할당된 메모리 블록(heap)
- 운영체제는 가상주소 공간을 관리해서 가상주소를 실제 프로세서 메모리상의 물리적 주소값으로 번역해준다.
- 하나의 기계어는 매우 기초적인 동작만을 수행한다.
- 레지스터들에 저장된 수를 더하고, 데이터 교환, 조건 분기
- pushq %rbx - 레지스터 rbx가 프로그램 스택에 저장 push 되어야 한다.
- 지역변수나 데이터 타입에 관한 모든 정보는 삭제됐다.
- .O파일 16진수 데이터
- 컴퓨터에 의해 실제 실행된 프로그램은 단순히 일련의 인스트럭션을 인코딩한 일련의 바이트. 컴퓨터는 인스트럭션들이 생성된 코드에 대한 정보를 거의 가지고 있지않다.
- 기계어 코드 파일을 조사하려면, 역 어셈블러라고 하는 프로그램이 중요하다.
기계어코드 몇 특징과 이들의 역어셈블된 표현
- X86-64 인스트럭션은 1~15byte 길이를 가진다. 인스트럭션 인코딩은 자주 사용되는 인스트럭션들과 오퍼랜드가 적은것들이 짧은 길이를 갖도록 하고, 그 반대는 좀 더 긴 인스트럭션 길이를 갖도록 한다.
- 오퍼랜드 - 연산을 수행하는데 필요한 데이터, 데이터 주소
- 인스트럭션 형식은 주어진 시작 위치에서부터 바이트들을 기계어 인스트럭션으로 유일하게 디코딩할 수 있도록 설계한다. pushq %rbx 인스트럭션만이 바이트값 53으로 시작할 수 있다.
- 역어셈블러는 기계어 코드 파일이 바이트 순서에만 전적으로 의존해서 어셈블리코드를 결정한다.
- 역어셈블러는 GCC가 생성한 어셈블리 코드와는 다른 명명법을 인스트럭션에 사용한다.
728x90
'책 > CSAPP' 카테고리의 다른 글
CSAPP 3.6.5 - 3.7 (1) | 2024.01.22 |
---|---|
CSAPP 3.5 - 3.6.4 (0) | 2024.01.20 |
CSAPP 3.4 (0) | 2024.01.20 |
CSAPP 1.7 - 1완 (0) | 2024.01.20 |
CSAPP 1.1 - 1.6 (0) | 2024.01.20 |