1. 程式人生 > >在arm-linux上用gdb除錯程式,出現“Program received signal SIGPIPE, Broken pipe”

在arm-linux上用gdb除錯程式,出現“Program received signal SIGPIPE, Broken pipe”

        出現這種情況大多是因為程式採用CS架構(伺服器/客戶端)在讀寫操作時出現,我第一次也是在這樣的情況下遇到的。首先我們都知道套接字的通訊方式是雙工的,同端即可寫也可讀。而出現Broken pipe這種情況的原因是寫段正在寫入時,另一端已關閉套接字,這樣程序就會向系統傳送SIGPIPE訊號,然後系統再回頭叫停執行緒,這樣就會出現管道破裂的訊號並且退出程式。這雖然是程序的一種保護機制,但是在執行過程中一般我們是不希望出現退出程式的保護。於是便上網查了一番。發現在main函式開始加一行“signal(SIGPIPE, SIG_IGN);”程式碼即可意思是遮蔽SIGPIPE訊號,但是加上後並沒有起作用,依舊還是出現管道破裂錯誤。後來在網上看到有大神說是因為signal函式設定的訊號處理只起一次作用,處理一次後就會再重置為預設處理,要用sigaction來設定自定義的訊號處理方式“struct sigaction sa;sa.sa_handler = SIG_IGN;sigaction( SIGPIPE, &sa, 0 );”,於是我自行寫了程式碼測試這種情況是不是如這位大神所說,發現並非如此,只好我用的這個開發板不是如此。於是又陷入在網上一番查詢。

        最後決定把這種情況復現出來,這樣更容易測試(這部分程式碼來自網路並自己修改的,如有侵權,請私信告訴我)

cli_test.c

#include <netinet/in.h> #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <errno.h>

#define HELLO_WORLD_SERVER_PORT 6666 #define BUFFER_SIZE 1024

void a(void) {     printf("123456789\n"); }

int main(int argc, char **argv) {   if (argc != 2)   {     printf("Usage: ./%s ServerIPAddress\n",argv[0]);     exit(1);   }   //signal(SIGPIPE, SIG_IGN);   signal(SIGPIPE, a);   //struct sigaction sa;   //sa.sa_handler = SIG_IGN;   //sigaction( SIGPIPE, &sa, 0 );

  struct sockaddr_in client_addr;   bzero(&client_addr,sizeof(client_addr));   client_addr.sin_family = AF_INET;   client_addr.sin_addr.s_addr = htons(INADDR_ANY);   client_addr.sin_port = htons(0);

  int client_socket = socket(AF_INET,SOCK_STREAM,0);

  if( client_socket < 0)   {     printf("Create Socket Failed!\n");     exit(1);   }

  if( bind(client_socket,(struct sockaddr*)&client_addr,sizeof(client_addr)))   {     printf("Client Bind Port Failed!\n");     exit(1);   }

  struct sockaddr_in server_addr;   bzero(&server_addr,sizeof(server_addr));   server_addr.sin_family = AF_INET;   if(inet_aton(argv[1],&server_addr.sin_addr) == 0)   {     printf("Server IP Address Error!\n");     exit(1);   }   server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);   socklen_t server_addr_length = sizeof(server_addr);   if(connect(client_socket,(struct sockaddr*)&server_addr, server_addr_length) < 0)   {     printf("Can Not Connect To %s!\n",argv[1]);     exit(1);   }

  char buffer[BUFFER_SIZE];   bzero(buffer,BUFFER_SIZE);   int length = recv(client_socket,buffer,BUFFER_SIZE,0);   if(length < 0)   {     printf("Recieve Data From Server %s Failed!\n", argv[1]);     exit(1);   }   printf("From Server %s :\t%s",argv[1],buffer);

  bzero(buffer,BUFFER_SIZE);   strcpy(buffer,"Hello, World! From Client\n");

  while(1){     sleep(1);     int ret = send(client_socket,buffer,BUFFER_SIZE,/*MSG_NOSIGNAL*/0);         if (ret == -1 && errno == EPIPE){           printf("receive sigpipe\n");           printf("receive %s\n", strerror(errno));     }   }

  close(client_socket);   return 0; }

