程序的机器级表示(二)

程序的机器级表示

数据传送指令

将数据从一个位置复制到另一个位置的指令是最频繁使用的指令。

 

算术逻辑指令

 

控制指令

目的:

         条件语句、循环语句和分支语句,要求有条件的进行,根据数据测试的结果来决定操作执行的顺序。机器代码提供两种基本的低级机制来实现有条件的行为:测试数据值,然后根据测试的结果来改变控制流或者数据流

控制指令-条件码

CPU维护着一组单个位的条件码寄存器,他们描述了最近的算术或者逻辑操作的属性。可以检测这些寄存器来执行条件分支指令

最常用的条件码:

CF:进位标志。最近的操作使最高位产生了进位。可以用来检查无符号操作数的溢出

ZF:零标志。最近的操作得出的结果为0.

SF:符号标志。最经的操作得到的结果为负数。

OF:溢出标志。最近的操作导致一个补码溢出——正溢出或者负溢出

比较和测试指令

有两类指令只设置条件码而不改变任何其他寄存器。

CMP指令根据两个操作数之间的差来设置条件码。

TEST指令的行为同and一样。典型的用法是,testl %eax%eax用来检查%eax是负数、零还是正数

控制指令

Do-while循环

源代码

do{

         body-statement 

}while(test-expr)

翻译成汇编的伪代码

loop:    

         body-statement

t = test-expr;

if(t)        

goto loop;

Do-while循环-举例

真实代码:

int fact_do(int n)

{

         int result = 1;

         do

         {

                   result *= n;

                   n = n-1;

         }while(n > 1);

        

         return result;

}

汇编代码:

_fact_do:

         pushl         %ebp

         movl        %esp, %ebp

         movl        8(%ebp), %edx//读取n

         movl        $1, %eax//写入1

L5:

         imull        %edx, %eax// result *= n

         decl        %edx// n = n-1

         cmpl        $1, %edx//判断n大于1

         jg      L5//是的话继续

         popl        %ebp

         ret

 

While循环

源代码

while(test-expr)

         body-statement

翻译成汇编的伪代码

t = test-expr;

if(!t)

    goto done;

loop:

    body-statement;

    t = test-expr;

    if(t)

        goto loop;

done:

While循环-举例

源代码:

int fact_while(int n)

{

         int val = 0;

         while(x)

         {

                   val ^= x;

                   x >>=1;

         }

         return val & 0x1;

}

汇编代码:

_fact_while:

         pushl         %ebp

         movl        %esp, %ebp

         movl        8(%ebp), %edx//读取n

         movl        $0, %eax//val = 0

         testl       %edx, %edx//x = 0?

         je      L25//是的话,return

L23:

         xorl         %edx, %eax

         shrl        %edx

         jne        L23//循环

L25:

         andl        $1, %eax

         popl        %ebp

         ret

 

For循环

源代码:

for(init-expr;

     test-expr;

     update-expr)

         body-statement;

翻译成汇编的伪代码:

init-expr;

 t = test-expr;

if(!t)

    goto done;

loop:

    body-statement;

    update-expr;

    t = test-expr;

    if(t)

        goto loop;

done:

For循环-举例

源代码:

int sum(int x)

{

         int sum = 0;

         int i;

         for(i = 0; i< x; i++)

         {

                   if(i & 1)

                            continue;

                   sum += i;

         }

         return sum;

}

汇编代码:

_sum:

         pushl         %ebp

         movl          %esp, %ebp

         movl          8(%ebp), %ecx//读取x

         movl          $0, %eax//sum=0

         movl          $0, %edx//i = 0

         cmpl          %ecx, %eax//判断 0>= x?

         jge    L33//如果成立,结束

L31:

         testb         $1, %dl//测试 1 and i

         jne    L29//如果不为0,跳转L29

         addl %edx, %eax//否则sum +=i

L29:

         incl   %edx//i++

         cmpl          %ecx, %edx//判断 i < x?

         jl       L31//如果成立,继续循环

L33:

         popl %ebp

         ret

 

Switch语句

int swichtest(int a, int b, int c)

{

         int answer;

         switch(a)

         {

                   case 5:

                            c = b^15;

                   case 0:

                            answer = c +115;

                            break;

                   case 2:

                   case 7:

                            answer = (c + b) << 2;

                            break;

                   case 4:

                            answer = a;

                            break;

                   default:

                            answer = b;

         }

         return answer;

}

