어셈블리어란 기계어와 1:1 대응되는 저급언어이다.
고급어라 불리는 .c
파일을 컴파일러를 통해 어셈블리어인 .s
로 , 그리고 어셈블러를 통해 목적파일인 .o
, 마지막으로 링커를 통해서 실행파일로 만들 수 있다.
문법에는 두 종류, Intel
, AT&T
가 있으며 나는 인텔사의 문법으로 진행한다.
과제를 진행하기 위해 알아야할 것들은 다음과 같다.
데이터, 텍스트 섹션
명령어
레지스터의 종류
레이블
하나하나 보도록 하자.
프로그램은 섹션으로 분리되어있다.
바로 data, text 섹션이다.
Data Section은 우리가 할당하는 데이터들이, Text는 코드가 들어간다.
어셈블리에서 사용하는 데이터 단위는 알고 있는 것과 크게 다르지 않다. 다음과 같다.
위와 같은 데이터 단위들을 data section, 또 다른 섹션에서 사용하기도 하는데 먼저 데이터섹션에서의 사용법을 확인해보자.
; [레이블] : [공백] [크기] [공백] [문자, 숫자의 초기값]
; 데이터 단위의 앞의 d는 데이터 섹션의 단위임을 표시한 것.
section .data ; data 섹션임을 선언
msg : db '%s %s' , 10, 00 ; char msg[] = "%s %s\n";
초기화처럼 사용한다.
Text Section에서도 마찬가지로 데이터를 표현하는데, 레지스터와 메모리를 사용한다.
레지스터는 CPU가 사용하는 저장공간이고 사용자는 이 레지스터에 값을 저장하거나 사용할 수 있다.
레지스터도 종류가 총 7가지정도 되는데 일단 다룰 것은 다음과 같다.
범용 레지스터
포인터 레지스터
레지스터 안에도 종류가 또 나뉘게 되는데 차례차례보도록 하자.
A B C D..만 외우자
AX BX CX DX종류가 있다. 이 앞에 E나 R이 따라오는데 이는 레지스터 용량이 32비트인지, 64비트인지를 나타내는 것이다.
레지스터 용량에 대해서 조금 더 알아볼 필요가 있다.
RAX로 설명을 해보자. 방금 말했듯이 레지스터 용량은 64비트로 되어있다(8바이트) 그럼 그 안에 구성은 어떻게 되어있을까(BX, CX, DX)모두 동일하다.
RAX안에 EAX, 안에 AX 그 안에 AH, AL로 나뉜다.
이는 레지스터 비트범위를 표현하는 것이다 총 64비트(RAX)에서 32비트를 EAX, 16비트를 AX라하고 그 안에 상위, 하위 8비트를 나누어 AH, AL로 표현하는 것이다.
그럼 이어서 A B C D레지스터에 대해서 각각 알아보자
범용레지스터에 속하지만 주로 데이터가 저장되어있는 메모리 주소를 가리키는 포인터로 사용됨
section .text ; text섹션을 알림
global _function_name ; 가장 먼저 실행되는 레이블을 정해줌
call _ft_strlen ; 함수 호출하는 명령어, 호출 전 다음 실행될 명령어의 주소를 스택에 저장
_function: ; 가장 먼저 실행되는 부분
mov rax, 0 ; rax레지스터에 0을 할당한다.
jmp rabel ; rabel으로 이동한다
; 라벨을 선언안해도 절차적으로 밑으로 내려감
rabel: ; rabel (라벨)
cmp rax, 0 ; rax값이 0인지 비교한다
je done ; cmp문의 비교, 피비교값이 동일하면 done 라벨로 이동
ja rabel ; cmp문의 왼쪽이 크면 true
jb rable ; cmp문의 오른쪽이 크면 true
done: ; rax의 값을 return한다.
ret
함수호출을 위해 스택을 사용
ESP
를 사용해 주소값을 저장함. x64의 경우, 스택에 저장될 때 8바이트 단위로 저장 되므로 1, 2바이트의 경우 8바이트로 변환해 저장해야함.
스택의 가장 낮은 주소값이 꼭대기 주소가 됨.
call을해서 함수를 호출하면 스택에는 call함수 다음의 함수를 스택에 쌓고 호출된다. => 다시 이전시점으로 되돌아가기 위함
rax값에 따라 syscall
이 호출하는 함수가 달라짐
테이블엔 3,4인데 왜 ? 2000003, 4?
Mac에선 syscall번호를 여러 클래스로 나누어두었다. write, read는 unix클래스에 속해서 최 상단비트를 2로 설정해두었다.
0x2000004
=> sys write
0x2000003
=> sys read
write, read와 같은 system call은 rax
에 오류(-1 ~ -4095)값을 반환하는데 jmp분기를 사용할 수 있는 상태레지스터는 z, c, s, o, p, a, t ,d가 있다
___error
함수는 에러넘버의 포인터(int )를 반환하므로 ___error를 호출하고 반환받은 주소를 값으로 반환해 return하면 되겠다. `int ` 포인터는 single byte가 아니라 dword라 한다.
사용예제는 출처란의 넷와이드 어셈블러를 참고하면 된다.
Man 2 write로 매뉴얼을 확인한다
ssize_t write(int fildes, const void *buf, size_t nbyte)
buf에 있는 내용을 count만큼 fd로 전달한다.
Extern [함수명]은 외부에 함수가 정의되어있고, 사용하겠다는 의미임
strcmp(const char s1, const char s2)
S1, s2이 null문자가 나올 때 까지 돌며 중간에 다른 문자가 있을 시 s1, s2을 비교해 s1 - s2의 아스키코드를 반환
strdup(const char *s1)
s1을 복사하기위해 메모리할당을 하고, 그 포인터를 반환한다. 충분치않은 메모리라면 NULL을 리턴한다
출처