Before a series of articles introduced from the aspects of CPU and memory the assembly language, but also no systematic understanding of the assembly language, assembly language as a second generation of computer language, can use some letters, easy to understand and memory word instead of a specific instruction, as the basis of high-level programming language, it is necessary to know the assembly language of the system, and So this article I hope you come with me to understand assembly language.

Assembly language and native code

As we discussed in previous articles, computer CPUS can only run native code (machine language) programs. Code written in high-level languages such as C needs to be compiled by the compiler and converted into native code before it can be interpreted and executed by the CPU.

But native code is very unreadable, so it needs to be replaced with a language that can be read directly. That is, in each native code, with an abbreviation for what it does. For example, add the abbreviation of Add (addition) in the local code of the addition operation, add the abbreviation of CMP (compare) in the local code of the comparison operator, etc., these symbols that express specific local code instructions through abbreviations are called mnemonics, and the language using mnemonics is called assembly language. In this way, by reading assembly language, you can also understand the meaning of native code.

However, even the source code written in assembly language must eventually be converted to native code before it can be run. The program responsible for doing this is called a compiler, and the conversion process is called assembly. An assembler and a compiler are the same in their ability to convert source code into native code.

Source code written in assembly language corresponds to native code one to one. Thus, native code can also be converted to code written in assembly language. The process of converting native code into assembly code is called disassembly, and the program that performs disassembly is called a disassembler.

Even source code written in C is converted to native code for a particular CPU when compiled. By disassembling it, you can obtain the source code of the assembly language and investigate its contents. However, converting native code into C source code decompilation is more difficult than converting native code into assembly code disassembly, because C code and native code are not one-to-one correspondence.

Through the compiler output assembly language source code

We mentioned above that native code can be disassembled into assembly code, but is this the only conversion? Obviously not. Source code written in C can also be compiled by a compiler called assembly code. Let’s try it out.

First you need to do some preparation first need to download the Borland c + + 5.5 compiler, for convenience, my side directly download the reader can be directly extracted from my baidu network location (link: pan.baidu.com/s/19LqVICpn… Password: hz1u)

