当前位置: 首页 > news >正文

404网站页面进入效果图施工图制作

404网站页面进入,效果图施工图制作,长沙竞价网站建设价格,沈阳seo合作朋友们#xff0c;我是从http://www.cnblogs.com/latifrons/archive/2009/09/17/1568198.html这里转载过来的#xff0c;很不错就收藏了#xff0c;如果转载#xff0c;请注明初始链接。 .3 GCC Inline ASM GCC 支持在C/C代码中嵌入汇编代码#xff0c;这些汇编代码被称作…朋友们我是从http://www.cnblogs.com/latifrons/archive/2009/09/17/1568198.html这里转载过来的很不错就收藏了如果转载请注明初始链接。 .3 GCC Inline ASM GCC 支持在C/C代码中嵌入汇编代码这些汇编代码被称作GCC Inline ASM——GCC内联汇编。这是一个非常有用的功能有利于我们将一些C/C语法无法表达的指令直接潜入C/C代码中另外也允许我们直接写 C/C代码中使用汇编编写简洁高效的代码。 1.基本内联汇编 GCC中基本的内联汇编非常易懂我们先来看两个简单的例子 __asm__(movl %esp,%eax); // 看起来很熟悉吧 或者是 __asm__( movl $1,%eax // SYS_exit xor %ebx,%ebx int $0x80 ); 或 __asm__( movl $1,%eax\r\t \ xor %ebx,%ebx\r\t \ int $0x80 \ ); 基本内联汇编的格式是 __asm__ __volatile__(Instruction List); 1、__asm__ __asm__是GCC关键字asm的宏定义 #define __asm__ asm __asm__或asm用来声明一个内联汇编表达式所以任何一个内联汇编表达式都是以它开头的是必不可少的。 2、Instruction List Instruction List是汇编指令序列。它可以是空的比如__asm__ __volatile__(); 或__asm__ ();都是完全合法的内联汇编表达式只不过这两条语句没有什么意义。但并非所有Instruction List为空的内联汇编表达式都是没有意义的比如__asm__ (:::memory); 就非常有意义它向GCC声明“我对内存作了改动”GCC在编译的时候会将此因素考虑进去。 我们看一看下面这个例子 $ cat example1.c int main(int __argc, char* __argv[])  {  int* __p (int*)__argc;  (*__p) 9999;  //__asm__(:::memory);  if((*__p) 9999)  return 5;  return (*__p);  } 在 这段代码中那条内联汇编是被注释掉的。在这条内联汇编之前内存指针__p所指向的内存被赋值为9999随即在内联汇编之后一条if语句判断__p 所指向的内存与9999是否相等。很明显它们是相等的。GCC在优化编译的时候能够很聪明的发现这一点。我们使用下面的命令行对其进行编译 $ gcc -O -S example1.c 选项-O表示优化编译我们还可以指定优化等级比如-O2表示优化等级为2选项-S表示将C/C源文件编译为汇编文件文件名和C/C文件一样只不过扩展名由.c变为.s。 我们来查看一下被放在example1.s中的编译结果我们这里仅仅列出了使用gcc 2.96在redhat 7.3上编译后的相关函数部分汇编代码。为了保持清晰性无关的其它代码未被列出。 $ cat example1.s main:  pushl %ebp  movl %esp, %ebp  movl 8(%ebp), %eax # int* __p (int*)__argc movl $9999, (%eax) # (*__p) 9999  movl $5, %eax # return 5 popl %ebp  ret 参 照一下C源码和编译出的汇编代码我们会发现汇编代码中没有if语句相关的代码而是在赋值语句(*__p)9999后直接return 5这是因为GCC认为在(*__p)被赋值之后在if语句之前没有任何改变(*__p)内容的操作所以那条if语句的判断条件(*__p) 9999肯定是为true的所以GCC就不再生成相关代码而是直接根据为true的条件生成return 5的汇编代码GCC使用eax作为保存返回值的寄存器。 我们现在将example1.c中内联汇编的注释去掉重新编译然后看一下相关的编译结果。 $ gcc -O -S example1.c $ cat example1.s main:  pushl %ebp  movl %esp, %ebp  movl 8(%ebp), %eax # int* __p (int*)__argc movl $9999, (%eax) # (*__p) 9999 #APP  # __asm__(:::memory) #NO_APP cmpl $9999, (%eax) # (*__p) 9999 ? jne .L3 # false  movl $5, %eax # true, return 5  jmp .L2  .p2align 2  .L3:  movl (%eax), %eax  .L2:  popl %ebp  ret 由于内联汇编语句__asm__(:::memory)向GCC声明在此内联汇编语句出现的位置内存内容可能了改变所以GCC在编译时就不能像刚才那样处理。这次GCC老老实实的将if语句生成了汇编代码。 可能有人会质疑为什么要使用__asm__(:::memory)向GCC声明内存发生了变化明明“Instruction List”是空的没有任何对内存的操作这样做只会增加GCC生成汇编代码的数量。 确 实那条内联汇编语句没有对内存作任何操作事实上它确实什么都没有做。但影响内存内容的不仅仅是你当前正在运行的程序。比如如果你现在正在操作的内存 是一块内存映射映射的内容是外围I/O设备寄存器。那么操作这块内存的就不仅仅是当前的程序I/O设备也会去操作这块内存。既然两者都会去操作同一块 内存那么任何一方在任何时候都不能对这块内存的内容想当然。所以当你使用高级语言C/C写这类程序的时候你必须让编译器也能够明白这一点毕竟高 级语言最终要被编译为汇编代码。 你可能已经注意到了这次输出的汇编结果中有两个符号#APP和#NO_APPGCC将内联汇编语 句中Instruction List所列出的指令放在#APP和#NO_APP之间由于__asm__(:::memory)中“Instruction List”为空所以#APP和#NO_APP中间也没有任何内容。但我们以后的例子会更加清楚的表现这一点。 关于为什么内联汇编__asm__(:::memory)是一条声明内存改变的语句我们后面会详细讨论。 刚才我们花了大量的内容来讨论Instruction List为空是的情况但在实际的编程中Instruction List绝大多数情况下都不是空的。它可以有1条或任意多条汇编指令。 当 在Instruction List中有多条指令的时候你可以在一对引号中列出全部指令也可以将一条或几条指令放在一对引号中所有指令放在多对引号中。如果是前者你可以将 每一条指令放在一行如果要将多条指令放在一行则必须用分号或换行符\n大多数情况下\n后还要跟一个\t其中\n是为了换行\t是为了 空出一个tab宽度的空格将它们分开。比如 __asm__(movl %eax, %ebx  sti  popl %edi  subl %ecx, %ebx);  __asm__(movl %eax, %ebx; sti  popl %edi; subl %ecx, %ebx); __asm__(movl %eax, %ebx; sti\n\t popl %edi subl %ecx, %ebx); 都是合法的写法。如果你将指令放在多对引号中则除了最后一对引号之外前面的所有引号里的最后一条指令之后都要有一个分号()或(\n)或(\n\t)。比如 __asm__(movl %eax, %ebx  sti\n  popl %edi;  subl %ecx, %ebx);  __asm__(movl %eax, %ebx; sti\n\t  popl %edi; subl %ecx, %ebx); __asm__(movl %eax, %ebx; sti\n\t popl %edi\n subl %ecx, %ebx); __asm__(movl %eax, %ebx; sti\n\t popl %edi; subl %ecx, %ebx); 都是合法的。 上述原则可以归结为 任意两个指令间要么被分号()分开要么被放在两行  放在两行的方法既可以从通过\n的方法来实现也可以真正的放在两行  可以使用1对或多对引号每1对引号里可以放任一多条指令所有的指令都要被放到引号中。 在基本内联汇编中“Instruction List”的书写的格式和你直接在汇编文件中写非内联汇编没有什么不同你可以在其中定义Label定义对齐(.align n )定义段(.section name )。例如 __asm__(.align 2\n\t  movl %eax, %ebx\n\t  test %ebx, %ecx\n\t  jne error\n\t  sti\n\t  error: popl %edi\n\t  subl %ecx, %ebx); 上面例子的格式是Linux内联代码常用的格式非常整齐。也建议大家都使用这种格式来写内联汇编代码。 3、__volatile__ __volatile__是GCC关键字volatile的宏定义 #define __volatile__ volatile __volatile__ 或volatile是可选的你可以用它也可以不用它。如果你用了它则是向GCC声明“不要动我所写的Instruction List我需要原封不动的保留每一条指令”否则当你使用了优化选项(-O)进行编译时GCC将会根据自己的判断决定是否将这个内联汇编表达式中的指 令优化掉。 那么GCC判断的原则是什么我不知道如果有哪位朋友清楚的话请告诉我。我试验了一下发现一条内联汇编语句如果是基本 内联汇编的话即只有“Instruction List”没有Input/Output/Clobber的内联汇编我们后面将会讨论这一点无论你是否使用__volatile__来修饰 GCC 2.96在优化编译时都会原封不动的保留内联汇编中的“Instruction List”。但或许我的试验的例子并不充分所以这一点并不能够得到保证。 为了保险起见如果你不想让GCC的优化影响你的内联汇编代码你最好在前面都加上__volatile__而不要依赖于编译器的原则因为即使你非常了解当前编译器的优化原则你也无法保证这种原则将来不会发生变化。而__volatile__的含义却是恒定的。 2、带有C/C表达式的内联汇编 GCC允许你通过C/C表达式指定内联汇编中Instrcuction List中指令的输入和输出你甚至可以不关心到底使用哪个寄存器被使用完全靠GCC来安排和指定。这一点可以让程序员避免去考虑有限的寄存器的使用也可以提高目标代码的效率。 我们先来看几个例子 __asm__ ( : : : memory ); // 前面提到的 __asm__ (mov %%eax, %%ebx : b(rv) : a(foo) : eax, ebx); __asm__ __volatile__(lidt %0: m (idt_descr)); __asm__(subl %2,%0\n\t sbbl %3,%1 : a (endlow), d (endhigh) : g (startlow), g (starthigh), 0 (endlow), 1 (endhigh)); 怎么样有点印象了吧是不是也有点晕没关系下面讨论完之后你就不会再晕了。当然也有可能更晕^_^。讨论开始—— 带有C/C表达式的内联汇编格式为 __asm__ __volatile__(Instruction List : Output : Input : Clobber/Modify); 从中我们可以看出它和基本内联汇编的不同之处在于它多了3个部分(InputOutputClobber/Modify)。在括号中的4个部分通过冒号(:)分开。 这4个部分都不是必须的任何一个部分都可以为空其规则为 如 果Clobber/Modify为空则其前面的冒号(:)必须省略。比如__asm__(mov %%eax, %%ebx : b(foo) : a(inp) : )就是非法的写法而__asm__(mov %%eax, %%ebx : b(foo) : a(inp) )则是正确的。  如果Instruction List为空则InputOutputClobber/Modify可以不为空也可以为空。比如__asm__ ( : : : memory );和__asm__( : : );都是合法的写法。  如 果OutputInputClobber/Modify都为空OutputInput之前的冒号(:)既可以省略也可以不省略。如果都省略则 此汇编退化为一个基本内联汇编否则仍然是一个带有C/C表达式的内联汇编此时Instruction List中的寄存器写法要遵守相关规定比如寄存器前必须使用两个百分号(%%)而不是像基本汇编格式一样在寄存器前只使用一个百分号(%)。比如 __asm__( mov %%eax, %%ebx : : )__asm__( mov %%eax, %%ebx : )和__asm__( mov %eax, %ebx )都是正确的写法而__asm__( mov %eax, %ebx : : )__asm__( mov %eax, %ebx : )和__asm__( mov %%eax, %%ebx )都是错误的写法。  如果InputClobber/Modify为空但Output不为空Input前的冒号(:)既可以省略也可以不省略。比如 __asm__( mov %%eax, %%ebx : b(foo) : )__asm__( mov %%eax, %%ebx : b(foo) )都是正确的。  如果后面的部分不为空而前面的部分为空则前面的冒号(:)都必须保留否则无法说 明不为空的部分究竟是第几部分。比如 Clobber/ModifyOutput为空而Input不为空则Clobber/Modify前的冒号必须省略前面的规则而Output 前的冒号必须为保留。如果Clobber/Modify不为空而Input和Output都为空则Input和Output前的冒号都必须保留。比如 __asm__( mov %%eax, %%ebx : : a(foo) )和__asm__( mov %%eax, %%ebx : : : ebx )。 从上面的规则可以看到另外一个事实区分一个内联汇编是基本格式的还是带有C/C表达式格式的其规则在于在Instruction List后是否有冒号(:)的存在如果没有则是基本格式的否则则是带有C/C表达式格式的。 两种格式对寄存器语法的要求不同基本格式要求寄存器前只能使用一个百分号(%)这一点和非内联汇编相同而带有C/C表达式格式则要求寄存器前必须使用两个百分号(%%)其原因我们会在后面讨论。 1. Output Output用来指定当前内联汇编语句的输出。我们看一看这个例子 __asm__(movl %%cr0, %0: a (cr0)); 这 个内联汇编语句的输出部分为r(cr0)它是一个“操作表达式”指定了一个输出操作。我们可以很清楚得看到这个输出操作由两部分组成括号括住 的部分(cr0)和引号引住的部分a。这两部分都是每一个输出操作必不可少的。括号括住的部分是一个C/C表达式用来保存内联汇编的一个输出 值其操作就等于C/C的相等赋值cr0 output_value因此括号中的输出表达式只能是C/C的左值表达式也就是说它只能是一个可以合法的放在C/C赋值操作中等号() 左边的表达式。那么右值output_value从何而来呢 答案是引号中的内容被称作“操作约束”Operation Constraint在这个例子中操作约束为a它包含两个约束等号()和字母a其中等号()说明括号中左值表达式cr0是一个 Write-Only的只能够被作为当前内联汇编的输入而不能作为输入。而字母a是寄存器EAX / AX / AL的简写说明cr0的值要从eax寄存器中获取也就是说cr0 eax最终这一点被转化成汇编指令就是movl %eax, address_of_cr0。现在你应该清楚了吧操作约束中会给出到底从哪个寄存器传递值给cr0。 另外需要特别说明的是很多 文档都声明所有输出操作的操作约束必须包含一个等号()但GCC的文档中却很清楚的声明并非如此。因为等号()约束说明当前的表达式是一个 Write-Only的但另外还有一个符号——加号()用来说明当前表达式是一个Read-Write的如果一个操作约束中没有给出这两个符号中的 任何一个则说明当前表达式是Read-Only的。因为对于输出操作来说肯定是必须是可写的而等号()和加号()都表示可写只不过加号() 同时也表示是可读的。所以对于一个输出操作来说其操作约束只需要有等号()或加号()中的任意一个就可以了。 二者的区别是等号()表示当前操作表达式指定了一个纯粹的输出操作而加号()则表示当前操作表达式不仅仅只是一个输出操作还是一个输入操作。但无论是等号()约束还是加号()约束所约束的操作表达式都只能放在Output域中而不能被用在Input域中。 另外有些文档声明尽管GCC文档中提供了加号()约束但在实际的编译中通不过我不知道老版本会怎么样我在GCC 2.96中对加号()约束的使用非常正常。 我们通过一个例子看一下在一个输出操作中使用等号()约束和加号()约束的不同。 $ cat example2.c int main(int __argc, char* __argv[])  {  int cr0 5;  __asm__ __volatile__(movl %%cr0, %0:a (cr0));  return 0;  } $ gcc -S example2.c $ cat example2.s main:  pushl %ebp  movl %esp, %ebp  subl $4, %esp  movl $5, -4(%ebp) # cr0 5 #APP  movl %cr0, %eax  #NO_APP  movl %eax, %eax  movl %eax, -4(%ebp) # cr0 %eax movl $0, %eax  leave  ret  这个例子是使用等号()约束的情况变量cr0被放在内存-4(%ebp)的位置所以指令mov %eax, -4(%ebp)即表示将%eax的内容输出到变量cr0中。 下面是使用加号()约束的情况 $ cat example3.c int main(int __argc, char* __argv[])  {  int cr0 5;  __asm__ __volatile__(movl %%cr0, %0 : a (cr0));  return 0;  } $ gcc -S example3.c $ cat example3.s main:  pushl %ebp  movl %esp, %ebp  subl $4, %esp  movl $5, -4(%ebp) # cr0 5 movl -4(%ebp), %eax # input ( %eax cr0 ) #APP  movl %cr0, %eax #NO_APP movl %eax, -4(%ebp) # output (cr0 %eax ) movl $0, %eax leave ret 从编译的结果可以看出当使用加号()约束的时候cr0不仅作为输出还作为输入所使用寄存器都是寄存器约束(字母a表示使用eax寄存器)指定的。关于寄存器约束我们后面讨论。 在Output域中可以有多个输出操作表达式多个操作表达式中间必须用逗号(,)分开。例如 __asm__(  movl %%eax, %0 \n\t  pushl %%ebx \n\t  popl %1 \n\t  movl %1, %2  : a(cr0), b(cr1), c(cr2)); 2、Input Input域的内容用来指定当前内联汇编语句的输入。我们看一看这个例子 __asm__(movl %0, %%db7 : : a (cpu-db7)); 例中Input域的内容为一个表达式a[cpu-db7)被称作“输入表达式”用来表示一个对当前内联汇编的输入。 像输出表达式一样一个输入表达式也分为两部分带括号的部分(cpu-db7)和带引号的部分a。这两部分对于一个内联汇编输入表达式来说也是必不可少的。 括 号中的表达式cpu-db7是一个C/C语言的表达式它不必是一个左值表达式也就是说它不仅可以是放在C/C赋值操作左边的表达式 还可以是放在C/C赋值操作右边的表达式。所以它可以是一个变量一个数字还可以是一个复杂的表达式比如ab/c*d。比如上例可以改为 __asm__(movl %0, %%db7 : : a (foo))__asm__(movl %0, %%db7 : : a (0x1000))或__asm__(movl %0, %%db7 : : a (va*vb/vc))。 引号号中的 部分是约束部分和输出表达式约束不同的是它不允许指定加号()约束和等号()约束也就是说它只能是默认的Read-Only的。约束中必须指定 一个寄存器约束例中的字母a表示当前输入变量cpu-db7要通过寄存器eax输入到当前内联汇编中。 我们看一个例子 $ cat example4.c int main(int __argc, char* __argv[])  {  int cr0 5;  __asm__ __volatile__(movl %0, %%cr0::a (cr0));  return 0;  } $ gcc -S example4.c $ cat example4.s main:  pushl %ebp  movl %esp, %ebp  subl $4, %esp  movl $5, -4(%ebp) # cr0 5  movl -4(%ebp), %eax # %eax cr0 #APP  movl %eax, %cr0  #NO_APP  movl $0, %eax  leave  ret  我们从编译出的汇编代码可以看到在Instruction List之前GCC按照我们的输入约束a将变量cr0的内容装入了eax寄存器。 3. Operation Constraint 每一个Input和Output表达式都必须指定自己的操作约束Operation Constraint我们这里来讨论在80386平台上所可能使用的操作约束。 1、寄存器约束 当你当前的输入或输入需要借助一个寄存器时你需要为其指定一个寄存器约束。你可以直接指定一个寄存器的名字比如 __asm__ __volatile__(movl %0, %%cr0::eax (cr0)); 也可以指定一个缩写比如 __asm__ __volatile__(movl %0, %%cr0::a (cr0)); 如果你指定一个缩写比如字母a则GCC将会根据当前操作表达式中C/C表达式的宽度决定使用%eax还是%ax或%al。比如 unsigned short __shrt; __asm__ (mov %0%%bx : : a(__shrt)); 由于变量__shrt是16-bit short类型则编译出来的汇编代码中则会让此变量使用%ex寄存器。编译结果为 movw -2(%ebp), %ax # %ax __shrt #APP movl %ax, %bx #NO_APP 无论是Input还是Output操作表达式约束都可以使用寄存器约束。 下表中列出了常用的寄存器约束的缩写。 约束 Input/Output 意义  r I,O 表示使用一个通用寄存器由GCC在%eax/%ax/%al, %ebx/%bx/%bl, %ecx/%cx/%cl, %edx/%dx/%dl中选取一个GCC认为合适的。  q I,O 表示使用一个通用寄存器和r的意义相同。  a I,O 表示使用%eax / %ax / %al  b I,O 表示使用%ebx / %bx / %bl  c I,O 表示使用%ecx / %cx / %cl  d I,O 表示使用%edx / %dx / %dl  D I,O 表示使用%edi / %di  S I,O 表示使用%esi / %si  f I,O 表示使用浮点寄存器  t I,O 表示使用第一个浮点寄存器  u I,O 表示使用第二个浮点寄存器  2、内存约束  如果一个Input/Output操作表达式的C/C表达式表现为一个内存地址不想借助于任何寄存器则可以使用内存约束。比如 __asm__ (lidt %0 : m(__idt_addr)); 或 __asm__ (lidt %0 : :m(__idt_addr)); 我们看一下它们分别被放在一个C源文件中然后被GCC编译后的结果 $ cat example5.c // 本例中变量sh被作为一个内存输入 int main(int __argc, char* __argv[])  {  char* sh (char*)__argc;  __asm__ __volatile__(lidt %0 : : m (sh));  return 0;  }  $ gcc -S example5.c $ cat example5.s main:  pushl %ebp  movl %esp, %ebp  subl $4, %esp  leal 8(%ebp), %eax  movl %eax, -4(%ebp) # sh (char*) __argc #APP  lidt -4(%ebp)  #NO_APP  movl $0, %eax  leave  ret  $ cat example6.c // 本例中变量sh被作为一个内存输出 int main(int __argc, char* __argv[])  {  char* sh (char*)__argc;  __asm__ __volatile__(lidt %0 : m (sh));  return 0;  }  $ gcc -S example6.c $ cat example6.s main: pushl %ebp movl %esp, %ebp subl $4, %esp leal 8(%ebp), %eax movl %eax, -4(%ebp) # sh (char*) __argc #APP lidt -4(%ebp) #NO_APP movl $0, %eax leave ret 首先你会注意到在这两个例子中变量sh没有借助任何寄存器而是直接参与了指令lidt的操作。 其次通过仔细观察你会发现一个惊人的事实两个例子编译出来的汇编代码是一样的虽然一个例子中变量sh作为输入而另一个例子中变量sh作为输出。这是怎么回事 原来使用内存方式进行输入输出时由于不借助寄存器所以GCC不会按照你的声明对其作任何的输入输出处理。GCC只会直接拿来用究竟对这个C/C表达式而言是输入还是输出完全依赖与你写在Instruction List中的指令对其操作的指令。 由 于上例中对其操作的指令为lidtlidt指令的操作数是一个输入型的操作数所以事实上对变量sh的操作是一个输入操作即使你把它放在 Output域也不会改变这一点。所以对此例而言完全符合语意的写法应该是将sh放在Input域尽管放在Output域也会有正确的执行结果。 所 以对于内存约束类型的操作表达式而言放在Input域还是放在Output域对编译结果是没有任何影响的因为本来我们将一个操作表达式放在 Input域或放在Output域是希望GCC能为我们自动通过寄存器将表达式的值输入或输出。既然对于内存约束类型的操作表达式来说GCC不会自动为 它做任何事情那么放在哪儿也就无所谓了。但从程序员的角度而言为了增强代码的可读性最好能够把它放在符合实际情况的地方。 约束 Input/Output 意义  m I,O 表示使用系统所支持的任何一种内存方式不需要借助寄存器  3、立即数约束 如果一个Input/Output操作表达式的C/C表达式是一个数字常数不想借助于任何寄存器则可以使用立即数约束。 由于立即数在C/C中只能作为右值所以对于使用立即数约束的表达式而言只能放在Input域。 比如__asm__ __volatile__(movl %0, %%eax : : i (100) );  立即数约束很简单也很容易理解我们在这里就不再赘述。 约束 Input/Output 意义  i I 表示输入表达式是一个立即数(整数)不需要借助任何寄存器  F I 表示输入表达式是一个立即数(浮点数)不需要借助任何寄存器  4、通用约束 约束 Input/Output 意义  g I,O 表示可以使用通用寄存器内存立即数等任何一种处理方式。  0,1,2,3,4,5,6,7,8,9 I 表示和第n个操作表达式使用相同的寄存器/内存。  通 用约束g是一个非常灵活的约束当程序员认为一个C/C表达式在实际的操作中究竟使用寄存器方式还是使用内存方式或立即数方式并无所谓时或者程 序员想实现一个灵活的模板让GCC可以根据不同的C/C表达式生成不同的访问方式时就可以使用通用约束g。比如 #define JUST_MOV(foo) __asm__ (movl %0, %%eax : : g(foo)) JUST_MOV(100)和JUST_MOV(var)则会让编译器产生不同的代码。 int main(int __argc, char* __argv[])  {  JUST_MOV(100);  return 0;  }  编译后生成的代码为 main:  pushl %ebp  movl %esp, %ebp  #APP  movl $100, %eax  #NO_APP  movl $0, %eax  popl %ebp  ret 很明显这是立即数方式。而下一个例子 int main(int __argc, char* __argv[])  {  JUST_MOV(__argc);  return 0;  }  经编译后生成的代码为 main:  pushl %ebp  movl %esp, %ebp  #APP  movl 8(%ebp), %eax  #NO_APP  movl $0, %eax  popl %ebp  ret  这个例子是使用内存方式。 一个带有C/C表达式的内联汇编其操作表达式被按照被列出的顺序编号第一个是0第2个是1依次类推GCC最多允许有10个操作表达式。比如 __asm__ (popl %0 \n\t movl %1, %%esi \n\t movl %2, %%edi \n\t : a(__out) : r (__in1), r (__in2)); 此例中__out所在的Output操作表达式被编号为0r(__in1)被编号为1r(__in2)被编号为2。 再如 __asm__ (movl %%eax, %%ebx : : a(__in1), b(__in2)); 此例中a(__in1)被编号为0b(__in2)被编号为1。 如 果某个Input操作表达式使用数字0到9中的一个数字假设为1作为它的操作约束则等于向GCC声明“我要使用和编号为1的Output操作表达 式相同的寄存器如果Output操作表达式1使用的是寄存器或相同的内存地址如果Output操作表达式1使用的是内存”。上面的描述包含两个 限定数字0到数字9作为操作约束只能用在Input操作表达式中被指定的操作表达式比如某个Input操作表达式使用数字1作为约束那么被指定的 就是编号为1的操作表达式只能是Output操作表达式。 由于GCC规定最多只能有10个Input/Output操作表达式所以事 实上数字9作为操作约束永远也用不到因为Output操作表达式排在Input操作表达式的前面那么如果有一个Input操作表达式指定了数字9作为 操作约束的话那么说明Output操作表达式的数量已经至少为10个了那么再加上这个Input操作表达式则至少为11个了以及超出GCC的限 制。 5、Modifier Characters修饰符 等号()和加号()用于对Output操作表达式的修 饰一个Output操作表达式要么被等号()修饰要么被加号()修饰二者必居其一。使用等号()说明此Output操作表达式是Write- Only的使用加号()说明此Output操作表达式是Read-Write的。它们必须被放在约束字符串的第一个字母。比如a(foo)是非 法的而g(foo)则是合法的。 当使用加号()的时候此Output表达式等价于使用等号()约束加上一个Input表达式。比如 __asm__ (movl %0, %%eax; addl %%eax, %0 : b(foo)) 等价于 __asm__ (movl %1, %%eax; addl %%eax, %0 : b(foo) : b(foo)) 但如果使用后一种写法Instruction List中的别名也要相应的改动。关于别名我们后面会讨论。 像 等号()和加号()修饰符一样符号()也只能用于对Output操作表达式的修饰。当使用它进行修饰时等于向GCC声明GCC不得 为任何Input操作表达式分配与此Output操作表达式相同的寄存器。其原因是修饰符意味着被其修饰的Output操作表达式要在所有的 Input操作表达式被输入前输出。我们看下面这个例子 int main(int __argc, char* __argv[])  {  int __in1 8, __in2 4, __out 3;  __asm__ (popl %0 \n\t movl %1, %%esi \n\t movl %2, %%edi \n\t : a(__out) : r (__in1), r (__in2)); return 0;  }  此 例中%0对应的就是Output操作表达式它被指定的寄存器是%eax整个Instruction List的第一条指令popl %0编译后就成为popl %eax这时%eax的内容已经被修改随后在Instruction List后GCC会通过movl %eax, address_of_out这条指令将%eax的内容放置到Output变量__out中。对于本例中的两个Input操作表达式而言它们的寄存器约 束为r即要求GCC为其指定合适的寄存器然后在Instruction List之前将__in1和__in2的内容放入被选出的寄存器中如果它们中的一个选择了已经被__out指定的寄存器%eax假如是__in1那 么GCC在Instruction List之前会插入指令movl address_of_in1, %eax那么随后popl %eax指令就修改了%eax的值此时%eax中存放的已经不是Input变量__in1的值了那么随后的movl %1, %%esi指令将不会按照我们的本意——即将__in1的值放入%esi中——而是将__out的值放入%esi中了。  下面就是本例的编译结果很明显GCC为__in2选择了和__out相同的寄存器%eax这与我们的初衷不符。 main:  pushl %ebp  movl %esp, %ebp  subl $12, %esp  movl $8, -4(%ebp)  movl $4, -8(%ebp)  movl $3, -12(%ebp)  movl -4(%ebp), %edx # __in1使用寄存器%edx movl -8(%ebp), %eax # __in2使用寄存器%eax #APP  popl %eax  movl %edx, %esi  movl %eax, %edi  #NO_APP  movl %eax, %eax  movl %eax, -12(%ebp) # __out使用寄存器%eax movl $0, %eax  leave  ret  为 了避免这种情况我们必须向GCC声明这一点要求GCC为所有的Input操作表达式指定别的寄存器方法就是在Output操作表达式a (__out)的操作约束中加入约束由于GCC规定等号()约束必须放在第一个所以我们写作a(__out)。  下面是我们将约束加入之后编译的结果 main:  pushl %ebp  movl %esp, %ebp  subl $12, %esp  movl $8, -4(%ebp)  movl $4, -8(%ebp)  movl $3, -12(%ebp)  movl -4(%ebp), %edx #__in1使用寄存器%edx movl -8(%ebp), %eax  movl %eax, %ecx # __in2使用寄存器%ecx #APP  popl %eax  movl %edx, %esi  movl %ecx, %edi  #NO_APP  movl %eax, %eax  movl %eax, -12(%ebp) #__out使用寄存器%eax movl $0, %eax  leave  ret  OK这下好了完全与我们的意图吻合。  如 果一个Output操作表达式的寄存器约束被指定为某个寄存器只有当至少存在一个Input操作表达式的寄存器约束为可选约束时可选约束的意思是可 以从多个寄存器中选取一个或使用非寄存器方式比如r或g时此Output操作表达式使用修饰才有意义。如果你为所有的 Input操作表达式指定了固定的寄存器或使用内存/立即数约束则此Output操作表达式使用修饰没有任何意义。比如 __asm__ (popl %0 \n\t  movl %1, %%esi \n\t  movl %2, %%edi \n\t  : a(__out)  : m (__in1), c (__in2));  此例中的Output操作表达式完全没有必要使用来修饰因为__in1和__in2都被指定了固定的寄存器或使用了内存方式GCC无从选择。 但如果你已经为某个Output操作表达式指定了修饰并指定了某个固定的寄存器你就不能再为任何Input操作表达式指定这个寄存器否则会出现编译错误。比如 __asm__ (popl %0 \n\t  movl %1, %%esi \n\t  movl %2, %%edi \n\t  : a(__out)  : a (__in1), c (__in2));  本例中由于__out已经指定了寄存器%eax同时使用了符号修饰则再为__in1指定寄存器%eax就是非法的。 反过来你也可以为Output指定可选约束比如r,g等让GCC为其选择到底使用哪个寄存器还是使用内存方式GCC在选择的时候会首先排除掉已经被Input操作表达式使用的所有寄存器然后在剩下的寄存器中选择或干脆使用内存方式。比如 __asm__ (popl %0 \n\t  movl %1, %%esi \n\t  movl %2, %%edi \n\t  : r(__out)  : a (__in1), c (__in2));  本例中由于__out指定了约束r即让GCC为其决定使用哪一格寄存器而寄存器%eax和%ecx已经被__in1和__in2使用那么GCC在为__out选择的时候只会在%ebx和%edx中选择。 前3 个修饰符只能用在Output操作表达式中而百分号[%]修饰符恰恰相反只能用在Input操作表达式中用于向GCC声明“当前Input操作表 达式中的C/C表达式可以和下一个Input操作表达式中的C/C表达式互换”。这个修饰符号一般用于符合交换律运算比如加()乘(*) 与()或(|)等等。我们看一个例子 int main(int __argc, char* __argv[])  {  int __in1 8, __in2 4, __out 3;  __asm__ (addl %1, %0\n\t  : r(__out)  : %r (__in1), 0 (__in2));  return 0;  } 在 此例中由于指令是一个加法运算相当于等式__out __in1 __in2而它与等式__out __in2 __in1没有什么不同。所以使用百分号修饰让GCC知道__in1和__in2可以互换也就是说GCC可以自动将本例的内联汇编改变为 __asm__ (addl %1, %0\n\t : r(__out) : %r (__in2), 0 (__in1));  修饰符 Input/Output 意义  O 表示此Output操作表达式是Write-Only的  O 表示此Output操作表达式是Read-Write的  O 表示此Output操作表达式独占为其指定的寄存器  % I 表示此Input操作表达式中的C/C表达式可以和下一个Input操作表达式中的C/C表达式互换  4. 占位符 什么叫占位符我们看一看下面这个例子 __asm__ (addl %1, %0\n\t : a(__out) : m (__in1), a (__in2)); 这 个例子中的%0和%1就是占位符。每一个占位符对应一个Input/Output操作表达式。我们在之前已经提到GCC规定一个内联汇编语句最多可以有 10个Input/Output操作表达式然后按照它们被列出的顺序依次赋予编号0到9。对于占位符中的数字而言和这些编号是对应的。 由于占位符前面使用一个百分号(%)为了区别占位符和寄存器GCC规定在带有C/C表达式的内联汇编中Instruction List中直接写出的寄存器前必须使用两个百分号(%%)。 GCC 对其进行编译的时候会将每一个占位符替换为对应的Input/Output操作表达式所指定的寄存器/内存地址/立即数。比如在上例中占位符%0对应 Output操作表达式a(__out)而a(__out)指定的寄存器为%eax所以把占位符%0替换为%eax占位符%1对应 Input操作表达式m(__in1)而m(__in1)被指定为内存操作所以把占位符%1替换为变量__in1的内存地址。 也许有人认为在上面这个例子中完全可以不使用%0而是直接写%%eax就像这样 __asm__ (addl %1, %%eax\n\t : a(__out) : m (__in1), a (__in2)); 和 上面使用占位符%0没有什么不同那么使用占位符%0就没有什么意义。确实两者生成的代码完全相同但这并不意味着这种情况下占位符没有意义。因为如果 不使用占位符那么当有一天你想把变量__out的寄存器约束由a改为b时那么你也必须将addl指令中的%%eax改为%%ebx也就是说你需要同 时修改两个地方而如果你使用占位符你只需要修改一次就够了。另外如果你不使用占位符将不利于代码的清晰性。在上例中如果你使用占位符那么你一 眼就可以得知addl指令的第二个操作数内容最终会输出到变量__out中否则如果你不用占位符而是直接将addl指令的第2个操作数写为%% eax那么你需要考虑一下才知道它最终需要输出到变量__out中。这是占位符最粗浅的意义。毕竟在这种情况下你完全可以不用。 但对于这些情况来说不用占位符就完全不行了 首 先我们看一看上例中的第1个Input操作表达式m(__in1)它被GCC替换之后表现为addl address_of_in1, %%eax__in1的地址是什么编译时才知道。所以我们完全无法直接在指令中去写出__in1的地址这时使用占位符交给GCC在编译时进行替 代就可以解决这个问题。所以这种情况下我们必须使用占位符。 其次如果上例中的Output操作表达式a(__out)改为 r(__out)那么__out在究竟使用那么寄存器只有到编译时才能通过GCC来决定既然在我们写代码的时候我们不知道究竟哪个寄存器被选 择我们也就不能直接在指令中写出寄存器的名称而只能通过占位符替代来解决。 5. Clobber/Modify 有时候你想通知GCC当前内联汇编语句可能会对某些寄存器或内存进行修改希望GCC在编译时能够将这一点考虑进去。那么你就可以在Clobber/Modify域声明这些寄存器或内存。 这 种情况一般发生在一个寄存器出现在Instruction List但却不是由Input/Output操作表达式所指定的也不是在一些Input/Output操作表达式使用r,g约束时由GCC 为其选择的同时此寄存器被Instruction List中的指令修改而这个寄存器只是供当前内联汇编临时使用的情况。比如 __asm__ (movl %0, %%ebx : : a(__foo) : bx); 寄存器%ebx出现在Instruction List中并且被movl指令修改但却未被任何Input/Output操作表达式指定所以你需要在Clobber/Modify域指定bx以让GCC知道这一点。 因 为你在Input/Output操作表达式所指定的寄存器或当你为一些Input/Output操作表达式使用r,g约束让GCC为你选择一 个寄存器时GCC对这些寄存器是非常清楚的——它知道这些寄存器是被修改的你根本不需要在Clobber/Modify域再声明它们。但除此之外 GCC对剩下的寄存器中哪些会被当前的内联汇编修改一无所知。所以如果你真的在当前内联汇编指令中修改了它们那么就最好在Clobber/Modify 中声明它们让GCC针对这些寄存器做相应的处理。否则有可能会造成寄存器的不一致从而造成程序执行错误。 在Clobber/Modify域中指定这些寄存器的方法很简单你只需要将寄存器的名字使用双引号( )引起来。如果有多个寄存器需要声明你需要在任意两个声明之间用逗号隔开。比如 __asm__ (movl %0, %%ebx; popl %%ecx : : a(__foo) : bx, cx ); 这些串包括 声明的串 代表的寄存器  al,ax,eax %eax  bl,bx,ebx %ebx  cl,cx,ecx %ecx  dl,dx,edx %edx  si,esi %esi  di, edi %edi  由上表可以看出你只需要使用ax,bx,cx,dx,si,di就可以了因为其它的都和它们中的一个是等价的。 如 果你在一个内联汇编语句的Clobber/Modify域向GCC声明某个寄存器内容发生了改变GCC在编译时如果发现这个被声明的寄存器的内容在此 内联汇编语句之后还要继续使用那么GCC会首先将此寄存器的内容保存起来然后在此内联汇编语句的相关生成代码之后再将其内容恢复。我们来看两个例 子然后对比一下它们之间的区别。 这个例子中声明了寄存器%ebx内容发生了改变 $ cat example7.c int main(int __argc, char* __argv[])  {  int in 8;  __asm__ (addl %0, %%ebx  : /* no output */  : a (in) : bx);  return 0;  } $ gcc -O -S example7.c $ cat example7.s main: pushl %ebp movl %esp, %ebp pushl %ebx # %ebx内容被保存  movl $8, %eax #APP addl %eax, %ebx #NO_APP movl $0, %eax movl (%esp), %ebx # %ebx内容被恢复 leave ret 下面这个例子的C源码与上一个例子除了没有声明%ebx寄存器发生了改变之外其它都相同。 $ cat example8.c int main(int __argc, char* __argv[])  {  int in 8;  __asm__ (addl %0, %%ebx  : /* no output */  : a (in) );  return 0;  } $ gcc -O -S example8.c $ cat example8.s main:  pushl %ebp  movl %esp, %ebp  movl $8, %eax  #APP  addl %eax, %ebx  #NO_APP  movl $0, %eax  popl %ebp  ret 仔细对比一下example7.s和example8.s你就会明白在Clobber/Modify域声明一个寄存器的意义。 另 外需要注意的是如果你在Clobber/Modify域声明了一个寄存器那么这个寄存器将不能再被用做当前内联汇编语句的Input/Output操 作表达式的寄存器约束如果Input/Output操作表达式的寄存器约束被指定为r或gGCC也不会选择已经被声明在 Clobber/Modify中的寄存器。比如 __asm__ (movl %0, %%ebx : : a(__foo) : ax, bx); 此例中由于Output操作表达式a(__foo)的寄存器约束已经指定了%eax寄存器那么再在Clobber/Modify域中指定ax就是非法的。编译时GCC会给出编译错误。 除 了寄存器的内容会被改变内存的内容也可以被修改。如果一个内联汇编语句Instruction List中的指令对内存进行了修改或者在此内联汇编出现的地方内存内容可能发生改变而被改变的内存地址你没有在其Output操作表达式使用m 约束这种情况下你需要使用在Clobber/Modify域使用字符串memory向GCC声明“在这里内存发生了或可能发生了改变”。例 如 void * memset(void * s, char c, size_t count) { __asm__(cld\n\t rep\n\t stosb : /* no output */ : a (c),D (s),c (count) : cx,di,memory); return s; } 此 例实现了标准函数库memset其内联汇编中的stosb对内存进行了改动而其被修改的内存地址s被指定装入%edi没有任何Output操作表达 式使用了m约束以指定内存地址s处的内容发生了改变。所以在其Clobber/Modify域使用memory向GCC声明内存内容发生了变 动。 如果一个内联汇编语句的Clobber/Modify域存在memory那么GCC会保证在此内联汇编之前如果某个内存的内 容被装入了寄存器那么在这个内联汇编之后如果需要使用这个内存处的内容就会直接到这个内存处重新读取而不是使用被存放在寄存器中的拷贝。因为这个 时候寄存器中的拷贝已经很可能和内存处的内容不一致了。 这只是使用memory时GCC会保证做到的一点但这并不是全部。因为使用memory是向GCC声明内存发生了变化而内存发生变化带来的影响并不止这一点。比如我们在前面讲到的例子 int main(int __argc, char* __argv[])  {  int* __p (int*)__argc;  (*__p) 9999;  __asm__(:::memory);  if((*__p) 9999)  return 5;  return (*__p);  } 本 例中如果没有那条内联汇编语句那个if语句的判断条件就完全是一句废话。GCC在优化时会意识到这一点而直接只生成return 5的汇编代码而不会再生成if语句的相关代码而不会生成return (*__p)的相关代码。但你加上了这条内联汇编语句它除了声明内存变化之外什么都没有做。但GCC此时就不能简单的认为它不需要判断都知道 (*__p)一定与9999相等它只有老老实实生成这条if语句的汇编代码一起相关的两个return语句相关代码。 当一个内联汇编 指令中包含影响eflags寄存器中的条件标志也就是那些Jxx等跳转指令要参考的标志位比如进位标志0标志等那么需要在 Clobber/Modify域中使用cc来声明这一点。这些指令包括adc, divpopflbtrbts等等另外当包含call指令时由于你不知道你所call的函数是否会修改条件标志为了稳妥起见最好也使用 cc。 我很少在相关资料中看到有关cc的确切用法只有一份文档提到了它但还不是i386平台的只是说cc是处理器平台 相关的并非所有的平台都支持它但即使在不支持它的平台上使用它也不会造成编译错误。我做了一些实验但发现使用cc和不使用cc所生成的代 码没有任何不同。但Linux 2.4的相关代码中用到了它。如果谁知道在i386平台上cc的细节请和我联系。 另外还可以在 Clobber/Modify域指定数字0到9以声明第n个Input/Output操作表达式所使用的寄存器发生了变化但正如我们在前面所提到的 如果你为某个Input/Output操作表达式指定了寄存器或使用g,r等约束让GCC为其选择寄存器GCC已经知道哪个寄存器内容发生了 变化所以这么做没有什么意义我也作了相关的试验没有发现使用它会对GCC生成的汇编代码有任何影响至少在i386平台上是这样。Linux 2.4的所有i386平台相关内联汇编代码中都没有使用这一点但S390平台相关代码中有用到但由于我对S390汇编没有任何概念所以也不知道这 么做的意义何在。
http://www.zqtcl.cn/news/169175/

