티스토리 뷰

42/others

libasm

stdbc 2020. 11. 19. 22:49

libasm 기본 참고 자료

www.notion.so/libasm-179fc489b80a4363996490c65e9f11a7

www.notion.so/Libasm-3c94bbc7df234499b012f6ae82b84dc2

 


어셈블리어 기초 공부

 

youtu.be/s6oLWpLj560

특히 C를 어셈블리어로 변환하는 부분이 과제에 도움이 많이 됨.

실제로 ft_함수들을 c로 먼저 만들고, 어셈블리어로 변환해서 학습했음.


레지스터

CPU(Central Proceessing Unit)가 요청을 처리하는 데이터의 임시저장 공간
레지스터의 종류

 

그 중 범용레지스터를 사용자가 적절하게 사용할 수 있다.

아래 그림은 범용레지스터의 구조

 

  • BYTE : 1 byte (char)
  • WORD : 2 byte (short)
  • DWORD : 4 byte (int)
  • QWORD : 8 byte (double)

 

MOV

NASM에서는 한 번에 두 군데 이상의 메모리를 참조할 수 없다 = 명령에서 근원지와 목적지에 동시에 메모리가 올 수 없다. 

따라서 어떤 메모리에 저장된 값을 다른 메모리로 복사하기 위해서는 레지스터에 먼저 보관하는 과정을 거쳐야만 한다. 어셈블리 튜토리얼 

 

 

MOVZX

sub 연산에서 필요했음.

[리버싱] 기본 어셈블리어 - MOV / MOVZX / MOVSX / MOVS

 

 

0 여부 판별 : (test eax, eax) vs (cmp eax, 0)

test eax,eax is almost identical to cmp eax,0, except that it shorter than cmp, 
because with cmp you have to supply 0 as an argument.

출처

 


errno 알아보기

#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>

int main(void)
{
	char str[100] = "hello world\n";
	int res;

	// case1
	res = write(1, str, strlen(str));
	printf("res: %d, errno: %d\n", res, errno);

	// case2
	res = write(-1, str, strlen(str));
	printf("res: %d, errno: %d\n", res, errno);
}

 

결과

// case1
hello world
res: 12, errno: 0

// case2
res: -1, errno: 9


에러 발생시 리턴값: -1
에러 발생시 errno: 해당 error number 

 

 

어셈블리어로 에러처리하기

[어셈블리어] ___error 함수로 에러 처리하기

 

ft_write.s

section .text
    global _ft_write
    extern ___error

_ft_write:
    mov	rax, 0x02000004
    syscall
    jnc .end
    push rax
    call ___error
    pop	qword [rax]
    mov	rax, -1

    .end:
        ret

1. syscall 호출

2. 리턴값이 음수가 아닐 경우 (jnc) 그대로 리턴

 

3. 음수)

syscall 리턴값 : errno

_error 리턴값 : errno의 주소

 

__error가 리턴하는 주소값 위치 syscall 리턴값(=errno)을 넣는다.

마지막으로 write 함수의 리턴값은 -1

 

 

참고 :eax vs [eax]

 

값이 1234인 ebx 레지스터가 있을 때

1. mov eax, ebx

결과) eax value = 1234

 

2. mov eax, [ebx]

결과) eax value = 1234 주소에 있던 어떤 값

 


push rbp
mov rbp, rsp
sub rsp, 40
mov [rbp - 8], rdi		
mov [rbp - 16], rsi		
mov [rbp - 24], rdx		
mov [rbp - 32], rcx		

보너스를 진행하면서 레지스터만으로 구현이 어려워져서 사용한 방법.

간단하게 요약하면

rdi, rsi, rdx, rcx 에 들어온 각각의 변수들을 스택에 주소로 저장해놓고, 필요할 때마다 주소로 접근해서 꺼내 쓴다.

 

 

호출 규약(Calling Convention)

함수의 호출과 복귀


 

 

 

ft_strlen.c

int ft_strlen(char *str)
{
    int i = -1;
    while (str[++i])
        ;
    return (i);
}

 

ft_strlen.s

section .text
	global _ft_strlen

_ft_strlen:
    mov rax, -1

    .loop :
        inc rax
        cmp byte[rdi + rax], 0
        jne .loop
        ret

 

실제로 disassembly

section .text
	global _ft_strlen

_ft_strlen:
	push	rbp
	mov	rbp, rsp
	mov	QWORD [rbp-0x8], rdi
	mov	DWORD [rbp-0xc], 0xffffffff

	.loop:
	mov	rax, QWORD [rbp-0x8]
	mov	ecx, DWORD [rbp-0xc]
	add	ecx, 0x1
	mov	DWORD [rbp-0xc], ecx
	movsxd	rdx, ecx
	cmp	BYTE [rax+rdx*1], 0x0
	jne	.loop

	.end:
		mov eax, DWORD [rbp-0xc]
		pop rbp
		ret

 

함수를 호출할 때 매개변수(rdi, rsi, rdx, rcx, r8, r9...), 레지스터(rbx...)에 값이 들어있으면 segfault 발생

ft_strlen는 rdi 값만 필요로 하지만 다른 레지스터에도 값이 들어있으면 안된다.

따라서 변수는 스택에 저장해놓는다.

 

Your code is wrong, because rdi is a caller saved register just like rax. 
There is no guarantee that __errno_location does not change it. 
Push it on the stack instead. 

출처

 

 

함수 call 정리

1. 함수를 call할 때 그 함수가 들어있는 레지스터는 무엇이든 상관없다. rax, rbx, r8 ...

2. 대신 이때 매개변수들은 함수 호출 규약에 맞게 들어있어야 한다. (rdi, rsi, rdx, rcx, r8, r9)

3. 사용하지 않는 매개변수 레지스터는 비어 있어야 한다.

4. call의 반환값은 rax에 저장된다.

5. (추측!!!!!) call 후 나머지 매개변수쪽 레지스터들은 초기화된다.

 

'42 > others' 카테고리의 다른 글

Makefile - relink, dependency  (0) 2020.10.29
ft_server 수정중  (0) 2020.09.05
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함