The Beautiful Future

어셈작성법 구조 본문

arm assembly

어셈작성법 구조

Small Octopus 2019. 3. 6. 21:24

http://jake.dothome.co.kr/inline-assembly/

https://wiki.kldp.org/KoreanDoc/html/EmbeddedKernel-KLDP/app3.basic.html


** 작성 구조

__asm__ __volatile__(

asms

: output

: input

: clobber

);

__asm__

인라인 어셈블러가 시작됨을 알려준다. 

ANSI 표준에는 asm 이정의 되어있지 않아서 __asm__으로 해주는것이 좋다.

__volatile__

컴파일러 최적화를 하지 말아라는 의미이다.

컴파일러에 의해서 의도한바와 다르게 프로그램이 해석될수 있음을 방지한다.

asms

실제 어셈블러가 작성되는 란이다. 따옴표로 둘러싸서 작성한다.

%를 사용해서 input, output파라미터를 참조할 수 있다.

output

결과 값을 출력하는 변수

input

어셈블러에 넘겨주는 파라미터를 적는다.

%0, %1등을 사용해 input, output 오퍼랜드를 나타낸다.

output에서 부터 시작해 input에 나열된 변수들의 순서 대로 %0, %1, ... 으로 번호가 매겨진다.

clobber

output, input에 명시되어있지 않지만 값이 변하는 것들을 젂는다.

ex)

static __inline__ int test_and_set_bit(int nr, volatile void * addr)

{

int oldbit;

__asm__ __volatile__( LOCK_PREFIX

"btsl %2,%1\n\t // 

                 sbbl %0,%0"

:"=r" (oldbit),"=m" (ADDR)

:"Ir" (nr) 

: "memory");

return oldbit;

}


input/output 설정법

""로 감싸져 있는 부분을 constraint라고 하며 input/output 의

속성을 설정할 수 있다. Arm Family Constraints는 아래와 같다.

f: floating point register

F: 0.0, 0.5, 1.0, 2.0, 3.0, 4.0, 5.0, 10.0 중의 하나를 나타내는 플로팅 포인트 상수

G: 음수인경우 F

I: 0~255 사이에서 2의 배수 값을 나타낸다.

J: -4095~4095 사이 정수

K: I를 만족하는 값을 1의 보수를 취한것

L: I를 만족하는 값을 음수로 취한 값(2의 보수)

M: 0~32 사이의 정수 값

Q: 한 레지스터에 담겨잇는 정확한 어드레스 메모리

R: constant pool 내에 담겨있는 아이템

S: 현재 파일의 텍스트 세그먼트 내의 심볼


strcpy 예제

static inline char * strcpy( char * dest, const char *src)

{

    int d0, d1, d2;

    __asm__ __volatile__(

        "1:\tlodsb\n\t" // 1:은 레이블, al 레시스터에 es:esi의 내용을 읽어오고 자동 1증가

"stosb\n\t" // al의 값을 es:edi에 저장하고 자동 1증가

"testb %%al,%%al\n\t" // al의 내용이 0인지 테스트

"jne 1b" // 0이 아닌경우 1로 점프

: "=&S" (d0), "=&D" (d1), "=&a" (d2)

:"0" (src),"1" (dest) 

        : "memory");

    return dest;

}

output

"=&S" (d0) : d0를 si 레지스터에 저장

"=&D" (d1) : d1을 di 레지스터에 저장

"=&a" (d2) : d2를 a 레지스터에 저장

이 output 설정은 어셈코드 종료후 값을 d0~2  값을 si, di, a 레지스터에 저장

기계어로 아래와 같이 재구성됨.

movl %esi,%ecx

movl %edi,%edx

movl %ecx,%ebx

input

"0" (src): src가 0번째 오퍼랜드인 d0과 같다라는 의미

           위에서 d0는 si 레지스터에 할당되어있으니 si레지스터는 src로 초기화 된다.

"1" (dest): di 레지스터는 dest로 초기화 된다.

movl 12(%ebp),%esi

movl 8(%ebp),%edi

memory

: "memory" : 어셈에세 변경되는 메모리를 알려줌, 어셈 로드전/후에 레지스터에 저장

                 되어 있는 모든 변수 값을 갱신한다.



인라인 어셈블리의 문법은 다음과 같으며, ARM 아키텍처를 위주로 설명한다.

     asm [volatile] ( AssemblerTemplate
                      : OutputOperands
                      [ : InputOperands
                      [ : Clobbers ] ])
     
     asm [volatile] goto ( AssemblerTemplate
                           :
                           : InputOperands
                           : Clobbers
                           : GotoLabels)

 

Qualifiers

volatile

  • gcc는 성능 향상(optimization)을 목적으로  상황에 따라 명령을 무시하거나 명령 위치를 변경할 수 있는데 volatile을 사용하면 이러한 optimization을 하지 않게 한다.
  • 참고: Volatile | 문c

 

goto

  • 어셈블리 코드안에서 C에서 사용하는 라벨로 점프할 수 있게 한다.
  • 이 때 OutputOperands는 사용할 수 없다.

 

