문자열 출력 서브루틴

어셈블리어에서 문자열 하나를 콘솔에

출력하는 것은 몇 줄의 코드가 소요됩니다.

 

C언어에서는 printf 함수 하나를 가지고

자유자재로 문자열과 숫자에 포맷으로

출력할 수 있지만 어셈블리어는

시스템콜을 직접사용하기 때문에

별도의 서브루틴을 만들어야 합니다.

 

쉽게말해서 printf 함수를 직접 만들어야한다 -

고 보면 될 것 같습니다. 숫자를 출력하는 것은

또 다른 서브루틴이 필요하므로 우선은

문자를 출력하는 서브루틴을 만들어 보겠습니다.

 

 

문자열의 출력은... 여러가지 방법이 있습니다.

여기서는 하나의 예제로 보면 됩니다.

 

일단 전체 소스 코드를 대략 흝어봅니다

매크로는 서브루틴 다음에 설명하겠습니다.

section .data

    text0: db 0 ;null text
    textWelcome: db "[Nasm x86_64 Assembly Language]",10,0

    text1: db "1. Hello World!",10,0
    text2: db "2. Nasm Assembly",10,0
    
    textKor1: db "-> 한글 텍스트",10,0
    textKor2: db "-> 출력 테스트",10,0
    
    textNull: db "*-- Sorry, null text...",10,0

    textMacro1: db "-> Macro Test...1",10,0
    textMacro2: db "-> Macro Test...2",10,0

;static constants

    SYS_write    equ  1
    FD_write     equ  1

    EXIT_SUCESS  equ  0
    SYS_exit     equ 60

;Nasm Macro
%macro print 1
    mov rax, %1
    call _printString
%endmacro

section .text
    global _start

_start:

    mov rax, textWelcome
    call _printString

    mov rax, text1
    call _printString

    mov rax, text2
    call _printString

    mov rax, textKor1
    call _printString

    mov rax, textKor2
    call _printString

    ;Nasm Macro
    print textMacro1
    print textMacro2

    ;null text message
    mov rax, text0
    call _printString

;terminate program
_last:
    mov rax, SYS_exit
    mov rdi, EXIT_SUCESS
    syscall


;**************************************************
;subroutines

;string at rax
_printString:
    push rax
    mov rbx, 0 ;rbx as a count

_checkFirstByte:
    mov cl, [rax] ;cl to compare rax value to 0
    cmp cl, 0 ;compare cl to 0
    je _printNull

_printLoop:
    inc rax
    inc rbx
    mov cl, [rax]
    cmp cl, 0
    jne _printLoop

;syscall write
    mov rax, SYS_write
    mov rdi, FD_write
    pop rsi
    mov rdx, rbx
    syscall
    ret

;if first byte is null (null means 0)
_printNull:
    mov rax, textNull
    call _printString
    call _last

 

Nasm 문자열 출력 서브루틴
출력결과

 

Hello World 를 출력해보면 system call write에

대한 부분은 알 수 있을 것 입니다. 여기서는

반복적인 설명이 되니까 Nasm x86 64에서

Hello World 출력하기는 아래 문서를 참고합니다.

 

어셈블리어 리눅스 Hello World 출력 - NASM x86_64 어셈블리어 1

 

어셈블리어 리눅스 Hello World 출력 - NASM x86_64 어셈블리어 1

x86 어셈블리어(Nasm) 어셈블리어는... 프로그래밍 계의 끝판왕입니다. 어려운 순서로는 기계어에 이어서 Top 2위인데, 요즘 시대에 메뉴얼로 0과1로 기계어를 작성하는 경우는 Intel 같은 CPU제조사를

digiconfactory.tistory.com

 

다음 서브루틴 내용이 핵심입니다.

 

;string at rax
_printString:
    push rax
    mov rbx, 0 ;rbx as a count

_checkFirstByte:
    mov cl, [rax] ;cl to compare rax value to 0
    cmp cl, 0 ;compare cl to 0
    je _printNull

_printLoop:
    inc rax
    inc rbx
    mov cl, [rax]
    cmp cl, 0
    jne _printLoop

