Quick Guide to Golang Assembly

Source: Internet
Author: User
Tags uppercase letter
This is a creation in Article, where the information may have evolved or changed.

This article is translated from Golang Official document, the original address: Https://golang.org/doc/asm

This document is for Go compiler suite (6g, 8g, etc.) A quick preview of the less commonly used assembly language, covering the surface is not very extensive.

The assembly language of Go is based on the compilation of Plan 9, which is described in detail on the page of Plan 9. If you want to write assembly language, you should read this document, although it is related to plan 9. This document summarizes the syntax of the compilation and describes the special use of assembly language and Go program interaction.

It is important to note that there is no direct representation of the underlying machine in the go assembly. Some assembly details can correspond directly to the machine, but some are not. This is because the compiler suite does not require assembly language during the general process. Instead, the compiler produces a binary, incomplete assembly instruction set that the linker will complete. In fact, the linker does a selection of assembly instructions, so when you see an instruction like MOV , the actual operation of the linker may not be a moving instruction, perhaps a purge or load. Or it may correspond to the actual machine instruction according to the name of the instruction. In general, machine-related instruction operations tend to reflect real machine instructions, but some common concepts like moving memory data, calling subroutines, and returning operations are more abstract. We apologise for the inaccuracy of the specifics and the architecture.

Assembler is a method of generating an intermediate code that is not a fully defined instruction set as input to the linker. If you want to see the assembly instruction set under a specific CPU architecture, such as AMD64, there are many examples in the source files of the Go standard library, in the runtime and Math/big packages. Alternatively, you can check the compiler's assembly output by referencing the following program:

$ cat x.gopackage mainfunc main() {    println(3)}$ go tool 6g -S x.go        # or: go build -gcflags -S x.go--- prog list "main" ---0000 (x.go:3) TEXT    main+0(SB),$8-00001 (x.go:3) FUNCDATA $0,gcargs·0+0(SB)0002 (x.go:3) FUNCDATA $1,gclocals·0+0(SB)0003 (x.go:4) MOVQ    $3,(SP)0004 (x.go:4) PCDATA  $0,$80005 (x.go:4) CALL    ,runtime.printint+0(SB)0006 (x.go:4) PCDATA  $0,$-10007 (x.go:4) PCDATA  $0,$00008 (x.go:4) CALL    ,runtime.printnl+0(SB)0009 (x.go:4) PCDATA  $0,$-10010 (x.go:5) RET     ,...

The funcdata and PCDATA directives are used to contain information that is required by the garbage collector. They are generated by the compiler.

Symbol

Some symbols, such as PCs,R0 , and SPS, are predefined and are references to a register. There are also two predefined symbols,SB(static base) and FP(frame pointer). All user-defined symbols, in addition to tag jumps, are offsets operations on pseudo-registers.

The SB pseudo register can be imagined as a memory address, so the symbol foo (SB) is a memory address represented by the name foo . This form is commonly used to name global functions and data. Adding a <> symbol to the name, like foo<> (SB), will make the name only visible in the current file, just like a predefined staticin the C file.

The FP pseudo-register is a virtual frame pointer used to point to the function's parameters. The compiler maintains a virtual stack pointer, using the form of the offsets operation on the pseudo register, pointing to the function parameters on the stack. So,0 (FP) is the first parameter,8 (FP) is the second (64-bit machine), and so on. When referencing a function parameter in this way, it is convenient to precede the symbol with a name, like first_arg+0 (FP) and second_arg+8 (FP). Some assembler enforces this convention by prohibiting a single 0 (FP) and 8 (FP). In the assembler function defined by the GO standard,go vet checks the names of the parameters and their matching ranges. On 32-bit systems, a high 32 and a low 32 bits of a 64-bit value are represented by the addition of the two suffix of _lo and _hi to a name, like arg_lo+0 (FP) or arg_hi+4 ( FP). If a go prototype function does not name its result, the expected name will be returned.

The SP Pseudo Register is a virtual stack pointer that points to the local variables of the stack frame and prepares the parameters for the function call . It points to the top of the local stack frame, so a reference to the stack frame must be a negative value and range between [-framesize:0] , for example: x-8 (sp),y-4 (SP), and so on. In the CPU architecture, there is a real register SP, the difference between the virtual stack register and the real SP Register is the prefix of the name. That is,x-8 (sp) and -8 (sp) are different memory addresses: The former refers to pseudo-stack pointer registers, but the latter is the actual SP registers in the hardware.

directives, registers, and assembly instructions are always expressed in uppercase letters, reminding you that assembly language programming is very worrying. (Exception: Under the ARM platform, the G register representing the current goroutine is renamed.) )

