Scenario analysis of go language scheduler source code fifth: assembly instructions

The following content is reproduced from  https://mp.weixin.qq.com/s/fuox6st_iXg_rpklxWXXRA

Awa love to write original programs Zhang  source Travels  2019-04-20

Assembly language is a language that every back-end programmer should master. Because you have learned assembly language, it is very important for us to debug programs or to study and understand some of the underlying operating principles of the computer, so it is recommended to be interested Readers can take more time to learn it well.

Like high-level programming languages, assembly language is also a complete computer programming language, and it involves a lot of knowledge content. Fortunately, our main goal is to be able to read and understand assembly code through the study of this section, not Use assembly language to write code, so this section will not fully introduce assembly language, but only select a subset of assembly language-assembly instructions to introduce. However, although the introduction here has been simplified, readers can rest assured that skilled use of this knowledge is enough to cope with the assembly code in the goroutine scheduler that this book will analyze.

Speaking of assembly instructions, I have to mention machine instructions. The binary format of machine instructions is the language that the CPU can understand. Because it is in the binary format, it is very convenient for the CPU to parse and execute, but it is not conducive to human reading and communication, so The assembly instructions that correspond to the machine instructions one-to-one are available. The assembly instructions use symbols to represent machine instructions. The following example is very intuitive to illustrate the difference between these two instructions:

0x40054d: add %rdx,%rax // assembly instructions 

(gdb) x/3xb 0x40054d 
0x40054d: 0x48 0x01 0xd0 // machine instructions 
(gdb)

The same is to add the values ​​in the rdx and rax registers. The assembly instruction is: add %rdx, %rax, while the machine instruction is three numbers: 0x48 0x01 0xd0. Obviously, the assembly instruction is more friendly to humans, and it is more Easy to remember, easy to read and easy to write.

Assembly instruction format

Because different CPUs support different machine instructions, their assembly instructions are also different. Even for the same CPU, different assembly tools and platforms use different assembly instruction formats. Because this book focuses on AMD64 Linux platform Under the go scheduler, we only introduce the AT&T format assembly instructions used under this platform. The basic format of AT&T assembly instructions is:

Opcode   [ operand ]

You can see that each assembly instruction usually consists of two parts:

  • Opcode : The opcode tells the CPU what operation to perform, such as performing addition, subtraction, or reading and writing memory. Every instruction must have an opcode.

  • Operand : The operand is the object of the operation. For example, an addition operation requires two addends, and these two addends are the operands of this instruction. The number of operands is generally 0, 1, or 2.

Look at some examples of assembly instructions

add   %rdx,%rax

The opcode of this instruction is add, which means to perform an addition operation. It has two operands, rdx and rax. If an instruction has two operands, then the first operand is called the source operand, and the second operand is called the destination operand. As the name suggests, the destination operand represents the place where the result of this instruction should be saved after execution. So the above instruction means to sum the values ​​in the rax and rdx registers, and save the result in the rax register. In fact, the second operand rax register of this instruction is both the source operand and the destination operand, because rax is not only one of the two addends of the addition operation, but also the result of the addition operation. After this instruction is executed, the value of the rax register has changed. The value before the instruction is overwritten and lost. If the previous value of the rax register is still useful, then the instruction must be used to save it to other registers or memory. .

Let's look at an example with only one operand:

callq  0x400526  

The opcode of this instruction is callq, which means the function is called, and the operand is 0x400526, which is the address of the called function.

Finally, let's look at an instruction without operands:

retq

This instruction has only the opcode retq, which means that the execution continues from the called function to the calling function.

In order to better understand the AT&T format assembly instructions, here is a brief description of its format:

1. In AT&T format assembly instructions, the register name needs to be prefixed with %, which we have seen before;

2. In an instruction with two operands, the first operand is the source operand, and the second is the destination operand. I have discussed it just now, but the source and destination in that instruction are not so clear. Let’s look at one To be straightforward, mov %eax,%esi, this instruction means to copy the value in the eax register to esi, the source and destination of this instruction are very clear;

