您好,欢迎来电子发烧友网! ,新用户?[免费注册]

您的位置:电子发烧友网>源码下载>汇编编程>

内联汇编和嵌入型汇编的使用

大小:0.4 MB 人气: 2017-10-19 需要积分:1
  内联汇编和嵌入型汇编是包含在C‘ target=’_blank‘ style=’cursor:pointer;color:#D05C38;text-decoration:underline;‘》C/C++编译器中的汇编器。使用它可以在C/C++程序中实现C/C++语言不能完成的一些工作。例如,在下面几种情况中必须使用内联汇编或嵌入型汇编。
  · 程序中使用饱和算术运算(Saturating arithmetic),如SSAT16 和 USAT16指令。
  · 程序中需要对协处理器进行操作。
  · 在C或C++程序中完成对程序状态寄存器的操作。
  使用内联汇编编写的程序代码效率也比较高。
  12.1.1 内联汇编
  1.内联汇编语法
  内联汇编使用“_asm”(C++)和“asm”(C和C++)关键字声明,语法格式如下所示。
  · __asm(“instruction[;instruction]”); // 必须为单条指令
  __asm{instruction[;instruction]}
  · __asm{
  。..
  instruction
  。..
  }
  · asm(“instruction[;instruction]”); // 必须为单条指令
  asm{instruction[;instruction]}
  · asm{
  。..
  instruction
  。..
  }
  内联汇编支持大部分的ARM指令,但不支持带状态转移的跳转指令,如BX和BLX指令,详见ARM相关文档。
  由于内联汇编嵌入在C或C++程序中,所有在用法上有其自身的一些特点。
  ① 如果同一行中包含多条指令,则用分号隔开。
  ② 如果一条指令不能在一行中完成,使用反斜杠“/”将其连接。
  ③ 内联汇编中的注释语句可以使用C或C++风格的。
  ④ 汇编语言中使用逗号“,”作为指令操作数的分隔符,所以如果在C语言中使用逗号必须用圆括号括起来。如,__asm {ADD x, y, (f(), z)}。
  ⑤ 内联汇编语言中的寄存器名被编译器视为C或C++语言中的变量,所以内联汇编中出现的寄存器名不一定和同名的物理寄存器相对应。这些寄存器名在使用前必须声明,否则编译器将提示警告信息
  ⑥ 内联汇编中的寄存器(除程序状态寄存器CPSR和SPSR外)在读取前必须先赋值,否则编译器将产生错误信息。下面的例子显示了内联汇编和真正汇编的区别。
  错误的内联汇编函数如下所示。
  int f(int x)
  {
  __asm
  {
  STMFD sp!, {r0} // 保存r0不合法,因为在读之前没有对寄存器写操作
  ADD r0, x, 1
  EOR x, r0, x
  LDMFD sp!, {r0} // 不需要恢复寄存器
  }
  return x;
  }
  将其进行改写,使它符合内联汇编的语法规则。
  int f(int x)
  {
  int r0;
  __asm
  {
  ADD r0, x, 1
  EOR x, r0, x
  }
  return x;
  }
  下面通过几个例子进一步了解内联汇编的语法。
  ① 字符串拷贝
  下面的例子使用一个循环完成了字符串的拷贝工作。
  #include 《stdio.h》
  void my_strcpy(const char *src, char *dst)
  {
  int ch;
  __asm
  {
  loop:
  LDRB ch, [src], #1
  STRB ch, [dst], #1
  CMP ch, #0
  BNE loop
  }
  }
  int main(void)
  {
  const char *a = “Hello world!”;
  char b[20];
  my_strcpy (a, b);
  printf(“Original string: ’%s‘\n”, a);
  printf(“Copied string: ’%s‘\n”, b);
  return 0;
  }
  ② 中断使能
  下面的例子通过读取程序状态寄存器CPSR并设置它的中断使能位bit[7]来禁止/打开中断。需要注意的是,该例只能运行在系统模式下,因为用户模式是无权修改程序状态寄存器的。
  __inline void enable_IRQ(void)
  {
  int tmp;
  __asm
  {
  MRS tmp, CPSR
  BIC tmp, tmp, #0x80
  MSR CPSR_c, tmp
  }
  }
  __inline void disable_IRQ(void)
  {
  int tmp;
  __asm
  {
  MRS tmp, CPSR
  ORR tmp, tmp, #0x80
  MSR CPSR_c, tmp
  }
  }
  int main(void)
  {
  disable_IRQ();
  enable_IRQ();
  }
  ③ 分隔符的计算
  下面的例子计算两个整数数组中分隔符“,”的个数。该例子显示了如何在内联汇编中访问C或C++语言中的数据类型。该例中的内联汇编函数mlal()被编译器优化为一条SMLAL指令,可以使用-S –interleave编译选项使编译器输出汇编结果。
  #include 《stdio.h》
  /* change word order if big-endian */
  #define lo64(a) (((unsigned*) &a)[0]) /* long long型的低32位 */
  #define hi64(a) (((int*) &a)[1]) /* long long型的高32位 */
  __inline __int64 mlal(__int64 sum, int a, int b)
  {
  #if !defined(__thumb) && defined(__TARGET_FEATURE_MULTIPLY)
  __asm
  {
  SMLAL lo64(sum), hi64(sum), a, b
  }
  #else
  sum += (__int64) a * (__int64) b;
  #endif
  return sum;
  }
  __int64 dotprod(int *a, int *b, unsigned n)
  {
  __int64 sum = 0;
  do
  sum = mlal(sum, *a++, *b++);
  while (--n != 0);
  return sum;
  }
  int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  int b[10] = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
  int main(void)
  {
  printf(“Dotproduct %lld (should be %d)\n”, dotprod(a, b, 10), 220);
  return 0;
  }
  2.内联汇编中的限制
  可以在内联汇编代码中执行的操作有许多限制。这些限制提供安全的方法,并确保在汇编代码中不违反 C 和 C++ 代码编译中的假设。
  ① 不能直接向程序计数器PC赋值。
  ② 内联汇编不支持标号变量。
  ③ 不能在程序中使用“。”或{PC}得到当前指令地址值。
  ④ 在16进制常量前加“0x”代替“&”。
  ⑤ 建议不要对堆栈进行操作。
  ⑥ 编译器可能会使用r12和r13寄存器存放编译的中间结果,在计算表达式值时可能会将寄存器r0~r3、r12及r14用于子程序调用。另外在内联汇编中设置程序状态寄存器CPSR中的标志位NZCV时,要特别小心,内联汇编中的设置很可能会和编译器计算的表达式的结果冲突。
  ⑦ 用内联汇编代码更改处理器模式是可能的。然而,更改处理器模式会禁止使用 C或 C++操作数或禁止对已编译C或C++代码的调用,直到将处理器模式更改回原设置之后之前的函数库才可正常使用。
  ⑧ 为Thumb状态编译C或C++时,内联汇编程序不可用且不汇编Thumb指令。
  ⑨ 尽管可以使用通用协处理器指令指定 VFP 或 FPA 指令,但内联汇编程序不为它们提供直接支持。
  不能用内联汇编代码更改 VFP 向量模式。内联汇编可包含浮点表达式操作数,该操作数可使用编译程序生成的 VFP 代码求出操作数值。因此,仅由编译程序修改 VFP 状态很重要。
  ⑩ 内嵌汇编不支持的指令有BX、BLX、BXJ和BKPT指令。而LDM、STM、LDRD和STRD指令可能被等效为ARM LDR或STR指令。
  3.内联汇编中的虚拟寄存器
  内联汇编程序提供对 ARM 处理器物理寄存器的非直接访问。如果在内联汇编程序指令中将某个ARM寄存器用作操作数,它就成为相同名称的虚拟寄存器的引用,而不是对实际物理ARM寄存器的引用。例如内联汇编指令中使用了寄存器r0,但对于C编译器,指令中出现的r0只是一个变量,并非实际的物理寄存器r0,当程序运行时,可能是由物理寄存器r1来存放r0所代表的值。
  下面的例子显示了编译器如何对内联汇编指令的寄存器进行分配。
  程序的源代码如下。
  #include 《stdio.h》
  void test_inline_register(void)
  {
  int i;
  int r5,r6,r7;
  __asm
  {
  MOV i,#0
  loop:
  MOV r5,#0
  MOV r6,#0
  MOV r7,#0
  ADD i,i,#1
  CMP i,#3
  BNE loop
  }
  }
  int main(void)
  {
  test_inline_register ();
  printf(“test inline register\n”);
  return 0;
  }
  由C编译器编译出的汇编码如下所示。
  test_inline_register:
  0000807C E3A00000 MOV r0,#0
  》》》 TEST_INLINE_REGISTER\#12 loop:
  00008080 E1A00000 NOP
  》》》 TEST_INLINE_REGISTER\#13 MOV r5,#0
  00008084 E3A01000 MOV r1,#0
  》》》 TEST_INLINE_REGISTER\#14 MOV r6,#0
  00008088 E3A02000 MOV r2,#0
  》》》 TEST_INLINE_REGISTER\#15 MOV r7,#0
  0000808C E3A03000 MOV r3,#0
  》》》 TEST_INLINE_REGISTER\#16 ADD i,i,#1
  00008090 E2800001 ADD r0,r0,#1
  》》》 TEST_INLINE_REGISTER\#17 CMP i,#3
  00008094 E3500003 CMP r0,#3
  00008098 0A000000 BEQ 0x80a0 《TEST_INLINE_REGISTER\#21》
  》》》 TEST_INLINE_REGISTER\#18 BNE loop
  0000809C EAFFFFF8 B 0x8084 《TEST_INLINE_REGISTER\#13》
  》》》 TEST_INLINE_REGISTER\#21 }
  000080A0 E12FFF1E BX r14
  》》》 TEST_INLINE_REGISTER\#25 {
  注意下面的代码是由Realview2.2编译出的代码,使用其他编译器结果可能有差异。同一段内嵌汇编经过不同版本的编译器编译后,在指令里可能使用不一样的实际寄存器,但是只要遵循文档里的编码指导,执行的功能肯定相同。
  例子中以“》》》”的开头的行是程序的源码部分,紧接其后的是由编译器编译出的汇编代码。从上例可以很清楚地看出,源程序中使用了r5、r6和r7,但由编译器编译后的代码使用了寄存器r1、r2和r3。
  另外,需要特别指出的是在内联汇编中使用寄存器必须先声明其变量类型,如上例中的“int r5,r6,r7”。如果不在使用前进行声明,编译器将给出以下错误信息。
  #1267-D: Implicit physical register R3 should be defined as a variable
  编译程序定义的虚拟寄存器有函数局部作用范围,即在同一个C函数中,涉及相同虚拟寄存器名称的多个asm语句或声明,访问相同的虚拟寄存器。
  内联汇编没有为pc(r15)、lr(r14)和sp(r13)寄存器创建虚拟寄存器,而且不能在内联汇编代码中读取或直接修改它们的值。如果内联汇编程序中出现了对这些寄存器的访问,编译器将给出以下错误消息。例如,如果指定r14:
  #20: identifier “r14” is undefined
  内联汇编可以直接使用CPSR和SPSR对程序状态字进行操作,因为内联汇编中不存在虚拟处理器状态寄存器(PSR)。任何对 PSR 的引用总是指向物理 PSR。
  4.内联汇编中的指令展开
  内联汇编代码中的ARM指令可能会在编译过程中扩展为几条指令。扩展取决于指令、指令中指定的操作数个数以及每个操作数的类型和值。通常,被扩展的指令有以下两种情况:
  · 含有常数操作的指令;
  · LDM、STM、LDRD 和 STRD指令;
  · 乘法指令MUL被扩展为一系列的加法和移位指令。
  下面的例子说明了编译器如何对含有常数操作的指令进行扩展。
  包含有常数操作的加法指令:
  ADD r0,r0,#1023
  被编译器编译为如下两条指令:
  ADD r0,r0,#1024
  SUB r0,r0,#1
  注意扩展指令对程序状态寄存器CPSR的影响:算术指令影响相应的NZCV标准位;其他指令设置NZ标志位不影响V标志位。
  所有的LDM和STM指令被扩展为等效的LDR和STR指令序列。然而,在优化过程中,编译程序可能因此将单独的指令重组为一条LDM或STM指令。
  5.内联汇编中的常数
  指令中的标志符“#”是可选的(前面的例子中,指令中常数前均加了标志符“#”)。如果在指令中使用了“#”,则其后的表达式必为常数。
  6.内联汇编指令对标志位的影响
  内联汇编指令可能显式或隐式地更新处理器程序状态寄存器的条件标志位。在仅包含虚拟寄存器操作数或简单表达式操作数的内联汇编中,其执行结果是可以预见。如果指令中指定了隐式或显式更新条件标志位,则条件标志位根据指令的执行进行设置。如果未指定更新,则条件标志不会更改。如果内嵌汇编指令的操作数都不是简单操作数时或指令不显式更新条件标志位,则条件标志位可能会被破坏。一般情况下,编译程序不易诊断出对条件标志的潜在破坏。然而,在构造析构C++临时函数的操作数时,如果指令试图更新条件标志,编译程序将给予警告,因为析构函数可能会破坏条件标志位。
  7.内联汇编指令中的操作数
  内联汇编指令中的操作数分为以下4种。
  · 虚拟寄存器
  · 表达式操作数
  · 寄存器列表
  · 中间操作数
  (1)虚拟寄存器
  在内联汇编指令中指定的寄存器表示虚拟寄存器而不是实际的物理寄存器。由编译器编译的汇编代码中使用的物理寄存器可能与在指令中指定的不同。每个虚拟寄存器的初值是不可预测的,必须在读取之前将初值写入虚拟寄存器。如果在写入之前试图读虚拟寄存器,编译程序会给予警告。
  (2)表达式操作数
  在内联汇编指令中,可将函数自变量、C或C++变量和其他C或C++表达式指定为寄存器操作数。用作操作数的表达式必须为整数类型,如char、short、int或long,(长整型long long除外)或指针类型。当表达式作为内联汇编指令的操作数时,编译器在编译时自动增加一段代码计算表示式的值并将其加载到指定的寄存器中。
  注意数据类型中除char和short(默认为无符号类型)外,其他均为有符号类型。
  下面的例子显示了编译器如何处理内联汇编中的表达式操作数。
  程序源代码如下所示。
  /* Example Operands */
  void my_operand(void)
  {
  int i,j,total;
  __asm
  {
  mov i,#0
  mov j,#1
  add total,j,i+j
  }
  }
  int main(void)
  {
  my_operand ();
  }
  由编译器编译出的汇编代码如下所示(其中只列出了内联汇编的一段代码)。
  my_operand:
  0000807C E3A01000 MOV r1,#0
  》》》 OPERANDS\#12 mov j,#1
  00008080 E3A00001 MOV r0,#1
  00008084 E0812000 ADD r2,r1,r0
  》》》 OPERANDS\#13 add total,j,i+j
  00008088 E0803002 ADD r3,r0,r2
  》》》 OPERANDS\#15 }
  0000808C E12FFF1E BX r14
  》》》 OPERANDS\#19 {
  从编译的代码可以看出,编译器将“add total,j,i+j”分为两步来完成,用户在编写自己的内联汇编应用程序时要特别注意这一点。
  包含多个表达式操作数的指令,没有指定表达式操作数求值的顺序。
  将C或C++表达式用作内联汇编程序操作数,如果表达式的值不能满足 ARM指令中所要求的指令操作数约束条件,一条指令将被扩展为多条指令。
  如果用作操作数的表达式创建需要析构的临时函数,析构将发生在执行内联汇编指令之后,这与C++析构临时函数的规则相类似。
  简单表达式操作数包含以下几种类型。
  · 变量值
  · 变量地址
  · 指针变量的反引用(the dereferencing of a point varable)
  · 伪操作指定的程序常量
  非简单表达式操作数包含以下几种类型。
  · 隐式函数调用,如除法,或显式函数调用
  · C++临时函数的构造
  · 算术或逻辑操作
  (3)寄存器列表
  寄存器列表最多可包含 16 个操作数。这些操作数可以是虚拟寄存器或表达式操作数。在寄存器列表中指定虚拟寄存器和表达式操作数的顺序非常重要。寄存器列表中操作数的读写顺序是从左到右。第一个操作数使用最低地址,随后的操作数的地址依次在前一地址基础上增加 4。这一点与LDM 或 STM 指令的普通操作(编号最低的物理寄存器总是存入最低的存储器地址)是不同的。之所以存在这种区别是因为在内联汇编中使用的寄存器被编译器虚拟化了。
  同一个表达式操作数或虚拟寄存器可以在寄存器列表中出现多次,重复使用。
  如果表达式操作数或虚拟寄存器被指定为指令中的基址寄存器,表达式或虚拟寄存器的值按照ARM指令寻址方式进行更新。更新将覆盖原表达式或虚拟寄存器的值。
  (4)中间操作数(Intermediate operands)
  在内联汇编指令中,可能将C或C++整型常量表达式用作立即数处理。用于指定直接移位的常量表达式的值必须在ARM指令规定的移位操作数的范围内;用于为存储器或协处理器数据传送指令指定直接偏移量的常量表达式,必须符合ARM体系结构中的内存对齐标准。
  8.函数调用和分支跳转
  利用内联汇编程序的BL和SWI指令可在常规指令字段后指定3个可选列表。这些指令格式有以下几种。
  SWI{cond} swi_num , { input_param_list }, { output_value_list }, { corrupt_reg_list }
  BL{cond} function, { input_param_list }, { output_value_list }, { corrupt_reg_list }
  其中,swi_num为SWI调用的中断号;function为被调用函数名;{input_param_list}为输入参数列表;{output_value_list}为输出参数列表;{corrupt_reg_list}为被破坏寄存器列表。
  注意内联汇编程序不支持BX、BLX和BXJ指令。不能在任何输入、输出或“被破坏的寄存器列表(corrupted register list)”中指定lr、sp或pc寄存器;任何SWI指令或函数调用不能更改sp寄存器。

非常好我支持^.^

(1) 100%

不好我反对

(0) 0%

      发表评论

      用户评论
      评价:好评中评差评

      发表评论,获取积分! 请遵守相关规定!