文章目录

https://github.com/wdfk-prog/linux-study
arch/arm/include/asm/cache.h
__read_mostly 大多数读取
__read_mostly
用于标记大多数读取的变量,在编译时会将该变量放到.data..read_mostly
段中
#define __read_mostly __section(".data..read_mostly")
__read_mostly
的作用是将变量放到.data..read_mostly
段中,该段在内核启动时会被清零,所以该变量在内核启动时会被清零
arch/arm/include/asm/cmpxchg.h
arch_xchg_relaxed __arch_xchg
- 交换数据的逻辑都一样
- 使用
ldrexb
加载数据,它还会设置一个标志,表示该地址被标记为“独占访问”,以便后续的存储操作可以检查是否有其他处理器访问了该地址。 - 使用
strexb
存储数据,如果成功,返回0;如果失败,返回1,并且会清除“独占访问”标志。 - 如果
strexb
失败,循环会重新尝试加载和存储数据,直到成功为止。 - 该函数的返回值是交换前的值。
__bad_xchg
函数用于处理不支持的交换大小,会导致链接时错误.在不支持的情况下触发链接时错误(link-time error),以防止程序在运行时出现未定义行为或错误结果
#define arch_xchg_relaxed(ptr, x) ({ \
(__typeof__(*(ptr)))__arch_xchg((unsigned long)(x), (ptr), \
sizeof(*(ptr))); \
})
static inline unsigned long
__arch_xchg(unsigned long x, volatile void *ptr, int size)
{
extern void __bad_xchg(volatile void *, int);
unsigned long ret;
#ifdef swp_is_buggy //仅有(CONFIG_CPU_SA1100) || defined(CONFIG_CPU_SA110)才需要修复
unsigned long flags;
#endif
#if __LINUX_ARM_ARCH__ >= 6 //armV6架构及以上
unsigned int tmp;
#endif
prefetchw((const void *)ptr); //写预取
//需要交换的数据大小
switch (size) {
#if __LINUX_ARM_ARCH__ >= 6
#ifndef CONFIG_CPU_V6 /* MIN ARCH >= V6K */
case 1:
asm volatile("@ __xchg1\n"
"1: ldrexb %0, [%3]\n"
" strexb %1, %2, [%3]\n"
" teq %1, #0\n"
" bne 1b"
: "=&r" (ret), "=&r" (tmp)
: "r" (x), "r" (ptr)
: "memory", "cc");
break;
case 2:
asm volatile("@ __xchg2\n"
"1: ldrexh %0, [%3]\n"
" strexh %1, %2, [%3]\n"
" teq %1, #0\n"
" bne 1b"
: "=&r" (ret), "=&r" (tmp)
: "r" (x), "r" (ptr)
: "memory", "cc");
break;
#endif
case 4:
asm volatile("@ __xchg4\n"
"1: ldrex %0, [%3]\n"
" strex %1, %2, [%3]\n"
" teq %1, #0\n"
" bne 1b"
: "=&r" (ret), "=&r" (tmp)
: "r" (x), "r" (ptr)
: "memory", "cc");
break;
#elif defined(swp_is_buggy)
#ifdef CONFIG_SMP
#error SMP is not supported on this platform
#endif
case 1:
raw_local_irq_save(flags);
ret = *(volatile unsigned char *)ptr;
*(volatile unsigned char *)ptr = x;
raw_local_irq_restore(flags);
break;
case 4:
raw_local_irq_save(flags);
ret = *(volatile unsigned long *)ptr;
*(volatile unsigned long *)ptr = x;
raw_local_irq_restore(flags);
break;
#else
case 1:
asm volatile("@ __xchg1\n"
" swpb %0, %1, [%2]"
: "=&r" (ret)
: "r" (x), "r" (ptr)
: "memory", "cc");
break;
case 4:
asm volatile("@ __xchg4\n"
" swp %0, %1, [%2]"
: "=&r" (ret)
: "r" (x), "r" (ptr)
: "memory", "cc");
break;
#endif
default:
/* Cause a link-time error, the xchg() size is not supported */
__bad_xchg(ptr, size), ret = 0;
break;
}
return ret;
}
arch/arm/include/asm/bug.h
BUG_INSTR 触发bug指令(未知指令)
- 将BUG_INSTR_VALUE转换为bug指令,转成字符串`“.short 0xde02 \n\t”
- 由于arm架构为小端存储,所以操作码的字节序需要转换,实际为
0x02de
和0xF200F0E7
- 根据手册可知该指令为
udf #2
,即undefined instruction
指令,会触发异常
#ifdef CONFIG_THUMB2_KERNEL
#define BUG_INSTR_VALUE 0xde02
#define BUG_INSTR(__value) __inst_thumb16(__value)
#else
#define BUG_INSTR_VALUE 0xe7f001f2
#define BUG_INSTR(__value) __inst_arm(__value)
#endif
BUG() 触发bug
unreachable
用标记该代码块不可达,用于编译器优化CONFIG_DEBUG_BUGVERBOSE
未启用,则使用BUG_INSTR
触发bugCONFIG_DEBUG_BUGVERBOSE
启用- 使用
BUG_INSTR
触发bug - 将当前段切换到只读数据段
- 将
__file
和__line
信息存储到只读数据段 - 将
__bug_table
段切换到可读写段,以2字节对齐 - 将
__bug_table
段中的1b
和2b
替换为当前指令和文件行号 - 将
__bug_table
段切换到只读段
- 使用
#define BUG() _BUG(__FILE__, __LINE__, BUG_INSTR_VALUE)
#define _BUG(file, line, value) __BUG(file, line, value)
#ifdef CONFIG_DEBUG_BUGVERBOSE //详细的错误信息,增加70k大小
/*
* 额外的间接操作是为了确保__FILE__字符串能够正常通过。
* 许多版本的gcc都不支持asm %c参数,这比asm %c要好得多。
* 我们使用可合并的字符串段来避免字符串出现在内核映像中的多个副本。
*/
#define __BUG(__file, __line, __value) \
do { \
asm volatile("1:\t" BUG_INSTR(__value) "\n" \
".pushsection .rodata.str, \"aMS\", %progbits, 1\n" \
"2:\t.asciz " #__file "\n" \
".popsection\n" \
".pushsection __bug_table,\"aw\"\n" \
".align 2\n" \
"3:\t.word 1b, 2b\n" \
"\t.hword " #__line ", 0\n" \
".popsection"); \
unreachable(); \
} while (0)
#else
#define __BUG(__file, __line, __value) \
do { \
asm volatile(BUG_INSTR(__value) "\n"); \
unreachable(); \
} while (0)
#endif /* CONFIG_DEBUG_BUGVERBOSE */
arch/arm/include/asm/irqflags.h
arch_local_irq_save 返回当前中断状态并禁用中断
/*
* CPU interrupt mask handling.
*/
#ifdef CONFIG_CPU_V7M
#define IRQMASK_REG_NAME_R "primask"
#define IRQMASK_REG_NAME_W "primask"
#define IRQMASK_I_BIT 1
#else
#define IRQMASK_REG_NAME_R "cpsr"
#define IRQMASK_REG_NAME_W "cpsr_c"
#define IRQMASK_I_BIT PSR_I_BIT
#endif
#if __LINUX_ARM_ARCH__ >= 6
#define arch_local_irq_save arch_local_irq_save
static inline unsigned long arch_local_irq_save(void)
{
unsigned long flags;
asm volatile(
/* 将当前的中断状态从寄存器(由 IRQMASK_REG_NAME_R 指定)加载到变量 flags 中。
mrs 指令用于从系统寄存器读取值。 */
" mrs %0, " IRQMASK_REG_NAME_R " @ arch_local_irq_save\n"
/* 禁用 IRQ(普通中断) */
" cpsid i"
/* 指定 flags 是一个输出操作数,并将其存储在一个通用寄存器中。
表示该汇编代码可能会修改内存和条件码寄存器(cc),因此编译器需要注意这些副作用。 */
: "=r" (flags) : : "memory", "cc");
return flags;
}
#endif
arch_local_save_flags 返回当前中断状态
/*
* Save the current interrupt enable state.
*/
#define arch_local_save_flags arch_local_save_flags
static inline unsigned long arch_local_save_flags(void)
{
unsigned long flags;
asm volatile(
" mrs %0, " IRQMASK_REG_NAME_R " @ local_save_flags"
: "=r" (flags) : : "memory", "cc");
return flags;
}
arch_local_irq_restore 恢复中断状态
- 使用汇编指令将 flags 的值写回到特定的处理器寄存器中,从而恢复中断状态
/*
* restore saved IRQ & FIQ state
*/
#define arch_local_irq_restore arch_local_irq_restore
static inline void arch_local_irq_restore(unsigned long flags)
{
asm volatile(
" msr " IRQMASK_REG_NAME_W ", %0 @ local_irq_restore"
:
: "r" (flags)
: "memory", "cc");
}
arch/arm/include/asm/opcodes.h 操作码兼容接口 thumb2和arm的中间层
- _mem_to_opcode*() 从内存中表示形式转换为规范形式
- _opcode_to_mem*() 从规范形式转换为内存中表示形式
/*
*作码注入帮助程序
*
* 在极少数情况下,需要组装一个作码,该作码的汇编程序不直接支持,或者通常支持被拒绝,因为 CFLAGS 或 AFLAGS 用于构建受影响的文件。
*
* 在使用这些宏之前,请仔细考虑是否可行来更改文件的构建标志,或者它是否真的在构建时支持旧的汇编程序版本是有意义的
* 特定的内核功能。
*
* 此处定义的宏应仅在没有可行的另类。
*
* 请注意,直接使用这些宏是一种糟糕的做法。 相反,您应该使用它们来定义人类可读的包装器宏,以对您关心的说明。 在可能运行在 ARMv7 或上,您通常可以使用 __inst_arm_thumb{16,32} 宏来同时指定 ARM 和 Thumb 替代方案。 这确保了
* 根据指令集发出正确的作码用于内核构建。
*
* 查看 opcodes-virt.h 有关如何使用这些宏的示例。
*/
___opcode_swab32 交换字节
/*
*作码字节交换辅助函数
* 这些宏有助于在规范整数之间转换指令
* 格式和内存中的表示形式,以与字节序无关的方式。
* __mem_to_opcode_*() 从内存中表示形式转换为规范形式。
* __opcode_to_mem_*() 从规范形式转换为内存中表示形式。
* 规范指令表示:
* ARM:0xKKLLMMNN
* Thumb 16 位:0x0000KKLL,其中 KK < 0xE8* Thumb 32 位:0xKKLLMMNN,其中 KK >= 0xE8
* 无法区分规范表示形式的 ARM 指令
* 来自 Thumb 指令(就像它们在内存中无法区分一样)。
* 如果这种区别很重要,则需要单独跟踪。
* 请注意,范围内的值0x0000E800..0xE7FFFFFF 有意不这样做
* 代表任何有效的 Thumb-2 指令。 对于此范围,
* __opcode_is_thumb32() 和 __opcode_is_thumb16() 都将为 false。
* ___asm 变体仅供此标头在以下情况下使用
* 涉及内联汇编器。 为。S 文件,常规的 __opcode_*() 宏
* 应该做正确的事情。
*/
#ifdef __ASSEMBLY__
#define ___opcode_swab32(x) ___asm_opcode_swab32(x)
#define ___opcode_swab16(x) ___asm_opcode_swab16(x)
#define ___opcode_swahb32(x) ___asm_opcode_swahb32(x)
#define ___opcode_swahw32(x) ___asm_opcode_swahw32(x)
#define ___opcode_identity32(x) ___asm_opcode_identity32(x)
#define ___opcode_identity16(x) ___asm_opcode_identity16(x)
#else /* ! __ASSEMBLY__ */
#include <linux/types.h>
#include <linux/swab.h>
#define ___opcode_swab32(x) swab32(x)
#define ___opcode_swab16(x) swab16(x)
#define ___opcode_swahb32(x) swahb32(x)
#define ___opcode_swahw32(x) swahw32(x)
#define ___opcode_identity32(x) ((u32)(x))
#define ___opcode_identity16(x) ((u16)(x))
#endif /* ! __ASSEMBLY__ */
__opcode_to_mem_thumb16 转换为内存(大端与小端的中间层)
#ifdef CONFIG_CPU_ENDIAN_BE8
#define __opcode_to_mem_arm(x) ___opcode_swab32(x)
#define __opcode_to_mem_thumb16(x) ___opcode_swab16(x)
#define __opcode_to_mem_thumb32(x) ___opcode_swahb32(x)
#define ___asm_opcode_to_mem_arm(x) ___asm_opcode_swab32(x)
#define ___asm_opcode_to_mem_thumb16(x) ___asm_opcode_swab16(x)
#define ___asm_opcode_to_mem_thumb32(x) ___asm_opcode_swahb32(x)
#else /* ! CONFIG_CPU_ENDIAN_BE8 */
#define __opcode_to_mem_arm(x) ___opcode_identity32(x)
#define __opcode_to_mem_thumb16(x) ___opcode_identity16(x)
#define ___asm_opcode_to_mem_arm(x) ___asm_opcode_identity32(x)
#define ___asm_opcode_to_mem_thumb16(x) ___asm_opcode_identity16(x)
#ifdef CONFIG_CPU_ENDIAN_BE32
#ifndef __ASSEMBLY__
/*
* On BE32 systems, using 32-bit accesses to store Thumb instructions will not
* work in all cases, due to alignment constraints. For now, a correct
* version is not provided for BE32, but the prototype needs to be there
* to compile patch.c.
*/
extern __u32 __opcode_to_mem_thumb32(__u32);
#endif
__inst_arm_thumb16
#include <linux/stringify.h>
#define __inst_arm(x) ___inst_arm(___asm_opcode_to_mem_arm(x))
#define __inst_thumb32(x) ___inst_thumb32( \
___asm_opcode_to_mem_thumb16(___asm_opcode_thumb32_first(x)), \
___asm_opcode_to_mem_thumb16(___asm_opcode_thumb32_second(x)) \
)
#define __inst_thumb16(x) ___inst_thumb16(___asm_opcode_to_mem_thumb16(x))
#ifdef CONFIG_THUMB2_KERNEL
#define __inst_arm_thumb16(arm_opcode, thumb_opcode) \
__inst_thumb16(thumb_opcode)
#define __inst_arm_thumb32(arm_opcode, thumb_opcode) \
__inst_thumb32(thumb_opcode)
#else
#define __inst_arm_thumb16(arm_opcode, thumb_opcode) __inst_arm(arm_opcode)
#define __inst_arm_thumb32(arm_opcode, thumb_opcode) __inst_arm(arm_opcode)
#endif
___inst_thumb16 转换为字符串
#ifdef __ASSEMBLY__
#define ___inst_arm(x) .long x
#define ___inst_thumb16(x) .short x
#define ___inst_thumb32(first, second) .short first, second
#else
#define ___inst_arm(x) ".long " __stringify(x) "\n\t"
#define ___inst_thumb16(x) ".short " __stringify(x) "\n\t"
#define ___inst_thumb32(first, second) \
".short " __stringify(first) ", " __stringify(second) "\n\t"
#endif
arch/arm/include/asm/timex.h
typedef unsigned long cycles_t;
#define get_cycles() ({ cycles_t c; read_current_timer(&c) ? 0 : c; })
/* 有时间使用时间作为熵值, 否则使用random_get_entropy_fallback() 作为备用方法获取熵值 */
#define random_get_entropy() (((unsigned long)get_cycles()) ?: random_get_entropy_fallback())
arch/arm/include/asm/stackprotector.h
- 栈保护器是一种安全机制,用于检测和防止栈溢出攻击。
栈保护器的工作原理
栈保护器通过在函数的栈帧(stack frame)中插入一个预定义的模式(称为 栈金丝雀(stack canary))来工作。具体步骤如下:
- 栈金丝雀的插入:
- 在函数栈帧的开始位置插入一个特殊的值(栈金丝雀)。
- 这个值通常是一个随机数,用于检测栈的完整性。
- 返回前的验证:
- 在函数返回之前,检查栈金丝雀的值是否被修改。
- 如果栈金丝雀的值与预期值不匹配,说明栈帧可能被溢出或篡改,程序会触发安全机制(如终止程序或记录错误)。
这种机制可以有效防止栈溢出攻击,因为攻击者在覆盖返回地址或其他敏感数据时,必须修改栈金丝雀的值,而这通常是不可预测的。
ARM 架构上的实现
在 ARM 架构上,GCC 期望栈金丝雀由一个全局变量 __stack_chk_guard
定义。以下是一些关键点:
- 全局变量
__stack_chk_guard
:- 栈金丝雀的值存储在全局变量
__stack_chk_guard
中。 - 在函数执行时,栈金丝雀的值会从这个全局变量中加载。
- 栈金丝雀的值存储在全局变量
- 多任务系统的限制:
- 在多处理器(SMP)系统中,所有任务默认共享同一个栈金丝雀值。
- 这可能会降低安全性,因为攻击者可以通过一个任务推测出其他任务的栈金丝雀值。
- GCC 插件的作用:
- 为了解决上述问题,可以启用一个 GCC 插件,将
__stack_chk_guard
的引用替换为每个任务独有的栈金丝雀值。 - 这样,每个任务都会有自己的栈金丝雀值,从而提高安全性。
- 为了解决上述问题,可以启用一个 GCC 插件,将
extern unsigned long __stack_chk_guard;
/*
* 初始化 stackprotector 金丝雀值。
*
* 注意:这只能从从不返回的函数中调用,并且必须始终内联。
*/
static __always_inline void boot_init_stack_canary(void)
{
unsigned long canary = get_random_canary();
current->stack_canary = canary;
#ifndef CONFIG_STACKPROTECTOR_PER_TASK
__stack_chk_guard = current->stack_canary;
#endif
}