int swichtest_impl(int a, int b, int c)

{

         static void *jt[8] = {

                   &&loc_A, &&loc_def, &&loc_B, &&loc_def,

                   &&loc_C , &&loc_D, &&loc_def , &&loc_B

         };

         int answer;

         if(a > 7)

                   goto loc_def;

         goto *jt[a];

         loc_def :

                   answer = b;

                   goto done;

          loc_D :

                   c = b^15;

          loc_A :

                   answer = c +115;

                    goto done;

          loc_B :

                   answer = (c + b) << 2;

                   goto done;

          loc_C :

                   answer = a;

                   goto done;

         done:

                   return answer;

}

 

_swichtest:

         pushl %ebp

         movl          %esp, %ebp

         movl          8(%ebp), %edx//读取a

         movl          12(%ebp), %ecx//读取b

         movl          16(%ebp), %eax//读取c

         cmpl          $7, %edx//如果 a大于 7default

         ja      L35//跳转default

         jmp  *L36(,%edx,4)//按照数组跳转

L30: //case 5

         movl          %ecx, %eax

         xorl  $15, %eax//顺延到下一个条件

L31: //case 0

         addl $115, %eax

         jmp  L29//返回

L33: //case 2,7

         addl %ecx, %eax

         sall   $2, %eax

         jmp  L29//返回

L34: //case 4

         movl          %edx, %eax

         jmp  L29//返回

L35: //default

         movl          %ecx, %eax

L29:

         popl %ebp

         ret

L36:

         .long         L31

         .long         L35

         .long         L33

         .long         L35

         .long         L34

         .long         L30

         .long         L35

         .long         L33

         .text

 

If-else语句

模板

if(test-expr)

         then-statement

else

         else-statement

一般情况