In the Go object file and binary, the full name of the symbol is the path of the package plus a period:FMT. Printf or math/rand. Int. But the assembler treats periods and slashes as punctuation marks, which cannot be used as identifiers for symbols. Instead, the midpoint character (Unicode character 00b7) and the division slash are allowed in the assembler (the original is division Slash,unicode character 2215, which is different from forward Slash) as identifiers and rewrite them as pure periods and slashes. In the assembly language source file, the above symbol is written FMT Printf and Math∕rand. Int. The period and slash are displayed directly in the assembly code list that is seen with the- s flag at compile time, instead of the Unicode substitution characters (referred to as the two special Unicode characters above) that are required in the assembler.

In most handwritten assembly files, do not include the full package path in the symbol name, because the linker inserts the path to the current object file before any name that begins with a period: In the assembly source file for the Math/rand package, the Int function of the RAND package is treated as a Int to reference. This convenience avoids the need to hardcode the import path in its own source code, making it easier to move code from one place to another.

Instructions

The assembler uses a variety of directives to bind text and data to symbol names. For example, here is a simple but complete function definition. The TEXT directive declares the symbol runtime Profileloop, which is immediately in the body similar to the function. The end of the TEXT block must be some form of jump, usually a RET(pseudo) instruction. (if not, the linker appends an instruction that jumps to the block itself, theTEXT block does not have a fallthrough) after the symbol, the parameter is the size of the flag and the stack frame , is a constant (but look at the following code):

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

The stack frame size of this function is 8 bytes (movq CX, 0 (SP) Operation stack pointer), no parameters

