歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

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

這裡說的“後門”並不是教你做壞事,而是讓你做好事,搭建自己的調試工具更好地進行調試開發。我們都知道,當程序發生異常錯誤時,我們需要定位到錯誤,有時我們還想,我們在不修改程序的前提下,就能通過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指令,文章後面會解釋如何實現。

三、解析指令 解析指令又可以分為兩個步驟:
  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;
        } 
四、搭建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級別,當然我們還可以做更多的事,只要依照這個框架往裡面加指令,以及對應的處理操作,就可以實現了。   五、總結 所謂後門,就是一個可以操控程序的接口,這個接口僅僅用於開發者調試開發,不會開放給客戶。所以這個後門的作用非常巨大,所以是開發者調試程序的一大利器。有人會想,我想用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 }

Copyright © Linux教程網 All Rights Reserved