Parameters(파라메터)

 

AssemblerTemplate(코드)

  • 어셈블리 코드 문자열
    • input/output 오퍼랜드와 %l(goto) 파라메터와 조합하여 사용한다.
    • 예) ARM
      • “mov r0, r0”
      • “mov %0, #10”
      • “ldr %0, [%1]”
      • “bne %l[err1]”
  • 어셈블리 명령 끝 처리
    • “\n” 또는 “\n\t”을 사용하여 여러 개의 명령을 사용한다.
    • 세미콜론(;)을 사용하여 명령을 구분할 수도 있는데 컴파일러에 따라 다르므로  “\n\t” 등이 권장된다.
    • 예) ARM
      • “mov r0, r0\n\t”   “mov r1, r1”
      • “mov r0, r0;    mov r1, r1;”
  • “%%”
    • “%” Input/Output 오퍼랜드에서 사용하는 “%”와 혼동을 피하기 위하여 어셈블리 코드내에 x86 어셈블리와 같이 “%” 문자열을 사용해야 하는 경우 사용한다.
    • 예) x86
      • “movl %%eax, %0”
  • Input/Output Operands 사용
    • %n: n번째 인수에 매핑된 arm 32bit 레지스터를 지정한다.
    • %Qn: n번째 64비트 인수 중 하위 비트에 매핑된 ARM 32bit 레지스터를 지정한다.
    • %Rn: n번째 64비트 인수 중 상위 비트에 매핑된 ARM 32bit 레지스터를 지정한다.
    • %Hn: n번째 64비트 인수 중 매핑된 2 개의 ARM 32bit 레지스터 중 레지스터 번호가 높은 레지스터를 지정한다.
    • 예) ARM
      • “mov %0, #10”
      • “mov %Q0, %1, %2”
      • “mov %R0, %R0, #0”
      • “ldrd %H0, [%1]”

 

Input  Operands(입력 인수) & Output Operands(출력 인수)

형식: [ [asmSymbolicName] ]    constraint    (C-expression)

  • AssemblerTemplate(code)에 있는 명령에 의해 C 변수들에서(로) 입력/출력된다.
  • 빈 항목도 허용한다.
  • [ [asmSymbolicName] ]
    • 생략 가능하고 지정하는 경우 %0, %1, …과 같이 인수의 순번을 사용하는 대신 심볼명을 사용할 수도 있다.
    • 예) ARM
      • asm (“add %[tmp], #2” : [tmp] “=r” (tmp));
  • constraint
    • 자주 사용하는 constraint 항목
      • “r”
        • C-expression을 범용 레지스터에 배정한다.
      • “I”~”P”
        • Immediate 위치로 C-expression이 상수이어야 한다.
        • 아키텍처마다 상수 표현 크기가 다르다.
        • ARM:
          • “I”: 0~255 값을 2의 차수(2^N) 단위로 만들 수 있는 상수 값
            • 예) 0x81(o), 0x8100_0000(o), 0x101
          • “J”: -4095~4095 범위의 상수
      • “m”
        • C-expression이 유효 메모리 주소이어야 한다.
      • “[digits]”
        • Input Operands에 사용되며 Output Operands의 순서와 똑같은 항목을 지칭한다. 이렇게 하면 컴파일러의 optimization이 Output Operands의 값이 수정된 것 처럼 속인다.
          • 예) __asm__ ( : =r(__ptr) : 0(ptr));
    • 메모리 access용 clobber를 지정할 수 있는 constraint 항목
      • “Q”, “Qo”
        • ARM clobber for memory
        • C-expression은 단일 레지스터에서 유효한 메모리 레퍼런스 주소이다.
        • gcc의 ARM용 clobber for memory로 input/output 오퍼랜드에서 메모리 access를 위해 사용한다.
        • ARM에서는 메모리 영역을 access 할 경우 clobber lists에서 “memory” 대신 “Q”를 사용한다.
        • “Qo”: optimization이 추가되어 코드가 일부 생략된다.
          • 보통 메모리 주소를 가리키는 레지스터 즉 “r”레지스터가 별도로 사용되면서 “r”과 “Q”에 대해 각각의 레퍼런스를 계산하기 위한 코드가 사용된다.  만일 “r”과 “Q”에서 사용되는 메모리 주소가 서로 같은 곳을 보는 경우 “Qo”를 사용하면 한 번의 계산을 생략할 수 있다.
          • 예) 아래와 같이 v->counter의 위치가 서로 같은 경우 “Qo”를 사용하여 코드를 절약할 수 있다.
            • asm (“ldrd %0, %H0, [%1]” : “=&r” (result) : “r” (&v->counter), “Qo” (v->counter);
    • constraint modifiers
      • “=”
        • OutputOperands에서 쓰기(write only)만 가능하다.
      • “+”
        • OutputOperands에서 읽고(read) 쓰기(write)가 가능하다.
      • “&”
        • early clobber modifier
        • OutputOperands에서 레지스터 할당 순서를 먼저 할 수 있도록 요청한다.
        • 보통 input operands에 레지스터를 할당하고 그 후 output operands의 레지스터를 사용하기 때문에 input operands에서 사용했던 레지스터를 output operands 레지스터로 배치하는 경우도 생기는데 그러면서 문제가 될 수 있는 곳에 “&”를 사용한다.
        • 보통 Output operands에  “&”를 사용하여 먼저 하나의 레지스터를 할당받아 사용하면서 다른 레지스터로 사용될 일을 막는다.
    • 사용 예)
      • “=r”
        • 쓰기만 하는 목적으로 해당 C-expression을 범용 레지스터에 배정한다.
      • “+rm”
        • 메모리 주소를 읽고 쓰는 목적으로 해당 C-expression을 범용 레지스터에 배정한다.
      • “Ir”
        • immediate 오퍼랜드 위치에서 사용하기 위하여 해당 C-expression을 범용 레지스터에 배정한다.
      • “=&r”
        • 쓰기만 하는 목적으로 해당 C-expression을 범용 레지스터에 먼저(early) 배정한다.
      • “+r”
        • 읽고 쓰는 목적으로 C-expression을 범용 레지스터에 배정한다.
      • “+Qo”
        • 읽고 쓰는 목적으로 C-expression을 메모리에 배정한다. (ARM clobber for memory)
  • (C-expression)
    • C 표현이 가능하다.
    • 예)
      • (var+10)
      • (*var)
      • (&var)

