Linux編程之給你的程序開後門

這裏說的「後門」並非教你作壞事,而是讓你作好事,搭建本身的調試工具更好地進行調試開發。咱們都知道, 當程序發生異常錯誤時,咱們須要定位到錯誤,有時咱們還想,咱們在不修改程序的前提下,就能經過log來定位錯誤呢?有人會說,我在個人程序里加多點打印就行了,程序每作一步我就加一行打印,到時一查log就知道程序在哪一步死掉的了。這個方法在小程序裏也許會行得通,可是,在一個大型系統,每秒的log達到幾百條,那時咱們怎麼能在這繁多的log裏找出咱們想要的那條的log的?這工做量大得誇張。工程中的解決方法就是給本身的程序開個後門專門給開發人員來調試程序。
 
我在上篇文章:《Linux編程之定製帶級別的log》裏介紹瞭如何自定義本身的帶級別log,咱們就能夠在咱們後門程序裏修改log 的level,從而使得log的級別發生改變,而無需從新修改程序。好比咱們初始化咱們的log級別是FATAL,那麼此時只會打印出FATAL的信息,可是有一次程序發生了錯誤,可是咱們沒法從FATAL的log裏看出問題,那咱們經過後臺修改log的級別爲ALARM,咱們經過ALARM的log查出了程序錯誤的緣由。固然咱們經過後門能作的不只僅是這些,具體來講,後門就是咱們程序眼和跑着的程序交流的一道門。
 
搭建這麼一個程序後門主要有這麼幾個關鍵點:
  • 在進程裏開一個線程用於充當debug center
  • 該線程經過fifo接收開發人員傳給它的命令
  • 解析這些命令
  • 用腳本搭建簡單的命令行界面

 

1、建立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;
}

 

 

2、建立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指令,文章後面會解釋如何實現。shell

 

3、解析指令
解析指令又能夠分爲兩個步驟:
  1. 將從fifo取得數據進行格式解析,好比我定義了d d的意思是display debug,即顯示如今的debug級別,那麼咱們程序就得首先對原始數據進行格式處理。
  2. 將指令進行命令解析,執行相應操做。
 
格式處理:
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;
        }
 

 

4、搭建debug界面
先上界面圖:

 
咱們敲的每一個命令都是經過該界面傳遞到程序那頭的,好比「d d」就表示展現出如今系統運行時的log級別,而「s d」就是設置咱們想要看的log級別,這樣咱們就能夠實現經過程序後門動態修改程序走向了。
 
那該如何實現這個看似簡單的交互界面呢?實現該交互界面,有幾個關鍵點:
  • 咱們需將程序的打印輸出重定向到一個文件裏
  • 使用shell腳本讀出文件的內容
  • 咱們輸入的命令需寫入到fifo中
以上三點就是作成界面的最重要的技術問題。
 
  1. 重定向輸出
  一開始我是打算將標準輸出、標準錯誤都重定向到文件裏的額,可是想了想,仍是想直接把打印內容直接輸出到文件就行了。好比這樣:
     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級別,固然咱們還能夠作更多的事,只要依照這個框架往裏面加指令,以及對應的處理操做,就能夠實現了。
 
5、總結
所謂後門,就是一個能夠操控程序的接口,這個接口僅僅用於開發者調試開發,不會開放給客戶。因此這個後門的做用很是巨大,因此是開發者調試程序的一大利器。有人會想,我想用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 }
相關文章
相關標籤/搜索