strlen은 C언어에서 글자수를 세는
내장 함수입니다. 정확히는 바이트의 카운트인데
아스키코드로 보면 글자수와 일치합니다.
(2바이트 이상 되는 유니코드 제외)
어셈블리어에서 스스로 구현하는 것은
전혀 새로운 관점에서 보게 하는데요.
물론 C언어로도 loop와 if로 구현할 수 있지만
어셈블리어에서는 레지스터를 사용해서
원조적으로 조작한다는 차이점이 있습니다.
범용레지스터라는게 16개가 있지만
각자 용도가 있어서 그렇게 마음대로
쓸 수 없기 때문에 잘생각해야 합니다.
이전 포스팅에서는 문자열을 출력하는
서브루틴에 글자수를 세는 것을
다 포함시켰다면 이번에는 분리해서
글자를 세는 것에 포커스를 맞춰 보겠습니다.
다음의 코드는 콘솔에 문자열을 출력합니다.
매크로 print에 보면 서브루틴을 분리해놨습니다.
section .data
text1 db "- Hello!", 10, 0
text2 db "- My name is Kay", 10, 0
text3 db "- We all friends! ", 10, 0
text0 db 0
textNewLine db 10,0
section .bss
%macro print 1
mov rdi, %1
call _strlen
call _printString
%endmacro
section .text
global _start
_start:
nop ;just placeholder
;call subroutines
mov rdi, text1
call _strlen
call _printString
;macro
print text2
print textNewLine
print text3
;nothing happens if first byte is 0
print text0
nop
_last:
;terminate the program
mov rax, 60
mov rdi, 0
syscall
;**************************************
;separate _strlen and _printString
_strlen:
push rdi ;push rdi
xor rcx, rcx
_str_next:
cmp [rdi], byte 0
jz _str_null
inc rcx
inc rdi
jmp _str_next
_str_null:
pop rdi ;pop rdi (rdi recovered)
ret
_printString:
mov rsi, rdi
mov rdx, rcx
mov rax, 1
mov rdi, 1 ;stdout
syscall
ret
주요하게 볼 내용은 _strlen: 입니다.
;**************************************
;separate _strlen and _printString
_strlen:
push rdi ;push rdi
xor rcx, rcx
_str_next:
cmp [rdi], byte 0
jz _str_null
inc rcx
inc rdi
jmp _str_next
_str_null:
pop rdi ;pop rdi (rdi recovered)
ret
_printString:
mov rsi, rdi
mov rdx, rcx
mov rax, 1
mov rdi, 1 ;stdout
syscall
ret
우선 rdi를 push 하는데 여기다가
문자열의 시작 주소를 받을 겁니다.
text1, text2 이 변수들은 [] 없으면
메모리의 주소입니다.
매크로에서 mov, rdi, text1 이렇게 변환되서
_strlen: 으로 넘깁니다. rdi 를 push하는
이유는 rdi 값을 inc (증가)시키면서
마지막 바이트인 0을 찾을 것이기 때문입니다.
rcx는 글자수 count 입니다.
xor rcx, rcx 는 mov rcx, 0과 같습니다.
바로 _str_next:에서 0과 비교합니다.
jz는 eflags에서 ZF 가 들어올 때 실행됩니다.
cmp [rdi], byte 0에서 rdi의 값(바이트)이
0과 같을 때 ZF가 확인되서 루프를 멈춥니다.
inc rcx
inc rdi
로 둘다 증가시켜 줄때 rcx는 0에서 부터
카운트하는 것이고 rdi는 메모리를 1바이트
이동시킵니다. "Hello!"가 있다면 첫번째 바이트는
'H' 다음은 'e' ... 이런 식으로 포인터가
이동합니다. jmp _str_next에서 루프를 돕니다.
이렇게 이동하다보면 "Hello!",10,0 는
마지막 cmp인 0에서 rcx가 7이되고
rdi는 text1 + 7 이되는데 eflags에
ZF가 나오면서 루프가 끝납니다.
이제 SYS_WRITE에 전달할 값은
문자열인 rdi 와 카운터인 rcx 입니다.
매크로에서 각각 rsi와 rdx에 전달합니다.
시스템콜에 대한 부분은 아래 문서를 참고합니다.
어셈블리어 리눅스 Hello World 출력 - NASM x86_64 어셈블리어 1
어셈블리어 리눅스 Hello World 출력 - NASM x86_64 어셈블리어 1
x86 어셈블리어(Nasm) 어셈블리어는... 프로그래밍 계의 끝판왕입니다. 어려운 순서로는 기계어에 이어서 Top 2위인데, 요즘 시대에 메뉴얼로 0과1로 기계어를 작성하는 경우는 Intel 같은 CPU제조사를
digiconfactory.tistory.com
레지스터의 배치를 달리하면
다양하게 구현할 수 있을 겁니다.
최적화가 잘되어 있고 포맷 기능이 뛰어난
C언어 라이브러리를 사용하면 되는데
굳이 만들어서 사용할 일은 거의 없겠지만
제한된 레지스터를 활용하는 측면에서
한번 정도 만들어 보면 나쁘지는 않은 것 같습니다.
어셈블리어 GDB 디버거 사용법(기본) - NASM x86_64 어셈블리어 7
어셈블리어 GDB 디버거 사용법(기본) - NASM x86_64 어셈블리어 7
GDB 디버거 GDB 디버거는 GNU Project Debugger의 약자로 GNU toolchain 핵심 구성요소 중에 하나입니다. www.sourceware.org 에서는 GDB의 정의를 GDB, the GNU Project debugger, allows you to see what is goi..
digiconfactory.tistory.com