3. The immediate operand needs to be prefixed with a $ sign, such as "mov $0x1 %rdi". The first operand in this instruction is not a register or a memory address, but a constant written directly in the instruction. This kind of operand is called immediate operand . This instruction means to put the value 0x1 into the rdi register.

4. The format of register indirect addressing is offset (%register) . If offset is 0, you can omit the offset and write it directly as (%register) without writing. What is indirect addressing? In fact, it means that the register in the instruction is not the real source operand or the destination operand. The value of the register is a memory address. The memory corresponding to this address is the real source or destination operand, such as mov %rax, (%rsp ) In this instruction, the name of the register in the second operand (%rsp) is enclosed in parentheses, indicating indirect addressing. The value of rsp is a memory address. The real intention of this instruction is to change the value of the rax register The value is assigned to the memory corresponding to the value of the rsp register (memory address). The value of the rsp register itself will not be modified. For comparison, let’s look at the mov %rax, %rsp instruction. Here the second operand is only missing The parentheses have become direct addressing, and the meaning is completely different. This instruction means to assign the value of rax to rsp, so that the value of the rsp register is modified to the same value as the rax register. The following two pictures show the difference between these two addressing modes:

image

Before executing the instruction mov %rax, %rsp, the value of the rsp register is x and the value of the rax register is y. After the instruction is executed, the value of the rax register is copied to the rsp register, so the value of the rsp register becomes y It can be seen that when the direct addressing mode is adopted, the value of the destination operand rsp register has changed before and after the instruction is executed, and the source operand has not changed. Look at the schematic diagram of indirect addressing:

 

image

 

Before executing the mov %rax, (%rsp) instruction, the value of the rax register is y, and the value of the rsp register is X, which is a memory address, as shown in the figure above, we used a red arrow to point from the rsp register The memory address is X; after the instruction is executed, the value of the rsp register has not changed, but the value in the memory pointed to by rsp has changed, because the destination operand of this instruction uses indirect addressing (%rsp ), the result of the instruction execution is that the value in the rax register is copied to the 8 memory units corresponding to the address stored in the rsp register. In addition, it should be noted that the memory address appearing in the instruction is only the starting address. The specific operation of several consecutive memory units with this address as the starting address depends on the specific instruction, such as mov %rax, in the figure above. (%rsp), because the source operand is a 64-bit register, this instruction will copy the 8 bytes stored in rax to addresses X, X+1, X+2, X+3, X+4, X+5, X+6, X+7 these 8 memory cells.

 

The previous offset in the indirect addressing format offset (%register) means the offset, such as -0x8 (%rbp), -0x8 is the offset, the whole means that the address value saved in the rbp register is first minus 8 (because the offset is Negative 8) The memory corresponding to the obtained address.

 

5.  The opcodes of some instructions related to memory will add b, w, l and q letters to indicate whether the operating memory is 1, 2, 4 or 8 bytes, such as the instruction movl $0x0,-0x8(%rbp ), the suffix letter l of this instruction opcode movl indicates that we want to assign 0 to the 4 memory units starting from the address -0x8 (%rbp). Some readers may ask, what if I want to operate 3 or 5 memory cells? It is a pity that the cpu does not provide the corresponding single instruction, we can only achieve the goal by combining multiple instructions.

 

Detailed explanation of common commands

 

There are thousands of x86-64 assembly instructions. I will not explain each one in detail here. Readers can refer to the assembly language tutorials if they are interested. We are here to focus on a few very common instructions or can help us understand the operating mechanism of the program.

 

  • mov instruction

Operand of mov source operation number

This instruction copies the source operand to the destination operand. example:

mov %rsp,%rbp # Direct addressing, copy the value of rsp to rbp, equivalent to rbp = rsp 
mov -0x8(%rbp),%edx # Indirect addressing of the source operand, direct addressing of the destination operand. Read 4 bytes from the memory to the edx register 
mov %rsi,-0x8(%rbp) # The source operand is directly addressed, and the destination operand is indirectly addressed. Write the 8-byte value in the rsi register to the memory
  • add/sub instructions

add operand of the number of 
source operations sub operand of the number of source operations

Addition and subtraction instructions. example:

