C指针原理教程之C内嵌汇编

内联汇编的重要性体现在它能够灵活操作,而且可以使其输出通过 C 变量显示出来。因为它具有这种能力,所以 "asm" 可以用作汇编指令和包含它的 C 程序之间的接口。简单得说,内联汇编,就是可以让程序员在C语言中直接嵌入汇编代码,并与汇编代码交互C程序中的C表达式,享受汇编的高运行效率。

内联汇编的格式是直接在C代码中插入以下格式:

asm(
....
....
)

其中的"..."为汇编代码,比如下面例子中,在 result=a*b和printf("%d\n",result)之间插入一段汇编,

下面的这段汇编什么都不做,每个nop指令占用一个指令的执行时间

 result=a*b;
 asm("nop\n\t"
 "nop\n\t"
 "nop\n\t"
 "nop");//4个nop指令,\n\t表示换行,然后加上TAB行首空,因为每个汇编指令必须在单独一行,需要换行,加上制表符是为了适应某些编译器的要求。
 printf("%d\n",result);

可以很明显地看到:

汇编代码之间用“\n\t”间隔,并且每条汇编代码单独占用一行,共有4个nop指令,每个指令后的“\n\t”表示换行,然后加上TAB行首空,因为每个汇编指令必须在单独一行,需要换行,加上制表符是为了适应某些编译器的要求。

下面是一个完整的例子,内嵌的汇编完成对2个C程序定义的全局变量c和d的相加,并将相加结果存入全局变量addresult中:

#include <stdio.h>
int c=10;
int d=20;
int addresult;
int main(void){
 int a=6;
 int b=2;
 int result;
 result=a*b;
 asm("nop\n\t"
 "nop\n\t"
 "nop\n\t"
 "nop");//4个nop指令,\n\t表示换行,然后加上TAB行首空,因为每个汇编指令必须在单独一行,需要换行,加上制表符是为了适应某些编译器的要求。
 printf("%d\n",result);
 asm("pusha\n\t"
 "movl c,%eax\n\t"
 "movl d,%ebx\n\t"
 "add %ebx,%eax\n\t"
 "movl %eax, addresult\n\t"
 "popa");//使用全局C变量c和d
 printf("%d\n",addresult);
 return 0;
}

编译上述代码

$ gcc -o test test.c
$ ./test
12
30

在汇编代码中可以直接使用变量名称操作C程序定义的全局变量,比如c、d和addresult就是全局变量:

 "movl c,%eax\n\t"
 "movl d,%ebx\n\t"
 "movl %eax, addresult\n\t"

内联汇编部分如果不需要编译器优化( 优化可能破坏汇编代码的内部结构,因为汇编代码直接操作寄存器,而寄存器使用优化是编译器提供的功能), 可以在 "asm" 后使用关键字 "volatile"。

asm volatile(
....
....
)

如果程序必须与 ANSI C 兼容,则应该使用 asm 和 volatile。

 __asm__ __volatile__(
 .........
 .........
 )

下面的代码和刚才代码功能一样,唯一的区别是不需要优化

 #include <stdio.h>
int c=10;
int d=20;
int addresult;
int main(void){
 int a=6;
 int b=2;
 int result;
 result=a*b;
 //ansi c标准的asm有其它用,所以用__asm__,__volatile__表示内联汇编部分不用优化(可以用volatile,但是ansi c不行),以防优化破坏内联代码组织结构
 __asm__ __volatile__("nop\n\t"
 "nop\n\t"
 "nop\n\t"
 "nop");//4个nop指令,\n\t表示换行,然后加上TAB行首空,因为每个汇编指令必须在单独一行,需要换行,加上制表符是为了适应某些编译器的要求。
 printf("%d\n",result);
 __asm__ __volatile__("pusha\n\t"
 "movl c,%eax\n\t"
 "movl d,%ebx\n\t"
 "add %ebx,%eax\n\t"
 "movl %eax, addresult\n\t"
 "popa");//使用全局C变量c和d
 printf("%d\n",addresult);
 return 0;
}

如何在内联汇编中访问C程序的局部变量呢,请看下面这段代码。

