c/c++语言开发共享Linux kernel中常见的宏整理

0x00 宏的基本知识 替换列表和标识符列表都是将字符串 token 化以后的列表。区别在于标识符列表使用,作为不同参数之间的分割符。每一个参数都是一个 token 化的列表。在宏中空白符只起到分割 token 的作用,空白符的多少对于预处理器是没有意义的。 宏的一些奇技淫巧: “https://g …


0x00 宏的基本知识

// object-like #define 宏名 替换列表 换行符 //function-like #define 宏名 ([标识符列表]) 替换列表 换行符

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

宏的一些奇技淫巧:

https://gaomf.cn/2017/10/06/c_macro/

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

linux内核中do{…}while(0)意义:

  • 辅助定义复杂的宏,避免引用的时候出错,如果不用{},if后面的语句只有第一条进行了判断。同时避免宏展开后“;”造成编译不通过.

  • 避免使用goto,对程序流进行统一的控制,使用break跳出

  • 避免空宏引起的warning

  • 定义一个单独的函数块来实现复杂的操作

0x01 常见宏整理

__concat宏

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

#define __concat(a, b) a ## b

bug_on(condition)

条件为真,产生崩溃, 原理:未定义的异常。

相对应的有 warn_on:

#define bug() assert(0) #define bug_on(x) assert(!(x))   /* does it make sense to treat warnings as errors? */ #define warn() bug() #define warn_on(x) (bug_on(x), false)

build_bug_on宏

#define build_bug_on(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
  1. condition为真时,sizeof(char[-1]),产生错误,编译不通过

  2. condition为假时,sizeof(char[1]),编译通过

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

struct { int : –!!(0); } -=> struct { int : 0; }

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

struct { int : –!!(1); } -=> struct { int : –1; }

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

typeof获得x的变量类型,根据传入参数类型的不同,产生不同的行为,实现“编译时多态”。实际typeof是在预编译时处理,最后实际转化为数据类型被编译器处理。

所以其中的表达式在运行时是不会被执行的,比如typeof(fun()),fun()函数是不会被执行的,typeof只是在编译时分析得到了fun()的返回值而已。

typeof还有一些局限性,其中的变量是不能包含存储类说明符的,如static、extern这类都是不行的。

typecheck宏

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

/*  * check at compile time that something is of a particular type.  * always evaluates to 1 so you may use it easily in comparisons.  */ #define typecheck(type,x)  ({ type __dummy;      typeof(x) __dummy2;      (void)(&__dummy == &__dummy2);      1;  }) /*gcc的一个扩展特性,形如({ ... })这样的代码块会被视为一条语句, * 其计算结果是{ ... }中最后一条语句的计算结果。 * 所以上述会返回1 */ /*  * check at compile time that 'function' is a certain type, or is a pointer  * to that type (needs to use typedef for the function type.)  */ #define typecheck_fn(type,function)  ({ typeof(type) __tmp = function;      (void)__tmp;  }) 

min宏

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

#define min(x, y) __careful_cmp(x, y, <) #define __cmp(x, y, op) ((x) op (y) ? (x) : (y)) #define __safe_cmp(x, y)          (__typecheck(x, y) && __no_side_effects(x, y)) #define __no_side_effects(x, y)          (__is_constexpr(x) && __is_constexpr(y))   #define __cmp_once(x, y, unique_x, unique_y, op) ({          typeof(x) unique_x = (x);          typeof(y) unique_y = (y);          __cmp(unique_x, unique_y, op); }) /*重新赋值为了防止x++这种重复+1 */ #define __careful_cmp(x, y, op)      __builtin_choose_expr(__safe_cmp(x, y),  //比较x, y的类型         __cmp(x, y, op),  //x,y类型一样时         __cmp_once(x, y, __unique_id(__x), __unique_id(__y), op))           //x, y类型不同时

__unique_id保证变量唯一。

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

/*  * this returns a constant expression while determining if an argument is  * a constant expression, most importantly without evaluating the argument.  * glory to martin uecker <martin.uecker@med.uni-goettingen.de>  */ #define __is_constexpr(x)      (sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))

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

