12 분 소요


Go 어셈블러에 대한 빠른 가이드(A Quick Guide to Go’s Assembler)를 정리한 글입니다.


이 포스트는 Go 어셈블러에 대해 소개하는 글입니다.


Go Assembler

Go 어셈블러에 대한 빠른 가이드

이 문서는 Go 컴파일러 gc가 사용하는 생소한 어셈블리 언어에 대해서 설명합니다.

Go 어셈블러의 기반은 Plan 9 어셈블러입니다.
Go에서 어셈블리 언어를 사용한다면, 비록 Plan 9에 한정되었지만,
A Manual for the Plan 9 assembler를 읽는 것이 좋습니다.
이 문서는 문법 및 Go 어셈블리와의 차이점을 설명합니다.


Go의 어셈블러는 내부 시스템(Underlying machine)을 직접 표현하지 않습니다.
세부 정보가 머신 명령과 정확하게 매핑될 수도 있지만, 그렇지 않을 수도 있습니다.

이는 컴파일러 스위트(Compiler Suite)가 일반적인 파이프 라인에서
어셈블러 단계(assembler pass)를 필요로 하지 않기 때문입니다.

Understanding assembler passes

  • First pass:
    첫 번째 단계에서 어셈블러는 명령이 현재 어셈블리 모드에서 올바른지 확인합니다.
  • Second pass:
    두 번째 단계에서 어셈블러는 저장 위치로의 기호 참조에 대한 피연산자를 검사하고
    기호 테이블의 정보를 사용하여 이러한 기호 참조를 확인합니다.(기호는 Symbol임.)


대신 컴파일러는 일종의 반-추상 명령어 세트(semi-abstract instruction set)로 작동하며,
명령어 선택은 부분적으로 코드 생성 후에 발생합니다.

어셈블러가 반-추상 형식으로 작동하므로 MOV와 같은 명령어에 대해 툴체인(toolchain)이 실제로
생성하는 것은 이동 명령이 아니라 지우기(clear) 또는 로드(load)일 수 있습니다.
물론 해당 이름의 기계 명령어와 정확히 일치할 수도 있습니다.
일반적으로 기계-별 작업(machine-specific operation)들은 그 자체로 나타나는 반면,
메모리 이동, 서브 루틴 호출, 리턴과 같은 일반적인 개념은 더 추상화됩니다.

세부 사항은 아키텍처에 따라 다르며 상황이 자세히 정의되지 않아 정확하지 않습니다.


어셈블러 프로그램은 반-추상 명령어 세트를 분석하여 링커에 입력할 명령어로 변환합니다.
주어진 아키텍처(예: amd64)에 대한 어셈블리의 명령을 확인하려면 표준 라이브러리인
runtime 이나 math/big과 같은 패키지 소스에 많은 예제가 있습니다.
또, 컴파일러가 생성한 어셈블리 코드를 확인할 수도 있습니다.
(실제 결과는 아래의 내용과 다를 수 있습니다.).

$ cat x.go
package main