Clobbers

  • AssemblerTemplate에 의해 변경되는 레지스터나 값들이다.
  • 즉 InputOperands 와 OutputOperands가 C로 부터 영향을 받거나 주는 경우를 지정하였지만 Clobbers는 어셈블리 코드에서 영향을 주는 것을 의미한다.
  • “cc”
    • 플래그 레지스터를 수정할 수 있다.
  • “memory”
    • 메모리 주소를 변경시킬 수 있다.
    • input/output operands에서 “Q” 또는 “Qo”를 사용하여 해당 항목에 사용할 수 있다. (최신 방법)
  • 예) “r9”, “cc”, “memory”
    • 어셈블리 코드로부터 r9 레지스터, 플래그 레지스터, 메모리가 수정되는 경우

 

Goto Labels

  • 라벨로 점프를 하는 기능이며, 이  기능을 사용할 경우 OutputOperands를 사용할 수 없으므로 비워둬야 한다.

goto.c

01#include <stdio.h>
02 
03int sub(int cnt)
04{
05        int x = 0;
06        asm goto "mov %0, %1\n\t"
07                   "cmp %0, #10\n\t"
08                   "bhi %l[err2]\n\t"
09                   "1: subs %0, #1\n\t"
10                   "bne 1b"
11                   :
12                   :    "r" (x), "r" (cnt)
13                   :    "cc"
14                   :    err2);
15        printf("cnt=%d\n", cnt);
16        return x;
17err2:
18        printf("err: cnt=%d\n", cnt);
19        return x;
20}
21 
22int main()
23{
24        sub(5);
25        sub(15);
26}
27 
28$ ./goto
29cnt=5
30err: cnt=15

 

기타

 

Clobber for Memory (“Q”)

  • Q”는 “memory”를 대신하여 사용되는 오퍼랜드 항목의 clobber for memory 이다.

qo.c

01#include <stdio.h>
02 
03int loop5(int * addr)
04{
05        int i;
06        int tmp = 0;
07 
08        for (i = 0; i < 10; i++)
09        {
10                asm ("add %0, #2\n      str %0, [%2]"
11                        "=r" (tmp), "=Qo" (*addr)
12                        "r" (addr));
13        }
14        return tmp;
15}
16 
17int loop6(int * addr)
18{
19        int i;
20        int tmp = 0;
21 
22        for (i = 0; i < 10; i++)
23        {
24                asm volatile ("add %0, #2\n     str %0, [%2]"
25                        "=r" (tmp), "=Qo" (*addr)
26                        "r" (addr));
27        }
28        return tmp;
29}
30 
31int main()
32{
33        int l5 = 1;
34        int l6 = 1;
35 
36        loop5(&l5);
37        loop6(&l6);
38}
  • 위의 소스와 같이 “memory” clobber를 사용하지 않고 “Qo”를 사용하여 구현한 예이다.
  • 메모리에 읽거나 쓰는 데이터는 레지스터가 아닌 값을 의미하므로 addr가 아닌 *addr이 된다.
  • input operands에 사용된 addr은 읽기 용도로 변경되지 않으며 output operands에 사용된 *addr 값이 메모리에 기록되는 값이므로 “Qo” 앞에 기록 전용의 modifier인 “=”을 붙인다.

 

참고



'arm assembly' 카테고리의 다른 글

명령어 정리  (0) 2019.05.18
ARM GCC Inline Assembler Cookbook  (0) 2019.03.10
cross product 분석  (0) 2019.03.09
NEON Register 구조  (0) 2019.03.08
Comments