Linux 内联函数与内联汇编

😊

1 内联 inline

1.1 内联函数

inline 函数引入的原因:如果一些函数被频繁调用,不断地有函数栈帧的开辟和回收(即 prologue/epilogue 处理),会有非常大的开销,导致性能下降。所以引入了 inline,将一段可能会被频繁使用的功能片段以函数的形式进行编码,然后以 inline/__inline__ 关键字进行修饰(称为内联函数),在内联函数被调用的地方,该函数的代码会像宏函数一样被展开。如果程序中已经使用了 inline 关键字,建议使用 __inline__inline__inline__ 是同义的,是由 gcc 定义的宏:#define __inline__ inline

inline 函数使用的特点:

  • 关键字 inline 必须与函数定义放在一起才能使函数成为内联,仅仅将 inline 放在函数声明前面不起任何作用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /* 函数 Foo 不能成为内联函数 */
    inline void Foo(int x, int y); // inline 仅与函数声明放在一起
    void Foo(int x, int y)
    {
    ...
    }

    /* 函数 Foo 是内联函数 */
    void Foo(int x, int y);
    inline void Foo(int x, int y) // inline 与函数定义体放在一起
    {
    ...
    }
  • 如果调用一个函数的开销远小于内联函数,则不建议在函数前加 inline

以下情况不宜使用内联:

  1. 如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。
  2. 如果函数体内出现循环(while, for 等),那么执行函数体内代码的时间要比函数调用的开销大。
  3. inline 只适合代码简单的函数数使用,不能包含复杂的结构控制语句例如 goto, switch,并且内联函数本身不能是直接递归自己。
  4. 如果一个函数包含静态变量。
  5. 如果一个函数是递归的。
  6. If a function return type is other than void, and the return statement doesn’t exist in a function body. 如果函数返回类型不是 void,并且 return 语句在函数主体中不存在。。

C++ 中内联函数和 inline 函数的区别

S.No. 内联功能 正常功能
1. 当它被调用时,它会内联展开。 这是一个为程序提供模块化的功能。
2. 它通常用于增加程序的执行时间。 它通常用于提高代码的可重用性,并使其更易于维护。
3. 它基本上是一个函数,当函数很小并且经常被调用时使用。 它基本上是一组执行特定任务的语句。当功能大时使用。
4. 它在其声明中需要 inline 关键字。 它在声明中不需要任何关键字。
5. 与正常功能相比,它通常执行得更快。 对于小尺寸函数,它通常比内联函数执行速度慢。
6. 在此中,函数主体被复制到使用它的每个上下文中,这反过来又减少了在存储设备或硬盘中搜索主体的时间。 在这方面,函数的主体存储在存储设备中,当每次调用该特定函数时,CPU必须将主体从硬盘加载到RAM进行执行。
7. 编译器总是在编译时调用该函数的每个点放置该函数的代码副本。 它不提供这种类型的功能。
8. 它通常只包括 2-3 行代码。 当行代码数量非常大时,即正常函数根据需要包含大量代码。
9. 与正常功能相比,它更难理解和测试。 与内联功能相比,它更容易理解和测试。
10。 类中存在的函数是隐式内联的。 存在于类之外的函数被认为是正常函数。
11. 太多的内联函数会影响编译后的文件大小,因为它复制了相同的代码。 太多的正常功能不会影响编译后的文件大小。

参考:

1.2 static inline 与 extern inline

为什么内联函数要在头文件,并且建议使用 static

  1. 内联函数放在头文件:可以让项目中使用这个内联函数的任何源文件,不必亲自再去定义一遍,直接包含这个头文件,即可像宏一样展开使用。如果是定义在 .c 源文件中,其他源文件引用时会当做 extern 函数调用而不展开;
  2. 使用 static 的原因:使用 inline 定义的内联函数,编译器不一定会内联展开,那么当多个文件都包含这个内联函数的定义时,编译时就有可能报重定义错误。在头文件内联函数使用 static 修饰,可以将这个函数的作用域局限在各自本地文件内,避免了重定义错误。