func main() {
	println(3)
}
$ GOOS=linux GOARCH=amd64 go tool compile -S x.go        # or: go build -gcflags -S x.go
"".main STEXT size=74 args=0x0 locals=0x10
	0x0000 00000 (x.go:3)	TEXT	"".main(SB), $16-0
	0x0000 00000 (x.go:3)	MOVQ	(TLS), CX
	0x0009 00009 (x.go:3)	CMPQ	SP, 16(CX)
	0x000d 00013 (x.go:3)	JLS	67
	0x000f 00015 (x.go:3)	SUBQ	$16, SP
	0x0013 00019 (x.go:3)	MOVQ	BP, 8(SP)
	0x0018 00024 (x.go:3)	LEAQ	8(SP), BP
	0x001d 00029 (x.go:3)	FUNCDATA	$0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
	0x001d 00029 (x.go:3)	FUNCDATA	$1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
	0x001d 00029 (x.go:3)	FUNCDATA	$2, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
	0x001d 00029 (x.go:4)	PCDATA	$0, $0
	0x001d 00029 (x.go:4)	PCDATA	$1, $0
	0x001d 00029 (x.go:4)	CALL	runtime.printlock(SB)
	0x0022 00034 (x.go:4)	MOVQ	$3, (SP)
	0x002a 00042 (x.go:4)	CALL	runtime.printint(SB)
	0x002f 00047 (x.go:4)	CALL	runtime.printnl(SB)
	0x0034 00052 (x.go:4)	CALL	runtime.printunlock(SB)
	0x0039 00057 (x.go:5)	MOVQ	8(SP), BP
	0x003e 00062 (x.go:5)	ADDQ	$16, SP
	0x0042 00066 (x.go:5)	RET
	0x0043 00067 (x.go:5)	NOP
	0x0043 00067 (x.go:3)	PCDATA	$1, $-1
	0x0043 00067 (x.go:3)	PCDATA	$0, $-1
	0x0043 00067 (x.go:3)	CALL	runtime.morestack_noctxt(SB)
	0x0048 00072 (x.go:3)	JMP	0
...

FUNCDATAPCDATA 지시어(Directive)는 가비지 컬렉터가 사용하는 정보를 포함하고;
컴파일러가 생성합니다.

링크 후 바이너리의 내용을 확인해 보려면 go tool objdump을 사용하면 됩니다.

$ go build -o x.exe x.go
$ go tool objdump -s main.main x.exe
TEXT main.main(SB) /tmp/x.go
  x.go:3		0x10501c0		65488b0c2530000000	MOVQ GS:0x30, CX
  x.go:3		0x10501c9		483b6110		CMPQ 0x10(CX), SP
  x.go:3		0x10501cd		7634			JBE 0x1050203
  x.go:3		0x10501cf		4883ec10		SUBQ $0x10, SP
  x.go:3		0x10501d3		48896c2408		MOVQ BP, 0x8(SP)
  x.go:3		0x10501d8		488d6c2408		LEAQ 0x8(SP), BP
  x.go:4		0x10501dd		e86e45fdff		CALL runtime.printlock(SB)
  x.go:4		0x10501e2		48c7042403000000	MOVQ $0x3, 0(SP)
  x.go:4		0x10501ea		e8e14cfdff		CALL runtime.printint(SB)
  x.go:4		0x10501ef		e8ec47fdff		CALL runtime.printnl(SB)
  x.go:4		0x10501f4		e8d745fdff		CALL runtime.printunlock(SB)
  x.go:5		0x10501f9		488b6c2408		MOVQ 0x8(SP), BP
  x.go:5		0x10501fe		4883c410		ADDQ $0x10, SP
  x.go:5		0x1050202		c3			RET
  x.go:3		0x1050203		e83882ffff		CALL runtime.morestack_noctxt(SB)
  x.go:3		0x1050208		ebb6			JMP main.main(SB)



상수(Constants)

어셈블러는 Plan 9 어셈블러 가이드를 따르지만 별개의 프로그램이므로 약간의 차이가 있습니다.
그 중 하나는 상수 평가(Constant evaluation)입니다.
어셈블러의 상수 표현식은 원래의 C 우선 순위가 아닌, Go 연산자 우선 순위를 사용하여
구문을 분석합니다.
따라서 3&1«20 이 아닌 4 입니다- 3&(1«2) 가 아닌, (3&1)«2 입니다.
또한 상수는 항상 64-bit unsigned integer 로 평가합니다. 따라서 -2 정수 2 가 아니라
동일한 비트 패턴을 가진 부호없는 64 비트 정수입니다.

모호성을 피하기 위해, 오른쪽 피연산자의 상위 비트가 셋된 경우에는 나누기나 오른쪽 시프트는
실패합니다.



기호(Symbols)

R1 또는 LR과 같은 일부 기호는 미리 정의되어 있으며 레지스터를 가리킵니다.
정확한 세트는 아키텍처에 따라 다릅니다.

