這裡說的“後門”並不是教你做壞事,而是讓你做好事,搭建自己的調試工具更好地進行調試開發。我們都知道,當程式發生異常錯誤時,我們需要定位到錯誤,有時我們還想,我們在不修改程式的前提下,就能通過log來定位錯誤呢?有人會說,我在我的程式裡加多點列印就好了,程式每做一步我就加一行列印,到時一查log就知道 ...
這裡說的“後門”並不是教你做壞事,而是讓你做好事,搭建自己的調試工具更好地進行調試開發。我們都知道,當程式發生異常錯誤時,我們需要定位到錯誤,有時我們還想,我們在不修改程式的前提下,就能通過log來定位錯誤呢?有人會說,我在我的程式裡加多點列印就好了,程式每做一步我就加一行列印,到時一查log就知道程式在哪一步死掉的了。這個方法在小程式里也許會行得通,但是,在一個大型系統,每秒的log達到幾百條,那時我們怎麼能在這繁多的log里找出我們想要的那條的log的?這工作量大得誇張。工程中的解決方法就是給自己的程式開個後門專門給開發人員來調試程式。 我在上篇文章:《Linux編程之定製帶級別的log》里介紹瞭如何自定義自己的帶級別log,我們就可以在我們後門程式里修改log 的level,從而使得log的級別發生改變,而無需重新修改程式。比如我們初始化我們的log級別是FATAL,那麼此時只會列印出FATAL的信息,但是有一次程式發生了錯誤,但是我們無法從FATAL的log里看出問題,那我們通過後臺修改log的級別為ALARM,我們通過ALARM的log查出了程式錯誤的原因。當然我們通過後門能做的不僅僅是這些,具體來說,後門就是我們程式眼和跑著的程式交流的一道門。 搭建這麼一個程式後門主要有這麼幾個關鍵點:
- 在進程里開一個線程用於充當debug center
- 該線程通過fifo接收開發人員傳給它的命令
- 解析這些命令
- 用腳本搭建簡單的命令行界面
一、創建debug center線程 這個就沒什麼好說了,我使用了上篇文章《Linux編程之自定義消息隊列》所搭建的消息隊列框架,將其中的msg_sender1改造為debug_center線程,作為我們的程式後門,我們跟程式交互就是從這裡開始的。
if(pthread_create(&debug_thread_id, NULL, (void*)debug_center, NULL)) { MY_LOG(FATAL,"create debug center fail!\n"); return -1; }
二、創建FIFO 為什麼要創建FIFO(有名管道)?因為我們需要跟我們的程式進行通信,我們需要把我們的指令告訴程式,那就需要一個通信途徑,FIFO就是一個很好的選擇。我們把我們的指令寫進管道,程式將指令從管道出,然後執行該指令,這樣子我們程式後門的通信模型就出來了。why解決了,是時候解決how了。 對於管道的操作,我是這麼做的:
system("rm /vob/ljsdpoenew3/exercise/debug_log"); //每次進入debug center我們都將原來的fifo文件刪除,避免影響後面操作 rc = mkfifo("/vob/ljsdpoenew3/exercise/debug_log", 0666); //創建fifo if(rc < 0) { MY_LOG(DEBUG, "make fifo fail!\n"); pthread_exit(0); } fp = fopen("/vob/ljsdpoenew3/exercise/debug_log", "r"); //打開fifo,讀取指令 if(fp == NULL) { MY_LOG(DEBUG, "open debug_log fail!\n"); pthread_exit(0); }
讀fifo我們解決了,那怎麼將我們的指令寫進fifo呢?這裡我打算使用shell的read指令,文章後面會解釋如何實現。
三、解析指令 解析指令又可以分為兩個步驟:
- 將從fifo取得數據進行格式解析,比如我定義了d d的意思是display debug,即顯示現在的debug級別,那麼我們程式就得首先對原始數據進行格式處理。
- 將指令進行命令解析,執行相應操作。
static int get_args(FILE *inputFile) { char tmpBuffer[100]; char *line = tmpBuffer; char separator[] = " ,\n\t"; char *token; int i; char eof; int num = 0; eof = !fgets(line, sizeof(tmpBuffer), inputFile); if (eof) return num; token = strtok(line, separator); while (num < MAX_NUM_ARGS && token) { strcpy(args[num], token); num++; token = strtok(NULL, separator); } for (i = num; i < MAX_NUM_ARGS; i++) args[i][0] = 0; return num; }
命令解析:
switch(args[0][0]) //解析指令,看每個指令對應哪些意思 { case 'd': //display switch(args[1][0]) { case 'q': //display queue show_MQ(fd); break; case 'd': //display debug show_debug_level(fd); break; default: help_manual(fd); break; } break; case 's': //set switch(args[1][0]) { case 'd': //set debug level n = atoi(args[2]); //將字元串轉化為整數 fprintf(fd," debug level change from %d to %d",global.debug_level,n); global.debug_level = n; //更改log級別 break; default: help_manual(fd); break; } break; default: help_manual(fd); break; }
四、搭建debug界面 先上界面圖: 我們敲的每個命令都是通過該界面傳遞到程式那頭的,比如“d d”就表示展示出現在系統運行時的log級別,而“s d”就是設置我們想要看的log級別,這樣我們就可以實現通過程式後門動態修改程式走向了。 那該如何實現這個看似簡單的交互界面呢?實現該交互界面,有幾個關鍵點:
- 我們需將程式的列印輸出重定向到一個文件里
- 使用shell腳本讀出文件的內容
- 我們輸入的命令需寫入到fifo中
- 重定向輸出
fd = fopen("/vob/ljsdpoenew3/exercise/debug_log2", "w+"); if(fd == NULL) { MY_LOG(DEBUG, "open debug_log2 fail!\n"); pthread_exit(0); } fprintf(fd," debug level change from %d to %d",global.debug_level,n);這樣我們在debug center產生的列印都輸出到文件debug_log2上了。
2.利用腳本讀出內容且將內容寫入fifo
要做到這點需要shell編程的簡單知識,我們怎麼將我們的輸入放到fifo呢?我們怎麼把log文件的內容讀出來顯示在顯示屏呢?我想到首先是再寫個client,進行進程間通信嘛,將命令寫到fifo,同時也讀出log文件的內容。但是,我發現利用shell就可以做到以上的事了,而且只需花費幾行代碼。#!/bin/bash my_inp_fifo=/vob/ljsdpoenew3/exercise/debug_log # 指定fifo文件 stty erase ^H while [ true ]; do read inp #迴圈讀入用戶輸入 if [ "$inp" == "quit" ];then #quit代表結束該界面 exit 0 fi echo $inp > $my_inp_fifo #寫入fifo cat debug_log2.txt #顯示log的內容 done
那看看這個命令行界面跑起來是怎麼的吧! 首先我們運行server進程
sever進程不斷產生消息並處理消息。 我們打開另一個視窗,並執行腳本./test.sh,進入界面 我們使用了d d命令看到了程式此時的debug級別,用d q看出了程式消息隊列的情況。 我們使用了s d指令將debug level設置為0,此時屏幕沒有任何列印輸出,當我們在使用s d指令將level設置為-1(即將所有位置一),此時所有列印級別都打開了,屏幕又開始瘋狂列印了。也就說,我們通過後門操控了程式,這裡我們只是僅僅修改了程式的log級別,當然我們還可以做更多的事,只要依照這個框架往裡面加指令,以及對應的處理操作,就可以實現了。 五、總結 所謂後門,就是一個可以操控程式的介面,這個介面僅僅用於開發者調試開發,不會開放給客戶。所以這個後門的作用非常巨大,所以是開發者調試程式的一大利器。有人會想,我想用socket來代替fifo進行進程通信可以不,這樣就可以做到遠程主機操控程式了。我覺得是可以的,但是感覺利用telnet到目的主機再運行腳本操作比較安全。 最後給出源代碼框架
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <math.h> 5 #include <sys/types.h> 6 #include <sys/stat.h> 7 #include <sys/prctl.h> 8 #include "msg_def.h" 9 #include "global.h" 10 11 extern Queue_t MsgQueue; 12 extern dashboard_t global; 13 14 15 #define MAX_NUM_ARGS 20 16 #define MAX_ARGS_SIZE 56 17 18 static char args[MAX_NUM_ARGS][MAX_ARGS_SIZE]; 19 20 static int get_args(FILE *inputFile,FILE *fd) 21 { 22 char tmpBuffer[100]; 23 char tmpBuffer2[100]; 24 char *line = tmpBuffer; 25 char separator[] = " ,\n\t"; 26 char *token; 27 int i; 28 char eof; 29 30 int num = 0; 31 32 eof = !fgets(line, sizeof(tmpBuffer), inputFile); 33 if (eof) 34 return num; 35 36 memcpy(tmpBuffer2,tmpBuffer,100); 37 38 39 token = strtok(line, separator); 40 while (num < MAX_NUM_ARGS && token) 41 { 42 strcpy(args[num], token); 43 num++; 44 token = strtok(NULL, separator); 45 } 46 47 for (i = num; i < MAX_NUM_ARGS; i++) 48 args[i][0] = 0; 49 50 if(num > 0) 51 { 52 fprintf(fd, "%s", tmpBuffer2); 53 } 54 55 return num; 56 } 57 58 59 static void help_manual(FILE* fd) 60 { 61 fprintf(fd,"\nd d : display current debug level\n"); 62 fprintf(fd,"d q : display msg queue length, head and tail\n"); 63 fprintf(fd,"s d [level] : set debug [level] \n"); 64 } 65 66 static void show_MQ(FILE* fd) 67 { 68 fprintf(fd," msg queue length:%d head:%d tail:%d \n",abs(MsgQueue.head-MsgQueue.rear),MsgQueue.head,MsgQueue.rear); 69 } 70 71 static void show_debug_level(FILE* fd) 72 { 73 fprintf(fd," current debug level: %d\n", global.debug_level); 74 } 75 76 77 78 void debug_center() 79 { 80 int rc,num,n; 81 FILE* fp; 82 FILE* fd; 83 84 MY_LOG(DEBUG,"Hi,debug!\n"); 85 86 87 system("rm /vob/ljsdpoenew3/exercise/debug_log"); 88 system("rm /vob/ljsdpoenew3/exercise/debug_log2"); 89 rc = mkfifo("/vob/ljsdpoenew3/exercise/debug_log", 0666); 90 if(rc < 0) 91 { 92 MY_LOG(DEBUG, "make fifo fail!\n"); 93 pthread_exit(0); 94 } 95 96 fp = fopen("/vob/ljsdpoenew3/exercise/debug_log", "r"); 97 if(fp == NULL) 98 { 99 MY_LOG(DEBUG, "open debug_log fail!\n"); 100 pthread_exit(0); 101 } 102 103 fd = fopen("/vob/ljsdpoenew3/exercise/debug_log2", "w+"); 104 if(fd == NULL) 105 { 106 MY_LOG(DEBUG, "open debug_log2 fail!\n"); 107 pthread_exit(0); 108 } 109 //freopen("/vob/ljsdpoenew3/exercise/debug_log2.txt", "w+", fd); 110 111 fprintf(fd," \n==================================================\n"); 112 fprintf(fd," Welcme to Debug Center!"); 113 fprintf(fd," \n==================================================\n\n"); 114 help_manual(fd); 115 //fflush(fd); 116 117 while(1) 118 { 119 fflush(fd); 120 fprintf(fd,"\n\nmy_diag>"); 121 num = get_args(fp,fd); 122 if(num < 1) 123 { 124 freopen("/vob/ljsdpoenew3/exercise/debug_log", "r", fp); 125 fflush(fd); 126 continue; 127 } 128 fprintf(fd,"\n\nmy_diag>"); 129 switch(args[0][0]) 130 { 131 case 'd': //display 132 switch(args[1][0]) 133 { 134 case 'q': //display queue 135 show_MQ(fd); 136 break; 137 case 'd': //display debug 138 show_debug_level(fd); 139 break; 140 141 default: 142 help_manual(fd); 143 break; 144 } 145 break; 146 147 case 's': //set 148 switch(args[1][0]) 149 { 150 case 'd': //set debug level 151 n = atoi(args[2]); 152 fprintf(fd," debug level change from %d to %d",global.debug_level,n); 153 global.debug_level = n; 154 break; 155 156 default: 157 help_manual(fd); 158 break; 159 } 160 break; 161 162 default: 163 help_manual(fd); 164 break; 165 } 166 167 } 168 }