어셈블리

수박·2020년 11월 10일
2

42seoul

목록 보기
3/3
  • 어셈블리어

어셈블리어란 기계어와 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레지스터에 대해서 각각 알아보자

AX (Accumulator)

  • 산술, 논리연산 syscall을 수행할때 사용되고 함수 반환값이 이 레지스터에 저장된다.

BX (Extended Base)

  • 메모리주소를 저장하기 위한 용도

CX(Extended Counter)

  • 반복명령어 사용시 반복 카운터로 사용됨.
  • 반복할 횟수를 지정하고 작업을 수행할 때 사용한다.

DX(Extended Data)

  • 큰수의 곱셈 또는 나눗셈 등의 연산이 이루어질 때 사용

포인터, 인덱스레지스터

범용레지스터에 속하지만 주로 데이터가 저장되어있는 메모리 주소를 가리키는 포인터로 사용됨

  • SP : 스택포인터. 스택 내 가장 최근 데이터 주소를 가리킴
  • BP : 베이스포인터 SP대신 스택 내 데이터 액세스시 사용
  • SI : Source Index : 문자열처리시 시작 주소 지정에 사용
  • DI : Destination Index : 문자열처리시 목적지 주소 지정에 사용

예제

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을해서 함수를 호출하면 스택에는 call함수 다음의 함수를 스택에 쌓고 호출된다. => 다시 이전시점으로 되돌아가기 위함

Syscall

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가 있다

  • JC는 Jump if Carry, Carry Flag가 1일때 Jmp를 수행하며 이와 반대되는 건 JNC (Jmp not carry)가 있다.
    • Carry Flag? : 최상단비트에서 자리올림 발생시 SET되는 플래그
    • 1111 + 0001 = (1)0000 과 같이 두 수의 합으로 발생한 carry가 부호비트보다 앞에 더해질 때
    • 0000 - 0001 = 1111 과 같이 두 수의 차로 발생한 carry로 인해 부호비트 앞 비트로부터의 borrow가 발생할때.
  • 에러발생시 상태레지스터의 carry flag는 set되므로 jc에 부합해 error처리 레이블로 분기될 수 있다.

errno

___error 함수는 에러넘버의 포인터(int )를 반환하므로 ___error를 호출하고 반환받은 주소를 값으로 반환해 return하면 되겠다. `int ` 포인터는 single byte가 아니라 dword라 한다.

참고 nasm에서의 Errno셋팅

어셈블리 포인터 값을 변환하기

사용예제는 출처란의 넷와이드 어셈블러를 참고하면 된다.

read

write

Man 2 write로 매뉴얼을 확인한다

ssize_t write(int fildes, const void *buf, size_t nbyte)

buf에 있는 내용을 count만큼 fd로 전달한다.

Extern [함수명]은 외부에 함수가 정의되어있고, 사용하겠다는 의미임

strcmp

strcmp(const char s1, const char s2)

S1, s2이 null문자가 나올 때 까지 돌며 중간에 다른 문자가 있을 시 s1, s2을 비교해 s1 - s2의 아스키코드를 반환

strdup

strdup(const char *s1)

s1을 복사하기위해 메모리할당을 하고, 그 포인터를 반환한다. 충분치않은 메모리라면 NULL을 리턴한다

출처

0개의 댓글