유사-레지스터(Pseudo-Registers) 를 가리키는 4 개의 미리 정의된 기호가 있습니다.
이들은 실제 레지스터가 아니라 툴체인에서 유지 관리하는 가상 레지스터입니다.
유사-레지스터 세트는 모든 아키텍처에서 동일합니다.

  • FP: Frame pointer: 인자와 지역(변수)(arguments and locals).
  • PC: Program counter: 점프와 분기(jumps and branches).
  • SB: Static base pointer: 정적 기본 포인터와 기호(global symbols).
  • SP: Stack pointer: 스택의 맨 위(top of stack).


모든 사용자-정의 기호는 유사-레지스터 FP(인자 및 로컬)와 SB(글로벌)에 대한 오프셋으로
기록됩니다.

SB 유사-레지스터는 메모리의 위치로 생각할 수 있으므로, 기호 foo(SB)는 메모리의 주소로서
foo라는 이름입니다. 이 형식은 전역 함수 및 데이터의 이름을 지정하는 데 사용됩니다.

foo<>(SB)처럼 이름에 <>를 추가하면, C 파일의 최상위 정적 선언과 같이 현재 소스 파일에만
이름이 표시됩니다.
이름에 오프셋을 추가하는 것은 기호 주소의 오프셋을 가리키므로 foo+4(SB)
foo로부터 4 바이트 떨어진 주소입니다.

FP 유사-레지스터는 함수 인자를 참조하는 데 사용되는 가상 프레임 포인터입니다.
컴파일러는 가상 프레임 포인터를 유지하고 스택의 인자를 이 유사-레지스터의 오프셋으로 참조합니다.
따라서 0(FP)는 함수에 대한 첫 번째 인자, 8(FP)은 두 번째 (64비트 시스템) 인자입니다.
그러나 이런 식으로 함수 인자를 참조할 때는 first_arg+0(FP)second_arg+8 (FP)처럼
이름을 앞에 추가해야 합니다.(프레임 포인터로부터 오프셋이라는 오프셋의 의미는 SB와 함께
사용할 때와 다릅니다. 여기서는 기호로부터의 오프셋입니다.)
어셈블러는 이 규칙을 적용하여, 이름이 빠진 0(FP)8(FP) 을 거부합니다.
실제 이름은 의미 상 관련이 없지만, 인자 이름은 문서화를 위해서 필요합니다.
FP는 하드웨어 프레임 포인터가 있는 아키텍처에서도 하드웨어 레지스터가 아니라 항상
유사-레지스터입니다.

Go 프로토타입이 있는 어셈블리 함수의 경우 go vet은 인자 이름과 오프셋이 일치하는지 확인합니다.
32 비트 시스템에서 64 비트 값의 하위 및 상위 32 비트는 arg_lo+0(FP)arg_hi+4(FP)처럼
이름에 _lo 또는 _hi 접미사를 추가하여 구분합니다.
Go 프로토타입이 리턴 결과의 이름을 지정하지 않으면 예상 어셈블리 이름은 ret 입니다.

SP 유사-레지스터는 프레임-로컬 변수와 함수 호출을 위한 인자를 가리키는 가상 스택 포인터입니다.
로컬 스택 프레임의 맨 위를 가리키므로 참조는 [-framesize, 0) 범위에서 음의 오프셋을 사용해야 합니다:
x-8(SP), y-4(SP) 처럼.

SP 라는 하드웨어 레지스터가 있는 아키텍처에서 이름 접두사는 가상 스택 포인터에 대한 참조와
아키텍처 SP 레지스터에 대한 참조를 구분합니다.
즉, x-8(SP)-8(SP)는 서로 다른 메모리 위치입니다.
첫 번째는 가상 스택 포인터 유사-레지스터를 나타내고 두 번째는 하드웨어의 SP 레지스터를 나타냅니다.