if(!test-expr

   goto false;

v = then-expr;

goto done;

false:

         v = else-expr;

done:

使用条件传送语句的格式

vt = then-expr;

v = else-expr;

t = test-expr;

if(t) v = vt;

 

If-else-一般情况举例

int absdiff(int x, int y)

{

         if(x < y)

                   return y-x;

         else

                   return x-y;

}

_absdiff: 

pushl         %ebp       

movl          %esp, %ebp    

movl          8(%ebp), %edx//读取x

movl          12(%ebp), %eax//读取y

cmpl         %eax, %edx//判断x>=y?      

jge        L2//是的话,跳转 

subl        %edx, %eax//y - x   

jmp         L3

L2:   

subl        %eax, %edx//x - y   

movl          %edx, %eax//将结果存到结果寄存器

L3:

         popl       %ebp       

ret

 

If-else-使用条件传送语句

int absdiff(int x, int y)

{

         if(x < y)

                   return y-x;

         else

                   return x-y;

}

_absdiff:

         pushl         %ebp

         movl          %esp, %ebp

         pushl         %ebx

         movl          8(%ebp), %ecx //读取x

         movl          12(%ebp), %edx //读取y

         movl          %edx, %ebx

         subl          %ecx, %ebx //y - x

         movl          %ecx, %eax //

         subl          %edx, %eax //x - y

         cmpl          %edx, %ecx //判断x < y

         cmovl        %ebx, %eax//是的话返回y - x

         popl         %ebx

         popl         %ebp

         ret

 

编译器在什么情况下选择什么方式

在分支计算量小的情况下,基于条件数据传送的代码比基于条件控制转移的代码性能要好。

这本书后面会介绍到处理器通过使用流水线来获得高性能,在碰到条件控制转移的时候,会采用精密的分支预测逻辑来猜测跳转指令的执行与否,但是有时候这个猜测是不可靠的,从而浪费了程序执行的时间。但是使用条件数据传送的方式只需要检查条件码,要么更新目的寄存器,要么保持不变。

编译器会根据分支预测所需要的时间和条件传送浪费的计算上进行权衡,编译成哪种逻辑才是合适的。

If-else-例外

int  cread(int *xp)

{

         return (xp? *xp : 0);

}

_cread:

         pushl         %ebp

         movl          %esp, %ebp

         movl          8(%ebp), %edx

         movl          $0, %eax

         testl         %edx, %edx

         je      L9

         movl          (%edx), %eax

L9:

         popl %ebp

         ret

我们看到编译器依然选择了使用分支跳转的方式进行编译的。原因是即使当xp测试为空的时候,cmovnexp的间接引用还是发生了,导致一个间接引用空指针的错误

 

.net程序集加载流程

对于一个已编译好的.NET程序集,Windows操作系统是如何启动执行的呢?日常使用中我们发现对于托管的和非托管的程序集编译器都会吧程序集编译成以.exe.dll等为扩展名的文件,可见Windows加载器并没有区分是托管还是非托管的程序集,而且我们也知道对非托管的程序集是在编译器直接编译成了机器码,自然可以由CPU直接执行,而托管的.NET 程序集是包含复杂结构的MSIL代码,执行时会使用JIT即时编译器将IL代码编译成机器码,再由CPU执行,当然这期间还需要执行其它许多的工作,如加载CLR、执行初始化等工作,那么这些是怎么自动实现的呢?

 首先我们要清楚的是对于托管还是非托管程序集,他们在编译器执行编译时都会编译成一个特殊的文件格式,即PE文件(可移植可执行文件格式),操作系统加载器通过加载这样的PE文件来执行程序集的。可以这么说吧,无论是托管程序还是非托管程序他们实际上都是编译成这样的PE文件(只是有部分内容不一样而已)。

  然后这个PE文件会指示如何执行托管程序集和非托管程序集,加载器首先会查找到PE头中的AddressOfEntryPoint域,这个域指示PE文件的入口点位置,在.NET程序集中是指向.text段中的CLR--〉包含一个结构IMAGE_COR20_HEADER—包含许多信息如托管代码应用程序的入口点,目标CLR的主版本号和从版本号,以及程序集的强名称签名等--Windows加载器根据这个数据结构决定加载哪个版本的CLR以及一些基本的程序集信息。在.text段中还包含了程序集的元数据表,MSIL以及非托管启动存根代码,而非托管启动存根代码包好了由Windows加载器执行役启动PE文件执行的代码,结构如图所示。

1、用户执行一个.NET程序集;

2Windows加载器查看AddressOfEntryPoint域,并找到PE映像文件的.text段;

3、位于AddressOfEntryPoint位置上的字节只是一个JMP(跳转)指令,这个指令跳转到mscoree.dll中的一个导入函数;

4、将执行控制转移到mscoree.dll中的_CorExeMain中,这个函数将启动CLR并把执行控制转移到程序集的入口点。

 

程序的机器级表示

数据传送指令

将数据从一个位置复制到另一个位置的指令是最频繁使用的指令。

 

算术逻辑指令

 

控制指令

目的:

         条件语句、循环语句和分支语句,要求有条件的进行,根据数据测试的结果来决定操作执行的顺序。机器代码提供两种基本的低级机制来实现有条件的行为:测试数据值,然后根据测试的结果来改变控制流或者数据流

控制指令-条件码

CPU维护着一组单个位的条件码寄存器,他们描述了最近的算术或者逻辑操作的属性。可以检测这些寄存器来执行条件分支指令

最常用的条件码:

CF:进位标志。最近的操作使最高位产生了进位。可以用来检查无符号操作数的溢出

ZF:零标志。最近的操作得出的结果为0.

SF:符号标志。最经的操作得到的结果为负数。

OF:溢出标志。最近的操作导致一个补码溢出——正溢出或者负溢出

比较和测试指令

有两类指令只设置条件码而不改变任何其他寄存器。

CMP指令根据两个操作数之间的差来设置条件码。

TEST指令的行为同and一样。典型的用法是,testl %eax%eax用来检查%eax是负数、零还是正数

控制指令

Do-while循环

源代码

do{

         body-statement 

}while(test-expr)

翻译成汇编的伪代码

loop:    

         body-statement

t = test-expr;

if(t)        

goto loop;

Do-while循环-举例

真实代码:

int fact_do(int n)

{

         int result = 1;

         do

         {

                   result *= n;

                   n = n-1;

         }while(n > 1);

        

         return result;

}

汇编代码:

_fact_do:

         pushl         %ebp

         movl        %esp, %ebp

         movl        8(%ebp), %edx//读取n

         movl        $1, %eax//写入1

L5:

         imull        %edx, %eax// result *= n

         decl        %edx// n = n-1

         cmpl        $1, %edx//判断n大于1

         jg      L5//是的话继续

         popl        %ebp

         ret

 

While循环

源代码

while(test-expr)

         body-statement

翻译成汇编的伪代码

t = test-expr;

if(!t)

    goto done;

loop:

    body-statement;

    t = test-expr;

    if(t)

        goto loop;

done:

While循环-举例

源代码:

int fact_while(int n)

{

         int val = 0;

         while(x)

         {

                   val ^= x;

                   x >>=1;

         }

         return val & 0x1;

}

汇编代码:

_fact_while:

         pushl         %ebp

         movl        %esp, %ebp

         movl        8(%ebp), %edx//读取n

         movl        $0, %eax//val = 0

         testl       %edx, %edx//x = 0?

         je      L25//是的话,return

L23:

         xorl         %edx, %eax

         shrl        %edx

         jne        L23//循环

L25:

         andl        $1, %eax

         popl        %ebp

         ret

 

For循环

源代码:

for(init-expr;

     test-expr;

     update-expr)

         body-statement;

翻译成汇编的伪代码:

init-expr;

 t = test-expr;

if(!t)

    goto done;

loop:

    body-statement;

    update-expr;

    t = test-expr;

    if(t)

        goto loop;

done:

For循环-举例

源代码:

int sum(int x)

{

         int sum = 0;

         int i;

         for(i = 0; i< x; i++)

         {

                   if(i & 1)

                            continue;

                   sum += i;

         }

         return sum;

}

汇编代码:

_sum:

         pushl         %ebp

         movl          %esp, %ebp

         movl          8(%ebp), %ecx//读取x

         movl          $0, %eax//sum=0

         movl          $0, %edx//i = 0

         cmpl          %ecx, %eax//判断 0>= x?

         jge    L33//如果成立,结束

L31:

         testb         $1, %dl//测试 1 and i

         jne    L29//如果不为0,跳转L29

         addl %edx, %eax//否则sum +=i

L29:

         incl   %edx//i++

         cmpl          %ecx, %edx//判断 i < x?

         jl       L31//如果成立,继续循环

L33:

         popl %ebp

         ret

 

Switch语句

int swichtest(int a, int b, int c)

{

         int answer;

         switch(a)

         {

                   case 5:

                            c = b^15;

                   case 0:

                            answer = c +115;

                            break;

                   case 2:

                   case 7:

                            answer = (c + b) << 2;

                            break;

                   case 4:

                            answer = a;

                            break;

                   default:

                            answer = b;

         }

         return answer;

}

int swichtest_impl(int a, int b, int c)

{

         static void *jt[8] = {

                   &&loc_A, &&loc_def, &&loc_B, &&loc_def,

                   &&loc_C , &&loc_D, &&loc_def , &&loc_B

         };

         int answer;

         if(a > 7)

                   goto loc_def;

         goto *jt[a];

         loc_def :

                   answer = b;

                   goto done;

          loc_D :

                   c = b^15;

          loc_A :

                   answer = c +115;

                    goto done;

          loc_B :

                   answer = (c + b) << 2;

                   goto done;

          loc_C :

                   answer = a;

                   goto done;

         done:

                   return answer;

}

 

_swichtest:

         pushl %ebp

         movl          %esp, %ebp

         movl          8(%ebp), %edx//读取a

         movl          12(%ebp), %ecx//读取b

         movl          16(%ebp), %eax//读取c

         cmpl          $7, %edx//如果 a大于 7default

         ja      L35//跳转default

         jmp  *L36(,%edx,4)//按照数组跳转

L30: //case 5

         movl          %ecx, %eax

         xorl  $15, %eax//顺延到下一个条件

L31: //case 0

         addl $115, %eax

         jmp  L29//返回

L33: //case 2,7

         addl %ecx, %eax

         sall   $2, %eax

         jmp  L29//返回

L34: //case 4

         movl          %edx, %eax

         jmp  L29//返回

L35: //default

         movl          %ecx, %eax

L29:

         popl %ebp

         ret

L36:

         .long         L31

         .long         L35

         .long         L33

         .long         L35

         .long         L34

         .long         L30

         .long         L35

         .long         L33

         .text

 

If-else语句

模板

if(test-expr)

         then-statement

else

         else-statement

一般情况

if(!test-expr

   goto false;

v = then-expr;

goto done;

false:

         v = else-expr;

done:

使用条件传送语句的格式

vt = then-expr;

v = else-expr;

t = test-expr;

if(t) v = vt;

 

If-else-一般情况举例

int absdiff(int x, int y)

{

         if(x < y)

                   return y-x;

         else

                   return x-y;

}

_absdiff: 

pushl         %ebp       

movl          %esp, %ebp    

movl          8(%ebp), %edx//读取x

movl          12(%ebp), %eax//读取y

cmpl         %eax, %edx//判断x>=y?      

jge        L2//是的话,跳转 

subl        %edx, %eax//y - x   

jmp         L3

L2:   

subl        %eax, %edx//x - y   

movl          %edx, %eax//将结果存到结果寄存器

L3:

         popl       %ebp       

ret

 

If-else-使用条件传送语句

int absdiff(int x, int y)

{

         if(x < y)

                   return y-x;

         else

                   return x-y;

}

_absdiff:

         pushl         %ebp

         movl          %esp, %ebp

         pushl         %ebx

         movl          8(%ebp), %ecx //读取x

         movl          12(%ebp), %edx //读取y

         movl          %edx, %ebx

         subl          %ecx, %ebx //y - x

         movl          %ecx, %eax //

         subl          %edx, %eax //x - y

         cmpl          %edx, %ecx //判断x < y

         cmovl        %ebx, %eax//是的话返回y - x

         popl         %ebx

         popl         %ebp

         ret

 

编译器在什么情况下选择什么方式

在分支计算量小的情况下,基于条件数据传送的代码比基于条件控制转移的代码性能要好。

这本书后面会介绍到处理器通过使用流水线来获得高性能,在碰到条件控制转移的时候,会采用精密的分支预测逻辑来猜测跳转指令的执行与否,但是有时候这个猜测是不可靠的,从而浪费了程序执行的时间。但是使用条件数据传送的方式只需要检查条件码,要么更新目的寄存器,要么保持不变。

编译器会根据分支预测所需要的时间和条件传送浪费的计算上进行权衡,编译成哪种逻辑才是合适的。

If-else-例外

int  cread(int *xp)

{

         return (xp? *xp : 0);

}

_cread:

         pushl         %ebp

         movl          %esp, %ebp

         movl          8(%ebp), %edx

         movl          $0, %eax

         testl         %edx, %edx

         je      L9

         movl          (%edx), %eax

L9:

         popl %ebp

         ret

我们看到编译器依然选择了使用分支跳转的方式进行编译的。原因是即使当xp测试为空的时候,cmovnexp的间接引用还是发生了,导致一个间接引用空指针的错误

 

.net程序集加载流程

对于一个已编译好的.NET程序集,Windows操作系统是如何启动执行的呢?日常使用中我们发现对于托管的和非托管的程序集编译器都会吧程序集编译成以.exe.dll等为扩展名的文件,可见Windows加载器并没有区分是托管还是非托管的程序集,而且我们也知道对非托管的程序集是在编译器直接编译成了机器码,自然可以由CPU直接执行,而托管的.NET 程序集是包含复杂结构的MSIL代码,执行时会使用JIT即时编译器将IL代码编译成机器码,再由CPU执行,当然这期间还需要执行其它许多的工作,如加载CLR、执行初始化等工作,那么这些是怎么自动实现的呢?

 首先我们要清楚的是对于托管还是非托管程序集,他们在编译器执行编译时都会编译成一个特殊的文件格式,即PE文件(可移植可执行文件格式),操作系统加载器通过加载这样的PE文件来执行程序集的。可以这么说吧,无论是托管程序还是非托管程序他们实际上都是编译成这样的PE文件(只是有部分内容不一样而已)。

  然后这个PE文件会指示如何执行托管程序集和非托管程序集,加载器首先会查找到PE头中的AddressOfEntryPoint域,这个域指示PE文件的入口点位置,在.NET程序集中是指向.text段中的CLR--〉包含一个结构IMAGE_COR20_HEADER—包含许多信息如托管代码应用程序的入口点,目标CLR的主版本号和从版本号,以及程序集的强名称签名等--Windows加载器根据这个数据结构决定加载哪个版本的CLR以及一些基本的程序集信息。在.text段中还包含了程序集的元数据表,MSIL以及非托管启动存根代码,而非托管启动存根代码包好了由Windows加载器执行役启动PE文件执行的代码,结构如图所示。

1、用户执行一个.NET程序集;

2Windows加载器查看AddressOfEntryPoint域,并找到PE映像文件的.text段;

3、位于AddressOfEntryPoint位置上的字节只是一个JMP(跳转)指令,这个指令跳转到mscoree.dll中的一个导入函数;

4、将执行控制转移到mscoree.dll中的_CorExeMain中,这个函数将启动CLR并把执行控制转移到程序集的入口点。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值