Linux 内核中,内联函数常常使用 static 修饰,是有原因的:

  • extern inline:首先函数是 extern 的(和不加该关键字效果一样,编译器默认函数是 extern),也就是说该函数可能是从其他文件引入的,如果本文件看不到定义,则只会做普通函数调用,如果能看到定义,则可以尝试 inline 展开。
  • static inline:这是一个 static 函数,也即函数是文件内链接的,内联函数的定义应该放在头文件中,即使多处包含也不会出现重定义问题(多个文件使用),如果你将它放进 .c 源文件反而会出错,因为这时其他文件看不到定义,所以必须定义在头文件,然后在调用处将会做内敛展开。总结为:
    • 为方便项目中多文件使用 inline 函数,应该将内联函数定义在头文件中;
    • 为让调用内联函数代码处,能对内联函数进行展开(而不是函数调用 call),应该使用 static 进行修饰。

如果 inline 函数的定义放在 .c 文件中的话,如果是 static inline 修饰,其他 .c 文件不能引用该函数;如果不用 static 进行修饰(相当于 extern),则其他 .c 文件会当做 call 调用该函数(并不是函数展开)。所以最好就是将内联函数定义在头文件中,定义成 static inline

参考C 内联函数:static inline 和 extern inline 的含义 C语言:inline,static inline

1.3 内联一定会展开吗?

inline仅是一个对编译器的建议,并不会强制内联,所以最后能否真正内联,取决于编译器,声明内联只是对编译器的一个建议而已。

说明:在 GCC 4.8.5 20150623 (Red Hat 4.8.5-44) 版本中进行测试,仅使用 static inline 修饰函数,不开启编译优化,不会将内联函数在被调用处进行展开;而如果使用 __attribute__((always_inline))__attribute__((gnu_inline)) 进行修饰(强制内联),则会在函数被调用处进行展开,即使不加 static __inline__。如果必须强制使用内联展开,则使用 __attribute__() 为主。

当一个函数使用 inline 关键字修饰,编译器在编译时一定会内联展开吗?未必。编译器也会根据实际情况,比如函数体大小、函数体内是否有循环结构、是否有指针、是否有递归、函数调用是否频繁来做决定。比如 GCC 编译器,一般是不会对内联函数展开的,只有当编译优化选项开到 -O2 以上,才会考虑是否内联展开。当我们使用 noinlinealways_inline 对一个内联函数作了属性声明后,编译器的编译行为就变得确定了。使用 noinline 声明,就是告诉编译器,不要展开;使用 always_inline 属性声明,就是告诉编译器,要内联展开。

——《noinline & always_inline 内联函数探究》、inline只是建议内联,并不会强制内联gcc内联函数 inline和__attribute __((always_inline))的区别

1.4 inline 函数与宏函数区别

宏函数和内联函数有什么区别:

