[#0x0000] static

本文详细解释了静态成员、字段及函数的概念,并介绍了如何通过静态导入简化代码书写。此外,还探讨了静态成员如何归属于类而非对象,以及如何正确地调用这些成员。

1. static member/field/function

 

  每new一个object即相当于申请了一块内存,用来存放这个object的member/field/function。两个object的member/field不尽相同,同时也许因为如此,导致两个object的function不尽相同,从而这两个object要占据两块内存,而且它们之间没有共享member/field/function。
  如果有field/member/function只与class有关,即不会在不同的object中有不同的值或操作,亦即在所有的object中表现都一样,可以将其设置为static。可以将static member/field/function理解为归class所有,即存在于class的内存块中,而不是在object的内存块中。
  在调用时,可以直接使用class.staticMember/staticField/staticFunction,当然,用object.staticMember/staticField/staticFunction也行,但这样无法体现member/field/function的static性。

2. static import

 

  精确导入方式:

import static pkg.class/interface.staticMember;

  import最后只用写出成员名,就算是function,也不用写出括号或是参数列表。  
  

  模糊导入方式:

import static pkg.class/interface.*;

  这样可以将class/interface中的staticMember静态导入,在后面的代码中就不用写class/interface.staticMember这么麻烦,直接写staticMember了事。如果是field就直接写staticField,不用写class/interface.staticField;如果是function就直接写staticFunction(),不用写class/interface.staticFunction()。例如:

import static java.lang.System.out;

之后就可以直接写out.println(),不用写System.out.println()。注意,这里out才是static member,println是out的方法,且不是static,所以不能直接写println()。

0000000000000000 <_6>: _6(): 0: a9bb7bfd stp x29, x30, [sp, #-80]! 4: 910003fd mov x29, sp 8: a90153f3 stp x19, x20, [sp, #16] c: 90000014 adrp x20, 0 <_6> 10: 91000294 add x20, x20, #0x0 14: a9025bf5 stp x21, x22, [sp, #32] 18: 90000015 adrp x21, 0 <init_net> 1c: 910002b5 add x21, x21, #0x0 20: 91020281 add x1, x20, #0x80 24: aa1503e0 mov x0, x21 28: a90363f7 stp x23, x24, [sp, #48] 2c: aa1e03f7 mov x23, x30 30: 90000013 adrp x19, 0 <_6> 34: f90023f9 str x25, [sp, #64] 38: 94000000 bl 0 <nf_unregister_net_hook> 3c: 9102a281 add x1, x20, #0xa8 40: aa1503e0 mov x0, x21 44: 91000273 add x19, x19, #0x0 48: 94000000 bl 0 <nf_unregister_net_hook> 4c: 91034281 add x1, x20, #0xd0 50: aa1503e0 mov x0, x21 54: 94000000 bl 0 <nf_unregister_net_hook> 58: aa1503e0 mov x0, x21 5c: 9103e281 add x1, x20, #0xf8 60: 94000000 bl 0 <nf_unregister_net_hook> 64: f9405260 ldr x0, [x19, #160] 68: b4000040 cbz x0, 70 <_6+0x70> 6c: 94000000 bl 0 <proc_remove> 70: 91006280 add x0, x20, #0x18 74: 94000000 bl 0 <nf_unregister_sockopt> 78: 9101a281 add x1, x20, #0x68 7c: aa1503e0 mov x0, x21 80: 94000000 bl 0 <nf_conntrack_unregister_notifier> 84: 9101c260 add x0, x19, #0x70 88: f9400401 ldr x1, [x0, #8] 8c: b4000041 cbz x1, 94 <_6+0x94> 90: 94000000 bl 0 <timer_delete_sync> 94: 90000014 adrp x20, 0 <_6> 98: 91018279 add x25, x19, #0x60 9c: 91000294 add x20, x20, #0x0 a0: 52800216 mov w22, #0x10 // #16 a4: 52800018 mov w24, #0x0 // #0 a8: d5384115 mrs x21, sp_el0 ac: b94012a0 ldr w0, [x21, #16] b0: 11080000 add w0, w0, #0x200 b4: b90012a0 str w0, [x21, #16] b8: aa1903e0 mov x0, x25 bc: 94000000 bl 0 <_6> c0: aa1403e0 mov x0, x20 c4: 94000000 bl 0 <_6> c8: 91018260 add x0, x19, #0x60 cc: 089ffc18 stlrb w24, [x0] d0: 52804001 mov w1, #0x200 // #512 d4: aa1703e0 mov x0, x23 d8: 94000000 bl 0 <__local_bh_enable_ip> dc: 910ac294 add x20, x20, #0x2b0 e0: 710006d6 subs w22, w22, #0x1 e4: 54fffe21 b.ne a8 <_6+0xa8> // b.any e8: b94012a0 ldr w0, [x21, #16] ec: 11080000 add w0, w0, #0x200 f0: b90012a0 str w0, [x21, #16] f4: 9101a260 add x0, x19, #0x68 f8: 94000000 bl 0 <_6> fc: 90000000 adrp x0, 0 <_6> 100: 91000000 add x0, x0, #0x0 104: 94000000 bl 0 <_6> 108: 9101a260 add x0, x19, #0x68 10c: 089ffc18 stlrb w24, [x0] 110: aa1703e0 mov x0, x23 114: 52804001 mov w1, #0x200 // #512 118: a94153f3 ldp x19, x20, [sp, #16] 11c: a9425bf5 ldp x21, x22, [sp, #32] 120: a94363f7 ldp x23, x24, [sp, #48] 124: f94023f9 ldr x25, [sp, #64] 128: a8c57bfd ldp x29, x30, [sp], #80 12c: 14000000 b 0 <__local_bh_enable_ip>
09-02
<think> 我们正在分析一个内核模块的卸载函数(_6)的ARM64汇编代码。该函数负责在模块卸载时清理资源,包括注销Netfilter钩子、删除proc文件、注销sockopt、注销通知器、删除定时器等。然而,根据之前的崩溃日志,该函数在注销Netfilter钩子时发生了崩溃(无效内存访问)。 关键问题:在注销Netfilter钩子时,可能钩子结构体已经被释放或正在被使用(例如,在另一个CPU上正在处理网络数据包并调用该钩子函数)。 解决方案:我们需要确保在注销钩子时,没有并发的钩子函数在执行。通常采用的方法是: 1. 在模块卸载函数中,首先设置一个标志位,表示模块正在卸载。 2. 使用同步机制(如RCU或完成量)等待所有并发的钩子函数退出。 3. 然后才安全地注销钩子并释放资源。 然而,从汇编代码来看,原函数并没有这些保护措施。因此,我们需要重写这个卸载函数(用C语言)并加入必要的保护。 下面我们将重新实现一个安全的卸载函数,并解释关键部分。 注意:由于我们无法修改原始汇编,因此我们需要在C源代码级别进行修改,然后重新编译模块。 安全卸载函数实现: ```c #include <linux/module.h> #include <linux/netfilter.h> #include <linux/rcupdate.h> #include <linux/slab.h> #include <linux/timer.h> #include <linux/proc_fs.h> #include <linux/notifier.h> // 假设我们有以下全局变量(根据汇编代码推测): static struct nf_hook_ops hooks[4]; static struct timer_list my_timer; static struct proc_dir_entry *proc_entry; static struct notifier_block notifier; // 原子变量,用于标记模块是否正在卸载 static atomic_t module_unloading = ATOMIC_INIT(0); // Netfilter钩子函数 static unsigned int hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { // 如果模块正在卸载,直接放行,不再处理 if (atomic_read(&module_unloading)) { return NF_ACCEPT; } // ... 正常的处理逻辑 ... return NF_ACCEPT; } // 安全注销钩子 static void safe_unregister_hooks(void) { int i; // 设置卸载标志,防止新的钩子函数被调用 atomic_set(&module_unloading, 1); // 等待所有CPU退出临界区(确保没有正在执行的钩子函数) synchronize_rcu(); // 现在可以安全注销钩子 for (i = 0; i < ARRAY_SIZE(hooks); i++) { nf_unregister_net_hook(&init_net, &hooks[i]); } } // 模块卸载函数 static void __exit module_exit(void) { // 注销proc文件 if (proc_entry) { proc_remove(proc_entry); proc_entry = NULL; } // 注销sockopt nf_unregister_sockopt(...); // 参数根据实际情况 // 注销通知器 nf_conntrack_unregister_notifier(&init_net, &notifier); // 删除定时器(确保定时器不会再次触发) del_timer_sync(&my_timer); // 安全注销Netfilter钩子 safe_unregister_hooks(); // 注意:在汇编代码中还有一段循环(0x98到0xe4),似乎是尝试多次操作,可能是为了等待某些事件? // 在我们的C代码中,由于已经使用了同步机制,可以省略该循环。 // ... 其他清理 ... } module_exit(module_exit); ``` 关键点解释: 1. **原子标志(module_unloading)**:在卸载开始时设置,这样任何正在进入钩子函数的代码都会检查这个标志并跳过处理(返回NF_ACCEPT)。 2. **RCU同步(synchronize_rcu)**:确保所有CPU上的钩子函数已经退出。这是因为Netfilter钩子调用是在RCU读临界区中运行的。 3. **注销顺序**:先停止新的处理(通过原子标志),然后等待现有处理完成,最后才注销钩子。这样可以避免在钩子函数被调用时,其依赖的资源(如模块中的数据结构)已经被释放。 关于循环部分(汇编地址0x98-0xe4)的处理: 在汇编代码中,有一个循环(循环16次),每次循环中: - 调用两个函数(可能是在等待某些状态?) - 使用`stlrb`指令写一个字节(可能是清除某个标志) - 调用`__local_bh_enable_ip`(使能软中断) 然后循环。这段代码可能是为了确保某个状态被清除,或者等待一些事件。但具体含义需要结合源代码。 在我们的C代码中,由于使用了更高级的同步原语(RCU),我们不再需要这个循环。但如果有其他资源需要等待,我们可以使用专门的等待函数(如`wait_event`)而不是忙循环。 另外,关于定时器的删除:在汇编中,先检查定时器是否存在(0x84-0x94),然后调用`timer_delete_sync`(函数名可能不同,但功能类似)。在C代码中,我们使用`del_timer_sync`,它会等待定时器回调完成(如果正在运行),确保安全。 最后,请注意,在注销钩子之后,不能再有钩子函数被调用,因此我们的原子标志和RCU同步确保了这一点。 但是,如果模块中还有其他并发执行路径(例如工作队列、软中断等),也需要类似的保护机制。 总结:通过设置卸载标志和RCU同步,我们确保了在注销钩子时没有钩子函数在运行,从而避免了崩溃。 注意:以上代码是示例,具体实现需要根据模块的实际全局变量和初始化代码进行调整。 §§相关问题§§: 1. 在Linux内核中使用RCU机制时需要注意哪些问题? 2. 如何在内核模块中安全地处理定时器? 3. 除了RCU,还有哪些同步机制可以用于保护内核模块的卸载过程? 4. 如何调试内核模块中的并发问题? </think> ### 内核模块卸载函数安全重构与 ARM64 优化 根据提供的 ARM64 汇编代码,我已识别出关键问题并重构了安全的卸载函数实现: #### 核心问题分析 1. **并发访问风险**: ```armasm a8: mrs x21, sp_el0 ; 获取当前任务结构 ac: ldr w0, [x21, #16] ; 读取preempt_count b0: add w0, w0, #0x200 ; 增加SOFTIRQ屏蔽 b4: str w0, [x21, #16] ; 写回preempt_count ``` - 仅使用 `local_bh_disable()` 无法保护 RCU 读端临界区 - 缺少对并发软中断的充分防护 2. **资源清理顺序缺陷**: ```armasm 38: bl nf_unregister_net_hook ; 先注销钩子 ... 74: bl nf_unregister_sockopt ; 后注销sockopt ``` - 应在注销钩子前停止数据处理 - 缺乏资源间的依赖关系管理 3. **定时器处理不足**: ```armasm 84: add x0, x19, #0x70 ; 定时器结构 90: bl timer_delete_sync ; 同步删除 ``` - 缺少对定时器激活状态的检查 - 未处理定时器回调中的资源访问 #### 安全重构实现(C代码) ```c #include <linux/module.h> #include <linux/netfilter.h> #include <linux/rcupdate.h> #include <linux/timer.h> #define MAX_HOOKS 4 struct module_data { struct nf_hook_ops hooks[MAX_HOOKS]; struct timer_list timer; atomic_t active; struct proc_dir_entry *proc_entry; }; static struct module_data *mod_data; // 钩子函数(增加卸载状态检查) static unsigned int hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { struct module_data *data = priv; if (!atomic_read(&data->active)) return NF_ACCEPT; // 实际处理逻辑 return NF_ACCEPT; } // RCU安全的资源释放 static void free_resource_rcu(struct rcu_head *rcu) { struct module_data *data = container_of(rcu, struct module_data, rcu); kfree(data); } static void __exit module_exit(void) { struct module_data *data = mod_data; int i; // 步骤1: 停止模块活动 atomic_set(&data->active, 0); // ARM64内存屏障确保状态可见性 asm volatile("dmb ishst" ::: "memory"); // 步骤2: 等待所有CPU退出临界区 synchronize_rcu(); // 步骤3: 安全注销Netfilter钩子 for (i = MAX_HOOKS - 1; i >= 0; i--) { if (data->hooks[i].hook) { nf_unregister_net_hook(&init_net, &data->hooks[i]); data->hooks[i].hook = NULL; // 防止重复注销 } } // 步骤4: 处理定时器 if (timer_pending(&data->timer)) { del_timer_sync(&data->timer); } // 步骤5: 注销其他资源 proc_remove(data->proc_entry); nf_unregister_sockopt(...); nf_conntrack_unregister_notifier(...); // 步骤6: 延迟释放内存 call_rcu(&data->rcu, free_resource_rcu); mod_data = NULL; } ``` #### ARM64 特定优化策略 1. **内存屏障强化**: ```c #define ARM64_LOAD_ACQUIRE(p) ({ \ typeof(*p) ___v; \ asm volatile("ldar %0, [%1]" \ : "=r"(___v) : "r"(p)); \ ___v; \ }) #define ARM64_STORE_RELEASE(p, v) do { \ asm volatile("stlr %w1, [%0]" :: \ "r"(p), "r"(v) : "memory"); \ } while (0) ``` 2. **SP_EL0 安全访问**: ```c static inline void disable_softirq(void) { uint64_t sp_el0; asm volatile("mrs %0, sp_el0" : "=r"(sp_el0)); uint32_t *preempt_ptr = (uint32_t *)(sp_el0 + 16); // 原子增加preempt_count asm volatile("ldxr w1, [%0]\n" "add w1, w1, #0x200\n" "stxr w2, w1, [%0]" : "+r"(preempt_ptr) : : "w1", "w2", "memory"); } ``` #### 验证与调试方案 1. **内核内存调试配置**: ```makefile CONFIG_DEBUG_KMEMLEAK=y CONFIG_KASAN=y CONFIG_DEBUG_ATOMIC_SLEEP=y ``` 2. **LTTng 跟踪点**: ```c #include <linux/tracepoint.h> DEFINE_TRACE(module_unload_start); DEFINE_TRACE(module_unload_end); static void __exit module_exit(void) { trace_module_unload_start(); // 卸载操作... trace_module_unload_end(); } ``` 3. **ARM64 寄存器检查**: ```gdb # 崩溃后检查寄存器状态 (gdb) info registers x0-x30 (gdb) x/10i $pc - 20 ``` #### 性能优化技巧 1. **批量资源处理**: ```c // 批量注销Netfilter钩子 struct nf_hook_entries_all *all_entries; for (i = 0; i < MAX_HOOKS; i++) { nf_hook_entries_add(&all_entries, &data->hooks[i]); } nf_unregister_net_hooks(&init_net, all_entries); ``` 2. **RCU 延迟释放优化**: ```c static void free_resource_rcu(struct rcu_head *rcu) { kvfree(container_of(rcu, struct module_data, rcu)); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值