SPPC가 전통적으로 물리적인, 숫자가 매겨진, 레지스터의 별칭(alias)인 시스템에서도
Go 어셈블러에서 SPPC라는 이름을 여전히 특별하게 다룹니다.
예를 들어 SP에 대한 참조에는 FP와 같은 기호가 필요합니다. 실제 하드웨어 레지스터에 접근하려면 R 이름을 사용합니다.
예를 들어 ARM 아키텍처에서 하드웨어 SPPCR13R15 로 접근할 수 있습니다.

분기 및 직접 점프(Direct Jump)는 항상 PC에 대한 오프셋 또는 레이블에 대한 점프로 작성합니다.


label:
	MOVW $0, R1
	JMP label


각 레이블은 정의된 함수 내에서만 볼 수 있습니다.
따라서 한 파일의 여러 함수가 동일한 레이블 이름을 정의하고 사용할 수 있습니다.
직접 점프 및 호출 명령은 이름(SB)과 같은 텍스트 기호를 대상으로 할 수 있지만,
이름+4(SB)와 같은 기호의 오프셋은 대상으로 할 수 없습니다.

명령어, 레지스터 및 어셈블러 지시문(Directives)은 어셈블리 프로그래밍이 어려운 작업임을
상기시키기 위해 항상 대문자로 표기합니다.(예외로 ARM에서 이름이 바뀐 g 레지스터가 있습니다.)

Go Object 파일이나 바이너리에서, 기호의 전체 이름은 패키지 경로 뒤에 마침표와 기호 이름
(fmt.Printf 또는 math/rand.Int)가 옵니다.
어셈블러의 파서는 마침표(Period)와 슬래시(Slash)를 구두점으로 취급하므로 이러한 문자열을
식별자 이름으로 직접 사용할 수 없습니다.
대신 어셈블러는 식별자에 중간점 문자 U+00B7 및 나누기 슬래시 U+2215 를 허용하고,
일반 마침표와 슬래시로 다시 씁니다.
어셈블러 소스 파일 내에서 위의 기호는 fmt·Printfmath∕rand·Int 로 작성됩니다.
-S 플래그를 사용할 때 컴파일러에 의해 생성된 어셈블리 목록은 어셈블러에 필요한 유니코드 대체 대신 마침표와 슬래시를 직접 표시합니다.

링커가 마침표로 시작하는 이름의 시작 부분에 현재 개체 파일의 패키지 경로를 삽입하기 때문에
대부분 직접 작성한 어셈블리 파일은 기호 이름에 전체 패키지 경로를 포함하지 않습니다.
math/rand 내의 어셈블리 소스 파일 패키지 구현에서 패키지의 Int 함수는 ·Int 라고
할 수 있습니다.
이 규칙을 사용하면 패키지의 가져오기 경로를 자체 소스 코드에 하드 코딩할 필요가 없으므로
코드를 한 위치에서 다른 위치로 쉽게 이동할 수 있습니다.



지시문(Directives)

어셈블러는 다양한 지시어을 사용하여 텍스트와 데이터를 기호 이름으로 바인딩합니다.

예를 들어, 여기 간단하고 완전한 함수 정의가 있습니다.
이 TEXT 지시문은 runtime·profileloop 기호와 함수 본문을 구성하는 명령들을 선언합니다.
TEXT 블록의 마지막 명령어는 일종의 점프, 일반적으로 RET (유사-)명령어이어야 합니다.
(그렇지 않은 경우 링커는 자체적으로 점프(jump-to-itself) 명령을 추가합니다;
TEXTs에서 폴스루(fallthrough)는 없습니다.)
기호 뒤에 인자는 플래그 (아래 참조)와 프레임 크기, 상수 (아래 내용 참조)입니다.


TEXT runtime·profileloop(SB),NOSPLIT,$8
	MOVQ	$runtime·profileloop1(SB), CX
	MOVQ	CX, 0(SP)
	CALL	runtime·externalthreadhandler(SB)
	RET