所以会出现以下两种情况:

sizeof(int) == sizeof(*((int *) (null))) // if `x` was an integer constant expression sizeof(int) == sizeof(*((void *)(....))) // otherwise

因为sizeof(void) = 1,所以如果x是整数常量表达式,则宏的结果为1,否则为0。

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

int __builtin_choose_expr(exp, e1, e2);

max宏

同min 宏。

roundup宏

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

#define roundup(x, y) (  {      const typeof(y) __y = y;      (((x) + (__y - 1)) / __y) * __y;  }  )

clamp 宏

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

/**  * clamp - return a value clamped to a given range with strict typechecking  * @val: current value  * @lo: lowest allowable value  * @hi: highest allowable value  *  * this macro does strict typechecking of @lo/@hi to make sure they are of the  * same type as @val. see the unnecessary pointer comparisons.  */ #define clamp(val, lo, hi) min((typeof(val))max(val, lo), hi)

abs宏

取绝对值:

/**  * abs - return absolute value of an argument  * @x: the value. if it is unsigned type, it is converted to signed type first.  * char is treated as if it was signed (regardless of whether it really is)  * but the macro's return type is preserved as char.  *  * return: an absolute value of x.  */ #define abs(x) __abs_choose_expr(x, long long,          __abs_choose_expr(x, long,          __abs_choose_expr(x, int,          __abs_choose_expr(x, short,          __abs_choose_expr(x, char,          __builtin_choose_expr(              __builtin_types_compatible_p(typeof(x), char),              (char)({ signed char __x = (x); __x<0?-__x:__x; }),              ((void)0)))))))   #define __abs_choose_expr(x, type, other) __builtin_choose_expr(      __builtin_types_compatible_p(typeof(x), signed type) ||      __builtin_types_compatible_p(typeof(x), unsigned type),      ({ signed type __x = (x); __x < 0 ? -__x : __x; }), other)

swap 宏

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

/*  * swap - swap value of @a and @b  */ #define swap(a, b)      do { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while (0)

container_of宏

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

#define offsetof(type, member) ((size_t) &((type *)0)->member) /*结构体地址为0,将member地址转成size_t类型作为偏移 /**  * container_of - cast a member of a structure out to the containing structure  * @ptr: the pointer to the member.  * @type: the type of the container struct this is embedded in.  * @member: the name of the member within the struct.  *  */ #define container_of(ptr, type, member) ({      const typeof( ((type *)0)->member ) *__mptr = (ptr);  //*__mptr保存该member变量的指针     (type *)( (char *)__mptr - offsetof(type,member) );}) //变量指针减去自身偏移得到指向结构体的指针

likely和unlikely宏

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

#define likely(x) __builtin_exp ect(!!(x), 1) #define unlikely(x) __builtin_exp ect(!!(x), 0)

gcc的内建方法会判断 exp == c 是否成立,成立则将if分支中的执行语句紧跟放在汇编跳转指令之后,否则将else分支中的执行语句紧跟汇编跳转指令之后。

这样cache在预取数据时就可以将分支后的执行语句放在cache中,提高cache的命中率。

align对齐宏

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

#define align(x, a) __align_kernel((x), (a)) #define __align_kernel(x, a) __align_kernel_mask(x, (typeof(x))(a) - 1) #define __align_kernel_mask(x, mask) (((x) + (mask)) & ~(mask)) #define __align_mask(x, mask) __align_kernel_mask((x), (mask))

__get_unaligned_le(ptr)宏
获取未对齐的数据,主要是识别数据大小:

