본문 바로가기

무언가 만들기 위한 지식/SPARC Assembler

Assembler i/o Scanf Printf 사용법

어떤 언어를 공부하든지 가장 먼저 해보는 명령어는 입출력 함수이다.
특히 흥미를 돋구기 위해서 printf문 즉 output 출력문을 연습하게 된다.
printf("Hello C\n"); 정도랄까.....

어셈에서도 역시 printf문과 scanf문이 존재하다.
사실 프로그래밍 언어쪽에 조금이라도 해본 분은 printf scanf에 익숙할 것이다.
이에 어셈블리어 상에서의 scanf printf에 대해 간단하게나마 고찰해보고자 한다.

사실 이 두가지를 할 줄 알아야 직접 확인이 가능하니, 기본적으로 알아야 하긴 하다.(아니면 디버거를 쓰세요~^^)

printf의 뜻에 대해 먼저 알아보자.
printf란 print Format의 약자이다.
C언어에서 printf문은 형식이
printf(" [출력 타입] ", 타입에 따른 값들......);
로 되어 있다.
즉, printf문의 첫번째 인자는 출력타입의 형식(Format), 두번째 인자부터는 첫번째 정한 타입에 따른 값(Value)이다.
값이라고 강조하는 이유는 scanf에서는 값이 아닌 주소값을 넘겨주기 때문이니 확실히 기억하길 바란다.

그 방식은 assembler에서도 이어진다.
printf를 call할때는 call하기전에 %o0 레지스터와 %o1부터 %o5까지 셋팅을 해주어야 한다.
(물론 %o1~%o5의 개수는 매개변수의 개수로 한정한다.)
%o0에 쓰이는 출력 형식(Format)은 스트링으로 이루어진 값을 사용한다.
Data영역에(Static공간) 설정해 놓고 %o0로 불러놓고 사용하게 된다. (ex: output : .asciz "Value is %d\n")
자세한 사용 예는 하단 예시를 보면서 언급하겠다.

다음은 scanf이다.
scanf의 f도 format의 약자이다. C언어를 공부하다보면 scanf를 자주 쓰게되는데 C에서의 형식은 다음과 같다.
scanf("%d",&a);  //a에 4byte 정수 값을 넣겠다는 의미
어셈블리어도 보이는 부분만 다르지 형식은 똑같다.
scanf에서는 %o0에 입력받을 형식(format)을, %o1~%o5 레지스터에는 입력받을 장소의 주소값! 을 넣는다.
물론 C에서도 무작정 형식만 외운사람이 아니라면 &a에서 인자값을 주소로 받는 다는 사실을 눈치 채었을 것이다.
어셈블리어에서도 C의 scanf와 같은 방식으로 주소를 받는다.(사실.. C보다 어셈이 먼저기는 하지만^^)
주소를 받을때는 %fp를 이용한 스택영역의 값을 이용한다.
이것저것 찾아봤는데, 레지스터로 곧장 받는 방식은 없고, stack영역에서 받지 않는다면 static영역의 data Section에서 받아 사용하는 방식도 있었다.
(↑근데 이건 돌려봤는데... 생각처럼 잘 않된다. 주소값이 나와 버려 연산에 차질이 생김. 사용법은 data영역에서 .word 0 를 이용한다.)

위 설명만 듣는다면 100%이해하기 힘드니 직접 예제를 관찰해 보자.
[작성환경 : solaris sparc machine 작성툴 : Vi editor]

위 코드는 아주 간단한 코드로
Scanf로 입력 받은 값을 저장하고,
레지스터로 전달한 후에 Printf문으로 그 값을 단순히 출력하는 부분이다.
Nop부분은 이해를 위해 그대로 두었으며, fp를 계산하는 부분이 잠깐 등장한다.
결과 값은 다음과 같다.


7134로 Input값을 주고 Output값을 출력한 것이다.