일반적인 경우 인자 크기 다음에 마이너스 기호(-)로 구분되어 프레임 크기가 옵니다.
(빼기가 아니라 단지 특이한 구문입니다.)
프레임 크기 $24-8은 함수에 24 바이트 프레임이 있고, 호출자의 프레임에 있는 8 바이트의 인자로
호출된다는 의미입니다.
TEXTNOSPLIT가 지정되지 않은 경우 인자 크기를 제공해야 합니다.
Go 프로토타입이 있는 어셈블리 함수의 경우 go vet은 인자 크기가 올바른지 확인해 줍니다.

기호 이름은 중간점을 사용하여 구성요소를 구분하며 정적 기본 유사-레지스터 SB
오프셋으로 지정됩니다.
이 함수는 간단한 이름 profileloop 를 사용하여 패키지 런타임 용 Go 소스에서 호출됩니다.

글로벌 데이터 기호는 일련의 초기화 DATA 지시문과 GLOBL 지시문으로 정의됩니다.
각 DATA 지시문은 해당 메모리의 섹션을 초기화합니다.
명시적으로 초기화되지 않은 메모리는 0이 됩니다.

DATA 지시문의 일반적인 형식은 다음과 같습니다:

DATA	symbol+offset(SB)/width, value


지정된 오프셋 및 너비에서 지정된 값으로 기호 메모리를 초기화합니다.
이 기호에 대한 DATA 지시문은 증가하는 오프셋으로 작성되어야 합니다.

GLOBL 지시어는 기호를 전역으로 선언합니다.
인자는 선택적인 플래그와 전역으로 선언되는 데이터의 크기이며 DATA 지시문이 초기화하지
않는 한 초기 값은 모두 0입니다.
GLOBL 지시어는 해당 DATA 지시문을 따라야 합니다.

예를 들면, 다음 지시문은


DATA divtab<>+0x00(SB)/4, $0xf4f8fcff
DATA divtab<>+0x04(SB)/4, $0xe6eaedf0
...
DATA divtab<>+0x3c(SB)/4, $0x81828384
GLOBL divtab<>(SB), RODATA, $64

GLOBL runtime·tlsoffset(SB), NOPTR, $4


4 바이트 정수 값의 읽기 전용 64 바이트 테이블인 divtab<>을 선언 및 초기화하고,
포인터가 없는 4 바이트의 암시적으로 0 초기화된 변수인 runtime·tlsoffset을 선언합니다.

지시문에는 하나 또는 두 개의 인자가 있을 수 있습니다. 두 개가 있는 경우 첫 번째는 숫자 표현식으로 작성된 플래그의 비트 마스크로 추가하거나 OR 될 수 있고, 또는 사람이 쉽게 이해할 수 있도록 기호로 설정될 수도 있습니다. 표준 #include 파일 textflag.h에 정의된 값은 다음과 같습니다.

  • NOPROF = 1
    (TEXT 항목에 대해서) 표시된 함수를 프로파일링 하지 말 것.
    (이 플래그는 더이상 사용되지 않습니다.)
  • DUPOK = 2
    단일 바이너리에서 이 기호의 인스턴스 여러 개를 포함할 수 있음.
    링커는 중복된 것들 중 하나를 선택하여 사용함.
  • NOSPLIT = 4
    (TEXT 항목에 대해서) 스택을 분할해야 하는지 확인하기 위해 서문(Preamble)을 삽입하지 말 것.
    루틴과 내부에서 발생하는 모든 호출의 프레임은 스택 세그먼트의 맨 위로부터의 공간에 맞아야 합니다.
    스택 분할 코드처럼 루틴을 보호하기 위해 사용됩니다.
  • RODATA = 8
    (DATA 와 GLOBL 항목에 대해서) 이 데이터를 읽기 전용 섹션에 넣을 것.
  • NOPTR = 16
    (DATA 와 GLOBL 항목에 대해서) 이 데이터에는 포인터가 포함되어 있지 않으므로 가비지 컬렉터에서
    스캔할 필요가 없음.
  • WRAPPER = 32
    (TEXT 항목에 대해서) 이것은 래퍼 함수이며 recover 비활성화로 간주해서는 안됨.
  • NEEDCTXT = 64
    (TEXT 항목에 대해서) 이 함수는 클로져(Closure)이므로 들어오는 컨텍스트 레지스터를 사용함.
  • LOCAL = 128
    이 기호는 동적 공유 객체의 로컬임.
  • TLSBSS = 256
    (DATA 와 GLOBL 항목에 대해서) 이 데이터를 쓰레드 로컬 저장소(Thread Local Storage)에 저장함.
  • NOFRAME = 512
    (TEXT 항목에 대해서) 리프(Leaf) 함수가 아니더라도 스택 프레임을 할당하고 반환 주소를 저장/복원하는
    명령어를 삽입하지 말 것. 프레임 크기를 0으로 선언하는 함수에서만 유효함.
  • TOPFRAME = 2048
    (TEXT 항목에 대해서) 이 함수는 호출 스택의 맨 위에 있음. 트레이스백(Traceback) 은 이 함수에서
    멈춰야 함.