In general, the size of the stack frame follows the size of the parameter, separated by a subtraction symbol. (It's not a minus sign, just a special syntax) the stack frame size is $24-8 describes the function with a 24-byte stack frame and requires a 8-byte parameter that exists in the caller's stack frame. If the nosplit flag is not specified for TEXT , the parameter size must be supplied. In the assembler function defined by the Go standard, go vet checks to see if the parameter size is correct.

Note that the symbol name is used to split the component with a midpoint and is defined as a offsets that begins with the pseudo register SB . In the Go source Runtime package, use the abbreviation profileloop to call.

Global data symbols are defined using a series of initialized data directives, followed by a global directive. Each data instruction Initializes a chunk of the specified memory area. Areas of memory that are not explicitly initialized are zeroed. The standard data instruction form is:

DATA    symbol+offset(SB)/width, value

This initializes the symbol, with the memory at the specified offset, with the specified width and the given value. The data directive in a symbol must be a growing offsets.

The global directive declares a symbol to be global. The parameter is an optional flag and the size of the data that needs to be declared as global, and is initialized to a value of 0 unless it has already been initialized in the data directive. The Global directive must follow the corresponding data directive.

Example:

DATA divtab<>+0x00(SB)/4, $0xf4f8fcffDATA divtab<>+0x04(SB)/4, $0xe6eaedf0...DATA divtab<>+0x3c(SB)/4, $0x81828384GLOBL divtab<>(SB), RODATA, $64GLOBL runtime·tlsoffset(SB), NOPTR, $4

Declares and initializes the divtab<>, a read-only 64-bit table that contains a 4-byte integer value. and declares a value of runtime Tlsoffset, a 4-byte and explicitly initialized by a value of 0, which does not contain pointers.

An instruction can contain one or two parameters. If there are two parameters, the first is the bit mask flag, can be written as a number expression, multiple masks can be added or done in logic or arithmetic, or can be written in a friendly and readable form. These values are defined in the header file textflag.h :

    • Noprof = 1 (text item used.) Functions that do not optimize noprof tags. This sign is obsolete.

    • Dupok = 2 allows multiple instances of a symbol in a binary file. The linker chooses one of them.

    • Nosplit = 4 (text item used.) Does not insert code that pre-detects whether to split the stack space. In the stack frame of the program, if any other code is called to increase the size of the stack frame, you must leave free space at the top of the stack. Used to protect the code itself that handles the splitting of the stack space.

    • Rodata = 8 (data and global entries are used.) Put this data in a read-only block.

    • Noptr = 16 This data does not contain pointers so there is no need for the garbage collector to scan.

    • WRAPPER = + (for TEXT items.) This was a wrapper function and should not count as disabling recover.

Coordinating runtime

In order for garbage collection to run correctly, the runtime must know the position of the pointer in the global data and most stack frames. The go compiler generates this information when compiling the go source file, but this information must be clearly defined in the assembler.

A data symbol with a noptr flag that does not contain pointers to data assigned by runtime. The data symbol with the rodata flag is allocated in read-only memory and is considered to be a well-defined noptr type of data. The total size is less than a pointer-sized data symbol and is also considered a well-defined noptr type. Symbols containing pointers cannot be defined in assembly language; Instead, symbols must be defined in the go source file. The assembly source file can still refer to a symbol by its name, even if the symbol is not defined using the data and global directives. A good general rule is to define non-read-only data in go code, not in assembler.

Each function also needs to give a note indicating the position of the pointer to its parameters, the returned result, and the local stack frame. If the assembly function does not have the result of a pointer type and there is no local stack frame, or if the function is not called, the only thing that needs to be done is to define a go function prototype for the function in a package of the same name. In more complex cases, explicit annotations are required. These annotations use pseudo-directives that are defined in the header file funcdata.h .

If a function has no parameters and no results are returned, the pointer information can be ignored. This can be indicated by using the parameter size $n-0 in the text directive. Otherwise, the Go prototype function in the go original file must provide pointers to information, even if the assembler function is not directly called by the go code. (This prototype will let go vet check for parameter references.) At the beginning of the function, the arguments are assumed to have been initialized, but the return result of the function is assumed to be uninitialized. If a pointer is hold in the result when the call command is executed, the function should initialize the returned result to a value of 0 at the beginning, and then execute the pseudo-instruction go_results_initialized. This instruction records that the current return result has been initialized and the scan returns results when the stack frame is transferred and garbage collected. It is very representative that the assembler function does not return a pointer or does not contain any call instructions; the assembler function in the GO standard library does not use go_results_initialized.

If a function does not have a local stack frame, it can ignore the pointer information. This can be indicated by using the stack frame size in the text instruction $0-n . If the function does not contain a call instruction, the pointer information can also be ignored. Otherwise, the local stack frame must not contain a pointer (in case the function does not have a local stack frame and contains a call instruction), which must be confirmed by No_local_pointers in the assembly. Because the stack's scaling is implemented using the move stack, the stack pointer may change when the function is called: Even the pointer to the stack data must not remain in the local variable.

Architecture-related Details

It is impractical to list all the instructions and details of a machine. If you want to see instructions for a particular machine, such as 32-bit Intel X86, look at the header file for the top level of the corresponding editor, here is 8l. That is, in the file $GOROOT/src/cmd/8l/8.out.h contains the C enumeration, called as, is the specification of the assembler of the schema and the instructions of the linker's machine instructions.

enum    as{    AXXX,    AAAA,    AAAD,    AAAM,    AAAS,    AADCB,    ...

In the preceding code, each instruction begins with an uppercase letter A, so AADCB represents the ADCB instruction (and the carry byte). The enumerator is sorted alphabetically, plus the appended content (Axxx occupies the No. 0 position and is treated as a separate instruction). For coding in the actual machine, these sequence of instructions need not be changed. Again, this is because the linker is responsible for the specifics.

In the example in the previous section, it is important to note that the order of the data in the instruction is left to right: movq $, CX clears CX. This rule applies even if the order is reversed on some schemas.

Here are some details about the architecture that go refers to.

32-bit Intel 386

The pointer to the G struct (goroutine) in runtime is maintained by other unused registers in the MMU (which is also feared in Golang). If the source contains a schema-related header file, the assembler defines an OS-related macro as follows:

#include "zasm_GOOS_GOARCH.h"

Inside runtime, theGET_TLS macro loads the G pointer into its parameter register, and the G struct contains the m pointer. The sequence of instructions to load g and m using the CX register is as follows:

get_tls(CX)MOVL    g(CX), AX     // Move g into AX.MOVL    g_m(AX), BX   // Move g->m into BX.

64-bit Intel 386 (AMD64)

The assembly that accesses the G and m pointers is similar to the 386, except that the instruction uses movqinstead of movl:

get_tls(CX)MOVQ    g(CX), AX     // Move g into AX.MOVQ    g_m(AX), BX   // Move g->m into BX.

Arm

Registers R10 and R11 are reserved by compilers and linker.

R10 points to the G (goroutine) structure. In the compiled source code, this pointer must be referenced by G ,R10 The name is not recognized.

To make it easier for humans and compilers to write assembly code, ARM's linker allows for generic addressing forms and pseudo-operations like DIV,MOD , which may not be represented by a single instruction. The linker uses multiple directives to implement these operations, often using R11 to hold temporary values. The R11 Register can be used in a handwritten assembler, but doing so requires confirming that the linker has not yet used R11 to implement the other instructions in the function.

When defining a TEXT segment, declaring the stack frame size $-4 tells the linker that this function is a leaf functions and does not need to hold the LR register at the portal.

Explanation of the leaf function:

Leaf function,A function that does not require a stack frame. A leaf function does not require a function table entry. It cannot call any functions, allocate space, or save any nonvolatile registers. It can leave the stack unaligned while it executes.

The name SP always refers to the previously mentioned virtual stack frame. The SP registers in the hardware use R13.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.