#include <stdio.h>
int main(void){
  //不使用全局变量,必须使用扩展GNU的asm
  //格式为:asm("汇编代码":输出位置:输入位置:改动的寄存器列表)
  //a为eax,ax,al;b为ebx等;c为ecx等;d为edx等;S为esi或si;D为edi或di
  //+读和写;=写;%如果必要,操作数可以和下一个操作数切换;&在内联函数完成之前,可以删除或重新使用操作数
  int xa=6;
  int xb=2;
  int result;
  //ansi c标准的asm有其它用,所以用__asm__,__volatile__表示内联汇编部分不用优化(可以用volatile,但是ansi c不行),以防优化破坏内联代码组织结构
  asm volatile(
  "add %%ebx,%%eax\n\t"
  "movl $2,%%ecx\n\t"
  "mul %%ecx\n\t"
  "movl %%eax,%%edx"
   :"=d"(result):"a"(xa),"b"(xb):"%ecx");//注意扩展方式使用2个%表示
  printf("%d\n",result);
  return 0;
}

这个例子完成这个计算:(xa+xb)2=(6+2)2=16

不使用全局变量与汇编代码交互,我们必须使用扩展GNU的asm ,格式为:

asm("汇编代码":输出位置:输入位置:改动的寄存器列表)

汇编代码中涉及寄存器部分的使用2个“%”,如:使用%%eax表示eax寄存器

输出位置、输入位置的特殊命名规则为:

a为eax,ax,al;b为ebx等;c为ecx等;d为edx等;S为esi或si;D为edi或di

+读和写

=写

%如果必要,操作数可以和下一个操作数切换

&在内联函数完成之前,可以删除或重新使用操作数

上述代码中,汇编代码部分为

输出位置、输入位置、改动的寄存器列表部分为:
     :"=d"(result):"a"(xa),"b"(xb):"%ecx"

先来看汇编代码部分,使用双%号表示寄存器,比如:

"add %%ebx,%%eax\n\t"

关于输出位置、输入位置部分,可以这么理解:将变量与寄存器绑定,绑定后,对寄存器的操作就是对变量的操作。

:"=d"(result):"a"(xa),"b"(xb)

将result与寄存器edx绑定,xa与寄存器eax绑定,xb与寄存器ebx绑定。
       %ecx属于需要改动的寄存器

#include <stdio.h>
int main(void){
  int xa=6;
  int xb=2;
  int result;
  //使用占位符,由r表示,编译器自主选择使用哪些寄存器,%0,%1。。。表示第1、2。。。个变量
  asm volatile(
  "add %1,%2\n\t"
  "movl %2,%0"
   :"=r"(result):"r"(xa),"r"(xb));
  printf("%d\n",result);
  return 0;
}

result、xa、xb绑定的寄存器由编译器决定,前面的例子中我们采用直接指定的方式,在这里我们改成由编译器

自主选择,"r"是占位符,表示由编译器自主选择使用哪些寄存器,不指定哪个变量绑定在哪个寄存器上,

:"=r"(result):"r"(xa),"r"(xb)

那我们如何知道这些变量绑定在哪些寄存器上呢,不知道绑定的寄存器,如何对变量进行操作呢,可以使用

%0,%1这样的符号来代替要操作的寄存器,%后的数字表示第几个变量,如:%0,%1。。。表示第1、2。。。个变量。

:"=r"(result):"r"(xa),"r"(xb)

上面这个输出和输入列表已经指定了变量的顺序,

result是第0个,xa是第1个,xb是第2个

下面的例子完成   xb=xb-xa的计算,问题出现了,可能会导致xb被分配了2个寄存器:

:"=r"(xb):"r"(xa),"r"(xb));

使用引用占位符能有效地使用可用寄存器,在这里我们指定xb使用第0个变量绑定的寄存器

:"=r"(xb):"r"(xa),"0"(xb));

第0个变量就是xb,即xb绑定的寄存器被修改后,结果仍写回原寄存器

下面是完整例子

#include <stdio.h>
int main(void){
int xa=2;
int xb=6;
asm volatile(
"subl %1,%0\n\t"
:"=r"(xb):"r"(xa),"0"(xb));
printf("%d\n",xb);
return 0;
}