Download is completed, need to be configured, the following is the configuration instructions (wenku.baidu.com/view/22e2f4…

First, use Windows Notepad and other text editors to write the following code

// A function that returns the sum of the values of two arguments
int AddNum(int a,int b){
  return a + b;
}

// Call the function of AddNum
void MyFunc(a){
  int c;
  c = AddNum(123.456);
}
Copy the code

After writing, save the file name as sample4.c, the extension of the C language source file, usually represented by.C, the above program is to provide two input parameters and return the sum of them.

In Windows, open the command prompt, switch to the folder where sample4.c is saved, and type in the command prompt

bcc32 -c -S Sample4.c
Copy the code

Bcc32 is the command to start Borland C++, the -c option is to compile only without linking, and the -s option is used to specify the source code to generate assembly language

As a result of compilation, an assembly language source named sample4.asm is generated in the current directory. The extension of the assembly language source file, usually represented by.asm, will be opened in the editor to look at the contents of Sample4.asm

.386p ifdef ?? version if ?? version GT 500H .mmx endif endif model flat ifndef ?? version ? debug macro endm endif ? debug S "Sample4.c" ? debug T "Sample4.c" _TEXT segment dword public use32 'CODE' _TEXT ends _DATA segment dword public use32 'DATA' _DATA ends _BSS segment dword public use32 'BSS' _BSS ends DGROUP group _BSS,_DATA _TEXT segment dword public use32 'CODE' _AddNum proc near ? live1@0: ; ; int AddNum(int a,int b){ ; push ebp mov ebp,esp ; ; ; return a + b; ; @1: mov eax,dword ptr [ebp+8] add eax,dword ptr [ebp+12] ; ; }; @3: @2: pop ebp ret _AddNum endp _MyFunc proc near ? live1@48: ; ; void MyFunc(){ ; push ebp mov ebp,esp ; ; int c; ; C = AddNum (123456); ; @4: push 456 push 123 call _AddNum add esp,8 ; ; }; @5: pop ebp ret _MyFunc endp _TEXT ends public _AddNum public _MyFunc ? debug D "Sample4.c" 20343 45835 endCopy the code

Thus, the compiler has successfully converted C into assembly code.

A pseudoinstruction that does not convert native code

While assembly code may seem difficult to first-time readers, it is actually relatively simple, and perhaps even simpler than C. There are a few points to note in order to read the source code for assembly code

The source code of assembly language is made up of instructions to convert native code (opcodes described below) and pseudo-instructions to the assembler. The pseudo-instruction is responsible for the construction of the program and assembly method to indicate to the assembler (converter). However, pseudoinstructions cannot be converted into native code by assembly. Here is the pseudo-instruction captured by the above program

_TEXT segment dword public use32 'CODE' _TEXT ends _DATA segment dword public use32 'DATA' _DATA ends _BSS segment dword  public use32 'BSS' _BSS ends DGROUP group _BSS,_DATA _AddNum proc near _AddNum endp _MyFunc proc near _MyFunc endp _TEXT ends endCopy the code

The part enclosed by the segment and ends directives is called a segment definition by adding a name to the collection of commands and data that make up the program. The English expression of section definition has the meaning of region. In this program, section definition refers to the collection of programs such as commands and data. A program consists of several section definitions.

At the beginning of the above code, three segment definitions are defined with names _TEXT, _DATA, and _BSS. _TEXT is the specified segment definition, _DATA is the segment definition of the data initialized (with an initial value), and _BSS is the segment definition of the data that has not been initialized. The name of this definition is defined by Borland C++ and is automatically assigned by the Borland C++ compiler, so the sequence of segment definitions is _TEXT, _DATA, _BSS, which also ensures memory continuity

_TEXT segment dword public use32 'CODE' _TEXT ends _DATA segment dword public use32 'DATA' _DATA ends _BSS segment dword  public use32 'BSS' _BSS endsCopy the code

A segment is used to distinguish or divide areas of scope. The segment directive in assembly language indicates the beginning of a segment definition, and the ENDS directive indicates the end of a segment definition. A segment definition is a contiguous segment of memory

The pseudo-instruction group indicates that the two segments _BSS and _DATA are defined to summarize the group named DGROUP

DGROUP	group	_BSS,_DATA
Copy the code

_TEXT segment and _TEXT ends enclose _AddNum and _MyFun as defined by _TEXT.

_TEXT	segment dword public use32 'CODE'
_TEXT	ends
Copy the code

Thus, even if instructions and data are jumbled up in source code, they are translated into neat native code after compilation and assembly.

The portions enclosed by _AddNum proc and _AddNum endp, and the portions enclosed by _MyFunc proc and _MyFunc endp represent the ranges of AddNum and MyFunc functions, respectively.

_AddNum	proc	near
_AddNum	endp

_MyFunc	proc	near
_MyFunc	endp
Copy the code

Borland C++ specifies that function names are preceded by an underscore _ after compilation. AddNum functions written in C are internally handled by the name _AddNum. The section enclosed by the proc and ENDP directives represents the scope of the procedure. In assembly language, this C equivalent of a function is called a procedure.

At the end of the end directive, indicating the end of source code.

The syntax of assembly language is opcode + operand

In assembly language, a line represents an instruction for a pair of cpus. The syntax structure of assembly language instructions is opcode + operand, there are also only opcode without operand instructions.

Opcodes represent instruction actions, and operands represent instruction objects. An opcode used with an operand is an English instruction. For example, from the perspective of English grammar, opcodes are verbs and operands are objects. For example, in the English command “Give me money”, “Give” is the opcode, “me” and “money” are the operands. When there are multiple operands in assembly language, they are separated by commas, as in Give me,money.

The form of opcodes that can be used depends on the type of CPU. The functions of opcodes are summarized below.

To run, native code needs to be loaded into memory, which holds the instructions and data that make up the native code. As the program runs, the CPU reads data and instructions from memory and places them in internal CPU registers for processing.

If you are not familiar with the cpu-memory relationship, please read another article by the author about CPU core knowledge programmers need to know.

Registers are storage areas in the CPU. In addition to temporary storage and calculation functions, registers also have computing functions. The following figure shows the main types and roles of the x86 series

Command parsing

The following instructions in the CPU are analyzed

The most commonly used MOV instruction

The most commonly used instruction is the MOV instruction for data storage in registers and memory. The two operands of the MOV instruction are used to specify the storage place and read source of data respectively. Operands can specify registers, constants, labels (appended to addresses), and those enclosed in square brackets ([]). If something is specified that is not enclosed in square brackets ([]), the value is processed; If you specify something enclosed in square brackets, the value of the square brackets is interpreted as a memory address, and the value of the memory address is read or written to. Let’s illustrate the code snippet above

	mov       ebp,esp
	mov       eax,dword ptr [ebp+8]
Copy the code

Mov eBP, ESP,esp register is stored directly in the EBP, that is, if the ESP register is 100 then the EBP register is 100.

In mov eAX,dword PTR [EBP +8], the eBP register value +8 will be resolved as the memory address. If the ebp

If the register value is 100, then the eAX register value is the address value of 100 + 8. A dword PTR (double Word pointer) is a pointer that reads 4 bytes of data from a specified memory address

Push and pop the stack

When the program runs, it allocates a data space in memory called the stack. Stack (stack) is a feature of last in first out, data is stored from the lower level of memory (large address number) gradually to the upper level (small address number), when read is read from the top down.

The stack is an area where temporary data is stored. It is characterized by data storage and readout through push instructions and POP instructions. Storing data onto the stack is called pushing, while reading data off the stack is called pushing. In 32-bit x86 cpus, a push or pop is used to process 32-bit data (4 bytes).

Function call mechanism

Now let’s analyze the function call mechanism, we wrote the above C language code for example. First, let’s start with the assembly language part where the MyFunc function calls the AddNum function to illustrate the function calling mechanism. The stack plays a huge role in the function call. Here is the assembly processing of the MyFunc function after processing

_MyFunc proc near push ebp ; (1) mov ebp,esp; Store the esp register value into the EBP register (2) Push 456; (3) Push 123; (4) call _AddNum; Call AddNum (5) add esp,8; Esp register value + 8 (6) pop eBP; The values in the readout stack are stored in the ESP register (7) RET; End the MyFunc function and return to the call source (8) _MyFunc endpCopy the code

The treatments (1), (2), (7), and (8) in the code explanation apply to all functions in C, and we’ll show what the AddNum function does later. I want you to focus on (3) – (6), which is crucial to understanding the mechanism of function calls.

(3) and (4) represent arguments passed to the AddNum function pushed onto the stack. In the C source code, the function AddNum(123,456) is described, but it is pushed in the order 456,123 first. That is, the next value is pushed first. This is the rule of C. (5) represents the call instruction, which will jump the program flow to the address of AddNum function instruction. In assembly language, the function name represents the memory address of the function. After the AddNum function completes, the flow must return to line (6). After the call instruction is executed, the memory address of the next line of the call instruction (i.e. line (6)) will be pushed automatically. This value is popped off the stack with the RET instruction at the end of the AddNum processing, and the program returns to line (6).

(6) The two parameters stored in the stack (456 and 123) will be destroyed. Although this can be done with two pop instructions, it is more efficient to use the ESP register + 8 (processing once). When the stack is entered and output with values, the units of values are 4 bytes. Therefore, by adding 4 times 8 to the ESP register responsible for stack address management, you can achieve the same effect as running the POP command twice. Although the data in memory actually remains, the data is destroyed by updating the VALUE of the ESP register to the data location in front of the data store address.

When I compile the sample4.c file, I get the message shown below

The value of c is defined in MyFunc but never used. This is a compiler optimized function. Since the variable c containing the return value of the AddNum function is not used later, the compiler considers this variable meaningless and does not generate the corresponding assembly language code.

The following figure shows the stack memory changes before and after the AddNum function is called

Internal processing of a function

After analyzing the entire sample4.c process in assembly code, we will now focus on the source code of the AddNum function, and analyze the mechanism for receiving, returning, and returning parameters

_AddNum 		proc		near
	push			ebp			                                                   (1)
	mov				ebp,esp                                                             (2)
	mov				eax,dword ptr[ebp+8]                                     (3)
	add				eax,dword ptr[ebp+12]                                   (4)
	pop				ebp									  (5)
	ret				                                                                          (6)
_AddNum			endp
Copy the code

The value of the EBP register is pushed in (1) and pushed out in (5). This is mainly to restore the contents of the EBP register used in the function to the state before the function call.

In (2), the esp register, which manages the stack address, is assigned to the EBP register. This is because the ARGUMENTS in square brackets in the MOV instruction are not allowed to specify the ESP register. Therefore, instead of using esp directly, the eBP register is used to read and write the contents of the stack.

(3) use [eBP + 8] to specify the first parameter 123 stored in the stack and read it into the EAX register. Like this, you can refer to the contents of the stack without using the POP instruction. The EAX register was chosen from multiple registers because EAX is the cumulative register responsible for computation.

The result of adding the value of the current EAX register to the second parameter is stored in the EAX register by the add instruction in (4). [eBP + 12] is used to specify the second parameter, 456. In C, it is also stipulated that the return value of a function must be returned through the EAX register. That is, the parameters of the function are passed through the stack and the return value is returned through the register.

After the RET instruction in (6) is run, the function returns the destination memory address and is automatically removed from the stack. Accordingly, the program flow will jump back to the next line in (6) (Call _AddNum). At this point, the stack state changes at the entry and exit of the AddNum function, as shown in the figure below

Global and local variables

After familiar with assembly language, next we will learn about global variables and local variables, variables defined outside the function is called global variables, variables defined inside the function is called local variables, global variables can be used in any function, local variables can only be used inside the function defined local variables. Let’s look at the differences between global variables and local variables in assembly language.

The following C code defines local and global variables, and assigns values to each variable. Let’s look at the source code section first

// Define the initialized global variable
int a1 = 1;
int a2 = 2;
int a3 = 3;
int a4 = 4;
int a5 = 5;

// Define uninitialized global variables
int b1,b2,b3,b4,b5;

// Define the function
void MyFunc(a){
  // Define local variables
  int c1,c2,c3,c4,c5,c6,c7,c8,c9,c10;
  
  // Assign values to local variables
  c1 = 1;
  c2 = 2;
  c3 = 3;
  c4 = 4;
  c5 = 5;
  c6 = 6;
  c7 = 7;
  c8 = 8;
  c9 = 9;
  c10 = 10;
  
  // Assign local variables to global variables
  a1 = c1;
  a2 = c2;
  a3 = c3;
  a4 = c4;
  a5 = c5;
  b1 = c6;
  b2 = c7;
  b3 = c8;
  b4 = c9;
  b5 = c10;
}
Copy the code

The above code is quite violent, but it does not matter, it is easy for us to analyze the assembly source code, we use Borland C++ compiled assembly code as follows, after compiling the source code is quite long, we only use part of it for analysis (we change the sequence of the section definition, remove some comments).

_DATA segment dword public use32 'DATA'
   align 4
  _a1 label dword
   			dd 1
   align 4
  _a2 label dword
   			dd 2
   align 4
  _a3 label dword
   			dd 3
   align 4
  _a4 label dword
   			dd 4
   align 4
  _a5 label dword
   			dd 5
_DATA ends

_BSS segment dword public use32 'BSS'
 align 4
  _b1 label dword
   			db 4 dup(?)
   align 4
  _b2 label dword
   			db 4 dup(?)
   align 4
  _b3 label dword
   			db 4 dup(?)
   align 4
  _b4 label dword
   			db 4 dup(?)
   align 4
  _b5 label dword
   			db 4 dup(?)
_BSS ends

_TEXT segment dword public use32 'CODE'
_MyFunc proc near

 push      ebp
 mov       ebp,esp
 add       esp,-20
 push      ebx
 push      esi
 mov       eax,1
 mov       edx,2
 mov       ecx,3
 mov       ebx,4
 mov       esi,5
 mov       dword ptr [ebp-4],6
 mov       dword ptr [ebp-8],7
 mov       dword ptr [ebp-12],8
 mov       dword ptr [ebp-16],9
 mov       dword ptr [ebp-20],10
 mov       dword ptr [_a1],eax
 mov       dword ptr [_a2],edx
 mov       dword ptr [_a3],ecx
 mov       dword ptr [_a4],ebx
 mov       dword ptr [_a5],esi
 mov       eax,dword ptr [ebp-4]
 mov       dword ptr [_b1],eax
 mov       edx,dword ptr [ebp-8]
 mov       dword ptr [_b2],edx
 mov       ecx,dword ptr [ebp-12]
 mov       dword ptr [_b3],ecx
 mov       eax,dword ptr [ebp-16]
 mov       dword ptr [_b4],eax
 mov       edx,dword ptr [ebp-20]
 mov       dword ptr [_b5],edx
 pop       esi
 pop       ebx
 mov       esp,ebp
 pop       ebp
 ret
 
_MyFunc   endp
_TEXT 	ends
Copy the code

Compiled programs are grouped into groups called segment definitions.

  • Initialized global variables are summarized into a segment definition named _DATA
_DATA segment dword public use32 'DATA'
...
_DATA ends
Copy the code
  • Global variables that are not initialized are summarized into a segment definition named _BSS
_BSS segment dword public use32 'BSS'
 ...
_BSS ends
Copy the code
  • Assembly code surrounded by segment definition _TEXT is Borland C++ definition
_TEXT segment dword public use32 'CODE'
_MyFunc proc near
...
_MyFunc   endp
_TEXT 	ends
Copy the code

Before we look at the above assembly code, let’s look at some more assembly instructions. This table is a continuation of some of the above opcodes and their functions

opcode The operand function
add A,B Add the values of A and B and assign the result to A
call A Call function A
cmp A,B A and B are compared, and the comparison result is automatically stored in the flag register
inc A Plus 1 for A
ige Tag name This command is combined with the CMP command. Jump to the label line
jl Tag name This command is combined with the CMP command. Jump to the label line
jle Tag name This command is combined with the CMP command. Jump to the label line
jmp Tag name This command is combined with the CMP command. Jump to the label line
mov A,B Assign the value of B to A
pop A Read the value from the stack and store it in A
push A Put the value of A on the stack
ret There is no Return processing to the calling source
xor A,B The bits of A and B are compared and the result is stored in A

Let’s first look at the contents of the _DATA section definition. _A1 label Dword defines the _A1 label. The tag represents the position relative to the start of the segment definition. Since _a1 is at the beginning of the _DATA section definition, the relative position is 0. _a1 is equivalent to the global variable a1. Compiled function and variable names are preceded by an (_), as Borland C++ requires. Dd 1 means that the application allocates 4 bytes of memory and stores the initial value of 1. Dd refers to the definition of double word indicating that there are two byte fields (word) of length 2, which means 4 bytes.

In Borland C++, the assembler converts a1 = 1 to _a1 label dword and dd 1, since the length of int is 4 bytes. Similarly, labels _a2-_A5 are defined that correspond to a2-a5 of the global variables, and their initial values 2-5 are also stored in their respective 4 bytes.

Next, let’s talk about what the _BSS section defines. Here we define the label _b1-_b5 that corresponds to the global variables B1-b5. Where db 4dup(?) Represents a field for which 4 bytes have been allocated, but the value has not yet been determined. (). Db (define Byte) Indicates that there is a 1-byte memory space. Therefore, db 4 dup(?) In this case, 4 bytes of memory.

Note: db 4 dup(?) Not to be confused with DD 4, the former refers to four memory Spaces of 1 byte length. Db 4 represents a double byte (= 4 bytes) memory space with a value of 4

Temporarily ensure the memory space used by local variables

As we know, local variables are temporarily stored in registers and stacks. A stack is used internally to store local variables. After a function call, local variable values are destroyed, but registers may be used for other purposes. Therefore, local variables are only temporarily stored in registers and stacks during the processing of functions.

Recall that the above code defines 10 local variables. This is to show that local variables are stored not only on the stack, but also in registers. To ensure c1-C10 required domains, registers are used when they are free and stacks are used when register space is low.

Let’s move on to the above code. The _TEXT section definition represents the scope of the MyFunc function. The memory area required for local variables defined in MyFunc functions. Will be allocated in registers as much as possible. You might think that using a high-performance register instead of regular memory is a waste of resources, but the compiler doesn’t think so, and it uses a register whenever it has space. Since the register access speed is much faster than memory, direct access to the register can be efficiently processed. The use of registers for local variables is optimized by the Borland C++ compiler.

The following in the code listing represents the part that allocates local variables to registers

mov       eax,1
mov       edx,2
mov       ecx,3
mov       ebx,4
mov       esi,5
Copy the code

It is not enough to define a local variable; it is only when a local variable is assigned that it is allocated to the memory area of the register. The above code is equivalent to assigning 5 local variables C1-C5 values 1-5 respectively. Eax, EDX, ECX, EBX, esi are the names of x86 32-bit CPU registers. It is up to the compiler to decide which register to use.

X86 cpus have more than a dozen registers that a program can operate on, with at most a few free. Thus, when the number of local variables exceeds the number of registers, there are not enough registers to allocate, in which case the compiler uses a stack to store the remaining local variables.

In this part of the code, after assigning registers to local variables C1-C5, there are not enough registers available. Thus, the remaining five local variables c6-C10 are allocated to the stack memory space. As shown in the code below

mov       dword ptr [ebp-4],6
mov       dword ptr [ebp-8],7
mov       dword ptr [ebp-12],8
mov       dword ptr [ebp-16],9
mov       dword ptr [ebp-20],10
Copy the code

Add ESP,-20 means to subtract 20 from the ESP register (stack pointer) where the stack data is stored. To ensure that the memory variables C6-C10 are on the stack, we need to keep the space needed for five local variables of type int (4 bytes * 5 = 20 bytes). Mov ebp,esp Assign the esp register value to the EBP register. Mov ESP eBP at the exit of the function is used to restore the ESP register value to its original state, thus freeing the stack space for allocation. In this case, the local variables used in the stack will disappear. This is also a stack cleanup. In the case of registers, local variables automatically disappear when the register is used for other purposes, as shown in the figure below.

 mov       dword ptr [ebp-4],6
 mov       dword ptr [ebp-8],7
 mov       dword ptr [ebp-12],8
 mov       dword ptr [ebp-16],9
 mov       dword ptr [ebp-20],10
Copy the code

These five lines of code are the part of inserting values into the stack space. Before applying memory space to the stack, the value of esp register was saved in the ESP register with the help of mov EBP and ESP. Therefore, By using [EBP-4], [EBP-8], [EBP-12], [EBP-16], [EBP-20], it is possible to allocate 20 bytes of stack memory space into 5 4 bytes of space. For example, mov dword PTR [EBP-4],6 represents 4 bytes of data stored in the address ([EBP-4]) from the lower end of the requested allocated memory space (the location indicated by the EBP register).

Loops control the processing of statements

The above are all sequential processes, so now let us analyze the processing of the loop process, see how to realize the flow control of c language programs such as the for loop and if conditional branch, we still take the code and the result after compilation as an example, see the process of the program control process.

// Define MySub
void MySub(a){
  // Do nothing
  
}

// Define MyFunc function
void Myfunc(a){
  int i;
  for(int i = 0; i <10; i++){// Call MySub ten timesMySub(); }}Copy the code

The above code takes the local variable I as a loop condition and loops through the MySub function ten times. Here is its main assembly code

xor ebx, ebx ; 0 @4 call _MySub; Call MySub inc ebx; Ebx register + 1 CMP ebx,10; Compare the value of the EBX register with 10. If it's less than 10, it jumps to at sign 4Copy the code

The for statement in C executes loop processing by specifying the initial value of the loop counter (I = 0), the continuation condition of the loop (I < 10), and the update of the loop counter (I ++) in parentheses. The opposite of this assembly code is implemented by comparison instructions (CMP) and jump instructions (JL).

Let’s explain the above code

The only local variable used in MyFunc is I, which allocates memory space to the EBX register. The I = 0 in the parentheses of the for statement is converted to xor ebx. With ebx, the xOR instruction xOR the first operand from the left and the second operand from the right, and stores the result in the first operand. Because the first and second operands are specified as ebx, it becomes an XOR operation on the same value. That is, no matter what the value of the current register is, the final result is 0. Similarly, we used MOV EBx,0, to get the same result, but the xOR instruction was faster and the compiler turned on optimization.

XOR refers to the XOR operation, which is based on the rule that if a and B have different values, the XOR result is 1. If a and B have the same value, the xOR result is 0.

XOR operation with the same value results in 0. XOR is computed by the rule that the result is 1 if the value is different and 0 if the value is the same. For example, 01010101 and 01010101 perform XOR operations on each digit bit. Because each digit is the same, the result is 0.

After the ebX register value is initialized, the _MySub function is called via call. After the return from _MySub, the Inc ebx instruction is executed to perform the + 1 operation on the ebx value.

I need to know the difference between I ++ and ++ I

I ++ is assigned first, and the + 1 operation is performed on I after the copy is complete

++ I performs the +1 operation first, and then the assignment is completed

Inc The CMP on the next line is the instruction that compares the values of the first and second operands. CMP ebx,10 is equivalent to the I < 10 processing in C, which means to compare the value of the EBX register with 10. The result of the assembly language comparison instruction is stored in the CPU flag register. However, the value of the flag register is not directly referenced by the program. So how do you judge the comparison?

There are multiple jump instructions in assembly language, and these jump instructions will judge whether to jump according to the value of the flag register. For example, JL in the last line will judge whether to jump according to the value of CMP EBX,10 instruction stored in the flag register. The jL command stands for jump on less than. If I is less than 10, it jumps to the instruction at sign 4 and continues.

So the meaning of assembly code can also be used in C language to rewrite, deepen understanding

		i ^= i;
L4: MySub();
		i++;
		if(i < 10) goto L4;
Copy the code

The first line of the code I ^= I refers to the XOR operation between I and I, the XOR operation, and the MySub() function is replaced by L4 tag, and then the I increment operation, if the value of I is less than 10, the MySub() function is repeated.

Conditional branch processing method

Conditional branches are handled in a similar way to loops, using CMP and jump instructions. The following is the conditional branch code written in C language

// Define MySub1 function
void MySub1(a){

 // Do nothing
}

// Define MySub2 function
void MySub2(a){
  
 // Do nothing
}

// Define MySub3 function
void MySub3(a){

 // Do nothing
}

// Define MyFunc function
void MyFunc(a){

 int a = 123;
 // Call different functions based on conditions
 if(a > 100){
  MySub1();
 }
 else if(a < 50){
  MySub2();
 }
 else{ MySub3(); }}Copy the code

Very simple a conditional judgment C language code, so we put it with Borland C++ compiler after the result is as follows

_MyFunc proc near push ebp mov ebp,esp mov eax,123 ; CMP eAX,100; Compare the value of the EAX register with 100. For 100 hours, jump to @8 tag call _MySub1; Call MySub1 JMP shor@11; @8: CMP eAX,50; Compare the value of the EAX register with 50. Above 50, jump to @10 tag call _MySub2; Call MySub2 JMP shor@11; Jump to @11 tag @10: call _MySub3; Call MySub3 @11: pop ebp ret _MyFunc endpCopy the code

The above code uses three jump instructions, which are respectively jLE (Jump on Less or equal) jump when the comparison result is small, JGE (Jump on greater or equal) jump when the comparison result is large, and JMP which will jump regardless of the result. These jump instructions are preceded by the comparison instruction CMP, which forms the main logical form of assembly code described above.

Understand the logic necessary to run the program

By comparing the above assembly code with the C language source code, we can have a new understanding of the operation mode of the program. Moreover, the knowledge obtained from the assembly source code is also helpful to understand the features of Java and other high-level languages, such as Java has variables modified by native keywords. So the underlying variable is written in C, and there are some Java syntactic sugar whose logic is only known through assembly code. In some cases, it is also helpful to find the cause of a bug.

All the programming methods we have seen above are serial processing. What are the features of serial processing?

One of the biggest features of serial processing is that you focus on doing one thing and then do another.

Computers support multithreading, the core of which is CPU switching, as shown in the following figure

As a practical example, let’s look at a piece of code

// Define global variables
int counter = 100;

/ / define MyFunc1 ()
void MyFunc(a){
  counter *= 2;
}

/ / define MyFunc2 ()
void MyFunc2(a){
  counter *= 2;
}
Copy the code

The above code is a C program that updates the value of counter. MyFunc1() and MyFunc2() both double the value of counter and then assign the value of counter to it. Here, we assume that we have called MyFunc1 and MyFunc2 at the same time, and that the value of the global variable counter should be programmed to be 100 * 2 * 2 = 400. If you have more than one thread open, you’ll notice that sometimes counter is also 200, and if you don’t know how the program works, it’s hard to figure out why that happens.

We convert the above code to assembly language as follows

mov eax,dword ptr[_counter] ; Read counter into the eax register add eax,eax; Enlarge the value of the EAX register by 2 times. mov dword ptr[_counter],eax ; Store the value of the EAX register into counter.Copy the code

In multithreaded programs, processing can be switched to another thread every time a line of code in assembly language runs. So, if MyFun1 reads counter 100, and before it writes its double 200 to counter, MyFun2 reads counter 100, the result will be 200.

To avoid this bug, we can use a locking method that disables thread switching as a unit of behavior in functions or C code, or use some thread-safe method to avoid the problem.

Almost no one writes programs in assembly language anymore, because high-level languages such as C and Java are much more efficient than assembly language. However, assembly language experience is still very important, through the use of assembly language, we can better understand the operation mechanism of the computer.

Article Reference:

Chapter 10 of How Programs Run

How does the program run