코드를 보면 2, 3번 줄이 출력과 입력에 쓰일 첫번째 인자. 즉 Format부분이 들어가 있는 것이다.
(.align은 4단위로 정렬한다는 뜻. Stack 영역할때 자세히^^)
이부분은 특별히 Lable이름을 각각 앞쪽에 지어주고 스트링 형으로 설정한다.
보면 이 영역은 .section ".data"부분과 .section ".text"사이에 설정이 되어 있는데 이 부분은 컴파일시간에 미리 할당되는 Static영역의 코드이다. printf문이 여러개라면 각 출력형식을 이 부분에서 지정해 주어야 한다.
여기서 output 레이블(lable)은 printf를 위한 형식이고, input 레이블은 scanf를 위한 형식이다.
주의해야할 점은 scanf의 경우 %d 식의 종류만 써주어야지 다른 문자를 집어넣으면 에러가 발생한다.
6, 7번줄은 stack어느 영역에서 나 사용할 수 있도록 global로 표시해준 것이지 현재 코드에서는 별 의미가 없다.(지워도 잘돌아간다.)
11번부터 14번까지가 scanf의 코드이다.
형식적으로 set [레이블], %o0 를 하면 Lable의 asicz 형식을 해당 레지스터에 저장한다.
이건 사실상 주소를 전달할때 사용되는데 set은 사실 내부적으로 sethi로 구성되어 있다.
C에서의 scanf도 사용하긴 간단하지만, 깊이 파일 I/O로 들어가서 buffer와 stream으로 해석하면 복잡해지므로 이 경우에도
일단 scanf나 printf시 처음 %o0값을 넣어주기위 사용된다고 알아두자.
12번의 경우 fp에다가 -4를 하여 %o1에다가 넣어준다. 이부분이 scanf로 받을 값을 저장할 주소이다.
주소값이란 stack영역의 여유공간 -4칸을 이용하였다.
save -96으로 10번줄에서 사용할 영역을 설정하였다. 어셈에서는 최소 64byte를 사용하고 나머지 32bit는 여유 공간이다. 32bit의 여유공간중 4byte 즉 저장될 정수크기만큼을 사용하였다. 이 fp와 영역에 관한 것은 stack영역 설명중에 할 것이다.
저장될 주소를 fp-4 로 지정하여 주었다.
이제 저장된 값을 꺼내오기 위해서는 fp-4의 주소를 가져오면 된다.
이 fp(frame pointer의 약자)의 주소란 레지스터가 아닌 메모리에 저장된 값을 의미한다.
(레지스터는 CPU에 아주 가까이에 있는 것이고 메모리는 멀리 떨어져 있다. 그렇기 때문에 메모리의 값을 호출하기 위해 다른 명령어를 사용해야 함. ld, ldsb, ldd, lduh 등등등이 존재한다.)

15번 줄부터 18번 라인까지는 printf문이다. 전에 언급했듯이 printf문은 %o0에서 형식을 나머지는 출력 인자 값을 넣어주면 된다.
특이점은 16번 라인에서 ld 명령을 사용했다. ld 다음에는 []를 이용하여 해당 주소에 있는 값을 %o1레지스터로 가져왔다. (메모리 →레지스터) , []란 포인터 기호와 비슷하게 해당 주소의 값을 가리킨다.(단 아무대서나 사용되는 것은 아니다.)


아래 코드는 scanf를 사용하는 약간 다른 방법이다. 원리는 같다.
[작성환경 : solaris sparc machine 작성툴 : Vi editor]

위의 코드와 다른점은 4, 13, 17번 Line이다.
위에서 한번 언급했던 약간 다른방식으로 scanf를 받는 것이다.
저장될 공간을 Static영역의 data에 .word 형식의 공간을 할당하고 그곳의 주소를 얻어 사용하는 것이다.
13번 라인을 보게되면 set을 이용하여 %o1번 레지스터에 4번줄의 주소를 넘겨주었다.
그후 Scanf가 호출되고 Scanf는 %o1에 있던 주소에 값을 넣게 된다.
필자는 처음 이 방식으로 해봤는데 아무리 출력해도 scanf 호출후 o0와 o1에는 이상한 값이 들어있는 것이었다.
나중에 알게된 것이지만 Scanf를 호출하게되면 %o0는 0으로 셋팅되고, %o1에 Scanf를 통해 받은 주소값이 저장되게 된다.
저장이 되었으면 이제 ld를 이용해 자유롭게 가져다 쓰면된다.
가져다 쓰는 양식은 17번에서 []을 이용하여 가져다가 레지스터로 옮긴 후 사용하였다.

즉, 한번에 요약하자면 printf나 scanf나 그 형식에 맞추어 사용하면 된다.
처음접하는 분들이 까다로운 부분은 ld부분이나 set부분일 것이다. 그러나 그것도 하나의 양식이라고 생각하고 지금 자유롭게 사용할 줄 안다면 후에 차차 그 세부내용을 알게 될 것이다.


※ 모든 세부내용을 언급하면 너무 길어지니 Stack영역 부터 차근차근 포스팅을 올리겠습니다.^^
    (아아.. 그전에 분기문, CC도 올려야 하는데 ㅠ.ㅜ;)

Format 에 들어가는 형식들...
d or i : decimal signed integer
o : Octal Integer (숫자 0을 앞에 첨가)
x or X : Hex Integer (0이 아닌 수에 0x를 숫자 앞에 첨가)
U : Unsigned Integer
c : Character
s : C-string, Null terminate String
f : double
e or E : double
g or G : double

Scanf를 통하여 수를 입력받으면 메모리 공간에 의해 값을 저장받으므로 32bit를 모두 받을 수 있다.
하지만 명령어상 수를 입력 받게 되면, 즉 immediate에 의해 수를 입력받으면 명령어 크기의 한계로 2의 13승 까지 밖에 수를 입력받지 못한다.
(하나의 명령어, 즉 코드상 한행이 32bit에 의해 이루어지는에 add나 sub기타 등등에서의 immediate는 13정도로 제한된다. 다른 레지스터로 사용되기 때문이다.)