我们编译运行一下

  $ gcc -o test test.c
  $ ./test

  4

用数字来表示变量的顺序也许很麻烦,我们可以使用更简单的方法,使用“[标识]”的格式标记绑定后的变量。  下面的代码完成xb=xb+xa的计算

#include <stdio.h>
int main(void){
  int xa=6;
  int xb=2;
  asm volatile(
  "add %[mya],%[myb]\n\t"
   :[myb]"=r"(xb):[mya]"r"(xa),"0"(xb));
  printf("%d\n",xb);
  return 0;
}

我们使用m标记可以直接在内存中对数进行操作,前面的例子对变量进行操作时都需要将变量值存储在要修改的寄存器中,然后将它写回内存位置中.

#include <stdio.h>
int main(void){
  int xa=2;
  int xb=6;
   asm volatile(
  "subl %1,%0\n\t"
   :"=r"(xb):"m"(xa),"0"(xb));
  printf("%d\n",xb);
  return 0;
}

我们直接从xa的内存地址中将xa取出,而不需要再将xa先存储在一个寄存器。

首先,我们看一下AT&T汇编各段的意义

节 含义
.text 已编译程序的机器代码
.rodata 只读数据,如pintf和switch语句中的字符串和常量值
.data 已初始化的全局变量
.bss 未初始化的全局变量
.symtab 符号表,存放在程序中被定义和引用的函数和全局变量的信息
.rel.text 当链接器吧这个目标文件和其他文件结合时,.text节中的信息需修改
.rel.data 被模块定义和引用的任何全局变量的信息
.debug 一个调试符号表。
.line 原始C程序的行号和.text节中机器指令之间的映射
.strtab 一个字符串表,其内容包含.systab和.debug节中的符号表

上面列表也许比较抽象,我们从一个C程序生成的中间汇编代码分析:

#include <stdio.h>

void main(){

  char *x="xxxx";

  char y[]="yy";//y的16进制ASCII码是97,9797的十进制为31097

  printf("%s-----%s",x,y);

  exit(0);

}

我们使用gcc -S testcr.c,查看编译生成的汇编代码(为便于理解,将生成的汇编代码进行了注释)

.file  "testcr.c"

  .section  .rodata

.LC0:

  .string "xxxx"#使用char *分配

.LC1:

  .string "%s-----%s"

  .text

.globl main

  .type  main, @function

main:

  pushl  %ebp

  movl  %esp, %ebp

  andl  $-16, %esp

  subl  $32, %esp#分配32字节栈空间,根据变量情况分配

  movl  $.LC0, 24(%esp)#x变量使用指针(4个字节大小),放入栈中,可以看到,变量分配靠近栈空间的尾部

  movw  $31097, 29(%esp)#字符'yy'移到main程序的栈中,直接将y变量的值放入栈中

  movb  $0, 31(%esp)#加上NULL标志,表示字符结束 

  movl  $.LC1, %eax

  leal  29(%esp), %edx

  movl  %edx, 8(%esp)

  movl  24(%esp), %edx

  movl  %edx, 4(%esp)

  movl  %eax, (%esp)

  call  printf

  movl  $0, (%esp)

  call  exit

  .size  main, .-main

  .ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"

  .section  .note.GNU-stack,"",@progbits

在MAIN函数中char *分配在只读数据段中,实际使用时,只在程序栈中分配一个指针的空间。char[] 在程序栈中分配空间,然后直接使用movl、movw之类的汇编直接把值放入栈中空间。那么在其它函数中声明的呢,可以从以下程序中看出,仍然如此。

#include <stdio.h>

void myprinf(){

  char *x="xxxx";

  char y[]="yy";//y的16进制ASCII码是97,9797的十进制为31097

  printf("%s-----%s",x,y);

}

void main(){

  int num=1;

  myprint();

  exit(0);

}

生成的中间汇编代码为:

.file "testcr.c"

  .section  .rodata

.LC0:

  .string "xxxx"

.LC1:

  .string "%s-----%s"

  .text

.globl myprinf

  .type  myprinf, @function

