C语言代码内嵌汇编的方法:
在C语言文件中以如下格式加入汇编代码
__asm__(
“汇编语句模板”
:输出部分
:输入部分
:“破坏描述部分”
)
asm可以由__asm__代替,为其别名。
可加上__volatile__表示不需要编译器优化代码。
用双下划线起始或结尾。
所有汇编语句在双引号内,并以\n\t结束每一行。
$后跟立即数。
寄存器用%%+寄存器名表示:%%eax
参数用%+参数序号表示:%0 , %1 。。。
输入输出部分跟在语句后,用()表示,每一行前有“:”符号
参数序号与参数出现顺序一一对应。
参数前可以跟说明符,用“ ”表示,常用限定符意义如下
具体寄存器
a,b,c,d 将变量放入eXx
s D 将变量放入esi edi
q 将变量放入e(a,b,c,d)x中的任一个
r 将变量放入(a,b,c,d,s,d)中任一个
A 把eax和edx合成一个64的寄存器
内存
m 内存变量
o 内存变量,寻址方式是偏移量类型
V 内存变量,寻址方式不是偏移量类型
, 内存变量,寻址方式为自动增量
p 操作数是一个合法的内存地址
寄存器或内存
g 将输入变量放入eax,ebx,ecx,edx中的一个或作为内存变量
X 操作数为任何类型
立即数
I 0-31之间的立即数(用于32位移位指令)
J 0-63立即数(用于64)
N 0-255立即数(用于out指令)
i 立即数
n 立即数,对不支持数字以外立即数的系统使用
操作数类型
= 只写(输出)
+ 读写类型(输入输出操作数)
浮点数
f 浮点数
t 第一个浮点寄存器
u 第二个浮点寄存器
G 标准的80387(指协处理器浮点运算部件80387)
% 该操作数可与下一个操作数交换位置
# 部分注释,从该字符到后面逗号之前的所有内容被忽略
\* 选用寄存器,则其后字母被忽略
匹配
“0”、“1”。。。“9”
表示被限制的操作数与某个指定的操作数匹配
以下面的程序为例讲一下应用:
1 #include<stdio.h> 2 #include <unistd.h> 3 int main() 4 { 5 int a,b; 6 pid_t t,asm_t; 7 t =getpid(); 8 a = t; 9 printf("pid = %d\n",a); 10 asm volatile( 11 "mov $0x14,%%eax\n\t" 12 "int $0x80\n\t" 13 "mov %%eax,%0\n\t" 14 :"=m"(asm_t) 15 ); 16 b = asm_t; 17 printf("asm_t = %d\n",b); 18 return 0; 19 }
程序里面用到了系统调用getpid。它的原型是:
pid_t getpid(void)
getpid的调用编号为20(0x14),所有系统调用的编号可以在unistd.h文件里找到。
发起系统调用的过程为:
- 应用程序通过调用C语言函数库中的外壳函数(wrapper),发起系统调用
- 外壳函数必须保证所有的系统调用参数可用,这些参数通过堆栈传入外壳函数,内核希望将这些参数置入通用寄存器。因此,外壳函数会将上述参数复制到寄存器,个数不超过6个(ebx,ecx,edx,esi,edi,ebp)每个参数长度不超过寄存器的位数--32位。
- 为了 让内核能区分每个系统调用,外壳函数会将系统调用编号复制到特定的通用寄存器中-->%eax
- 在此之后,外壳函数执行一条中断指令,引发用户态到核心态的切换,并执行系统中断0x80(128d)的中断矢量所指向的代码
- 内核调用system_call以响应0x80中断,过程如下:
- 在内核栈中保存寄存器值
- 审核调用编号的有效性
- 以系统调用编号对存放所有调用服务程序的列表(sys_call_table)进行索引,查找并调用相应的系统调用服务程序。若服务程序需要参数,则检查参数的有效性。
- 从内核栈中恢复各寄存器值,将系统调用返回置于栈中。
- 返回至外壳函数,同时将处理器切换回用户态
- 若系统调用的返回值表明调用有误,外壳函数会使用该值设置全局变量errno,无误则返回调用程序,并返回一个整型值,表明系统调用是否成功。
执行结果如下:

by 昆仑雪狐
原创作品转载请注明出处
《Linux内核分析》
MOOC课程http://mooc.study.163.com/course/USTC-1000029000