Machine-Level Representation of Programs
3.1 A Historical Perspective
几乎是每5年,以数量级的速度在增长。。。
3.2 Program Encodings
Suppose we write a C program as two files p1.cand p2.c. We can then compile this code on an IA32 machine using a Unix command line:
unix> gcc -O1 -o p p1.c p2.c
The gcc command actually invokes a sequence of programs to turn the source code into executable code.
First, the C preprocessor expands the source code to include any files specified with #include commands and to expand any macros, specified with #define declarations.
Second, the compiler generates assembly-code versions of the two source files having names p1.s and p2.s. Next, the assembler converts the assembly code into binary object-code files p1.o and p2.o. Object code is one form of machine code—it contains binary representations of all of the instructions, but the addresses of global values are not yet filled in.
3.2.1 Machine-Level Code
Two of these are especially important for machine-level programming.
First, the format and behavior of a machine-level program is defined by the instruction set architecture , or “ISA,” defining the processor state, the format of the instructions, and the effect each of these instructions will have on the state.
Second, the memory addresses used by a machine-level program are vir-tual addresses, providing a memory model that appears to be a very large byte array.
The compiler does most of the work in the overall compilation sequence, transforming programs expressed in the relatively abstract execution model provided by C into the very elementary instructions that the processor executes.
IA32 machine code differs greatly from the original C code. Parts of the processor state are visible that normally are hidden from the C programmer:
The program counter(commonly referred to as the “PC,” and called %eip in
IA32) indicates the address in memory of the next instruction to be executed.
The integer register file contains eight named locations storing 32-bit values.
These registers can hold addresses (corresponding to C pointers) or integer
data. Some registers are used to keep track of critical parts of the program
state, while others are used to hold temporary data, such as the local variables
of a procedure, and the value to be returned by a function.
The condition code registers hold status information about the most recently
executed arithmetic or logical instruction. These are used to implement conditional changes in the control or data flow, such as is required to implement
if and while statements.
A set of floating-point registers store floating-point data.
The operating system manages this virtual address space, translating virtual addresses into the physical addresses of values in the actual processor memory.
3.2.2 Code Examples
C代码:
int accum = 0;
int sum(int x, int y)
{
int t=x+y;
accum += t;
return t;
}
相应的汇编形式:
sum:
pushl %ebp
movl %esp, %ebp
movl 12(%ebp), %eax
addl 8(%ebp), %eax
addl %eax, accum
popl %ebp
ret
3.3 Data Formats
3.4 Accessing Information
3.4.1 Operand Specifiers
The first type, immediate , is for constant values.
The second type, register, denotes the contents of one of the registers, either one of the eight 32-bit registers (e.g., %eax) for a double-word operation, one of the eight 16-bit registers (e.g., %ax ) for a word operation, or one of the eight single-byte register elements (e.g.,%al ) for a byte operation.
The third type of operand is a memory reference, in which we access some memory location according to a computed address, often called the effective ad-dress.
3.4.2 Data Movement Instructions
IA32 imposes the restriction that a move instruction cannot have both operands refer to memory locations. Copying a value from one memory location to another requires two instructions—the first to load the source
value into a register
不要尝试memory to memory
理解下面这个错误说明更能透彻的理解关于mov的用法,o(∩_∩)o ~
A stack is a data structure where values can be added or deleted, but only according to a “last-in, first-out”
discipline. We add data to a stack via a push operation and remove it via a pop operation, with the property that the value popped will always be the value that was most recently pushed and is still on the stack
3.5 Arithmetic and Logical Operations
The operations are divided into four groups: load effective address, unary, binary,and shifts
3.5.1 Load Effective Address
The load effective address instruction leal is actually a variant of the movl instruction. It has the form of an instruction that reads from memory to a register, but it does not reference memory at all.
注意逻辑位移和算术位移的区别。
The right shift instructions differ in that sar performs an arithmetic shift (fill with copies of the sign bit), whereas shr performs a logical shift (fill with zeros). The destination operand of a shift operation can be either a register or a memory location. We denote the two different right shift operations in Figure 3.7 as >>A (arithmetic) and >>L (logical)
3.5.5 Special Arithmetic Operations
The product is then stored in registers %edx(high-order 32 bits)and %eax(low-order 32 bits).
3.6 Control
3.6.1 Condition Codes
CF: Carry Flag. The most recent operation generated a carry out of the most significant bit. Used to detect overflow for unsigned operations.
ZF: Zero Flag. The most recent operation yielded zero.
SF: Sign Flag. The most recent operation yielded a negative value.
OF: Overflow Flag. The most recent operation caused a two’s-complement overflow—either negative or positive.
比较的时候要注意防止数据溢出而造成的无意义比较
3.6.2 Accessing the Condition Codes
Rather than reading the condition codes directly, there are three common ways of using the condition codes: (1) we can set a single byte to 0 or 1 depending on some combination of the condition codes, (2) we can conditionally jump to some other part of the program, or (3) we can conditionally transfer data.
aisin %edx,bisin %eax
cmpl %eax, %edx Compare a:b
setl %al Set low order byte of %eax to 0 or
movzbl %al, %eax Set remaining bytes of %eax to 0
3.6.3 Jump Instructions and Their Encodings
A jump instruction can cause the execution to switch to a completely
new position in the program
Indirect jumps are written using ‘*’ followed by an operand specifier using one of the formats described in Section 3.4.1. As examples, the instruction jmp *%eax uses the value in register %eaxas the jump target, and the instruction jmp *(%eax) reads the jump target from memory, using the value in %eax as the read address.
对于if判断选择和while循环之类的,都是用goto来实现的。至于具体细节。。。看书吧。。。巨细
这里是我看goto的时候遇到的难题以及解答
http://blog.csdn.net/cinmyheart/article/details/24430599
3.6.6 Conditional Move Instructions
3.7 Procedures
3.7.1 Stack Frame Structure
The portion of the stack allocated for a single procedure call is called a stack frame . Figure 3.21 diagrams the general structure of a stack frame. The topmost stack frame is delimited by two pointers, with register %ebp serving as the frame pointer , and register %esp serving as the stack pointer .
3.7.2 Transferring Control
Instruction Description
call Label Procedure call
call *Operand Procedure call
leave Prepare stack for retur
ret Return from call
3.7.3 Register Usage Conventions
Although only one procedure can be active at a given time, we must make sure that when one procedure (the caller) calls another (the callee), the callee does not overwrite some register value that the caller planned to use later.
3.7.4 Procedure Example
int rfact(int n)
{
int result;
if (n <= 1)
result = 1;
else
resul t = n* rfact(n-1);
return result;
}
相应的汇编代码:
rfact:
pushl %ebp Save old %ebp
movl %esp, %ebp Set %ebp as frame pointer
pushl %ebx Save callee save register %e
subl $4, %esp Allocate 4 bytes on stack
movl 8(%ebp), %ebx Get n
movl $1, %eax result = 1
cmpl $1, %ebx Compare n:1
jle .L53 If <=, goto done
leal -1(%ebx), %eax Compute n-1
movl %eax, (%esp) Store at top of stack
call rfact Call rfact(n-1)
imull %ebx, %eax Compute result = return valu
.L53: done:
addl $4, %esp Deallocate 4 bytes from stac
popl %ebx Restore %ebx
popl %ebp Restore %ebp
ret Return result
3.8 Array Allocation and Access
As an example, consider the following structure declaration:
struct rec
{
int i;
int j;
int a[3];
int *p;
};
3.9.3 Data Alignment
The IA32 hardware will work correctly regardless of the alignment of data. However, Intel recommends that data be aligned to improve memory system performance. Linux follows an alignment policy where 2-byte data types (e.g.,short ) must have an address that is a multiple of 2, while any larger data types (e.g., int , int*, float , and double) must have an address that is a multiple of 4. Note that this requirement means that the least significant bit of the address of an object of type short must equal zero. Similarly, any object of type int , or any pointer, must be at an address having the low-order 2 bits equal to zero.
对于alignment理解的至关重要的一段话!!
同样要弄明白下面这个小题目,分析清楚了才能明白alignment
虽然这章没什么编程的,感觉有蛮大的收获,但是汇编是从0开始的,重新翻开linux里面汇编程序的时候最起码不会畏惧了!让我有自信只要啃下去,是可以搞定的。不至于一开始很慌神汇编。我觉得这章的汇编讲的正的很好。需要一点基础开始,所以之前看了赵炯的<linux内核剖析>讲汇编的部分,看了一点,大概知道,才开始看这章的。花了大概三四天吧。。。感觉很值得。blog并不会和书上一样很细致的讲学的内容,仅仅是作为自己的回忆笔记。便于自己快速"恢复记忆"