myprinf:

  pushl  %ebp

  movl  %esp, %ebp

  subl  $40, %esp

  movl  $.LC0, -16(%ebp)

  movw  $31097, -11(%ebp)

  movb  $0, -9(%ebp)

  movl  $.LC1, %eax

  leal  -11(%ebp), %edx

  movl  %edx, 8(%esp)

  movl  -16(%ebp), %edx

  movl  %edx, 4(%esp)

  movl  %eax, (%esp)

  call  printf

  leave

  ret

  .size  myprinf, .-myprinf

.globl main

  .type  main, @function

main:

  pushl  %ebp

  movl  %esp, %ebp

  andl  $-16, %esp

  subl  $32, %esp

  movl  $1, 28(%esp)

  call  myprint

  movl  $0, (%esp)

  call  exit

  .size  main, .-main

  .ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"

  .section  .note.GNU-stack,"",@progbits

内存的常用分配方式有:

第一,静态分配,所有名字在编译时绑定某个存储位置。不能在运行时改变
第二,栈分配,活动时压入系统栈。
第三,堆分配,以任意次序分配

(0)

相关推荐

  • C指针原理教程之编译原理-小型计算器实现

    1.打开cygwin,进入home目录,home目录在WINDOWS系统的cygwin安装目录映射为home目录. 2.首先,在home目录中新建文件夹,在文件夹中放置如下内容的test1.l /*统计字数*/ %{ int chars=0; int words=0; int lines=0; %} %% [a-zA-Z]+ {words++;chars+=strlen(yytext);} \n {chars++;lines++;} . {chars++;} %% main(int argc,c

  • C指针原理教程之语法树及其实现

    下面完成一个简单的计算器通过语法树进行计算,首先定义一个语法树的结构,然后编写flex文件,解析数字或符号,对于 符号返回本身,对于数字,返回NUMBER,并对yylval的d进行赋值,yylval指向一个联合类型,接着,在语法分析器中完成语法树的节点的增加,分别对应数字和符号有不同的增加方式,最后有一个单独的C代码处理计算,以及语法树相关计算的函数.对结果的计算的方式是对语法树进行递归. 词法分析器为: dp@dp:~/flexbison % cat myast.l %option noyyw

  • C指针原理教程之垃圾回收-内存泄露

    一.内存泄露 1.正常的链表操作 下面程序建立一个10元素的链表,输出它们的节点,每个节点是一个员工的工号和年龄.最后删除每个节点,释放列表. dp@dp:~/memorytest % cat 1.c #include <stdlib.h> #include <stdio.h> //code:myhaspl@myhaspl.com //author:myhaspl //date:2014-01-10 typedef struct listnode mynode; struct li

  • C指针原理教程之C快速入门

    一.C简介 1.C语言简介 C语言是一门语法 精简的语言,它的关键字仅有32个,C语言以main函数为主函数,程序编译运行后后,执行的就是main函数的内容,因此,纵观很多C语言程序,形成了一道有趣的风景线:头文件和许多c代码文件以main函数为中心和起始点构造,在main函数中调用了这些文件中编写的代码,引用头文件.C语言程序实质就是在程序中调用 C标准库提供的函数.其它C库提供的函数.操作系统提供的API接口.自己定义的函数,同时应用适当的数据结构和算法来完成工作. C语言虽然精简,但却很强

  • C指针原理教程之C指针基础

    tcctok.h定义了C语言的词法分析的基本元素,主要定义了关键字. / keywords /      DEF(TOK_INT, "int")      DEF(TOK_VOID, "void")      DEF(TOK_CHAR, "char")      DEF(TOK_IF, "if")      DEF(TOK_ELSE, "else")      DEF(TOK_WHILE, "wh

  • C指针原理教程之AT&T汇编

    汇编在LINUX系统下的意义远远大于WINDOWS系统,LINUX内核部分代码就是汇编编写的.然后,绝大多数 Linux 程序员以前只接触过DOS/Windows 下的汇编语言,这些汇编代码都是 Intel 风格的.但在 Unix 和 Linux 系统中,更多采用的还是 AT&T 格式,两者在语法格式上有着很大的不同,因此应对AT&T汇编应有一个基本的了解和熟悉. 我们在LINUX下用C编写一段最简单的helloworld程序,命令为hello.c #include <stdio.h

  • C指针原理教程之Ncurses介绍

    1.安装Ncurses Ncurses是一个能提供功能键定义(快捷键),屏幕绘制以及基于文本终端的图形互动功能的动态库. Ncurses是一个能提供基于文本终端窗口功能的动态库. Ncurses可以: · 只要您喜欢,您可以使用整个屏幕 · 创建和管理一个窗口 · 使用8种不同的彩色 · 为您的程序提供鼠标支持 · 使用键盘上的功能键 Ubuntu下 mysea@mysea-desktop:~$ sudo apt-get install libncurses5-dbg libncurses5-d

  • C指针原理教程之C内嵌汇编

    内联汇编的重要性体现在它能够灵活操作,而且可以使其输出通过 C 变量显示出来.因为它具有这种能力,所以 "asm" 可以用作汇编指令和包含它的 C 程序之间的接口.简单得说,内联汇编,就是可以让程序员在C语言中直接嵌入汇编代码,并与汇编代码交互C程序中的C表达式,享受汇编的高运行效率. 内联汇编的格式是直接在C代码中插入以下格式: asm( .... .... ) 其中的"..."为汇编代码,比如下面例子中,在 result=a*b和printf("%d\

  • 分析易语言内嵌汇编取变量指针的示例

    经本人研究内嵌汇编不能取自定义类型(成员非4字节,取来的是对齐结构的指针,比如成员为字节型分配4个字节),和非小数和整数,文本型数组不可以取指针,其它的都可以 要注意的是易的子程序前有EBP入栈操作,所以要有出栈操作才行 这是我写的取整数型指针的例子(字节型,短整数型,小数型,日期时间型,双精度小数型都通用,注意参数要为参考): .版本 2 .支持库 spec .子程序 取整数型指针, 整数型, 公开 .参数 整数, 整数型, 参考 'push ebp <------------这两句已经是易原

  • C语言内嵌汇编API内存搜索引擎实例

    本文实例讲述了C语言内嵌汇编API内存搜索引擎的方法,分享给大家供大家参考.具体实现方法如下: 复制代码 代码如下: // apisearchEngine.cpp : Defines the entry point for the console application.  //    #include "stdafx.h"  #include <Windows.h>     DWORD __stdcall GetStrLengthA(char* szName)  {   

  • C++内嵌汇编示例详解

    目录 汇编语言 汇编语言的特点 1.内嵌汇编介绍 2.汇编版本Hello, World! 3.内联汇编A+B 汇编语言 汇编语言是一种功能很强的程序设计语言,也是利用了计算机所有硬件特性并能直接控制硬件的语言.在汇编语言中,用助记符(Memoni)代替操作码,用地址符号(Symbol)或标号(Label)代替地址码.这样用符号代替机器语言的二进制码,就把机器语言变成了汇编语言. 汇编语言比机器语言易于读写.调试和修改,同时也具有机器语言执行速度快.占用内存空间少等优点.但在编写复杂程序时,相对高

  • 关于js函数解释(包括内嵌,对象等)

    常用写法: function add(a,b) { return a + b; } alert(add(1,2)); // 结果 3 当我们这么定义函数的时候,函数内容会被编译(但不会立即执行,除非我们去调用它).而且,也许你不知道,当这个函数创建的时候有一个同名的对象也被创建.就我们的例子来说,我们现在有一个对象叫做"add"(要更深入了解,看底下函数:对象节.) 匿名函数: 我们也可以通过指派一个变量名给匿名函数的方式来定义它. var add = function(a,b) {

  • PyQt5内嵌浏览器注入JavaScript脚本实现自动化操作的代码实例

    概要 应同学邀请,演示如何使用 PyQt5 内嵌浏览器浏览网页,并注入 Javascript 脚本实现自动化操作. 下面测试的是一个廉价机票预订网站(http://www.flyscoot.com/),关键点如下 使用 QWebEngineView 加载网页,并显示进度. 在默认配置(QWebEngineProfile)中植入 Javascript 内容,这样脚本会在所有打开的网页中执行,不论跳转到哪个网址. Javascript 脚本使用网址中的路径名,判断当前网页位置,从而决定执行哪种操作.

随机推荐