相关文章:

  • 中介做网站的别打电话有没有教做健身餐的网站
  • 山东电力建设网站雷州市网站建设
  • 企业网站的意义公司网站建app
  • 网站设计模板免费国庆图片制作小程序
  • 包头焦点网站建设郑州包装设计公司
  • 建行官方网站首页做跨境电商亏死了
  • 河北智能网站建设平台卖链接的网站
  • 网站建设简单点的服装搭配网站建设策划书
  • 哪一个军事网站做的比较好今天第四针最新消息
  • 黄页网站推广app软件查企业公司用什么软件
  • 网站设计机构培训全自动网页制作系统源码
  • 外贸网站建设收益深圳建设厅官网
  • 跟网站开发有关的内容东莞市生态环境局
  • dw软件做的东西怎么在网站用网站备案抽查通过
  • 重庆建设集团网站首页wordpress主题inn
  • 对京东网站建设的总结湖北做网站的
  • 杭州网站开发后端招郑州工装定制
  • 网站搭建论文filetype ppt 网站建设
  • 个人做营利性质网站会怎么样如何引用网站上的资料做文献
  • 新网站制作市场泰安做网站哪家好
  • 常熟苏州网站建设flash如何制作网站
  • 电商网站都是用什么做的网站服务器维护方案
  • 简述企业网站建设的流程手机怎么自己做网页
  • 网站备案信息管理呼图壁网站建设
  • 网站建设学习资料开发一套软件需要多少钱
  • 大庆网站设计衡阳seo网站推广
  • 基层科普网站建设的现状自己做的网站怎样链接数据库
  • 网站建设工程师的职位要求化妆品行业网站开发
  • 做海报有什么素材网站知乎什么样的蓝色做网站做好看
  • 餐饮网站建设网站wordpress优酷视频插件下载