런타임 조정(Runtime Coordination)

가비지 컬렉션이 올바르게 동작하려면, 런타임이 모든 전역 데이터와 거의 모든 스택 프레임의
포인터 위치를 ​​알아야 합니다.
Go 컴파일러가 소스 파일을 컴파일할 때 이 정보를 내보내지만, 어셈블리 프로그램은 이를
명시적으로 정의해야 합니다.

NOPTR 플래그로 표시된 데이터 기호는 런타임 할당 데이터에서 포인터가 없는 것으로 처리합니다.
RODATA 플래그가 있는 데이터 기호는 읽기 전용 메모리에 할당되므로 암시적으로 NOPTR 플래그가
표시된 것으로 처리합니다.
포인터보다 전체 크기가 작은 데이터 기호도 암시적으로 NOPTR 플래그가 표시된 것으로 처리합니다.
어셈블리 소스 파일에서 포인터가 포함된 기호를 정의할 수 없습니다.
대신 이런 기호는 Go 소스 파일에 정의되어야 합니다.
어셈블리 소스는 여전히 DATAGLOBL 지시문이 없어도 이름으로 기호를 참조할 수 있습니다.
일반적인 방법은 어셈블리 대신 Go에서 모든 non-RODATA 기호를 정의하는 것입니다.

또 각 함수에는 인자, 결과 및 로컬 스택 프레임에 있는 라이브 포인터들의 위치를 알려주는 주석이
필요합니다.
어셈블리 함수에서 포인터 결과가 없거나 로컬 스택 프레임이나 함수 호출이 없는 경우 유일한 요구사항은 동일한 패키지의 Go 소스 파일에 함수에 대한 Go 프로토타입을 정의하는 것입니다.
어셈블리 함수의 이름은 패키지 이름 구성요소를 포함하지 않아야 합니다
(예를 들어, 패키지 syscall 의 함수 SyscallTEXT 지시문에서 동일한 이름
syscall·Syscall 대신 Syscall을 사용해야 합니다).
더 복잡한 상황에서 명시적인 주석이 필요할 수 있습니다.
이러한 주석은 표준 #include 파일 funcdata.h 에 정의된 유사-명령어를 사용합니다.

함수에 인자가 없고 결과가 없는 경우 포인터 정보를 생략할 수 있습니다.
이 경우 TEXT 명령어에서 $n-0 의 인자 크기 주석으로 표시됩니다.
그렇지 않으면 Go에서 직접 호출되지 않는 어셈블리 함수이라도 Go 소스 파일에서 함수에 대한
Go 프로토타입에 포인터 정보가 제공되어야 합니다. (프로토타입은 go vet이 인자 참조를 확인하는 것을 허용합니다.)
함수 시작시 인자는 초기화된 것으로 간주하지만 결과는 초기화되지 않은 것으로 간주합니다.
call 명령 중 결과에 라이브 포인터가 있는 경우, 함수는 결과를 0으로 설정한 다음 유사-명령어
GO_RESULTS_INITIALIZED 를 실행하는 것으로 시작해야 합니다.
이 명령어는 결과가 이제 초기화되었으며 스택 이동 및 가비지 수집 중에 스캔되어야 함을 기록합니다.
일반적으로 어셈블리 함수가 포인터를 반환하지 않거나 call 명령을 포함하지 않도록 정렬하는 것이 더 쉽습니다. 표준 라이브러리의 어셈블리 함수는 GO_RESULTS_INITIALIZED 를 사용하지 않습니다.

