云班课学习内容
一、C语言中嵌入汇编代码
1、内嵌汇编语法
(1)C语言中嵌入汇编代码的写法:
asm(
汇编语句模板:
输出部分:
输入部分:
破坏描述部分);
说明:输出部分和输入部分对应着C语言中的函数调用时的参数(return也是一个输出部分)
例:
printf("val1:%d,val2:%d,val3:%d
",val1,val2,val3);
asm volatile(
/*asm是GCC关键字asm的宏定义,表示内嵌汇编语句,与__sam__同,volatile是GCC关键字volatile的宏定义,告诉编译器不要对代码进行优化,与__volatile同*//
"movl $0,%%eax
" /*clear %eax to 0,两个%中第一个用于转义*/
"addl %1,%%eax
" /*%eax+=val1,是输入输出中的第二个数*/
"addl %2,%%eax
" /*%eax+=val2,是输入输出中的第三个数*/
"movl %%eax,%0
" /*val3=%eax*/
:"=m"(val3) /*output=m,只写,m表示写入内存
:"c"(val1),"d"(val2) /*input c:ecx d:edx*/
);
printf("val1:%d+val2:%d=val3:%d
",val1,val2,val3);
return 0;
(2)内嵌汇编常用限定符
限定符 | 描述 |
---|---|
“a” | 将输入变量放入eax |
“b” | 将输入变量放入ebx |
“c” | 将输入变量放入ecx |
“d” | 将输入变量放入edx |
“s” | 将输入变量放入esi |
“D” | 将输入变量放入edi |
“q” | 将输入变量放入eax,ebx,ecx,edx中的一个 |
“r” | 将输入变量放入通用寄存器,也就是eax,ebx,ecx,edx,esi,edi中的一个 |
“A” | 放入eax和edx,吧、把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” | 立即数,有些系统不支持数字以外的立即数,这些系统应该使用n |
“=” | 操作数在指令中是只写的(输出操作数) |
“+” | 操作数在指令中是读写类型的(输入输出操作数) |
“%” | 该操作数可以和下一个操作数交换位置 |
二、三个法宝:存储程序计算机、函数调用堆栈、中断
1、计算机是如何工作的?(总结)--三个法宝
(1)存储程序计算机:所有计算机基础性的逻辑框架。
(2)函数调用堆栈:计算机中非常基础性的东西。最早的计算机没有高级语言,只有机器语言和汇编语言时没有函数的概念。而高级语言中有函数的概念,需要堆栈机制,是高级语言可以运行的基础。
堆栈是C语言程序运行时必须的一个记录调用路径和参数的空间。
--函数调用框架
--传递参数
--保存返回地址(保存返回值,如eax)
--提供局部变量空间
--等等
C语言编译器对堆栈的使用有一套规则,同一段C语言程序在不同的操作系统中产生的汇编代码可能会有一些差异。
了解堆栈存在的目的和编译器对堆栈使用的规则是理解操作系统一些关键性代码的基础。
(3)中断:计算机帮程序员做的一些工作。
中断
早期计算机没有中断时,需要执行完一个程序之后再执行另一个程序。
有了中断,就有了多道程序设计,即在系统中同时跑多道程序。
当中断发生时,CPU会把当前的eip,esp,ebp都压到一个内核堆栈中。CPU和内核代码共同实现了保存和回复现场。
2、利用mykernel实验模拟计算机硬件平台。
(1)搭建一个虚拟平台
(2)使用Linux源代码把CPU的配置配置好
(3)执行
cd LinuxKernel/Linux-3.9.4
qeum -kernel arch/x86/boot/bzImage /*加载内核*/
实验中遇到问题
①编译Linux内核出现include/linux/compiler-gcc.h:103:30: fatal error: linux/compiler-gcc5.h: No such file or directory
解决方案
②
解决方案:将用户转换为root即可
编译中:
完成:
qemu -kernel arch/x86/boot/bzImage
在mykernel目录中:
cd mykernel
输入ls命令会发现,该文件夹中含有mymain.c和myinterrupt.c两个文件。
/*mymain.c中开始启动操作系统,入口*/
void __init_my_start_kernel(void)
{
int i = 0;
while(1)
{
i++;
if(i%100000 == 0)
printk(KERN_NOTICE "my_start_kernel here %d
",i);/*每循环十万次,打印一条消息*/
}
void my_timer_handler(void)/*myinterrupt.c中,时钟中断处理入口*/
{
printk(KERN_NOTICE "N>>>>>>>>>>>>>>>>>>>>>my_timer_handler here <<<<<<<<<<<
");
}
当按照课本敲完代码只有,出现了很多错误,都是因为敲代码的时候不认真,多了字母、少了字母或者是代码敲错。其中值得一提的一个错误是在mymain.c文件中的初始化函数,init前面是两个下划线,而不是一个。
之后运行成功:
三、深入理解函数调用堆栈
1、(1)esp:堆栈指针
(2)ebp:基址指针
(3)堆栈操作:①push栈顶地址减少4个字节(32位)
②pop栈顶地址增加4个字节(32位)
(4)ebp在C语言中用作记录当前函数调用基址
(5)其他关键寄存器
cs:eip总是指向下一条的指令地址
顺序执行:总是指向地址连续的下一条指令
跳转/分支:执行这样的指令的时候,cs:eip的值会根据程序需要被修改
call:将当前cs:eip的值压入栈顶,cd:eip指向被调用函数的入口地址
ret:从栈顶弹出原来保存在这里的cs:eip的值,放入cs:eip中
(6)建立被调用者函数的堆栈框架
pushl %ebp
movl %esp,%ebp
/*被调用者函数体*/
/*拆除被调用者函数的堆栈框架*/
movl %ebp,%esp
popl %ebp
ret
2、堆栈调用
(1)首先使用gcc -g生成test.c的debug版本的可执行文件test,然后使用objdump -S 获得test的反汇编文件。
objdump -S test > test.txt
注意为什么在反汇编代码中,是用变址寻址,而不是直接用x或者y?
是因为:参数已经放在了堆栈中,只需要根据地址寻找即可。