어셈블리 예제 풀이 (helloworld, strlen, strcpy,strcmp,write,read,strdup), c언어 errno 테이블

이호용·2021년 3월 28일
1

42_Libasm

목록 보기
4/5
post-thumbnail

어셈블리 helloWorld 찍어보기.

  • 파일명 : hello.s
  • 환경 : (11.2 big sur) or (10.15.7 catalina)
section .text
    global _main

_main : 
    mov rax, 0x2000004
    mov rdi, 1
    mov rsi, msg
    mov rdx, 12
    syscall
    mov rax, 0x2000001
    mov rdi, 0
    syscall

section .data
    msg db "Hello World"
nasm -f macho64 hello.s
ld -lSystem hello.o -o hello
./hello

nasm : intel 어셈블리어 컴파일은 컴퓨터 아키텍쳐에 따라 나뉘는데, intel 어셈블러 문법 컴파일러중 하나가 nasm이다.

우선 hello.s파일을 컴파일 하고, 컴파일 된 파일을 실행 파일로 바꾼다.

여기서 0x2000004은 write함수인데, lsystem 라이브러리 헤더에 해당 함수 번호가 저장되어 있다.

/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/sys/syscall.h
  • syscall 파일 경로

만약 mac os를 big sur이상 최신버전으로 업데이트 했다면?..
: 맥에서 최근 big sur로 업그레이드 되면서 라이브러리 연동이, 자동으로 이루어지지 않는다.

이를 해결하기 위해, 컴파이릉 할 떄 라이브러리 경로를 지정해주어 컴파일이 되도록 연동시켜주었다.

nasm -f macho64 hello.s
ld -lSystem hello.o -o hello -macosx_version_min 11.0 -L ~/../../Library/Developer/CommandLineTools/SDKs/MacOSX11.1.sdk/usr/lib -lsystem
./hello

커맨드라인툴에 있는 system 라이브러리 경로를 설정한건데, 폴더가 없다면, Xcode 커맨드라인툴을 설치하면 생긴다.

커맨드라인툴 경로는, 기본적으로 사용자 컴퓨터 cd ~ 인 루트 폴더 library/Developer폴더에 생성이 되는데, 나는 찾아 보니 없어서, 루트 폴더의 상위 폴더로 이동하여, library에 접근하여, system library에 접근 하였다.

예제) 1. strlen

함수설명

  • 입력되는 매개변수에서 null이 올떄까지 카운트하고 반환합니다.

풀이 코드

c언어 코드

size_t	ft_strlen(const char *s)
{
	int a;

	a = 0;
	while (s[a])
		a++;
	return (a);
}

어셈블리어 코드

section .text
    global _ft_strlen

_ft_strlen :
	mov rcx, 0
	jmp count

count :
	cmp BYTE [rdi + rcx], 0
	je end
	inc rcx
	jmp count

end :
	mov rax, rcx
	ret

설명

  • global _ft_strlen : ft_strlen을 다른 모듈에서도 사용하기 위해 선언해줌
  • ft_strlen 으로가서 rcx에 초기값 0을 넣어주고 count로 이동한다.
  • 만약 ft_strlen 매게변수로 주어지는 문자열중 rcx위치 인덱스가 NULL이면, cmp에서 ZF = 1이 저장되고, je를 타고 end로 이동해, 반환값 rax에 rcx값을 넣고 넣고 끝나게 된다.
  • [rdi] : rdi에는 주소가 저장되어있는 상황인데, 저장된 대괄호로 둘러싸면, 저장된 주소의 값을 가지고 온다는 뜻입니다.
  • cmp flag 사용법
  • 만약 널이 아니면, rcx가 증가하며 계속 카운트한다.

아래부터는 42서울 과제 정답이 있습니다. 보시기 전에, 직접 풀어보시고, 막혔을 때 참고하시길 권장합니다. 바로 보면, 남는게 없어요 :)

예제) 2. strcpy

char *
     stpcpy(char * dst, const char * src);

