http://www.cnpack.org
翻译:SkyJacker
时间:2007
This is the second lesson in the BASM for beginner’s series. The first lesson was a short
introduction to integer type code. This lesson will continue on this subject and introduce
more instructions that use the general purpose integer registers eax, ebx, ecx and edx. The
example function from lesson 1 introduced the instructions: mov, mul, shl, add and ret.
The example function for this lesson looks like this
这是 BASM 初学者入门系列的第二课。
第一课简单介绍了整数类型的代码。这一课将继续这一主题,并介绍更多的关于通用整型寄存器 eax, ebx, ecx 和 edx 的指令。
在第一课的例子函数中讲述了指令: mov, mul, shl, add 和 ret。
这一课的例子函数如下:
function DivideTwoNumbers1(I1, I2 : Integer) : Integer;
begin
Result := I1 div I2;
end;
It divides the Integer number I1 by I2 and returns the result.
The possible rest from the division is thrown away.
The compiler compiled the function into this assembler code.
用 I2 去除 I1 并返回结果。除法产生的余数将被扔掉。
编译器将函数编译为如下汇编代码:
function DivideTwoNumbers2(I1, I2 : Integer) : Integer;
begin
{
push ebx
mov ebx,edx
mov ecx,eax
}
Result := I1 div I2;
{
mov eax,ecx
cdq
idiv ebx
}
{
pop ebx
ret
}
end;
The first three lines of assembler code
这三行汇编代码
{
push ebx
mov ebx,edx
mov ecx,eax
}
is a kind of initialization.
是一种初始化的形式。
The ebx register cannot be modified freely by the function and must be backed up.
ebx 寄存器不能被函数自由的修改,必须被保存。
The line,
这一行
push ebx,
copies the value of ebx onto the stack and decreases the stackpointer by 4.
Logically we would expect the stackpointer to be increased
because the stack grows when something is pushed onto it, but the stack grows downward on the Intel
architecture.
将 ebx 的值拷贝到堆栈上, 同时栈顶指针减 4。
逻辑上,我们可能认为栈顶指针会增加,因为一些数据被压入堆栈而使堆栈增长。
事实上,在 Intel 体系结构中堆栈是向下增长的。
All registers are 32 bit, which are 4 bytes and therefore the stackpointer
changes by 4 each time a register is pushed on the stack or popped of it.
The two parameters I1 and I2 are transferred to the function in the registers eax and edx.
I1 is in eax and I2 is in edx as determined by the register calling convention.
The next two lines use the mov instruction to copy their values into ebx and ecx.
The function body consist of 1 line of Pascal only and three lines of ASM is generated from it.
所有的寄存器都是 4 个字节 32 位的寄存器。因此,每次寄存器入栈或弹出,堆栈指针都会变化 4。
参数 I1, I2 通过寄存器 EAX, EDX 传入函数。
I1 在 EAX 中,I2 在 EDX 中,这是由寄存器调用约定决定的。
接下来的两行使用 mov 指令将它们的值再复制到 ebx, eax 中。
函数体仅由 1 行 pascal 代码组成,其产生了 3 行 asm 代码。
Result := I1 div I2;
{
mov eax,ecx
cdq
idiv ebx
}
The parameter I1 was copied into ecx in the initialization part and now it is copied back
into eax again. This is redundant because eax has not been overwritten.
The cdq instruction is described in the IA32 Intel Architecture Software Developer’s Manual Volume 2 –
Instruction Set Reference on page 212. It converts a double word into a quadword by means of
sign extension. Sign extension means that the sign bit in eax, which is bit 31, is copied to
all bits in edx. The eax register is source and the register pair edx:eax is destination.
The cdq instruction is needed before the idiv instruction because the idiv instruction
divides the 64 bit value held in edx:eax by the 32 bit value held in ebx in this example.
参数 I1 在初始化部分被复制到 ecx 中, 现在它又被复制回 eax,这是冗余的,因为eax 没有被重写。
cdq 指令在 IA32 Intel 软件开发手册第 2 卷指令集参考中的第 212 上被介绍。
它转换一个双字到一个四个字,叫做符号扩展。符号扩展的意思是指将 eax 的符号位,即第 31 位复制到
整个 edx 中。 eax 是源操作数,寄存器对 edx:eax 是目标操作数。
cdq 指令在执行整除之前是必需的,因为 idiv 指令是用 edx:eax 中的 64 位数除以 ebx 中的 32 位数.
The result of the division is the quotient, which is returned in eax and the reminder which
is returned in edx.
The function returns the quotient in eax where it conveniently is placed by the idiv
instruction and the only thing needed to do is to pop ebx of the stack again and returning
from the function.
相除返回的结果是商,存放在 eax 中,余数存放在 edx 中。
函数用 eax 返回商,正好能很方便的被 idiv 指令置结果值, 剩下的事情就是再将 ebx 出栈,
并从函数中返回。
{
pop ebx
ret
}
Now we have analyzed the output from the compiler we can use it to write our own BASM
version of the function. This is simply done by changing the begin keyword by the asm
keyword, enabling the assembler code and disabling the Pascal code. The ret instruction is
supplied by the inline assembler and it can be removed from our code.
现在我们已经分析了编译器产生的汇编,因为可以写出函数的 BASM 版了。
转换过程很简单,将 begin 关键字改为 asm,让汇编代码有效并注释掉 pascal 代码。
返回指令也可以删除,因为它会由内联汇编器生成。
function DivideTwoNumbers3(I1, I2 : Integer) : Integer;
asm
push ebx
mov ebx,edx
mov ecx,eax
//Result := I1 div I2;
mov eax,ecx
cdq
idiv ebx
pop ebx
//ret
end;
An easy introduction to optimization is to remove the redundant mov instruction our analysis
of the compiler generated code revealed
优化代码很容易,就是将编译器产生的冗余代码删除。
function DivideTwoNumbers4(I1, I2 : Integer) : Integer;
asm
push ebx
mov ebx,edx
mov ecx,eax
//Result := I1 div I2;
//mov eax,ecx
cdq
idiv ebx
pop ebx
end;
We also saw that the copy of I1 in ecx was not used and the line that copies eax to ecx is
not needed.
我们也发现在 ecx 中的 I1 拷贝也没有被使用,复制 eax 到 ecx 这一行不需要。
function DivideTwoNumbers5(I1, I2 : Integer) : Integer;
asm
push ebx
mov ebx,edx
//mov ecx,eax
//Result := I1 div I2;
cdq
idiv ebx
pop ebx
end;
Ecx is not in use now and because it can be used freely it is better to use it as source for
the idiv instruction instead of ebx. This way it is not necessary to push and pop ebx and
the function becomes even simpler.
Ecx 现在没有被使用。又因为 ecx 可以自由的被使用,因此很适合替换 ebx 作为 idiv 的操作数。
这种方法不需要将 ebx 入栈、出栈,函数将变得更简单。
function DivideTwoNumbers6(I1, I2 : Integer) : Integer;
asm
//push ebx
//mov ebx,edx
mov ecx,edx
//Result := I1 div I2;
cdq
//idiv ebx
idiv ecx
//pop ebx
end;
After cleaning up we can see that the function is pretty clean now.
整理之后,我们看到函数现在是非常的简单漂亮。
function DivideTwoNumbers7(I1, I2 : Integer) : Integer;
asm
mov ecx,edx
//Result := I1 div I2;
cdq
idiv ecx
end;
This ended the second lesson which introduced the instructions push, pop, cdq and idiv.
总结:这一课我们学习了指令 push, pop, cdq 和 idiv。