함수에 로컬 스택 프레임이 없는 경우 포인터 정보를 생략할 수 있습니다.
이는 TEXT 명령어에서 $0-n 의 로컬 프레임 크기 주석으로 표시됩니다.
함수에 호출 명령이 없는 경우에도 포인터 정보를 생략할 수 있습니다.
그렇지 않으면 로컬 스택 프레임에 포인터가 없어야 하며 어셈블리는 유사-명령어 NO_LOCAL_POINTERS 를 실행하여 이 사실을 확인해야 합니다. 스택 크기 조정은 스택 이동으로 구현되기 때문에 함수 호출 중에 스택 포인터가 변경될 수 있습니다. 스택 데이터에 대한 포인터도 로컬 변수에 보관해서는 안됩니다.

인수 및 결과에 대한 포인터 정보를 제공하고 go vet이 이들에 접근하는 데 사용되는 오프셋이 올바른 지 확인할 수 있도록, 어셈블리 함수에 항상 Go 프로토타입을 제공해야 합니다.




아키텍처 별 세부 정보

각 컴퓨터에 대한 모든 명령어들과 기타 세부 정보를 리스팅하는 것은 비현실적입니다.
ARM과 같이 특정 머신에 대해 정의된 명령어는 src/cmd/internal/obj/arm 디렉토리에
해당 아키텍처에 대한 obj 지원 라이브러리의 소스에 있습니다.
그 디렉토리에는 a.out.go 파일이 있습니다; 다음과 같이 A로 시작하는 긴 상수 목록을 포함하고 있습니다.


const (
	AAND = obj.ABaseARM + obj.A_ARCHSPECIFIC + iota
	AEOR
	ASUB
	ARSB
	AADD
	...


이는 해당 아키텍처에 대한 어셈블러 및 링커에 알려진 명령어 및 철자 목록입니다.
각 명령어는 이 목록에서 초기 대문자 A로 시작하므로 AAND 는 비트 및 명령어 AND(선행 A 제외)를 나타내며 어셈블리 소스에서 AND로 작성됩니다. 열거형은 대부분 알파벳 순입니다.(cmd/internal/obj 패키지에 정의된 아키텍처 독립적인 AXXX는 잘못된 명령어를 나타냅니다.)
A 이름의 순서는 기계 명령어의 실제 인코딩과 관련이 없습니다.
cmd/internal/obj 패키지가 세부 사항을 처리합니다.

386 및 AMD64 아키텍처에 대한 명령어들은 cmd/internal/obj/x86/a.out.go 에 리스팅되어 있습니다.

아키텍처는 (R1)(레지스터 간접), 4(R1) (레지스터 간접 + 오프셋) 및 $foo(SB)(절대 주소)와 같은 공통 주소 지정 모드(Common Address Mode)에 대한 구문을 공유합니다.
어셈블러는 또한 각 아키텍처에 특정한 일부 (전부는 아님) 주소 지정 모드도 지원합니다.
아래 섹션에 이러한 항목이 리스팅되어 있습니다.

이전 섹션의 예제에서 분명한 한 가지 세부 사항은 명령의 데이터가 왼쪽에서 오른쪽으로 흐른다는 것입니다.
MOVQ $ 0, CXCX를 지웁니다. 이 규칙은 기존 표기법이 반대 방향을 사용하는 아키텍처에도 적용됩니다.

지원되는 아키텍처에 대한 주요 Go 관련 세부 정보에 대한 설명은 다음 기회에 정리하겠습니다.




참고자료

Gloang

Assembly

댓글남기기