首页
社区
课程
招聘
[原创] Linux kernel中常见的宏整理
发表于: 2019-11-28 21:24 7858

[原创] Linux kernel中常见的宏整理

2019-11-28 21:24
7858

替换列表和标识符列表都是将字符串 token 化以后的列表。区别在于标识符列表使用,作为不同参数之间的分割符。每一个参数都是一个 token 化的列表。在宏中空白符只起到分割 token 的作用,空白符的多少对于预处理器是没有意义的。

宏的一些奇技淫巧:https://gaomf.cn/2017/10/06/C_Macro/

以下是整理的一些linux kernel中的常见宏,由于不同体系架构,或者不同模块的宏定义不同,只挑选了其中容易看懂的宏作为记录,实现的功能大体一样。

Linux内核中do{...}while(0)意义:

"##"用于粘贴两个参数,"#"用于替换参数

条件为真,产生崩溃, 原理:未定义的异常
相对应的有 WARN_ON

!!(e) 对 e 的结果进行两次求非. 如果e为0,则结果为0; 如果 e 不为 0, 则结果为1。所以上述表达式的结果有两种:

检查表达式e是否为0为0编译通过且返回0;如果不为0,则编译不通过.

如果e为0,则该结构体拥有一个int型的数据域,并且规定它所占的位的个数为0。

如果e非0,结构体的int型数据域的位域将变为一个负数,产生语法的错误。

typeof获得x的变量类型,根据传入参数类型的不同,产生不同的行为,实现“编译时多态”。实际typeof是在预编译时处理,最后实际转化为数据类型被编译器处理。所以其中的表达式在运行时是不会被执行的,比如typeof(fun()),fun()函数是不会被执行的,typeof只是在编译时分析得到了fun()的返回值而已。typeof还有一些局限性,其中的变量是不能包含存储类说明符的,如static、extern这类都是不行的。

宏typecheck用于检查x是否为type类型,如果不是会抛出(warning: comparison of distinct pointer types lacks a cast),typecheck_fn用于检查函数function是否为type类型,不一致跑出(warning: initialization from incompatible pointer type)。

通过type进行隐式转换安全通过编译,否则会跑出warning

__UNIQUE_ID保证变量唯一

判断x是否为整数常量表达式

如果x是常量表达式,则(void )((long)(x) 0l)是一个空指针常量,就会使用第三个操作数即((int *)8)的类型。如果不是常量表达式,则会使用第二个操作数void类型。
所以会出现以下两种情况:

因为sizeof(void) = 1,所以如果x是整数常量表达式,则宏的结果为1,否则为0。
https://stackoverflow.com/questions/49481217/linux-kernels-is-constexpr-macro

描述:此函数为GNU扩展,用来判断两个类型是否相同,如果type_a与 type_b相同的话,就会返回1,否则的话,返回0。

同min 宏

返回一个能够整除y并且大于x,最接近x的值,向上取整,可用于地址的内存对齐

判断val是否在lo和hi的范围内,如果小于lo,返回lo,如果大于hi则返回hi,如果在lo和hi之间就返回val

取绝对值

利用typeof获取要交换变量的类型

根据一个结构体变量中的成员变量来获取整个结构体变量的指针。

把分支预测的信息提供给编译器,以降低因为指令跳转带来的分支下降

GCC的内建方法会判断 EXP == C 是否成立,成立则将if分支中的执行语句紧跟放在汇编跳转指令之后,否则将else分支中的执行语句紧跟汇编跳转指令之后。这样cache在预取数据时就可以将分支后的执行语句放在cache中,提高cache的命中率

http://www.169it.com/article/17243108930910839727.html

对齐是采用上对齐的方式,例如0x123以16对齐,结果是0x130,因为对齐常在分配内存时使用,所以分配的要比需要的大

获取未对齐的数据,主要是识别数据大小

编译器默认会对结构体采用字节对齐的方式,__packed关键字可以取消字节对齐,采用1字节对齐。
类似:

写入未对齐的数据