#define __get_unaligned_le(ptr) ((__force typeof(*(ptr)))({      __builtin_choose_expr(sizeof(*(ptr)) == 1, *(ptr),      __builtin_choose_expr(sizeof(*(ptr)) == 2, get_unaligned_le16((ptr)),      __builtin_choose_expr(sizeof(*(ptr)) == 4, get_unaligned_le32((ptr)),      __builtin_choose_expr(sizeof(*(ptr)) == 8, get_unaligned_le64((ptr)),      __bad_unaligned_access_size()))));   }))    static inline u32 get_unaligned_be32(const void *p) {     return __get_unaligned_cpu32((const u8 *)p); }   static inline u32 __get_unaligned_cpu32(const void *p) {     const struct __una_u32 *ptr = (const struct __una_u32 *)p;     return ptr->x; }   struct __una_u16 { u16 x; } __packed; struct __una_u32 { u32 x; } __packed; struct __una_u64 { u64 x; } __packed;

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

__put_unaligned_le宏

写入未对齐的数据。

#define __put_unaligned_le(val, ptr) ({      void *__gu_p = (ptr);      switch (sizeof(*(ptr))) {      case 1:          *(u8 *)__gu_p = (__force u8)(val);          break;      case 2:          put_unaligned_le16((__force u16)(val), __gu_p);          break;      case 4:          put_unaligned_le32((__force u32)(val), __gu_p);          break;      case 8:          put_unaligned_le64((__force u64)(val), __gu_p);          break;      default:          __bad_unaligned_access_size();          break;      }      (void)0; })    static inline void put_unaligned_be32(u32 val, void *p) {     __put_unaligned_cpu32(val, p); }   static inline void __put_unaligned_cpu32(u32 val, void *p) {     struct __una_u32 *ptr = (struct __una_u32 *)p;     ptr->x = val; }

access_once 宏

访问目标地址一次,先取得x的地址,然后把这个地址转换成一个指向这个地址类型的指针,然后再取得这个指针所指向的内容,达到了访问一次的目的。volatile表示不进行优化,强制访问一次。

在一些并发的场景中对变量进行优化有可能导致错误,需要时刻得到变量的最新值,所以用volatile强制访问一次进行更新。

使用 access_once() 的两个条件是:

  • 在无锁的情况下访问全局变量

  • 对该变量的访问可能被编译器优化成合并成一次或者拆分成多次

#define access_once(x) (*(volatile typeof(x) *)&(x))

access_ok宏

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

/**  * access_ok: - checks if a user space pointer is valid  * @addr: user space pointer to start of block to check  * @size: size of block to check  *  * context: user context only. this function may sleep if pagefaults are  * enabled.  *  * checks if a pointer to a block of memory in user space is valid.  *  * returns true (nonzero) if the memory block may be valid, false (zero)  * if it is definitely invalid.  *  * note that, depending on architecture, this function probably just  * checks that the pointer is in the user space range - after calling  * this function, memory access functions may still return -efault.  */ #define access_ok(addr, size)  ({      warn_on_in_irq();      likely(!__range_not_ok(addr, size, user_addr_max()));  }) /*__range_not_ok返回0才能验证通过   #define __range_not_ok(addr, size, limit)  ({      __chk_user_ptr(addr);      __chk_range_not_ok((unsigned long __force)(addr), size, limit);  })   /*  * test whether a block of memory is a valid user space address.  * returns 0 if the range is valid, nonzero otherwise.  */ static inline bool __chk_range_not_ok(unsigned long addr, unsigned long size, unsigned long limit) {     /*      * if we have used "sizeof()" for the size,      * we know it won't overflow the limit (but      * it might overflow the 'addr', so it's      * important to subtract the size from the      * limit, not add it to the address).      */     if (__builtin_constant_p(size))         return unlikely(addr > limit - size);     /*__builtin_constant_p判断编译时是否为常数,如果是则返回1 */     /* arbitrary sizes? be careful about overflow */     addr += size;     if (unlikely(addr < size))         return true;     return unlikely(addr > limit); }

mdelay宏

忙等待函数,在延迟过程中无法运行其他任务,会占用cpu时间,延迟时间是准确的。

msleep是休眠函数,它不涉及忙等待.用msleep(200)的时候实际上延迟的时间,大部分时候是要多于200ms,是个不定的时间值。