sub $0x350,%rsp # The source operand is an immediate operand, and the destination operand is directly addressed. rsp = rsp-0x350 
add %rdx,%rax # Direct addressing. rax = rax + rdx 
addl $0x1,-0x8(%rbp) # The source operand is the immediate operand, and the destination operand is indirectly addressed. The value in the memory is increased by 1 (addl suffix letter l represents the operation of 4 bytes in the memory)
  • call/ret instruction

call target address 
ret

The call instruction performs a function call. When the CPU executes the call instruction, it first puts the value in the rip register into the stack, and then sets the rip value to the target address. Because the rip register determines the next instruction to be executed , it will jump when the CPU finishes executing the current call instruction. Go to the target address to execute.

The ret instruction returns to the calling function from the called function. Its realization principle is to pop the return address of the call instruction into the stack to the rip register.

The following examples illustrate the principles of these two instructions.


#Calling function fragment 0x0000000000400559: callq 0x400526 <sum> 
0x000000000040055e: mov %eax,-0x4(%rbp) #Called 

function fragment 
0x0000000000400526: push %rbp 
...... 
0x000000000040053f: retq  

In the above code snippet, the calling function uses the callq 0x400526 instruction to call the function at 0x400526, and 0x400526 is the address of the first instruction of the called function. The called function executes the retq instruction at 0x40053f and returns to the calling function to continue executing the instruction at the address 0x40055e. Note that these two instructions will involve stacking and popping operations, so they will affect the value of the rsp register.

image

From the figure above, you can see that at the beginning of the call instruction execution, the value of the rip register is the address of the instruction immediately following the call, that is, 0x40055e, but when the call instruction is completed but before the next instruction is executed, the value of the rip register changes It becomes the operand of the call instruction, that is, the address of the called function is 0x400526, so the CPU will jump to the called function to execute.

At the same time, it should be noted that when the call instruction is executed, the address 0x40055e of the instruction after the call instruction is PUSH onto the stack, so a call instruction modifies the value of 3 places: the rip register, rsp and the stack .

Let's take a look at the ret instruction that is executed when the called function returns from the called function. The schematic diagram is as follows:

image

It can be seen that the operation performed by the ret instruction is completely opposite to the operation performed by the call instruction. When the ret instruction starts to execute, the value of the rip register is the address immediately after the ret instruction, which is 0x400540, but the previous call will be called during the execution of the ret instruction. The return address 0x40055e POP of the instruction PUSH to the stack is given to the rip register, so that when the ret execution is completed, it will return from the called function to the next instruction of the call instruction of the calling function to continue execution. It should also be noted here that the retq instruction will also modify the value of the rsp register.

  • jmp/je/jle/jg/jge, etc. instructions beginning with j

These are all jump instructions. The address to jump to or the register with the address is directly followed by the opcode. These instructions correspond to statements such as goto and if in high-level programming languages. Example usage:

jmp 0x4005f2 
jle 0x4005ee 
jl 0x4005b8
  • push/pop instructions

push source operand 
pop destination operand

Dedicated to the stack and pop instructions of the function call stack, these two instructions will automatically modify the rsp register .

When pushing onto the stack, the value of the rsp register is first subtracted by 8 to save the stack position, and then the operand is copied to the position pointed to by rsp. The push instruction is equivalent to:

sub $8,%rsp 
mov source operand, (%rsp)

image

 

The push instruction needs to pay attention to the changes of the rsp register.

When pop is popped from the stack, the data at the location pointed to by the rsp register is copied to the destination operand, and then the value of the rsp register is increased by 8. The pop instruction is equivalent to:

mov (%rsp), destination operand 
add $8,%rsp

image

 

 

Similarly, the pop instruction also needs to pay attention to the changes in the rsp register.

 

  • leave instruction



     

The leave instruction has no operands. It is generally placed before the ret instruction at the end of the function to adjust rsp and rbp. This instruction is equivalent to the following two instructions:

mov %rbp,%rsp
pop %rbp

 

We will introduce so much AMD64 assembly. In the next section, we will introduce the go assembly language used in go runtime. It is similar to the AMD64 assembly introduced here, but there are some differences. After understanding the content of this section, go assembly is easy to understand.

Guess you like

Origin blog.csdn.net/pyf09/article/details/115219384