访问目标地址一次,先取得x的地址,然后把这个地址转换成一个指向这个地址类型的指针,然后再取得这个指针所指向的内容,达到了访问一次的目的。volatile表示不进行优化,强制访问一次。在一些并发的场景中对变量进行优化有可能导致错误,需要时刻得到变量的最新值,所以用volatile强制访问一次进行更新。
使用 ACCESS_ONCE() 的两个条件是:

https://blog.csdn.net/ganggexiongqi/article/details/24603363

CVE-2017-5123(waitid系统调用),检查指针是不是属于用户空间的,x86架构下ACCESS_OK宏的实现:

忙等待函数,在延迟过程中无法运行其他任务,会占用CPU时间,延迟时间是准确的。
msleep是休眠函数,它不涉及忙等待.用msleep(200)的时候实际上延迟的时间,大部分时候是要多于200ms,是个不定的时间值。

linux 内核中最常见的宏使用之一,系统调用

以open系统调用为例:
SYSCALL_DEFINE 后面跟系统调用所带的参数个数n,第一个参数为系统调用的名字,然后接2*n个参数,每一对指明系统调用的参数类型及名字。

为什么要将系统调用定义成宏?CVE-2009-0029,CVE-2010-3301,Linux 2.6.28及以前版本的内核中,将系统调用中32位参数传入64位的寄存器时无法作符号扩展,可能导致系统崩溃或提权漏洞。
内核开发者通过将系统调用的所有输入参数都先转化成long类型(64位),再强制转化到相应的类型来规避这个漏洞。

time_after32(a, b)宏:返回true时,说明time a比b大,在后面

time_before32(b, a)宏:返回true时,说明time b在a前
只比较两个32位的数:

内存屏障,该语句不产生任何代码,但是执行后刷新寄存器对变量的分配。

执行该语句后cpu中的寄存器和cache中已缓存的数据将作废,重新读取内存中的数据。这就阻止了cpu将寄存器和cache中的数据用于去优化指令,而避免去访问内存。例如:

第三行中,GCC不会用存放b的寄存器给a赋值,而是invalidate b 的cache line,重新读取内存中的b值给a赋值。

另外的内存屏障宏定义:

内存对于缓存更新策略,要区分Write-Through和Write-Back两种策略。前者更新内容直接写内存并不同时更新Cache,但要置Cache失效,后者先更新Cache,随后异步更新内存。通常X86 CPU更新内存都使用Write-Back策略。

一些常量宏同时在汇编和C中使用,然而,我们不能像注释C的常量宏那样加一个“UL”或其他后缀。所以我们需要使用以下的宏解决这个问题。

例如调用:#define DEMO_MACRO _AT(1, UL):在C中会被解释为 #define DEMO_MACRO 1UL; 而在汇编中什么都不做,就是: #define DEMO_MACRO 1

判断是否支持大文件

linux 内核的一些错误码,以它们的负数来作为函数返回值,简单地使用大于等于-4095的虚拟地址来分别表示相应的错误码。在32位系统上,-4095转换成unsigned long类型的值为0xFFFFF001,也就是说地址区间[0xFFFFF001, 0xFFFFFFFF]被分别用来表示错误码从-4095到-1。

判断一个函数返回的指针到底是有效地址还是错误码

错误码与相应地址的互换

递归宏,颠倒字节

交换宏,不需要额外定义变量

 
 
 
 
 
 
 

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2019-11-28 21:25 被笔墨编辑 ,原因:
收藏
免费 2
支持
分享
最新回复 (5)
雪    币: 106
活跃值: (140)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
拜读了
2019-11-29 08:27
0
雪    币: 26245
活跃值: (63297)
能力值: (RANK:135 )
在线值:
发帖
回帖
粉丝
3
感谢分享!
2019-11-29 09:27
0
雪    币: 292
活跃值: (810)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
4
感谢分享
2019-11-29 10:44
0
雪    币: 14517
活跃值: (17538)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
5
mark,楼主辛苦了
2019-12-1 09:27
0
雪    币: 32
活跃值: (17)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
6
mark,楼主辛苦了!
2019-12-3 11:15
0
游客
登录 | 注册 方可回帖
返回
//