#define max_udelay_ms 5 #define mdelay(n) ( /*延迟毫秒级*/     (__builtin_constant_p(n) && (n)<=max_udelay_ms) ? udelay((n)*1000) :      ({unsigned long __ms=(n); while (__ms--) udelay(1000);}))   static void udelay(int loops) /*延迟微秒级 */ {     while (loops--)         io_delay(); /* approximately 1 us */ }   static inline void io_delay(void) {     const u16 delay_port = 0x80;     asm volatile("outb %%al,%0" : : "dn" (delay_port)); } /*对 i/o 端口 0x80 写入任何的字节都将得到 1 us 的延时*/

系统调用宏

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

#define syscall_define1(name, ...) syscall_definex(1, _##name, __va_args__) #define syscall_define2(name, ...) syscall_definex(2, _##name, __va_args__) #define syscall_define3(name, ...) syscall_definex(3, _##name, __va_args__) #define syscall_define4(name, ...) syscall_definex(4, _##name, __va_args__) #define syscall_define5(name, ...) syscall_definex(5, _##name, __va_args__) #define syscall_define6(name, ...) syscall_definex(6, _##name, __va_args__) /*…:省略号代表可变的部分,用__va_aegs__ 代表省略的变长部分*/ #define syscall_define_maxargs    6  /*系统调用最多可以带6个参数*/

以open系统调用为例:

syscall_define

后面跟系统调用所带的参数个数n,第一个参数为系统调用的免费精选名字大全,然后接2*n个参数,每一对指明系统调用的参数类型及免费精选名字大全。

syscall_define3(open, const char __user *, filename, int, flags, umode_t, mode) {     if (force_o_largefile())         flags |= o_largefile;       return do_sys_open(at_fdcwd, filename, flags, mode); }  syscall_define3(open, const char __user *, filename, int, flags, umode_t, mode) 展开之后是: syscall_definex(3, _open, __va_args__)

再次展开为:

__syscall_definex(3, _open, __va_args__) #define __syscall_definex(x, name, ...)      asmlinkage long sys##name(__map(x,__sc_decl,__va_args__)) 

最后展开为:

asmlinkage long sys_open(__map(3,__sc_decl,__va_args__))   #define __map0(m,...) #define __map1(m,t,a) m(t,a) #define __map2(m,t,a,...) m(t,a), __map1(m,__va_args__) #define __map3(m,t,a,...) m(t,a), __map2(m,__va_args__) #define __map4(m,t,a,...) m(t,a), __map3(m,__va_args__) #define __map5(m,t,a,...) m(t,a), __map4(m,__va_args__) #define __map6(m,t,a,...) m(t,a), __map5(m,__va_args__) #define __map(n,...) __map##n(__va_args__)   #define __sc_decl(t, a) t a   __map(3,__sc_decl,__va_args__) -->__map3(__sc_decl,const char __user *, filename, int, flags, umode_t, mode) -->__sc_decl(const char __user *, filename), __map2(__sc_decl,__va_args__) -->const char __user * filename,__sc_decl(int, flags),__map1(__sc_decl,__va_args__) -->const char __user * filename, int flags, __sc_decl(umode_t, mode) -->const char __user * filename, int flags, umode_t mode

最后调用asmlinkage long sys_open(const char __user *filename,int flags, umode_t mode);

为什么要将系统调用定义成宏?cve-2009-0029,cve-2010-3301,linux 2.6.28及以前版本的内核中,将系统调用中32位参数传入64位的寄存器时无法作符号扩展,可能导致系统崩溃或提权漏洞。

内核开发者通过将系统调用的所有输入参数都先转化成long类型(64位),再强制转化到相应的类型来规避这个漏洞。

asmlinkage long __se_sys##name(__map(x,__sc_long,__va_args__))  {          long ret = __do_sys##name(__map(x,__sc_cast,__va_args__));         __map(x,__sc_test,__va_args__);          __protect(x, ret,__map(x,__sc_args,__va_args__));          return ret;  }      #define __type_as(t, v) __same_type((__force t)0, v) /*判断t和v是否是同一个类型*/ #define __type_is_l(t) (__type_as(t, 0l)) /*判断t是否是long 类型,是返回1*/ #define __type_is_ul(t) (__type_as(t, 0ul)) /*判断t是否是unsigned long 类型,是返回1*/ #define __type_is_ll(t) (__type_as(t, 0ll) || __type_as(t, 0ull))/*是long类型就返回1*/ #define __sc_long(t, a) __typeof(__builtin_choose_expr(__type_is_ll(t), 0ll, 0l)) a /*将参数转换成long类型*/ #define __sc_cast(t, a) (__force t) a /*转成成原来的类型*/  # define __force __attribute__((force))

