[시스템 프로그래밍] 어셈블리어
C같은 고급 언어를 사용해 코드를 작성하면, 작성한 코드를 어셈블리어로 변환하는 과정을 거쳐 기계어 코드를 생성한다.
어셈블리어로 변환하는 과정은 컴파일러가 담당하고, 컴파일러를 통해 고급 언어로 작성한 코드를 최적화한 어셈블리어를 얻을 수 있다.
이렇게만 들으면 어셈블리어는 어차피 컴파일러가 만들어주니 배울 필요가 없다고 생각할 수 있지만, 어셈블리어를 읽을 수 있으면 컴파일러의 최적화 성능을 알 수 있고 하드웨어를 직접 제어해야 하거나 속도에 민감한 프로그램을 작성할 때 어셈블리어를 사용해야 한다.
그러니 컴파일러가 생성한 어셈블리어를 읽고 이해할 수 있는 정도를 목표로 어셈블리어를 공부하자.
어셈블리어는 기게어와 일대일로 대응되는 언어이고, 컴퓨터 구조에 따라 사용하는 기계어가 달라 기게어에 대응되는 어셈블리어도 각각 달라진다. (ex. mano의 컴퓨터구조 책에서는 저자인 mano가 정의한 어셈블리어를 사용한다)
이 책 (CS APP) 에서는 x86-64 어셈블리어를 다룬다. (CISC이다. RISC와 다르다)
작성한 C코드로 어셈블리어를 생성하는 예시이다.
어셈블러는 .s 어셈블리 파일을 목적 파일인 .o파일로 번역하는 역할을 한다.
역으로 .o파일을 통해 .s를 얻을 수도 있는데, 이를 목적 코드의 역어셈블이라고 한다.
x86-64 명령어 체계에서 사용하는 레지스터들이다.
레지스터들의 이름이 모두 r로 시작함을 확인할 수 있는데, 이전 명령어 체계에서 사용하던 이름을 최대한 남기기 위해서 앞 글자만 바꿔서 사용했기 때문이다.
위의 그림에서 %rxx의 절반은 %exx가 차지하고 있다.
이전 체계에서는 4바이트씩 접근할 수 있었고 그 레지스터를 %exx로 이름지어서인데, x86-64 명령어 체계를 호환하기 위해 1 2 4 8 바이트 단위로 데이터에 접근하는 작업도 허용한다.
p로 끝나는 레지스터들은 포인터 레지스터로, 주소를 저장하는 역할을 한다.
위에서 %rsp와 %rbp 레지스터는 프로시저와 연관된 스택을 관리하는 레지스터이고, 나머지 레지스터는 일반적인 목적으로 사용된다.
x86-64 명령어는 Assembly Instruction / Operand / Comment 로 구성된다.
Operand의 유형으로는 세 가지가 있다.
1. Immediate : $로 시작하고, 상수를 의미한다. 1 2 4 바이트로 인코딩 될 수 있다.
2. Register : %로 시작한다. %rsp는 스택에 접근할 때 사용하는 특별한 레지스터이다.
3. Memory : 레지스터에 지정되는 주소에 저장된 8개의 연속된 메모리 바이트를 말한다. ex. (%rax) 미리 rax 레지스터에 메모리 주소를 저장해 둬야 한다.
mov
데이터를 이동시키는 명령어로, mov 뒤에 붙은 q는 quad word로 데이터를 다루는 단위를 나타낸다. (8바이트)
movq Source Dest : Source를 읽어서 Dest에 복사한다.
x86-64 명령어 체계에서는 Source와 Dest 에 모두 메모리가 오는 경우를 고려하지 않았다. (두 개의 명령어로 수행해야 한다)
어셈블리어와 C코드간 변환을 자유롭게 할 수 있도록 익숙해지자.
레지스터의 길이는 8바이트이지만, 여러 단위로 접근 할 수 있다.
타입과 타입에 해당하는 바이트를 보고 어떤 접미사를 사용할 지 결정하자.
movq (%rcs) %rax
Source 부분에 위치한 요소를 하나의 배열로 생각하고 메모리 배열의 주소를 인덱스로 사용해 읽는다.
movq 8 (%rbp) %rdx
괄호 앞에 숫자를 추가하면 숫자는 offset을 의미하게 된다.
C코드를 어셈블리로 다룰 때 효율적으로 다루기 위해 이런 포맷을 지원한다.
예시를 하나 살펴보자.
앞의 내용을 통해 C코드를 보고 CISC 어셈블리 코드로 변환하는건 어렵지 않다.
함수의 마지막에 ret (return) 이 위치하고, 메모리의 주소는 16진수로 표현함에 주의하자.
C에서 struct 혹은 배열로 복잡한 형태로 메모리에 접근하는 경우 위의 문법만으로 어셈블리어를 구현하기는 힘들다.
따라서 위와 같은 문법을 제공한다.
base 레지스터 부분이 생략 될 수도 있는데, 0을 더한다고 생각하면 된다.
S (Scale) 은 C코드에서 for 혹은 while 루프 작성 시 어셈블리에서 즉각적으로 데이터의 크기만큼 읽을 수 있기 위해 사용된다.
lea
Load Effective Address의 약자로, 주소를 계산하는 명령어이다.
메모리를 참조하지 않고 주소만 계산할 때 사용한다. (곱 연산에서도 사용된다)
leaq Src, Dst : Src는 메모리를 나타내는 주소 모드의 수식을 의미하고, Dst에는 수식으로 표현된 주소값이 저장된다.
%ras 레지스터에 2 * %rdi + %rdi 값을 저장한다.
sal = shift arithmetic left
3x 를 두 칸 shift 하면 3 * 4 즉 12를 곱한 결과를 얻는다.
mov 명령어는 주소에 있는 값을 가져오지만, lea 명령어는 주소 그 자체를 가져온다.
이 부분에 집중해서 연산 최적화에도 사용하는것..
나머지 연산자들도 한 번 읽어보자.
'Computer Science > System Programming' 카테고리의 다른 글
[시스템 프로그래밍] 프로시저 (0) | 2022.10.28 |
---|---|
[시스템 프로그래밍] 어셈블리어 - 반복문 (0) | 2022.10.24 |
[시스템 프로그래밍] 어셈블리어 - 조건문 (0) | 2022.10.23 |
[시스템 프로그래밍] 정수의 산술연산과 실수의 표현 (0) | 2022.09.29 |
[시스템 프로그래밍] 컴퓨터 시스템과 정보의 표현 (0) | 2022.09.15 |
댓글
이 글 공유하기
다른 글
-
[시스템 프로그래밍] 어셈블리어 - 반복문
[시스템 프로그래밍] 어셈블리어 - 반복문
2022.10.24 -
[시스템 프로그래밍] 어셈블리어 - 조건문
[시스템 프로그래밍] 어셈블리어 - 조건문
2022.10.23 -
[시스템 프로그래밍] 정수의 산술연산과 실수의 표현
[시스템 프로그래밍] 정수의 산술연산과 실수의 표현
2022.09.29 -
[시스템 프로그래밍] 컴퓨터 시스템과 정보의 표현
[시스템 프로그래밍] 컴퓨터 시스템과 정보의 표현
2022.09.15