(目录)


一、基本内联汇编

asm格式

asm("assembly code");

要求:

  • 汇编指令必须在双引号里。
  • 指令超过一条必须使用换行符\n\t(或者“;”)——换行的每一条汇编指令都必须位于双引号中。
asm( "movl $1, %%eax\n\t"
     "movl $0, %%ebx\n\t"
     "int $0x80");

只有c中的全局变量才能在基本内联汇编中使用。

int a = 10;
int b = 20;
int main(){
	asm( "movl a, %%eax\n\t"
         "movl b, %%ebx\n\t");
}

不能在asm语句中使用局部变量。

基址变址寻址表达式 movl -4(, %edi, 4), %eax 或者 movl 4(%edi), %eax base_addr(offset_addr, index, size) 相当于 base_addr + offset_addr + index * size

基本汇编结构

// if-then
if:
  <condition to evaluate>
  jxx else
  <code to implement the "then" statements>
  jmp end
else:
  <code to implement the "else" statements>
end:

// for
for:
  <condition to evaluate for loop counter value>
  jxx forcode
  jmp end
forcode:
  <for loop code to execute>
  <increment for loop counter>
  jmp for
end:

// loop
  <code fefore the loop>
  movl $10, %ecx
loop1:
  <code to loop through>
  loop loop1
  <code after the loop>

二、使用volatile([ˈvɑːlətl])修饰符

把volatile修饰符放在asm语句中表示不希望编译器优化这个汇编代码段。

asm volatile ("assembly code");

如果使用ANSI C约定,需使用__asm__替换asm关键字。

__asm__ __volatile__ ("assembly code");

在基本内联汇编中应该不去改变任何寄存器的值。

三、扩展asm格式

asm("assembly code" 
    : output locations 
    : input operands 
    : changed registers
);

冒号分割的后三个部分不是都必须出现。“output locations”输出为空的部分要保留冒号分隔符,最后一个部分“changed registers”为空,相应的冒号分隔符可以省略。
“changed registers” 部分一般不使用(如果使用可能反而会报错——编译器假设输入和输出使用的寄存器会被改动)。这部分正确的使用场景是如果内联汇编没有在 “output locations” 和 “input operands” 部分初始地声明某寄存器,则需要在这里显式声明,以告知编译器不要再使用该寄存器。

1. 指定输入值和输出值

可以从寄存器和内存位置给输入和输出赋值。输入和输出值列表的格式是:

"constraint" (variable)

其中variable是c变量。扩展asm格式中c代码的局部和全局变量都可以使用。constraint定义把变量存放在寄存器中还是内存中。约束字符如下:

image.png

输出修饰符:

image.png

asm ("assembly code" : "=a"(result) : "d"(data1), "c"(data2));

把变量data1的值输入到寄存器EDX中,把变量data2的值输入到寄存器ECX中。内联汇编代码的结果(函数返回值)一般放在寄存器EAX中,把EAX中的值输出到变量result中。

2. 在汇编中使用寄存器

8个通用寄存器:EAX,EBX,ECX,EDX,EDI,ESI,EBP(指向栈底),ESP(指向栈顶元素) 指令修饰:

  • l 32位
  • w 16位
  • b 8位

有输入有输出:

#include <stdio.h>

int main(){
  int data1 = 3;
  int data2 = 5;
  int result = 0;

  asm volatile (
    "addl %%ecx, %%ebx;"
    "movl %%ebx, %%eax"
    :"=a"(result)    // <-- output
    :"c"(data1), "b"(data2)  // <-- input
  );

  printf("sum: %d\n",result);

  return 0;
}

有输入,没有输出

#include <stdio.h>

int main(){
  char input[30]={"Hello world.\n"};
  char output[30];
  int len=14;

  asm volatile (
    "cld;"
    "rep movsb"
    :
    :"S"(input), "D"(output), "c"(len)
  );
  
  printf("%s", output);
  return 0;
}

这里volatile关键字不能省略,因为asm语句没有输出,会被编译器优化掉。

3. 位置序号占位符

在asm的汇编代码中可以通过%0,%1,%2,...等来对位置上的寄存器进行引用。

#include <stdio.h>

int main(){
  int data1=10;
  int data2=20;
  int result;

  asm(
    "addl %1, %2;"
    "movl %2, %0"
    :"=r"(result)  // output: result对应的寄存器 -> 占位符%0
    :"r"(data1), "r"(data2) // input: data1对应的寄存器 -> 占位符%1, data2对应的寄存器 -> 占位符%2
  );

  printf("sum: %d\n", result);
  return 0;
}

4. 引用位置序号占位符

#include <stdio.h>

int main(){
  int data1=10;
  int data2=20;

  asm(
    "addl %1, %0"
    :"=r"(data2) // 占位符%0
    :"r"(data1), "0"(data2) // 引用%0处的寄存器
  );

  printf("sum: %d\n", data2);
  return 0;
}

5. 位置名称占位符

格式:

%[name]"constraint"(variable)

使用方法:

#include <stdio.h>

int main(){
  int data1=10;
  int data2=20;

  asm(
    "addl %[v1], %[v2]"
    :[v2]"=r"(data2)
    :[v1]"r"(data1), "0"(data2)
  );

  printf("sum: %d\n",data2);
  return 0;
}

6. 内联汇编changed registers部分的使用场景

#include <stdio.h>

int main(){
  int data1=10;
  int result=20;

  asm(
    "movl %1, %%eax;"
    "addl %%eax, %0"
    :"=r"(result) // output locations
    :"r"(data1), "0"(result) // input operands
    :"%eax"  // 注意只有一个%,寄存器EAX将不再被编译器使用
  );

  printf("sum: %d\n",result);
  return 0;
}

如果内联汇编没有在 “output locations” 和 “input operands” 部分初始地声明某寄存器,则需要在这里显式声明,以告知编译器不要再使用该寄存器。

7. 使用内存位置

#include <stdio.h>

int main(){
  int data1=10;
  int data2=20;
  int result;

  asm(
    "addl %1, %2;"
    "movl %2, %0"
    :"=m"(result)  // 这里的m表示使用内存单元存储
    :"b"(data1), "a"(data2)
  );

  printf("sum: %d\n",result);
  return 0;
}

使用的c局部变量的内存单元实际是栈中的位置。