2016-03-31 86 views
1

是的,我知道這聽起來很瘋狂,但這是我現在可以描述它的唯一方法。我正在爲一個模仿終端的類編寫一個程序,因爲它將命令作爲輸入並執行它們。 (我將在下面放置一些代碼)正如您將看到的,該程序包含歷史命令historyArgs,以便用戶可以執行最近的命令。未執行的代碼是否會導致段錯誤?

當用戶執行Ctrl-C時會列出命令歷史記錄。最近的命令是通過命令'r'(對於最近的)和r'x'(其中x與近期歷史中的命令的第一個字母匹配)來訪問的。當我開始實施'r'命令時,我開始獲取這個段錯誤。然後,我恢復了所有的更改並一次添加了一行。我發現添加偶數的原始變量聲明會導致段錯誤(int temp = 10;)但這是它變得陌生的地方。我相信導致segfault(int temp = 10;)的行永遠不會被訪問。我把printf語句並在if塊的開始處刷新輸出,以查看塊是否已經輸入,但是它們不執行。

安裝程序提供給我們。它接受用戶輸入並將其置於char * args []中,即input = ls -a -C,args = {「ls」,「-a」,「-C」,NULL,... NULL)。我標記了主要的行,導致段錯誤。

#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <signal.h> 
#include <string.h> 
#define BUFFER_SIZE 50 
static char buffer[BUFFER_SIZE]; 

#define MAX_LINE 80 /* 80 chars per line, per command, should be enough. */ 

char *historyArgs[10][MAX_LINE/2 + 1]; 
int historyCount = 0; 
int indexOfLatestCommand = 0; 

/* the signal handler function */ 
void handle_SIGINT() { 
    //write(STDOUT_FILENO,buffer,strlen(buffer)); 
    if(historyCount > 0){ 
     printf("\n%i command(s), printing most recent:\n", historyCount); 
     int i; 
     for(i = 0; i < historyCount && i < 10; i++){ 
      printf("%i.] %s ", i+1, historyArgs[i][0]); 
      //print args 
      int j = 1; 
      while(historyArgs[i][j] != NULL){ 
       printf("%s ", historyArgs[i][j]); 
       j++; 
      } 
      printf("\n"); 
     } 
    } 
    else{ 
     printf("\nNo recent commands.\n"); 
    } 
    fflush(stdout); 
} 

/** 
* setup() reads in the next command line, separating it into distinct tokens 
* using whitespace as delimiters. setup() sets the args parameter as a 
* null-terminated string. 
*/ 

void setup(char inputBuffer[], char *args[],int *background) 
{ 
    int length, /* # of characters in the command line */ 
     i,  /* loop index for accessing inputBuffer array */ 
     start, /* index where beginning of next command parameter is */ 
     ct;  /* index of where to place the next parameter into args[] */ 

    ct = 0; 

    /* read what the user enters on the command line */ 
    length = read(STDIN_FILENO, inputBuffer, MAX_LINE); 

    start = -1; 
    if (length == 0) 
     //exit(0);   /* ^d was entered, end of user command stream */ 
    if (length < 0){ 
     perror("error reading the command"); 
    exit(-1);   /* terminate with error code of -1 */ 
    } 

    /* examine every character in the inputBuffer */ 
    for (i = 0; i < length; i++) { 
     switch (inputBuffer[i]){ 
     case ' ': 
     case '\t' :    /* argument separators */ 
      if(start != -1){ 
       args[ct] = &inputBuffer[start]; /* set up pointer */ 
       ct++; 
      } 
      inputBuffer[i] = '\0'; /* add a null char; make a C string */ 
      start = -1; 
      break; 

     case '\n':     /* should be the final char examined */ 
      if (start != -1){ 
       args[ct] = &inputBuffer[start]; 
       ct++; 
      } 
      inputBuffer[i] = '\0'; 
      args[ct] = NULL; /* no more arguments to this command */ 
      break; 

     case '&': 
      *background = 1; 
      inputBuffer[i] = '\0'; 
      break; 

     default :    /* some other character */ 
      if (start == -1) 
       start = i; 
    } 

    }  
    args[ct] = NULL; /* just in case the input line was > 80 */ 
} 


