linux空指针异常能捕获到吗,一次kernel panic分析--空指针in handle_IRQ_event
一、故障現象
內核panic,打印如下:
點擊(此處)折疊或打開
Unable to handle kernel NULL pointer dereference at 0000000000000039 RIP:
[] handle_IRQ_event+0x44/0xa6
PGD 61df63067 PUD 61ea5a067 PMD 0
Oops: 0000 [1] SMP
last sysfs file: /devices/system/cpu/cpu0/cpufreq/scaling_max_freq
CPU 8
Modules linked in: ossmod(U) tipc(U) bsp_smbus_ctrl(U) bonding autofs4 hidp rfcomm l2cap bluetooth lockd sunrpc ipv6 xfrm_nalgo crypto_api cpufreq_ondemand acpi_cpufreq freq_table dm_mirror d
m_multipath scsi_dh video hwmon backlight sbs i2c_ec button battery asus_acpi acpi_memhotplug ac lp floppy esh(U) snd_hda_intel snd_seq_dummy snd_seq_oss snd_seq_midi_event snd_seq snd_seq_de
vice sg snd_pcm_oss snd_mixer_oss snd_pcm snd_timer snd_page_alloc snd_hwdep snd igb i2c_i801 i2c_core soundcore 8021q serio_raw parport_pc parport e1000(U) pcspkr dm_raid45 dm_message dm_reg
ion_hash dm_log dm_mod dm_mem_cache ata_piix libata shpchp mptsas mptscsih mptbase scsi_transport_sas sd_mod scsi_mod ext3 jbd uhci_hcd ohci_hcd ehci_hcd
Pid: 0, comm: swapper Tainted: G 2.6.18-164.el5 #1
RIP: 0010:[] [] handle_IRQ_event+0x44/0xa6
RSP: 0018:ffff81032fca7f28 EFLAGS: 00010202
RAX: 0000000000000001 RBX: 0000000000000000 RCX: 00000000000000b2
RDX: ffff810009028520 RSI: ffff81032fc98000 RDI: ffff81032b1c7180
RBP: 0000000000000001 R08: ffff81032fca0000 R09: ffffffff800967c5
R10: ffff81032fca7f30 R11: ffff81032fca1ee8 R12: 00000000000000b2
R13: 0000000000000001 R14: ffff81032fca1df8 R15: ffff81032fca1df8
FS: 0000000000000000(0000) GS:ffff81010b373b40(0000) knlGS:0000000000000000
CS: 0010 DS: 0018 ES: 0018 CR0: 000000008005003b
CR2: 0000000000000039 CR3: 000000061f37c000 CR4: 00000000000006e0
Process swapper (pid: 0, threadinfo ffff81032fca0000, task ffff81033ab0f7e0)
Stack: ffffffff803e3580 000000000000b200 00000000000000b2 ffff81032acdee40
ffffffff803e35bc ffffffff800b9808 ffffffff8001235a 00000000000000b2
ffff81032fca1df8 00000000009c1418 ffff81062dc4d000 0000000000000800
Call Trace:
[] __do_IRQ+0xa4/0x103
[] __do_softirq+0x89/0x133
[] do_IRQ+0xe7/0xf5
[] ret_from_intr+0x0/0xa
[] acpi_processor_idle+0x274/0x463
[] acpi_processor_idle+0x26a/0x463
[] acpi_processor_idle+0x0/0x463
[] acpi_processor_idle+0x0/0x463
[] cpu_idle+0x95/0xb8
[] start_secondary+0x45a/0x469
從打印看,是在handle_IRQ_event函數中遇到了空指針
二、Vmcore分析
1、反匯編handle_IRQ_event,獲取RIP所在的指令
點擊(此處)折疊或打開
crash> dis -l handle_IRQ_event
/usr/src/debug/kernel-2.6.18/linux-2.6.18.x86_64/kernel/irq/handle.c: 134
0xffffffff80010b30 : push %r14
include/trace/irq.h: 7
0xffffffff80010b32 : cmpl $0x0,3859215(%rip) # 0xffffffff803bee48 <__tracepoint_irq_entry.10785>
/usr/src/debug/kernel-2.6.18/linux-2.6.18.x86_64/kernel/irq/handle.c: 134
0xffffffff80010b39 : mov %rsi,%r14
0xffffffff80010b3c : push %r13
0xffffffff80010b3e : push %r12
0xffffffff80010b40 : mov %edi,%r12d
0xffffffff80010b43 : push %rbp
0xffffffff80010b44 : mov %rdx,%rbp
0xffffffff80010b47 : push %rbx
include/trace/irq.h: 7
0xffffffff80010b48 : je 0xffffffff80010b68
0xffffffff80010b4a : mov 3859199(%rip),%rbx # 0xffffffff803bee50 <__tracepoint_irq_entry.10785>
0xffffffff80010b51 : test %rbx,%rbx
0xffffffff80010b54 : je 0xffffffff80010b68
0xffffffff80010b56 : mov %r14,%rsi
0xffffffff80010b59 : mov %r12d,%edi
0xffffffff80010b5c : callq *(%rbx)
0xffffffff80010b5e : add $0x8,%rbx
0xffffffff80010b62 : cmpq $0x0,(%rbx)
0xffffffff80010b66 : jmp 0xffffffff80010b54
/usr/src/debug/kernel-2.6.18/linux-2.6.18.x86_64/kernel/irq/handle.c: 142
0xffffffff80010b68 : testb $0x20,0x8(%rbp)
0xffffffff80010b6c : jne 0xffffffff80010b6f
include/asm/irqflags.h: 80
0xffffffff80010b6e : sti
0xffffffff80010b6f : xor %r13d,%r13d
0xffffffff80010b72 : xor %ebx,%ebx
/usr/src/debug/kernel-2.6.18/linux-2.6.18.x86_64/kernel/irq/handle.c: 146
0xffffffff80010b74 : mov 0x38(%rbp),%rsi
0xffffffff80010b78 : mov %r14,%rdx
從之前的堆棧可以看出RIP為:handle_IRQ_event+0x44/0xa6
0x44折算成十進制為68,所以需要關注handle_IRQ_event+68行:
0xffffffff80010b74 : ? ? ? mov ? ?0x38(%rbp),%rsi
從之前的匯編分析,rsi的值應該是上級函數傳入,不太可能在其中出現空指針,問題應該出在0x38(%rbp)上,也就是rbp上。
2、結合handle_IRQ_event源代碼分析
handle_IRQ_event代碼如下:
點擊(此處)折疊或打開
132 irqreturn_t handle_IRQ_event(unsigned int irq, struct pt_regs *regs,
133 struct irqaction *action)
134 {
135 irqreturn_t ret, retval = IRQ_NONE;
136 unsigned int status = 0;
137
138 trace_irq_entry(irq, regs);
139
140 handle_dynamic_tick(action);
141
142 if (!(action->flags & IRQF_DISABLED))
143 local_irq_enable_in_hardirq();
144
145 do {
146 ret = action->handler(irq, action->dev_id, regs);
147 if (ret == IRQ_HANDLED)
148 status |= action->flags;
149 retval |= ret;
150 action = action->next;
151 } while (action);
152
153 if (status & IRQF_SAMPLE_RANDOM)
154 add_interrupt_randomness(irq);
155 local_irq_disable();
156
157 trace_irq_exit(irq, retval);
158
159 return retval;
160 }
之前反匯編中有代碼行提示:
/usr/src/debug/kernel-2.6.18/linux-2.6.18.x86_64/kernel/irq/handle.c: 146
可以看出問題出在146行:
146 ? ? ? ? ret = action->handler(irq, action->dev_id, regs);
可以看出0x38(%rbp)應該對應action->dev_id(因為通過偏移訪問,結構體訪問基本如此),如此可見rbp中存放的即action變量的的值,
分析源代碼,action是通過入參傳入的,但是在146-151行的循環中,150行,對action重新賦值了。
150 ? ? ? ? action = action->next;
如此,可以推測action->next出問題了,也就是通過入參傳入的action的next成員出問題了,of course,也可能是一開始的傳入的如此就有問題了。
繼續分析action入參的值。
3、入參(action)分析
action被放入了rbp寄存器,看如下匯編行:
0xffffffff80010b44 : ? ? ? mov ? ?%rdx,%rbp
可以看出,action入參是通過rdx寄存器傳入的,并不是我們通常認為的通過堆棧。需要進一步分析上級行數中的rdx寄存器的值。
4、上級函數__do_IRQ分析
點擊(此處)折疊或打開
crash> bt
PID: 0 TASK: ffff81033ab0f7e0 CPU: 8 COMMAND: "swapper"
#0 [ffff81032fca7c80] crash_kexec at ffffffff800ac5b9
#1 [ffff81032fca7d40] __die at ffffffff80065127
#2 [ffff81032fca7d80] do_page_fault at ffffffff80066da7
#3 [ffff81032fca7e70] error_exit at ffffffff8005dde9
[exception RIP: handle_IRQ_event+68]
RIP: ffffffff80010b74 RSP: ffff81032fca7f28 RFLAGS: 00010202
RAX: 0000000000000001 RBX: 0000000000000000 RCX: 00000000000000b2
RDX: ffff810009028520 RSI: ffff81032fc98000 RDI: ffff81032b1c7180
RBP: 0000000000000001 R8: ffff81032fca0000 R9: ffffffff800967c5
R10: ffff81032fca7f30 R11: ffff81032fca1ee8 R12: 00000000000000b2
R13: 0000000000000001 R14: ffff81032fca1df8 R15: ffff81032fca1df8
ORIG_RAX: ffffffffffffffff CS: 0010 SS: 0018
#4 [ffff81032fca7f20] handle_IRQ_event at ffffffff80010b81
#5 [ffff81032fca7f50] __do_IRQ at ffffffff800b9808
#6 [ffff81032fca7f90] do_IRQ at ffffffff8006c997
--- ---
可見,上級函數為__do_IRQ。
對照__do_IRQ源代碼,可以看出調用handle_IRQ_event的地方為240行,所以關注匯編中的相關行附件代碼即可
點擊(此處)折疊或打開
174 fastcall unsigned int __do_IRQ(unsigned int irq, struct pt_regs *regs)
175 {
176 struct irq_desc *desc = irq_desc + irq;
177 struct irqaction *action;
178 unsigned int status;
...
235 for (;;) {
236 irqreturn_t action_ret;
237
238 spin_unlock(&desc->lock);
239
240 action_ret=handle_IRQ_event(irq,regs,action);
241 if (!noirqdebug)
242 note_interrupt(irq, desc, action_ret, regs);
243
244 spin_lock(&desc->lock);
245 if (likely(!(desc->status & IRQ_PENDING)))
246 break;
247 desc->status &= ~IRQ_PENDING;
248 }
...
260 }
繼續反匯編__do_IRQ
點擊(此處)折疊或打開
/usr/src/debug/kernel-2.6.18/linux-2.6.18.x86_64/kernel/irq/handle.c: 222
0xffffffff800b97f1 <__do_irq>: je 0xffffffff800b9845 <__do_irq>
include/asm/spinlock.h: 62
0xffffffff800b97f3 <__do_irq>: movl $0x1,0x3c(%rbx)
/usr/src/debug/kernel-2.6.18/linux-2.6.18.x86_64/kernel/irq/handle.c: 240
0xffffffff800b97fa <__do_irq>: mov %r13,%rdx
0xffffffff800b97fd <__do_irq>: mov %r15,%rsi
0xffffffff800b9800 <__do_irq>: mov %r12d,%edi
0xffffffff800b9803 <__do_irq>: callq 0xffffffff80010b30
/usr/src/debug/kernel-2.6.18/linux-2.6.18.x86_64/kernel/irq/handle.c: 241
0xffffffff800b9808 <__do_irq>: cmpl $0x0,3358161(%rip) # 0xffffffff803ed5e0
0xffffffff800b980f <__do_irq>: jne 0xffffffff800b9821 <__do_irq>
/usr/src/debug/kernel-2.6.18/linux-2.6.18.x86_64/kernel/irq/handle.c: 242
0xffffffff800b9811 <__do_irq>: mov %r15,%rcx
0xffffffff800b9814 <__do_irq>: mov %eax,%edx
0xffffffff800b9816 <__do_irq>: mov %rbx,%rsi
0xffffffff800b9819 <__do_irq>: mov %r12d,%edi
可以看出edx的值來源于r13,再回看handle_IRQ_event函數的反匯編,在該函數的開始對r13寄存器進行了壓棧
點擊(此處)折疊或打開
0xffffffff80010b30 : push %r14
include/trace/irq.h: 7
0xffffffff80010b32 : cmpl $0x0,3859215(%rip) # 0xffffffff803bee48 <__tracepoint_irq_entry.10785>
/usr/src/debug/kernel-2.6.18/linux-2.6.18.x86_64/kernel/irq/handle.c: 134
0xffffffff80010b39 : mov %rsi,%r14
0xffffffff80010b3c : push %r13
所以,可以從handle_IRQ_event函數的棧幀中找到action的值。r13應該位于該棧幀中的第3個值,因為第一個必然為函數的返回地址,第二個值為r14(在r13之前壓入)。
看看handle_IRQ_event函數的棧
點擊(此處)折疊或打開
crash> bt -f
PID: 0 TASK: ffff81033ab0f7e0 CPU: 8 COMMAND: "swapper"
#0 [ffff81032fca7c80] crash_kexec at ffffffff800ac5b9
ffff81032fca7c88: ffff81032fca1df8 ffff81032fca1df8
ffff81032fca7c98: 0000000000000001 00000000000000b2
ffff81032fca7ca8: 0000000000000001 0000000000000000
ffff81032fca7cb8: ffff81032fca1ee8 ffff81032fca7f30
ffff81032fca7cc8: ffffffff800967c5 ffff81032fca0000
ffff81032fca7cd8: 0000000000000001 00000000000000b2
ffff81032fca7ce8: ffff810009028520 ffff81032fc98000
ffff81032fca7cf8: ffff81032b1c7180 ffffffffffffffff
ffff81032fca7d08: ffffffff80010b74 0000000000000010
ffff81032fca7d18: 0000000000010202 ffff81032fca7f28
ffff81032fca7d28: 0000000000000018 ffff81032fca7e78
ffff81032fca7d38: ffffffff802a4d0d ffffffff80065127
.......
#4 [ffff81032fca7f20] handle_IRQ_event at ffffffff80010b81
ffff81032fca7f28: ffffffff803e3580 000000000000b200
ffff81032fca7f38: 00000000000000b2 ffff81032acdee40
ffff81032fca7f48: ffffffff803e35bc ffffffff800b9808
#5 [ffff81032fca7f50] __do_IRQ at ffffffff800b9808
ffff81032fca7f58: ffffffff8001235a 00000000000000b2
ffff81032fca7f68: ffff81032fca1df8 00000000009c1418
ffff81032fca7f78: ffff81062dc4d000 0000000000000800
ffff81032fca7f88: ffffffff803ea360 ffffffff8006c997
可以看出action的值應該為ffff81032acdee40,看看其中的內容,順便驗證下。
action為irqaction類型的結構體:
點擊(此處)折疊或打開
crash> struct irqaction ffff81032acdee40
struct irqaction {
handler = 0xffffffff8827628b,
flags = 0,
mask = {
bits = {0, 0, 0, 0}
},
name = 0xffff81032b1c7000 "eth0-rx-3",
dev_id = 0xffff81032bef56b8,
next = 0x1,
irq = 178,
dir = 0xffff810328db2180
}
可以看出該irqaction結構體對應于eth0網卡的接收中斷,中斷號為178,注意其中的next,為0x1,這就是問題所在。
從log信息可以看出空指針具體的值為:0000000000000039 ,正好=0x1+38,所以說明分析是正確的。
分析代碼邏輯和irqaction的定義:
點擊(此處)折疊或打開
crash> struct irqaction
struct irqaction {
irqreturn_t (*handler)(int, void *, struct pt_regs *);
long unsigned int flags;
cpumask_t mask;
const char *name;
void *dev_id;
struct irqaction *next;
int irq;
struct proc_dir_entry *dir;
}
SIZE: 88
這里的next應該在有中斷共享時使用,即同一個irq號對應多個ISR,如果有共享,那么這里的next指向共享該irq的下一個action.
從/proc/interrupts信息,可以看出178號中斷并沒有共享的情況,所以,這里的next應該為null,為0x1顯然有問題了。
從irqaction中的值分析,next前后的數據都是正常的,只有next從0突變成1了,基本可以排除由于內存越界導致內存寫壞的情況。
分析設置next指針的所有代碼,只有設置地址和刪除的代碼,并沒有發現有直接設置為1的地方。
那為什么如此呢?要繼續分析就很困難了。
5、分析irq_desc
沒有其它分析思路了,嘗試分析action所在的結構體irq_desc,看看其中的內容,該結構體的值需要從上級函數__do_IRQ分析,見其代碼176行:
176 ? ? struct irq_desc *desc = irq_desc + irq;
其中irq_desc為全局變量(數組),存放所有的irq_desc,按編號排列。
對照其匯編:
/usr/src/debug/kernel-2.6.18/linux-2.6.18.x86_64/kernel/irq/handle.c: 176
0xffffffff800b9784 : ? ? ? lea ? ?0xffffffff803d8380(%rbp),%rbx
lea指令的意思是將0xffffffff803d8380(%rbp)地址(即rbp+0xffffffff803d8380),賦值給rbx,這里顯然0xffffffff803d8380就是irq_desc全局變量的地址。
確認一下:
點擊(此處)折疊或打開
crash> struct irq_desc 0xffffffff803d8380
struct irq_desc {
handle_irq = 0,
chip = 0xffffffff803ec7e0,
handler_data = 0x0,
chip_data = 0x0,
action = 0xffffffff803018a0,
status = 0,
depth = 0,
wake_depth = 0,
irq_count = 22386,
irqs_unhandled = 0,
lock = {
raw_lock = {
slock = 1
}
},
affinity = {
bits = {1, 0, 0, 0}
},
cpu = 0,
pending_mask = {
bits = {0, 0, 0, 0}
},
move_irq = 0,
dir = 0xffff81062ddbd180
}
crash> struct irqaction 0xffffffff803018a0
struct irqaction {
handler = 0xffffffff8006e9da ,
flags = 32,
mask = {
bits = {0, 0, 0, 0}
},
name = 0xffffffff802b3dfa "timer",
dev_id = 0x0,
next = 0x0,
irq = 0,
dir = 0x0
}
可以看出位于該全局變量的第一個元素為時鐘中斷對應的描述符,說明分析正確。但我們這里需要分析的是178號中斷(irq好前面已經分析得到了),應該怎么查找呢。
繼續看匯編代碼,可以看出178號中斷對應的描述符在irq_desc數組中的偏移量存放在rbp中了,查看rbp后續的匯編代碼,并沒有對進行壓棧處理,且當前函數不是最頂級的堆棧函數,所以無法直接從vmcore中獲取rbp的值,只能想其它辦法了。
繼續看匯編代碼:
/usr/src/debug/kernel-2.6.18/linux-2.6.18.x86_64/kernel/irq/handle.c: 176
0xffffffff800b977c : ? ? ? mov ? ?%rcx,%rbp
0xffffffff800b977f : ? ? ? shl ? ?$0x8,%rbp
這里的rcx應該存放的是irq的編號,也就是178,將rcx左移8位后即得到rbp(shl ? ?$0x8,%rbp),即178*256=rbp=0xb200
0xffffffff803d8380+0xb200=0xffffffff803e3580,該地址即為178號中斷對應的描述符的地址了:
點擊(此處)折疊或打開
crash> struct irq_desc FFFFFFFF803E3580
struct irq_desc {
handle_irq = 0,
chip = 0xffffffff80323c40,
handler_data = 0x0,
chip_data = 0x0,
action = 0xffff81032acdee40,
status = 65536,
depth = 0,
wake_depth = 0,
irq_count = 78785,
irqs_unhandled = 0,
lock = {
raw_lock = {
slock = 1
}
},
affinity = {
bits = {256, 0, 0, 0}
},
cpu = 0,
pending_mask = {
bits = {32768, 0, 0, 0}
},
move_irq = 1,
dir = 0xffff810328db2380
}
crash> struct irqaction 0xffff81032acdee40
struct irqaction {
handler = 0xffffffff8827628b,
flags = 0,
mask = {
bits = {0, 0, 0, 0}
},
name = 0xffff81032b1c7000 "eth0-rx-3",
dev_id = 0xffff81032bef56b8,
next = 0x1,
irq = 178,
dir = 0xffff810328db2180
exactly,正是我們需要的,于是找到了描述符,看看其內容,其中
irq_count = 78785,
irq_count用于監測中斷是否掛起的計數,不為0表明之前已處理過該中斷,說明該中斷之前正在被正常處理,突然直接出了問題。
至此,確實無法繼續分析了,從故障現象看,內核問題可能性不大,更像是是內存的1bit跳變導致的問題,但是通常的服務器環境中,內存(控制器)硬件應該有ECC校驗功能,可以自動糾正1bit跳變的情況才對。
核對了一下出現問題的硬件環境,dmicode命令中可以看到內存硬件確實有ECC校驗功能,但是發現CPU cache硬件中沒有包含ECC校驗功能:
Error Correction Type: None
看了看其它類型的服務器,一些服務器有ECC校驗功能:
Error Correction Type: Single-bit ECC
因此,沒有其它疑點,只能懷疑是內存(或者說是cache)的1bit跳變硬件故障導致。
但沒有進一步證據了,有其它想法歡迎討論。
總結
以上是生活随笔為你收集整理的linux空指针异常能捕获到吗,一次kernel panic分析--空指针in handle_IRQ_event的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 优盘文件打不开怎么修复 优盘文件打不开的
- 下一篇: linux ntp 'ntp_reque