宏(#define) inline 函数
展开时机 预编译阶段展开。 编译阶段展开,并不总是保证内联展开,视编译器优化情况而定。
参数传递 简单的做文本替换,可能会产生副作用(二义性)。宏函数只是文本替换,并不是参数传递。 进行参数传递。
参数类型安全 无参数类型检查;直接文本替换。如:
#define add(a, b) a > b ? a:b
并不会对参数的类型进行检查,如果传递字符串则发生错误结果。
会对参数类型做安全检查或自动类型转换,参数和返回值的类型检查。因为内联并总是被展开,所以内联函数传参和常规函数无异。
用法 宏可用于定义常量、表达式、文字文本替换和定义函数等。 内联函数可用于最小化程序的执行时间。
调试 由于直接文本替换,调试可能更难。 更容易调试,因为它们的行为就像函数一样。

参考:

2 内联汇编

GCC 支持在 C 代码项目中使用汇编代码,称为内联汇编(inline assembly)。使用汇编写的代码可以是一个完成的函数,被其他地方调用;也可以在一个 C 代码的函数中内嵌使用汇编代码。

在 Intel CPU 基础上,Linux 平台更喜欢使用 AT&T 风格的汇编,Windows 平台更喜欢使用 Intel 风格的语法(我是从Windows 内核转过来,更习惯 Intel 语法的汇编)。

内联汇编按格式分为两大类:基本内联汇编扩展内联汇编

  • 基本内联汇编:常用来编写一个完全由汇编代码写成的函数(也可以嵌入到 C 函数的代码中,但是不能使用局部变量);可以使用程序中的全局变量,如 mov QWORD PTR [var_global] rax
  • 扩展内联汇编:常嵌入在 C 函数里的汇编代码,可以使用 C 函数定义的局部变量,但要遵循复杂的语法。

2.1 基本内联汇编

基本内联汇编的使用方法如下:

1
asm [asm_qualifiers] ("assembly code");
组成部分 说明
asm 用于声明内联汇编表达式,如果没有 [asm_qualifiers] 修饰部分的话,asm (" ") 之前有无空格是无所谓的。
假如代码中已经给 asm 关键字定义为宏或做他用,则可以使用 GCC 扩展关键字 __asm__,两者是同义的。 GCC 定义的宏:#define __asm__ asm
[asm_qualifiers] 可选,基本内联汇编的限定符,有 2 种:
- volatile :代码编译时,编译器会对代码进行优化,达不到汇编效果,或者产生错误的结果。可以使用 volatile 告知编译器不要优化该片段,原样保留代码。可使用 GCC 定义的宏:#define __volatile__ volatile,可避免发生冲突。基本内联汇编代码块,默认都是 volatile 的。
- inline:建议编译器将该段汇编代码内联,但是不强制,编译器会自己决定。
(“assembly code”); 该部分是写汇编代码的部分,括号外要有 ; 以表示结束。具体写法如下:

("assembly code"); 说明如下:

  • 关于 " "

    • 可将所有汇编代码写到一对 " " 中,指令之间必须使用 ;\n\n\t 表示结束,最后一句汇编指令可写可不写 ;。代码跨行必须要使用反斜杠 \

      1
      2
      3
      4
      5
      6
      __asm__ __volatile__ ("                 \
      .intel_syntax noprefix; \
      pushfq; \
      mov QWORD PTR [rcx], rax; \
      popfq; \
      ");
    • 可将每个汇编指令都使用 " " 括起来,每个指令必须;\n\n\t 表示结束。代码代码跨行可不使用 \

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      __asm__ __volatile__ (
      ".intel_syntax noprefix;"
      "pushfq;"
      "mov QWORD PTR [rcx], rax;"
      "popfq;"
      );

      /* 或写成以下形式 */
      __asm__ __volatile__ (".intel_syntax noprefix");
      __asm__ __volatile__ ("pushfq");
      __asm__ __volatile__ ("mov QWORD PTR [rcx], rax");
      __asm__ __volatile__ ("popfq");
  • 关于语句分隔:指令之间必须使用 ;\n\n\t(增加可读性),进行分隔。

以上使用 Intel 语法汇编,第一句汇编指令必须指定为 .intel_syntax noprefix,同时在 GCC 编译代码时要指定 -masm=intel 参数。(GCC 默认使用 AT&T 语法,要使用 Intel 语法就需要特别指定)

6.2 扩展内联汇编

扩展内联汇编常嵌入到 C 代码中,除了可以使用函数中 C 代码定义的局部变量外,还能降低程序错误率(编译器会对某些未修正的代码或内存进行处理,但编码者不应对此充满信任)。

语法如下:

1
2
3
4
5
asm [qualifiers] ( assembler template 
[: output operands] /* 可选的,输出操作数 */
[: input operands ] /* 可选的,输入操作数 */
[: list of clobber/modify] /* 可选的,修改列表(寄存器/内存) */
);

和基本内联汇编相比,扩展内联汇编在圆括号中变成了 4 部分,多了输出操作数、输入操作数和修改列表(clobber/modify,描述汇编代码中哪些寄存器、内存内修改)三项。其中的每一部分都可以省略,甚至包括 assembler template。省略的部分要保留冒号分隔符来占位,如果省略的是最靠后的一个或多个连续的部分,分隔符也不用保留,比如省略了 clobber/modify,不需要保留 input 后面的冒号。如下:

1
2
3
4
5
__asm__ ("		\
mov %%rax, %%rcx; \
: \
: \
");
组成部分 说明
asm 同基本内联汇编用法。
[asm_qualifiers] 扩展汇编有 3 种限定符:
- volatile :同基本内联汇编用法。
- inline :同基本内联汇编用法。
- goto :此限定符通知编译器,汇编语句可能会执行一个跳转,跳转目的是 goto 标签里的一个。Goto Labels
assembler template 汇编模板,也就是写指令的地方。在基本内联汇编的基础上,扩展汇编的代码中允许存在操作数占位符,如 %0,%1等,以便对函数中 C 定义的局部变量进行使用。
值得注意的一点这是,在基本内联汇编中,寄存器表示方法和直接写汇编没什么区别,都是以 % 开头,后面跟着寄存器名称,比如 %eax;但是在扩展内联汇编中,单个 % 有了新的用途,用来表示占位符,所以在扩展内联汇编中的寄存器名称前要用两个 % 做前缀。
占位符分为序号占位符名称占位符两种。具体参见章节 6.5

2.3 修改列表

扩展汇编的最后一个冒号 : 后面表示的是修改列表。汇编代码执行后会修改一些内存或寄存器的值,通过此项通知编译器,可能造成寄存器或内存数据的破坏,这样 GCC 就知道哪些寄存器或内存需要提前保存起来。

每个被修改过的寄存器名称(不包括堆栈寄存器 rbp, rsp)或内存使用双引号括 " " 起来,多个的话使用 , 分割双引号。主要分为三种:

  • 通用寄存器:将寄存器名称写在双引号中,"%rcx", "%rbx" 等;
  • 标志寄存器:使用 "cc",表示标志寄存器 rflags 中的某些位被修改(在使用 asm 前后);
  • 内存:使用 "memory",表示在使用 asm 后内存有被修改。

如:

1
2
3
4
5
6
__asm__ ("		\
mov %%rax, %%rcx; \
: \
: \
: "cc", "%rcx" \
");

2.4 输入输出操作数

输出操作数:在汇编代码片段中,将数据输出到 C 定义的局部变量中(类似于 Windows 中的 OUT),以便给 C 代码使用。

输入操作数:可以将函数中 C 代码定义的局部变量或函数的参数,输入给汇编使用。定义格式与输出操作数一样。

定义格式如下:

1
[变量别名]"[操作数修饰符] 约束条件"(C变量名)

双引号和括号都不能忽略,如果是有多个输出操作数,使用 , 分割:"=r"(a), "=o"(b)

  • 操作数修饰符(modifier):可选的,只能用于输出,提供输出变量的行为信息(如只读/读写、是否可以在指令中交换次序等)。
  • 约束条件(constraint):提供输入/输出操作数位置的信息(如分配到寄存器还是内存中)。
  • 变量别名(asmSymboicName):可选的,我们可以给 C变量名 起一个只能在该 asm 中使用的别名,并通过 %[asmSymbolicName] 访问它。比如:[value] "=m" (i),可以在 asm 块中通过 %[value] 访问它。

一、操作数修饰符(modifier)

操作数修饰符(modifier)只能用于输出变量上。

  • =:操作数是只写的。这意味着操作对象中原先的值可以被丢弃,并且写入新数据。
  • +:操作数是可读写的。即可以在 asm 中合法的读取操作对象,并且 C 变量在进入 asm 块时就已经加载到对应的位置中。
  • %:表示该操作对象可以交换次序。该操作数可以和下一个输入操作数互换。
  • &:表示此输出操作数要独占所约束(分配)的寄存器,任何输入操作数中所分配的寄存器不能与此相同。注意,当表达式中有多个修饰符时,& 要与约束名挨着,不能分隔。

其他注意事项:

  • GCC不保证在进入 asm 块时,输出变量已被加载到约束条件(constraint)指定的位置中。如果需要 GCC 在进入 asm 块时将变量加载到约束条件(constraint)指定的位置中,则操作数修饰符(modifier)应该使用 +
  • 约束条件(constraint)是指定变量在 asm 块中的位置,而不是在函数中的位置。如变量的约束条件(constraint)为 r 说明它在进入/退出 asm 块时被分配到通用寄存器中,但在进入 asm 块前它的位置是不确定的。如果要控制 asm 块外变量被分配的位置,可以使用 GNU C 的寄存器变量拓展。

二、约束条件(constraint)

约束条件:描述了操作数是否使用了寄存器,用的是哪种寄存器;是否引用了内存,用的是哪种地址;是否是立即数,是哪种类型的立即数等等。它所起的作用就是把 C 代码中的操作数(变量、立即数)映射为汇编中所使用的操作数,实际就是描述 C 中的操作数如何变成汇编操作数。分为以下几种:

  • 寄存器约束
  • 内存约束
  • 立即数约束
  • 数字约束
约束类型 描述
寄存器约束 在使用汇编代码片段时(不包含 C 代码),将操作数存储在寄存器中。
常见的寄存器约束有:
r:表示 rax/rbx/rcx/rdx/rsi/rdi 这 6 个通用寄存器中任意一个
a:表示 a 系列寄存器 rax/eax/ax/al
b:表示 b 系列寄存器 rbx/ebx/bx/bl
c:表示 c 系列寄存器 rcx/ecx/cx/cl
d:表示 d 系列寄存器 rdx/edx/dx/dl
D:表示寄存器 rdi/edi/di
S:表示寄存器 rsi/esi/si
q:表示 rax/rbx/rcx/rdx 这 4 个通用寄存器中任意一个
g:表示可以存放到任意地点(寄存器和内存)。相当于除了同 q 一样外,还可以让 gcc 安排在内存中
A:a 和 d 寄存器,用于返回结果保存在a 和 d 寄存器的指令。
f:表示浮点寄存器
t:表示第 1 个浮点寄存器
u:表示第 2 个浮点寄存器
p:表示内存地址,给 “load address” 和 “push address” 指令使用

注意:我们在写 AT&T 汇编代码时,想使用 movb, movd, movq 等操作指定字节大小的操作数时,代码编译会发生错误,还需要对占位符进一步精确使用,具体见章节 6.5 占位符部分
内存约束 表示在汇编代码中直接操作 C 变量的内存地址(操作的是变量地址),不需要寄存器做中转,直接进行内存读写,也就是汇编代码的操作数是 C 变量的指针。主要有以下几种:
m:表示内存操作数,可以是计算机支持的任何类型的地址。
o:也是内存操作数,但访问它是通过偏移量的形式访问,即包含 offset_address 的格式。如 asm ("movq 16+%1, %0" : "=r"(element) : "o"(arr)); 将变量 1 地址偏移 16 的数值写入到变量 0。
V : 一个不允许偏移的内存操作数。换言之,任何适合 m 约束而不适合 o 约束的操作数。
立即数约束 常数。立即数不是变量,只能作为右值,只用于输入操作数,此约束要求 gcc 在传值的时候不通过寄存器或内存,直接作为立即数传给汇编代码
i:表示操作数为整数立即数
F:表示操作数为浮点数立即数
I(大写的i):表示操作数为0~31之间的立即数
J:表示操作数为0~63之间的立即数
N:表示操作数为0~255之间的立即数
O:表示操作数为0~32之间的立即数
X:表示操作数为任何类型立即数
数字约束 0~9:此约束只用于输入操作数,表示该输入 操作数与输出操作数中第 n 个操作数用相同的寄存器或内存。
asm ("incl %0" :"=a"(var):"0"(var));。这里的 0 用于指定与第 0 个输出变量相同的约束。
x86 特有的约束 r:寄存器操作数约束,查看上面给定的表格
q:寄存器 a、b、c 或者 d
I:范围从 0 到 31 的常量(对于 32 位移位)
J:范围从 0 到 63 的常量(对于 64 位移位)
K:0xff
L:0xffff
M:0、1、2 或 3 (lea 指令的移位)
N:范围从 0 到 255 的常量(对于 out 指令)
f:浮点寄存器
t:第一个(栈顶)浮点寄存器
u:第二个浮点寄存器
A:指定 “a” 或 “d” 寄存器。这主要用于想要返回 64 位整形数,使用 “d” 寄存器保存最高有效位和 “a” 寄存器保存最低有效位。

2.5 汇编模板中的占位符

章节 6.2 和 6.4 中提到过,汇编模板(assembler template)中使用 2 种占位符:序号占位符、名称占位符。

  1. 序号占位符%0~%9,按照操作数在输出操作数列表 --> 到输入操作数列表从左到右出现的次序,从 0 开始编号,一直到 9,引用它的格式是 %0~%9,也就是说最多支持 10 个序号占位符。

    如下,输出操作数列表出现的第一个操作数为 b 变量,汇编代码中的 %0 即表示引用该变量。

    同理,出现的第二个操作数是 a 变量,汇编代码中的 %1 即表示引用该变量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include <stdio.h> 

    void main() {
    int a = 10, b;
    asm ("movl %1, %%eax;"
    "movl %%eax, %0;"
    :"=r"(b) /* output */
    :"r"(a) /* input */
    :"%eax" /* clobbered register */
    );
    printf("Now, b is: %d\n", b);
    }
  2. 名称占位符:输出操作数的格式为:

    1
    [变量别名]"[操作数修饰符] 约束条件"(C变量名)

    其中可选的变量别名(asmSymboicName):我们可以给 C变量名 起一个只能在该 asm 中使用的别名,并通过 %[asmSymbolicName] 访问它。比如:[value] "=m" (i),可以在 asm 块中通过 %[value] 访问它。

关于章节 6.4 中提到的,在汇编模板(assembler template)中写 AT&T 汇编代码时,想使用 movb, movd, movq 等操作指定字节大小的操作数时,代码编译会发生错误,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int main(int argc, char* argv[], char* env[])
{
long a = 6, b = 0;

__asm__ __volatile__ ( \
"movl %1, %0;" \
:"=q"(b) \
:"q"(a) \
);

printf("[*] b = %d\n", b);
return 0;
}

GCC 编译时会产生错误,提示 mov 指令的后缀没用对,正常情况下不应高提示错误。因为 a, b 变量是 64 bits,代码只取低 32 bits。

1
2
3
[alvin@centos-7 program]$ gcc -g -w -fPIC -o 1225_exp_asm.exe 1225_exp_asm.c 
1225_exp_asm.c: Assembler messages:
1225_exp_asm.c:7: Error: incorrect register `%rax' used with `l' suffix

还需要对占位符进一步精确使用:所以应该使用更精确的占位符。如下表所示,取 %rax 的低 32 bits,应该是使用 %kn 占位符。

The table below shows the list of supported modifiers and their effects.

Modifier Description Operand ‘att’语法 ‘intel’语法
A Print an absolute memory reference. %A0 *%rax rax
b Print the QImode name of the register. %b0 %al al
B print the opcode suffix of b. %B0 b
c Require a constant operand and print the constant expression with no punctuation. %c1 2 2
d print duplicated register operand for AVX instruction. %d5 %xmm0, %xmm0 xmm0, xmm0
E Print the address in Double Integer (DImode) mode (8 bytes) when the target is 64-bit. Otherwise mode is unspecified (VOIDmode). %E1 %(rax) [rax]
g Print the V16SFmode name of the register. %g0 %zmm0 zmm0
h Print the QImode name for a “high” register. %h0 %ah ah
H Add 8 bytes to an offsettable memory reference. Useful when accessing the high 8 bytes of SSE values. For a memref in (%rax), it generates %H0 8(%rax) 8[rax]
k Print the SImode name of the register. %k0 %eax eax
l Print the label name with no punctuation. %l3 .L3 .L3
L print the opcode suffix of l. %L0 l
N print maskz. %N7 {z} {z}
p Print raw symbol name (without syntax-specific prefixes). %p2 42 42
P If used for a function, print the PLT suffix and generate PIC code. For example, emit foo@PLT instead of ’foo’ for the function foo(). If used for a constant, drop all syntax-specific prefixes and issue the bare constant. See p above.
q Print the DImode name of the register. %q0 %rax rax
Q print the opcode suffix of q. %Q0 q
R print embedded rounding and sae. %R4 {rn-sae}, , {rn-sae}
r print only sae. %r4 {sae}, , {sae}
s print a shift double count, followed by the assemblers argument delimiterprint the opcode suffix of s. %s1 $2, 2,
S print the opcode suffix of s. %S0 s
t print the V8SFmode name of the register. %t5 %ymm0 ymm0
T print the opcode suffix of t. %T0 t
V print naked full integer register name without %. %V0 eax eax
w Print the HImode name of the register. %w0 %ax ax
W print the opcode suffix of w. %W0 w
x print the V4SFmode name of the register. %x5 %xmm0 xmm0
y print “st(0)” instead of “st” as a register. %y6 %st(0) st(0)
z Print the opcode suffix for the size of the current integer operand (one of b/w/l/q). %z0 l
Z Like z, with special suffixes for x87 instructions.

如上的示例改成如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>

int main(int argc, char* argv[], char* env[])
{
long a = 6, b = 0;

__asm__ __volatile__ ( \
"movl %k1, %k0;"\ /* 修改改行 */
:"=q"(b) \
:"q"(a) \
);

printf("[*] b = %d\n", b);
return 0;
}

/* 输出如下 */
[alvin@centos-7 program]$ gcc -g -w -fPIC -o 1225_exp_asm.exe 1225_exp_asm.c
[alvin@centos-7 program]$ ./1225_exp_asm.exe
[*] b = 6

参考内容: