您好,登錄后才能下訂單哦!
在《遠(yuǎn)程觸發(fā)SYSRQ獲取最新的dmesg信息-一個(gè)幾乎沒有什么用的方案》中,我認(rèn)為遠(yuǎn)程觸發(fā)SYSRQ并沒有什么實(shí)際的用處,系統(tǒng)沒有掛起時(shí),用SSH等標(biāo)準(zhǔn)方式會(huì)好很多,系統(tǒng)掛起時(shí),遠(yuǎn)程觸發(fā)在多數(shù)情況下均無法得到響應(yīng)。那么有什么方法在系統(tǒng)panic的時(shí)候通知外部呢?
當(dāng)然采用crash kexec
kernel的方式會(huì)是一個(gè)好方法,但是那畢竟是為了debug而生的,有的時(shí)候只是想知道dmesg的最后幾行就好,要知道此時(shí)系統(tǒng)已經(jīng)完全沒有機(jī)會(huì)記
錄日志了,用戶態(tài)的任何進(jìn)程均已經(jīng)全軍覆沒。但是別忘了還有一個(gè)口子可以將最后的信息送出去,那就是網(wǎng)絡(luò)。為了使得信息更加可能被接收,我覺得采用廣播會(huì)
比較好。具體的方式我在《遠(yuǎn)程觸發(fā)SYSRQ獲取最新的dmesg信息-一個(gè)幾乎沒有什么用的方案》的最后已經(jīng)給出了。本文主要給出一個(gè)切實(shí)可以運(yùn)行的代碼。這個(gè)代碼和平時(shí)的代碼有所不同,那就是這個(gè)代碼是在系統(tǒng)已經(jīng)panic了以后被觸發(fā)的,此時(shí)的系統(tǒng)很可能全然混亂,因此此時(shí)最好的做法就是:
1.減少內(nèi)存操作;
2.不處理異常流;
3.盡可能簡(jiǎn)化,不再計(jì)算校驗(yàn)值;
4.避免ARP交互,直接廣播。
代碼的邏輯如下:
1.加載模塊:預(yù)先分配skb和get device,為了不在panic以后處理內(nèi)存;注冊(cè)通知鏈。
2.panic之后:調(diào)用通知鏈的通知操作,用最簡(jiǎn)化的方式廣播沒有校驗(yàn)碼的數(shù)據(jù)包。
代碼如下:
#include <linux/module.h> #include <linux/skbuff.h> #include <net/ip.h> struct sk_buff * skb = NULL; struct net_device * dev = NULL; u8 ethhdr [ETH_HLEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0c, 0x29, 0xdd, 0xaa, 0xfd, 0x00, 0x00 }; u8 iphdr [20] = { 0x45, 0x00, 0x00, 0x00, //total length 0x00, 0x00, //ID 0x40, 0x00, //Don't fragment 0x40, //TTL 0xff, // UDP? 0x00, 0x00, //checksum 0x02, 0x02, 0x02, 0x02, 0xff, 0xff, 0xff, 0xff }; u8 data[1024] = {0}; void do_nothing (struct sk_buff *skb) { /* 不再進(jìn)行任何內(nèi)存操作 */ //dev_put(skb->dev); } static int send_last_msg(struct notifier_block *self, unsigned long event, void *unused) { int ret; struct ethhdr *eth; struct iphdr *ip; char *p; /* 獲取內(nèi)核緩沖區(qū)中的數(shù)據(jù) */ kernel_log_buffer(data, sizeof(data)); skb->dev = dev; skb->pkt_type = PACKET_OTHERHOST; skb->protocol = htons(ETH_P_IP); skb->ip_summed = CHECKSUM_NONE; skb->destructor = do_nothing; skb->priority = 0; /* 保留skb區(qū)域 */ skb_reserve (skb, 2 + ETH_HLEN + sizeof(struct iphdr) + sizeof(data)); /* 構(gòu)造數(shù)據(jù)區(qū)(使用UDP會(huì)比較好,但是懶的封裝了) */ p = skb_push(skb, sizeof(data)); memcpy(p, &data[0], sizeof(data)); skb_reset_transport_header(skb); /* 構(gòu)造IP頭 */ p = skb_push(skb, sizeof(struct iphdr)); memcpy(p, &iphdr, sizeof(struct iphdr)); ip = (struct iphdr*)p; ip->tot_len = htons(sizeof(data) + sizeof(struct iphdr)); skb_reset_network_header(skb); /* 構(gòu)造以太頭 */ p = skb_push(skb, sizeof(struct ethhdr)); eth = (struct ethhdr*)p; eth->h_proto = htons(ETH_P_IP); memcpy(p, hdr, sizeof(struct ethhdr)); skb_reset_mac_header(skb); /* 發(fā)射 */ ret = dev_queue_xmit(skb); if (ret < 0) { /* 由于panic了,不再處理內(nèi)存,不處理異常流 */ //kfree_skb (skb); //dev_put(dev); goto out; } out: return ret; } static struct notifier_block on_panic_send = { .notifier_call = send_last_msg, }; static int __init panic_sendmsg_init(void) { int ret = -1; dev = dev_get_by_name (&init_net, "eth3"); if (!dev) { printk("Can't get device\n"); goto out; } /* 不在通知鏈中分配,因?yàn)槟菚r(shí)已經(jīng)panic,故預(yù)先分配 */ skb = alloc_skb(1500, GFP_ATOMIC); if (!skb) { dev_put(dev); printk("Alloc skb failed\n"); goto out; } /* 注冊(cè) */ ret = atomic_notifier_chain_register(&panic_notifier_list, &on_panic_send); if (ret) { dev_put(dev); kfree_skb (skb); printk("Register notifier chain failed\n"); goto out; } return 0; out: return ret; } static void __exit panic_sendmsg_exit(void) { atomic_notifier_chain_unregister(&panic_notifier_list, &on_panic_send); if (dev) { dev_put(dev); } if (skb) { kfree_skb (skb); } } module_init(panic_sendmsg_init); module_exit(panic_sendmsg_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("marywangran <marywangran@126.com>");
需要指出的
是kernel_log_buffer接口,這個(gè)接口是我自己添加進(jìn)內(nèi)核并EXPORT出來的。我認(rèn)為這是必要的,printk將信息放在了內(nèi)核的一個(gè)緩
沖區(qū)中,這個(gè)緩沖區(qū)可以被用戶態(tài)進(jìn)程通過syslog系統(tǒng)調(diào)用讀取。我們知道,常規(guī)的辦法是內(nèi)核信息被syslog守護(hù)進(jìn)程讀取,然后發(fā)送到網(wǎng)絡(luò)或者保存
成文件。但是panic之后呢?既然用戶態(tài)已經(jīng)完全不再運(yùn)行,那么就不存在通過什么守護(hù)進(jìn)程存日志的希望了,當(dāng)然在內(nèi)核panic通知鏈鉤子中直接操作磁
盤日志文件是可以的,但是那太不可靠,因?yàn)榇疟P的IO邏輯同樣存在于內(nèi)核態(tài)代碼或者驅(qū)動(dòng)代碼中,此時(shí)內(nèi)核已經(jīng)panic了...雖然從網(wǎng)絡(luò)發(fā)送數(shù)據(jù)也同樣
要用到已經(jīng)panic的內(nèi)核執(zhí)行流,但是畢竟協(xié)議棧操作要比磁盤操作的調(diào)用深度要淺很多(數(shù)據(jù)的最終解釋,排序,保存操作都在對(duì)端進(jìn)行,磁盤IO則不
然)。
不管怎么樣,不管是磁盤操作還是網(wǎng)絡(luò)操作,都需要將內(nèi)核log緩沖區(qū)中的內(nèi)存dump下來才好,然而內(nèi)核log緩沖區(qū)本身并沒有被EXPORT,它只能通過不多的幾個(gè)系統(tǒng)調(diào)用接口來獲取,而這些接口很難在內(nèi)核里面調(diào)用,因此我自己添加了一個(gè):
int kernel_log_buffer(char *buf, int max_len);
其中,buf為內(nèi)核log保存到的緩沖區(qū),max_len為buf的長(zhǎng)度,返回值為實(shí)際返回信息的長(zhǎng)度。這個(gè)接口完全類似于read系統(tǒng)調(diào)用。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。