ser_test.c

#include <netinet/in.h> #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <string.h>

#define HELLO_WORLD_SERVER_PORT 6666 #define LENGTH_OF_LISTEN_QUEUE 20 #define BUFFER_SIZE 1024

int main(int argc, char **argv) {   struct sockaddr_in server_addr;   bzero(&server_addr,sizeof(server_addr));   server_addr.sin_family = AF_INET;   server_addr.sin_addr.s_addr = htons(INADDR_ANY);   server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);

  int server_socket = socket(AF_INET,SOCK_STREAM,0);   if( server_socket < 0)   {     printf("Create Socket Failed!");     exit(1);   }

  if( bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr)))   {     printf("Server Bind Port : %d Failed!", HELLO_WORLD_SERVER_PORT);     exit(1);   }

  if ( listen(server_socket, LENGTH_OF_LISTEN_QUEUE) )   {     printf("Server Listen Failed!");     exit(1);   }

  while (1)   {     struct sockaddr_in client_addr;     socklen_t length = sizeof(client_addr);

    int new_server_socket = accept(server_socket,(struct sockaddr*)&client_addr,&length);     if ( new_server_socket < 0)     {       printf("Server Accept Failed!\n");       break;     }

    char buffer[BUFFER_SIZE];     bzero(buffer, BUFFER_SIZE);     strcpy(buffer,"Hello,World from server!");     strcat(buffer,"\n");     send(new_server_socket,buffer,BUFFER_SIZE,0);

    bzero(buffer,BUFFER_SIZE);         while(1){       length = recv(new_server_socket,buffer,BUFFER_SIZE,0);       if (length < 0)       {         printf("Server Recieve Data Failed!\n");         exit(1);       }       printf("\n%s",buffer);         }     close(new_server_socket);   }   close(server_socket);   return 0; }

Makefile

CC = arm-hisiv500-linux-gcc //我用的交叉編譯工具,根據自己情況進行修改編譯工具 #CC = gcc #CFLAGS = -g -Wall -O3 SRCS = cli_test.c ser_test.c

SER = ser CLI = cli

OBJS = $(SRCS:.c=.o)

%.o:%.c     $(CC) $(CFLAGS) -o [email protected] -c $<

all:$(SER) $(CLI) $(SER):ser_test.o     $(CC) -o [email protected] $^

$(CLI):cli_test.o     $(CC) -o [email protected] $^     rm -rf *.bak

clean:     rm -rf $(SER) $(CLI) $(OBJS) *.bak

        make編譯,分別將ser端和cli端執行起來,正常接收資訊後,Ctrl+c結束ser程式,cli端即出現管道破裂錯誤。

        最後經過測試發現上面提到過的兩種遮蔽方式在gcc編譯工具生成的檔案都是可行的,都能遮蔽成功,唯獨在arm-linux下不行。後來又找到一種臨時可用的拼比方法,那就是將send的flags位(最後一位引數)傳入MSG_NOSIGNAL,這是遮蔽send所產生的所有訊號,當然包括段錯誤訊號,這樣就會很危險,當然也是一種臨時可行的方法。

         後來發現用arm-linux和gcc兩種編譯及操作方式唯一不同的就是我在arm-linux上用了gdb除錯,於是猜想會不會是gdb在搞鬼呢。於是不用gdb直接執行程式碼,經過測試發現還真的是gdb。

        後來再經過查詢發現,原因是這樣的,當程序出現Broken pipe錯誤時,會將該訊號傳送給系統,系統收到訊號後會反過來再發給程序來叫停程序,當用gdb除錯時,收到系統發的訊號的並不是程序,而是讓gdb給半路攔截下來了,當gdb收到訊號後預設處理方式是暫停程式,將錯誤打印出來。這樣一來我們的程序實際在並沒有收到訊號的情況下就被叫停了,所以在程式裡面不管怎樣處理訊號都是做無用功。

        所以要想繼續用gdb除錯執行,則需要修改gdb對訊號的處理方式

        handle SIGPIPE nostop