함수설명

  • The stpcpy() and strcpy() functions copy the string src to dst (including
      the terminating `\0' character.)
  • strcpy는 dst메게변수에 src를 복사합니다. (널 문자를 포함해서 복사해야함)
  • 반환값은, dst를 반환합니다.

풀이코드

어셈블리 코드

section .text
    global _ft_strcpy

_ft_strcpy :
	mov rcx, 0
	jmp copy

copy :
	cmp BYTE [rsi + rcx], 0
	je end
	mov al, BYTE [rsi + rcx]
	mov BYTE [rdi + rcx], al
	inc rcx
	jmp copy

end :
	mov rax, rdi
	ret

예제) 3. strcmp

int
     strcmp(const char *s1, const char *s2);

함수 설명

  • 매게변수로 들어온 두개의 문자열을 비교하여 문자열이 같다면 0, 다르면 음수 혹은 양수를 반환하는 함수이다.
  • 여기서 -1, 1 은 매개변수로 들어온 문자열들을 비교하다가 다른 문자가 나왔을떄 아스키 코드로 비교
  • str1 < str2 인 경우 음수반환
  • str1 > str2 인 경우 양수반환
  • str1 == str2 인 경우 0반환

설명

  • 입력되는 첫번째 메개변수에 들어있는 값을 뒤에 문자열에 넣어준다.
  • rsi 두번쨰 매개변수가 null이 나올떄 까지 반복문을 돌리고,
  • null이 아니면, rdi에 값들을 하나씩 넣어준다. 리턴값은 복사한 rdi를 반환해줌.
  • rax대신 al을 사용한 이유는 BYTE [rsi + rcx]는 1바이트 인데, rax로 받게 되면 사이즈가 달라 에러가 발생한다.

풀이코드

section .text
    global _ft_strcmp

_ft_strcmp :
	mov rcx, 0
	jmp compare

compare :
	cmp BYTE [rdi + rcx], 0
	je length_check
	cmp BYTE [rsi + rcx], 0
	je end2
	mov al, BYTE [rsi + rcx]
	cmp BYTE [rdi + rcx], al
	ja end2
	jb end3
	inc rcx
	jmp compare

length_check :
	cmp BYTE [rsi + rcx], 0
	je end
	mov rax, -1
	ret

end :
	mov rax, 0
	ret

end2 :
	mov rax, 1
	ret

end3 :
	mov rax, -1
	ret

설명

  • 첫번쨰 문자열이나, 두번쨰 문자열이 널일떄 까지 반복문을 돌리고, 만약 어느 한쪽이 더 짧으면, 조건에 맞게 반환값을 rax에 넣어준다.
  • 그리고 현재 인덱스에서 두 문자 모두 널이 아니면, 문자간 인덱스를 비교하여, 리턴시켜주었다.

예제) 4. write

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

함수설명

  • write ()는 buf주소의 값들을 nbyte만큼 쓰기를 시도합니다. 쓰는 위치는 filedes에서 참조하는 객체에 의해 가리키는 버퍼입니다.
  • 리턴값은 성공하면 write한 글자수를 리턴하고, 실패하면 -1 을 리턴합니다.
  • 만약 write도중 에러가 발생하면, 전역변수 errno에 저장됩니다.
  • 어셈블리에서 call 함수를 호출할떄!
  • r12, r13, r14, r15, rbx, rsp, rbp 을 제외한 나머지 레지스터들은 휘발성이라, 데이터가 사라진다.
  • 유의해서 사용할것!!!!
section .text
    global _ft_write

extern ___error

_ft_write :
	mov rax, 0x2000004
	syscall
	jc end
	ret

end :
	push rax
	call ___error
	pop qword [rax]
	mov rax, -1
	ret

설명

  • 0x2000004 : write함수 시스템 콜하기 위해 사용
  • 만약에 write함수에서 에러가 발생하면 carry flag 가 이 되고 jc에서 end함수로 점프함.
  • __error를 호출 할 때 rax의 에러 넘버를 보고 해당 errno의 주소를 리턴해준다.
  • __error에서 rax넘버를 보고 찾은 주소를 rax에 담아준다.(rax 반환하는 값은 주소값이므로 dword이다.)
  • mov [rax], rdi : 에러 주소값에, 에러넘버 값을 저장해준다.
  • rax에 담긴 메모리주소에 에러 넘버가 저장되어 메모리에 저장 기록이 남고, rax는 리턴값으로 다시 사용되므로 에러를 표시하는 -1을 저장해준다.

예제) 5. read

함수 설명

  • n바이트만큼 fildes에서 읽어옵니다.
  • 읽어온 데이터는 buf에 저장됩니다.
  • 성고하면, 읽어온 글자수를 리턴값으로 반환하고 실패하면 -1을 리턴합니다.
  • write와 마찬가지로, errno변수에, 에러넘버를 반환합니다.

풀이코드

section .text
    global _ft_read

extern ___error

_ft_read :
	mov rax, 0x2000003
	syscall
	jc end
	ret

end :
	mov rcx, rax
	call ___error
	mov [rax], rcx
	mov rax, -1
	ret

함수설명

  • dst,src 문자열중 null값이 나올떄까지 rcx값을 증가시키고, 인덱스를 증가시키는 도중, dst,src문자중 하나가 더 크거나 작은 경우에 맞춰 반환값을 주었다.
  • ja는 왼쪽이 더 클떄 작동하고 ('>') jb는 오른쪽이 더 클때 작동한다. ('\<')

예제) 6. strdup

char *
     strdup(const char *s1);

함수설명

  • 입력으로 넣어주는, 문자열과 똑같은 문자열을 할당해서, 첫 주소값을 반환해준다.
  • 리턴값은 매개변수 첫번쨰

풀이코드

extern _malloc
extern _ft_strlen
extern _ft_strcpy
extern ___error

section .text
    global _ft_strdup

_ft_strdup :
	call _ft_strlen
	jmp call_malloc

call_malloc :
	inc rax
	push rdi
	mov rdi, rax
	call _malloc
	cmp rax, 0
	je error_malloc
	pop rsi
	mov rdi, rax
	call _ft_strcpy
	ret

error_malloc :
	mov rax, 12
	push rax
	call ___error
	pop qword [rax]
	mov rax, 0
	ret

설명

  • 앞에서 사용했던 함수들을 그대로 가지고와서 사용했다.
  • 문제는 할당할때인데, malloc함수는, rdi에 할당한 사이즈를 넣어주고, malloc을 실행하면, rax에 할당된 주소를 반환한다.
  • 만약 할당이 실패했거나 안했으면, rax에 0이 들어있고, malloc으로 할당이 실패했다는걸 저장하기위해, error_malloc함수로, errono에 저장해주었다.
#define EPERM           1
#define ENOENT          2
#define ESRCH           3
#define EINTR           4
#define EIO             5
#define ENXIO           6
#define E2BIG           7
#define ENOEXEC         8
#define EBADF           9
#define ECHILD          10
#define EAGAIN          11
#define ENOMEM          12
#define EACCES          13
#define EFAULT          14
#define EBUSY           16
#define EEXIST          17
#define EXDEV           18
#define ENODEV          19
#define ENOTDIR         20
#define EISDIR          21
#define EINVAL          22
#define ENFILE          23
#define EMFILE          24
#define ENOTTY          25
#define EFBIG           27
#define ENOSPC          28
#define ESPIPE          29
#define EROFS           30
#define EMLINK          31
#define EPIPE           32
#define EDOM            33
#define ERANGE          34
#define EDEADLK         36
#define ENAMETOOLONG    38
#define ENOLCK          39
#define ENOSYS          40
#define ENOTEMPTY       41
#define EILSEQ          42
#define STRUNCATE       80
#define EADDRINUSE      100
#define EADDRNOTAVAIL   101
#define EAFNOSUPPORT    102
#define EALREADY        103
#define EBADMSG         104
#define ECANCELED       105
#define ECONNABORTED    106
#define ECONNREFUSED    107
#define ECONNRESET      108
#define EDESTADDRREQ    109
#define EHOSTUNREACH    110
#define EIDRM           111
#define EINPROGRESS     112
#define EISCONN         113
#define ELOOP           114
#define EMSGSIZE        115
#define ENETDOWN        116
#define ENETRESET       117
#define ENETUNREACH     118
#define ENOBUFS         119
#define ENODATA         120
#define ENOLINK         121
#define ENOMSG          122
#define ENOPROTOOPT     123
#define ENOSR           124
#define ENOSTR          125
#define ENOTCONN        126
#define ENOTRECOVERABLE 127
#define ENOTSOCK        128
#define ENOTSUP         129
#define EOPNOTSUPP      130
#define EOTHER          131
#define EOVERFLOW       132
#define EOWNERDEAD      133
#define EPROTO          134
#define EPROTONOSUPPORT 135
#define EPROTOTYPE      136
#define ETIME           137
#define ETIMEDOUT       138
#define ETXTBSY         139
#define EWOULDBLOCK     140

고쳐야할거...

  • strdup함수에서, extern error를 까먹고 안함.
  • 메인문 strlen, 출력할떄 ld로 printf가능하고, 헤더파일에도 int를 size_f로 바꿔야하마.
  • 내기전에 테스트 케이스 다시해보고 내자.

0개의 댓글