Linux内核完全注释之编程语言和环境(二)
c程序的编译和链接
使用gcc汇编器编译c语言程序时通常会经历四个阶段,即预处理阶段、编译阶段、汇编阶段、链接阶段,如下图。
例如:
gcc -o hello hello.c 生成可执行文件hello
gcc -S -o hello.s hello.c
gcc -c -o hello.o hello.c
嵌入式汇编(内联汇编)
1、格式
asm("汇编语句"
:输出寄存器
:输入寄存器
:会被修改的寄存器)
asm是内联汇编语句的关键词;输出寄存器与输入寄存器都分别对应着一个C变量或常数值,会被修改的寄存器表示你已经对于列出的寄存器中的值进行了修改,gcc编译器不能再依赖于它原先对这些寄存器加载的值。如果必要的话,gcc需要重新加载这些寄存器,因此需要把那些没有在输入输出寄存器部分列出,但是在汇编语句中明确用到或者隐含用到的寄存器名列在这个部分中。
如以下代码的seg代表一指定的内存段值,而addr表示一内存偏移地址量;且该宏函数的作用就是从指定的段和段偏移的内存地址处取出一个字节
#define get_seg_byte(seg, addr) \
({ \
register char __res; \
__asm__("push %%fs; //保存fs寄存器的原值
mov %%ax, %%fs; //将seg设置到fs
movb %%fs:%, %%a1; //将seg:addr处的一个字节放置到a1寄存器中
pop %%fs "\
: "=a" (__res) \ //输出寄存器列表
:"" (seg), "m"(*(addr)));\ //输入寄存器列表 __res;})
嵌入式汇编程序规定把输出和输入寄存器统一按顺序编号,顺序从输出寄存器序列从左到右从上到下,以%0开始,分别记为%0, %1, ... %9。
输入语句中“0”加载符表示与输出行相同位置相同寄存器
代码 | 说明 | 代码 | 说明 |
a | 使用寄存器eax | m | 使用内存地址 |
b | 使用寄存器ebx | o | 使用内存地址并可以加偏移值 |
c | 使用寄存器ecx | I | 使用常数0-31 |
d | 使用寄存器edx | J | 使用常数0-63 |
S | 使用寄存器esi | K | 使用常数0-255 |
D | 使用寄存器edi | L | 使用常数0-65535 |
q |
使用动态分配字节可寻址寄存器 (eax、ebx、ecx、edx) |
M | 使用常数0-3 |
r | 使用任意动态分配寄存器 | N | 使用1字节常数(0-255) |
g |
使用通用的有效地址即可 (eax、ebx、ecx、edx或内存变量) |
O | 使用常数0-31 |
A | 使用eax与edx联合(64位) | = | 输出操作数,输出值将替换当前值 |
+ | 表示操作数可读可写 | & | 早期会变操作数,表示在使用完操作数之前,内容会被修改 |
指令leal用于计算有效地址,leal (r1, r2, 4), r3语句表示r1+r2*4 -> r3
在代码执行时,若不希望汇编语句被gcc优化而修改,就需要在asm符号后面添加关键字volatile。其格式 asm valatile (......); __asm__ __valatile__(......)
关键字valatile 也可以放在函数名前来修饰函数,用来通知gcc该函数不会返回,从而避免gcc对于不返回函数的告警
volatile void do_exit(long code); static inline volatile void oom(void)
{
printf("out of memory\n");
do_exit(SIGSEGV);
}
圆括号中的组合语句 ({.......}),且最后一个语句代表表达式的值,若最后一条语句不是表达式,则表示该组合语句具有void类型;实际使用中通常都是用宏来实现
寄存器变量分为全局全局寄存器变量和局部寄存器变量
register int res __asm__("ax")
c与汇编程序相互调用
1、c函数调用机制
Linux内核程序boot/head.s执行完基本初始化后,就会跳转到去执行init/main.c程序,那么head.s程序是如何把执行控制权交给main.c的?
c函数的调用机制和控制权转移
函数调用包括从一块代码到另一块代码之间的双向数据传递和执行控制权转移;数据传递通过函数参数和返回值来进行,另外还包括局部变量的申请与释放。数据传递与局部变量 存储空间的回收和分配都是通过栈操作来实现的。
2、栈帧结构和控制权转移方式
栈被用来传递函数参数、存储返回信息、临时保存寄存器原有值以备恢复、存储局部变量。单个函数调用操作所使用的栈部分被称为栈帧(stack frame)结构。帧结构的两端由两个指针来指定,寄存器ebp通常用作帧指针,而esp则用作栈指针,在函数执行的过程中,由于栈指针会随着数据的入栈与出栈而移动,故函数中对大部分数据的访问都是基于帧指针而进行。
指令CALL和RET用于处理函数调用和返回操作,调用指令CALL的作用是把返回地址压入栈中,并且跳转到被调用函数开始处执行。返回地址是程序中紧随调用指令CALL后面一条指令的地址,因此当调用函数返回时,就会从该位置继续执行。
返回指定RET用于弹出栈顶处的地址并跳转到该地址处,使用该指令前,应该先正确处理栈中内容,使得当前栈指针所指位置内容正是先前CALL指令保存的返回地址
3、Intel CPU采用了所有函数必须遵循的寄存器用法统一惯例:
寄存器eax,ecx,edx的内容必须由调用者自己负责保存,也就是被调用函数B可以不管调用函数A是否使用而随意使用
寄存器ebx,esi,edi,ebp,esp的内容必须由被调用者来保护,当被调用者B需要使用此中某寄存器时,需要先将寄存器中的当前值保存到栈中,并在退出时恢复其内容
汇编调用c程序
linux0.11目标文件格式
System.map文件
1、当运行GNU链接器gld(ld)时,若使用了“-M”,或者使用nm命令,则会在标准输出设备(通常是屏幕)上打印出链接映像(link map)信息,即是指由链接程序产生的目标程序内存地址映像信息,其中列出了程序装入到内存中的位置信息。具体来讲有如下信息:
- 目标文件及符号映射到内存中的位置
- 公共符号如何放置
- 链接中包含的所有文件成员以及引用的符号
2、通常我们会把送到标准输出设备的链接映像信息重定向到一个文件中(例如system.map)。符号表是所有内核的符号及其对应地址的一个列表(包括上文提到的end,etext, edata等符号的地址信息)。
3、每次内核编译都会产生一个新的对应system.map文件,当内核运行出错时,通过system.map文件中的符号表解析,就可以查到一个地址值对应的变量名,或反之。
4、符号表示例
第一列表示符号值(地址);第二栏是符号类型,指明符号位于目标文件的哪个区或其属性;第三栏是对应的符号名称。
符号类型 | 名称 | 说明 |
A | Absolute | 符号值是绝对值,并且在进一步链接过程中不会改变 |
B | BSS | 符号在bss区中,即bss段 |
C | Common |
符号是公共的,公共符号是未初始化数据,在链接时,多个公共符号可能具有同一名称。 如果该符号定义在其他地方,则公共符号被看作是未定义的引用 |
D | Data | 符号在已初始化的数据中 |
G | Global |
符号是在小对象已初始化数据区中的符号。某些目标文件的格式允许对小数据对象(例如一个 全局整型变量)可进行更有效的访问 |
I | Indirect | 符号是对另一个符号的间接引用 |
N | Debugging | 符号是一个调试符号 |
R | readonly | 符号在一个只读数据区中 |
S | Small | 符号是小对象未初始化数据区中的符号 |
T | Text | 符号是代码区中的符号 |
U | Undefined | 符号是外部符号,并且值为0(未定义) |
- | stabs | 符号是a.out目标文件中的一个stab符号,用于保存调试信息 |
? | unknown | 符号的类型未知,或者是与具体文件格式有关 |
Linux内核完全注释之编程语言和环境(二)的更多相关文章
- Linux内核完全注释之编程语言和环境(一)
as86汇编器 1.来源与对于linux的用途 as86来源minix-386开发的intel 8086.80386汇编编译程序和链接程序,他主要为linux创建16位的启动引导扇区程序boot/bo ...
- 赵炯博士《Linux内核完全注释》
赵炯:男,1963年10月5日出生,江苏苏州人,汉族. 同济大学机械工程学院机械电子教研室副教授,从事教学和科研工作. 现在主要为硕士和博士研究生开设<计算机通信技术>.<计算机控制 ...
- linux内核参数注释与优化
目录 1.linux内核参数注释 2.两种修改内核参数方法 3.内核优化参数生产配置 参数解释由网络上收集整理,常用优化参数对比了网上多个实际应用进行表格化整理,使查看更直观. 学习linux也有不少 ...
- (转)linux内核参数注释与优化
linux内核参数注释与优化 原文:http://blog.51cto.com/yangrong/1321594 http://oldboy.blog.51.cto.com/2561410/13364 ...
- 读《linux内核完全注释》的FAQ
以下只是个人看了<linux内核完全注释>的一点理解,如果有错误,欢迎指正! 1 eip中保存的地址是逻辑地址.线性地址还是物理地址? 这个应该要分情况.eip保存的是下一条要执行的指令地 ...
- LINUX内核完全注释
学习教材:LINUX内核完全注释,内核版本0.11,修正版V3.0 赵炯编著 参考教材:UNIX操作系统设计--M. J. Bach, programming the 80x86 --John H. ...
- linux内核代码注释 赵炯 第三章引导启动程序
linux内核代码注释 第三章引导启动程序 boot目录中的三个汇编代码文件 bootsect.s和setup.s采用近似intel的汇编语法,需要8086汇编器连接器as86和ld86 head ...
- 2017-2018-1 《Linux内核原理与设计》第十二周作业
<linux内核原理与设计>第十二周作业 Sql注入基础原理介绍 分组: 和20179215袁琳完成实验 一.实验说明 SQL注入攻击通过构建特殊的输入作为参数传入Web应用程序,而这 ...
- 2019-2020-1 20199329《Linux内核原理与分析》第十二周作业
<Linux内核原理与分析>第十二周作业 一.本周内容概述: 通过编程理解 Set-UID 的运行机制与安全问题 完成实验楼上的<SET-UID程序漏洞实验> 二.本周学习内容 ...
随机推荐
- java多线程模拟停车系统
import java.util.Random; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent ...
- order by多个字段对索引的影响
某前台sql语句,简化后如下SELECT products_name,products_viewed FROM `products_description` ORDER BY products_vie ...
- 定时组件quartz系列<二>quartz的原理
Quartz是一个大名鼎鼎的Java版开源定时调度器,功能强悍,使用方便. 一.核心概念 Quartz的原理不是很复杂,只要搞明白几个概念,然后知道如何去启动和关闭一个调度程序即可. 1. ...
- Android中的事件分发和处理
上次跟大家分享了一下自定义View的一下要点,这次跟大家聊一下View的事件分发及处理,为什么主题都是View,因为作为一名初级应用层Android工程师,跟我打交道最多的莫过于各种各样的View,只 ...
- jQuery Mobile中文手册:开发入门
jQuery Mobile 以“Write Less, Do More”作为目标,为所有的主流移动操作系统平台提供了高度统一的 UI 框架:jQuery 的移动框架可以让你为所有流行的移动平台设计一个 ...
- C++中,申请字符串数组可用new实现
C++中,申请字符串数组可用new实现: char ** list = new char*[MAX_NUM]; for (int i = 0; i< MAX_LOOP; i++) list[i] ...
- ZOJ 2599 Graduated Lexicographical Ordering (数位DP)
首先要吐两行槽:看到集训队论文上有这道题,由于数位DP一律写成记忆化搜索形式的强迫症,就没去看论文上的几个函数是什么……:结果被这道题虐的脑细胞死光……,最后是用随机数据对拍AC程序然后发现BUG改掉 ...
- Sunday算法(字符串查找、匹配)
字符串查找算法中,最著名的两个是KMP算法(Knuth-Morris-Pratt)和BM算法(Boyer-Moore).两个算法在最坏情况下均具有线性的查找时间.但是在实用上,KMP算法并不比最简单的 ...
- 我的日常工具——gdb篇
我的日常工具——gdb篇 03 Apr 2014 1.gdb的原理 熟悉linux的同学面试官会问你用过gdb么?那好用过,知道gdb是怎么工作的么?然后直接傻眼... gdb是怎么接管一个进程?并且 ...
- C#语言基础01
Console.WriteLine("hello"); Console.ReadKey();// 按一个按键继续执行 string s=Console.ReadLine();//用 ...