表示所定义的变量类型可以做强制类型转换

barrier()宏

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

/* optimization barrier */ /* the "volatile" is due to gcc bugs */ #define barrier() __asm__ __volatile__("": : :"memory")

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

int a = 5, b = 6; barrier(); a = b;

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

另外的内存屏障宏定义:

  • mfence:在mfence指令前的读写操作当必须在mfence指令后的读写操作前完成。

  • lfence:在lfence指令前的读操作当必须在lfence指令后的读操作前完成,不影响写操作

  • sfence:在sfence指令前的写操作当必须在sfence指令后的写操作前完成,不影响读操作

  • lock 前缀(或cpuid、xchg等指令)使得本cpu的cache写入内存,该写入动作也会引起别的cpu invalidate其cache。用来修饰当前指令操作的内存只能由当前cpu使用

内存对于缓存更新策略,要区分write-through和write-back两种策略。前者更新内容直接写内存并不同时更新cache,但要置cache失效,后者先更新cache,随后异步更新内存。通常x86 cpu更新内存都使用write-back策略。

ifdef assembly宏

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

例如调用:#define demo_macro _at(1, ul):在c中会被解释为 #define demo_macro 1ul; 而在汇编中什么都不做,就是:#define demo_macro 1

#ifdef __assembly__ #define _ac(x,y) x #define _at(t,x) x #else #define __ac(x,y) (x##y) #define _ac(x,y) __ac(x,y) #define _at(t,x) ((t)(x)) #endif   #define _ul(x) (_ac(x, ul)) #define _ull(x) (_ac(x, ull))

force_o_largefile宏

判断是否支持大文件。

define force_o_largefile() (personality(current->personality) != per_linux32)

per_linux32 = 0x0008,
per_mask = 0x00ff,
/*,

  • return the base personality without flags.
    */

    define personality(pers) (pers & per_mask)

逻辑地址和物理地址互相转换

#define __pa(x) __virt_to_phys((unsigned long)(x)) #define __va(x) ((void *)__phys_to_virt((unsigned long)(x)))

错误码相关的宏

linux 内核的一些错误码,以它们的负数来作为函数返回值,简单地使用大于等于-4095的虚拟地址来分别表示相应的错误码。

在32位系统上,-4095转换成unsigned long类型的值为0xfffff001,也就是说地址区间[0xfffff001, 0xffffffff]被分别用来表示错误码从-4095到-1。

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

#define max_errno 4095   #define is_err_value(x) unlikely((x) >= (unsigned long)-max_errno)   static inline long __must_check is_err(const void *ptr) {     return is_err_value((unsigned long)ptr); }

错误码与相应地址的互换:

static inline void * __must_check err_ptr(long error) {     return (void *) error; }

长整型转化为指针

static inline long __must_check ptr_err(const void *ptr) {     return (long) ptr; }

指针转化为长整型

额外有意思的宏

递归宏,颠倒字节:

#define bswap_8(x) ((x) & 0xff) #define bswap_16(x) ((bswap_8(x) << 8) | bswap_8((x) >> 8)) #define bswap_32(x) ((bswap_16(x) << 16) | bswap_16((x) >> 16)) #define bswap_64(x) ((bswap_32(x) << 32) | bswap_32((x) >> 32))

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

#define swap(a, b)  (((a) ^= (b)), ((b) ^= (a)), ((a) ^= (b)))

本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。

ctvol管理联系方式QQ:251552304

本文章地址:https://www.ctvol.com/c-cdevelopment/600683.html

(0)
上一篇 2021年5月9日
下一篇 2021年5月9日

精彩推荐