;syscall write
    mov rax, SYS_write
    mov rdi, FD_write
    pop rsi
    mov rdx, rbx
    syscall
    ret

;if first byte is null (null means 0)
_printNull:
    mov rax, textNull
    call _printString
    call _last

 

위에서 부터 라벨단위로 진행이 됩니다.

 

push rax 는 rax에서 문자열을 받아서

스택에 집어넣습니다.

(text 바이트의 첫번째 주소)

 

rbx는 문자열 바이트를 세기 위한 카운터입니다.

 

이전에

 

text1 db "Hello World",10

text1_L db $-text1

 

같은 방식으로 바이트 수를 계산했는데

이 방식은 코드수가 적어서 좋지만

꼭 텍스트 다음 라인에 카운팅을 하니까

프로그램의 확장성이 떨어집니다.

data 세그먼트가 아닌 곳에 저장된

문자열을 출력하고 싶으면 서브루틴에서

바이트 개수를 세야 합니다.

 

해서 rbx에 카운팅을 합니다.

 

_checkFirstByte:

 

첫번째 바이트가 0이면 Null 문자열입니다.

printNull 을 출력하도록 합니다.

cl 값이 0과 같으면 je (jump equal)로

_printNull 레이블로 갑니다. (프로그램 종료)

 

_printLoop는 첫번째 바이트가 0이 아닌

문자열을 카운팅하는 루프입니다.

inc rax와 inc rbx는 rax는 주소를 증가시키는 거고

rbx는 카운팅 값을 증가시킵니다.

[rax]로 메모리에 있는 값을 cl에 가져와서

다시 cl 값이 null (0)인지 체크합니다.

jne (jump not equal -> 0이 아니면) 점프로

0 이 나올 때 까지 반복합니다.

문자열의 끝에 10, 0을 넣은 것은 10 (new line)하고

0에서 체크하기 위함입니다. 참고로 C의 문자열의

끝에도 null문자가 들어갑니다.

 

0이 되면 점프하지 않으므로

시스템콜을 합니다. 이때 처음에 push한

rax 값이 텍스트의 첫번째 바이트이고

rbx에는 카운트가 들어있으니까

pop rsi

mov rdx, rbx

에서 인수가 다 들어갑니다.

 

_start: 에서 다음과 같이 사용합니다.

 

    mov rax, text1
    call _printString

 

C의 함수적으로 보면 rax가 서브루틴

_printString의 인수입니다. 매개변수를

스택에 넣는다는 것도 대략 비슷합니다.

물론 이건 단순 출력하는 코드기 때문에

스택에 넣지 않고 다른 레지스터나

메모리를 사용할 수도 있습니다.

내부를 어떻게 설계하느냐는 프로그래머에게

달려있어서 이것은 하나의 예입니다.

 

이 서브루틴은 첫번째 바이트가 0인

텍스트는 출력하지 않고 대신

별도로 null 메시지를 출력합니다.

 

Nasm Macro

Nasm 매크로는 Nasm 컴파일러의 기능입니다.

x86의 기계어와는 상관없는 Nasm의 문법입니다.

서브루틴 보다 좀더 함수적으로 사용할 수 있습니다.

 

;Nasm Macro
%macro print 1
    mov rax, %1
    call _printString
%endmacro

매크로는 라벨과는 다르게

소스코드 순서상 위쪽에 위치해야합니다.

서브루틴의 wrapper 처럼 보입니다.

print 1은 argument의 개수입니다.

0은 no argument 1은 1개 2는 2개...

매크로 내부에서 %1 은 첫번재 argument,

%2는 두번째 argument 같은 방식입니다.

 

print textMacro1 는

 

mov rax, textMacro1

call _printString

 

으로 변환됩니다.

 

 

*******

*******

*******

 

숫자값을 출력하는 서브루틴은 문자열과는

별도의 코드가 필요합니다.

숫자를 문자열로 변환하는 과정이 필요합니다.

공유하기

facebook twitter kakaoTalk kakaostory naver band