int main(void) 
{ 
    int i; 
    char inputBuffer[MAX_LINE]; /* buffer to hold the command entered */ 
    int background;    /* equals 1 if a command is followed by '&' */ 
    char* args[MAX_LINE/2+1];/* command line (of 80) has max of 40 arguments */ 
    int status; 

    struct sigaction handler; 
    handler.sa_handler = handle_SIGINT; 
    sigaction(SIGINT, &handler, NULL); 
    strcpy(buffer,"Caught <ctrl><c>\n"); 

    while (1){   /* Program terminates normally inside setup */ 
     background = 0; 
     printf("COMMAND->"); 
     fflush(0); 
     setup(inputBuffer, args, &background);  /* get next command */ 

     //If command wasn't empty 
     if(args[0] != NULL){ 
      if(strcmp(args[0], "r") != 0){ 
       //Copy into history if not a recent call 
       for(i = 0; i < MAX_LINE/2+1 && args[i] != NULL; i++){ 
        historyArgs[historyCount%10][i] = malloc(strlen(args[i])); 
        strcpy(historyArgs[historyCount%10][i], args[i]); 
       } 
       indexOfLatestCommand = historyCount%10; 
       historyCount++; 
      } 


      //Create child process 
      int pid = fork(); 

      //In child process 
      if(pid == 0){ 
       if(strcmp(args[0], "r") == 0){ 
        //If only "r" was entered, execute most recent command 
        if(args[1] == NULL){ 
         printf("Entering recent?\n"); 
         fflush(stdout); 
         int temp = 10; //SEGFAULTS HERE IF THIS IS INCLUDED 
         execvp(historyArgs[indexOfLatestCommand][0], &historyArgs[indexOfLatestCommand][0]); 
        } 
        else{ 
         //Find in args[1][0] history, run if found 
         for(i = indexOfLatestCommand; i >= 0; i--){ 
          if(historyArgs[i][0][0] == args[1][0]){ 
           execvp(historyArgs[i][0], &historyArgs[i][0]); 
           break; 
          } 
         } 
         if(i == -1){ 
          for(i = historyCount > HISTORY_SIZE ? HISTORY_SIZE : historyCount; i > indexOfLatestCommand; i--){ 
           if(historyArgs[i][0][0] == args[1][0]) 
           execvp(historyArgs[i][0], &historyArgs[i][0]); 
           break; 
          } 
         } 
        } 
       } 
       else execvp(args[0], &args[0]); 
      } 
      //In parent process 
      else if (pid > 0){ 
       /*If child isn't a background process, 
       wait for child to terminate*/ 
       if(background == 0) 
        while(wait(&status) != pid); 
      } 

     } 
    } 
} 

另一件值得一提的事情是,在該位置聲明變量不會導致段錯誤。只給一個新變量賦值。在該部分重新分配全局變量也不會導致段錯誤。

編輯:什麼觸發崩潰。命令正確執行。當你運行它時,你可以輸入任何命令,並且應該可以工作。直到我執行Ctrl-C並打印出程序段錯誤的歷史記錄。

例輸入:

LS
LS -a
grep的
按Ctrl-C

請注意:如果你決定要運行這個,知道結束任務,你會可能需要使用kill命令,因爲我沒有實現「q」來退出。

+4

哦,男人,在信號處理程序中的未定義的行爲。你有沒有聽說過異步安全? – EOF

+0

下面是一些可以嘗試的方法:(1)在['valgrind'](http://valgrind.org/)下運行程序;這是一個很好的機會揭示出這個bug並不在你認爲的地方。 (2)關閉所有優化('-O0')。 (3)將'if(pid == 0){'塊中的所有代碼提取到它自己的函數中。 – zwol

+0

完全有可能,通過添加一個變量並賦值給它,你正在改變棧的佈局(並初始化它的一部分),所以如果你通過一個懸掛指針錯誤地訪問它,你可能會得到不同的結果(可能段錯誤)。這經常發生在錯誤地使用指向已返回函數的局部變量的指針時。 –

回答

2

您所看到的症狀(無關的代碼更改似乎影響崩潰的性質)通常意味着您的程序更早引起未定義的行爲。行爲的本質會發生變化,因爲您的程序依賴於它在某個階段讀取或寫入的垃圾值。

要調試它,請嘗試並刪除程序中未定義行爲的所有來源。最明顯的是你的void handle_SIGINT()函數的內容。該只有的東西,你可以在一個信號發生器可移植做是:

  • 設置volatile sig_atomic_t類型或其他無鎖類型的變量,並返回
  • 做其他的東西,並呼籲_Exitabort或相似。

尤其是,您不能調用任何庫函數,因爲它們可能不可重入。

有關完整規範,請參閱當前C標準的第7.14.1節。如果您還遵循其他一些標準,例如POSIX,它可以指定信號處理程序中允許的其他一些內容。

如果你不打算退出,那麼你必須設置一個標誌,然後在你的主「線程」中測試該標誌,看看是否產生了一個信號。

+0

@PaulGriffiths很對! –

相關問題