티스토리 뷰
libasm 기본 참고 자료
www.notion.so/libasm-179fc489b80a4363996490c65e9f11a7
www.notion.so/Libasm-3c94bbc7df234499b012f6ae82b84dc2
어셈블리어 기초 공부
특히 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
어셈블리어로 에러처리하기
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 에 들어온 각각의 변수들을 스택에 주소로 저장해놓고, 필요할 때마다 주소로 접근해서 꺼내 쓴다.
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 |