glibc detected.

This is a discussion on glibc detected. within the C Programming forums, part of the General Programming Boards category; I've been trying to write a chat with C sockets and I've built the basic program (interaction with only 2 ...

  1. #1
    Registered User
    Join Date
    Oct 2007
    Posts
    242

    glibc detected.

    I've been trying to write a chat with C sockets and I've built the basic program (interaction with only 2 users, not using select)
    Anyways, I've come across a weird situation...I was interacting with the client until I've got this error:
    Code:
    *** glibc detected *** ./server.o: malloc(): memory corruption (fast): 0x0804a020 ***
    ======= Backtrace: =========
    /lib/libc.so.6[0xb7eb7007]
    /lib/libc.so.6[0xb7eb9750]
    /lib/libc.so.6(__libc_malloc+0x8d)[0xb7eba7ed]
    ./server.o[0x80487f6]
    /lib/libc.so.6(__libc_start_main+0xe0)[0xb7e66450]
    ./server.o[0x8048611]
    ======= Memory map: ========
    08048000-08049000 r-xp 00000000 08:03 483770     /home/executer/server.o
    08049000-0804a000 rw-p 00000000 08:03 483770     /home/executer/server.o
    0804a000-0806b000 rw-p 0804a000 00:00 0          [heap]
    b7d00000-b7d21000 rw-p b7d00000 00:00 0 
    b7d21000-b7e00000 ---p b7d21000 00:00 0 
    b7e37000-b7e43000 r-xp 00000000 08:03 98656      /lib/libgcc_s.so.1
    b7e43000-b7e44000 rw-p 0000b000 08:03 98656      /lib/libgcc_s.so.1
    b7e4f000-b7e50000 rw-p b7e4f000 00:00 0 
    b7e50000-b7f7f000 r-xp 00000000 08:03 99969      /lib/libc-2.7.so
    b7f7f000-b7f80000 r--p 0012f000 08:03 99969      /lib/libc-2.7.so
    b7f80000-b7f82000 rw-p 00130000 08:03 99969      /lib/libc-2.7.so
    b7f82000-b7f85000 rw-p b7f82000 00:00 0 
    b7f8e000-b7f92000 rw-p b7f8e000 00:00 0 
    b7f92000-b7fac000 r-xp 00000000 08:03 99966      /lib/ld-2.7.so
    b7fac000-b7fae000 rw-p 00019000 08:03 99966      /lib/ld-2.7.so
    bfa4d000-bfa62000 rw-p bfa4d000 00:00 0          [stack]
    ffffe000-fffff000 r-xp 00000000 00:00 0          [vdso]
    The code is this:
    Code:
    	while(1)
    	{
    		char *receivedData = (char *)malloc(20);
    		int bytesRead = 0;
    		if( (bytesRead = recv(newfd, receivedData, 20, 0)) == -1)
    			exit(1);
    		receivedData[bytesRead] = '\0';
    		printf("Client says %s", receivedData);
    		
    		char *sendData = (char *)malloc(20);
    		printf("Speak to client: ");
    		fgets(sendData, 20, stdin);
    		if(send(newfd, sendData, strlen(sendData), 0) == -1)
    			exit(1);
    		free(receivedData);
    		free(sendData);
    	}
    What's the problem? Why does this happen?

  2. #2
    Deathray Engineer MacGyver's Avatar
    Join Date
    Mar 2007
    Posts
    3,211
    If you recv() 20 chars and bytesRead is set to 20, is receivedData[20] valid to write to?

    Hint: No, it's not.

  3. #3
    and the hat of wrongness Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    32,506
    > receivedData[bytesRead] = '\0';
    If you received 20 bytes, then this will write to the 21st byte (which is outside the array you allocated).
    Either +1 in the alloc, or -1 in the call to recv(), so that there's always room for a \0
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.
    I support http://www.ukip.org/ as the first necessary step to a free Europe.

  4. #4
    Registered User
    Join Date
    Oct 2007
    Posts
    242
    Yeah I fixed this problem but it doesn't seem to be the cause for the error, does it? (because it says that memory is being corrupted very fast, and as you see, I'm allocating and freeing memory on an eternal loop)

  5. #5
    and the hat of wrongness Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    32,506
    Perhaps there are similar other errors in your code where you forgot to count the \0.

    Post more code showing your malloc calls and how you use that memory.

    Oh, and read the FAQ on casting the return result of malloc.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.
    I support http://www.ukip.org/ as the first necessary step to a free Europe.

  6. #6
    Registered User
    Join Date
    Oct 2007
    Posts
    242
    Quote Originally Posted by Salem View Post
    Perhaps there are similar other errors in your code where you forgot to count the \0.

    Post more code showing your malloc calls and how you use that memory.

    Oh, and read the FAQ on casting the return result of malloc.
    Erm this is the only place atm.
    And I know that only in C++ you should cast the return result of malloc.

  7. #7
    and the hat of wrongness Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    32,506
    > And I know that only in C++ you should cast the return result of malloc.
    If this really is C++, then you should be using new/delete.

    How about posting your latest code?
    It's all very well describing what you did, but people still find plenty of ways to make a mess of it.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.
    I support http://www.ukip.org/ as the first necessary step to a free Europe.

  8. #8
    Registered User
    Join Date
    Oct 2007
    Posts
    242
    Quote Originally Posted by Salem View Post
    > And I know that only in C++ you should cast the return result of malloc.
    If this really is C++, then you should be using new/delete.

    How about posting your latest code?
    It's all very well describing what you did, but people still find plenty of ways to make a mess of it.
    Nope, this is C.
    ---
    It's fine, I haven't received any of these errors yet.
    I'm gonna post here the chat when it's ready (I'm gonna work on it in a few hours again)

  9. #9
    Woof, woof! zacs7's Avatar
    Join Date
    Mar 2007
    Location
    Australia
    Posts
    3,459
    Perhaps add error checking (like malloc() failure etc).

    And it exits if the client has dropped and you can't send data!? That's silly.

  10. #10
    Registered User
    Join Date
    Oct 2007
    Posts
    242
    Quote Originally Posted by zacs7 View Post
    Perhaps add error checking (like malloc() failure etc).

    And it exits if the client has dropped and you can't send data!? That's silly.
    So how can I know whether the client is connected or not?

  11. #11
    Registered User ssharish2005's Avatar
    Join Date
    Sep 2005
    Location
    Cambridge, UK
    Posts
    1,682
    Quote Originally Posted by eXeCuTeR View Post
    So how can I know whether the client is connected or not?
    Well, when you send the data from server to client, you use function send. If that client closed ther connection. The send function cant send the data and generates and error return -1.

    Code:
    /* note there could many reasons why the connection might have been closed. 
    You could use perror function to get the right description for the loss of connection. */
    
    if( send(...) == -1 )      
       printf("Client closed the connection");
    else
       printf("Message sent!");
    ssharish

  12. #12
    Registered User
    Join Date
    Oct 2007
    Posts
    242
    Quote Originally Posted by ssharish2005 View Post
    Well, when you send the data from server to client, you use function send. If that client closed ther connection. The send function cant send the data and generates and error return -1.

    Code:
    /* note there could many reasons why the connection might have been closed. 
    You could use perror function to get the right description for the loss of connection. */
    
    if( send(...) == -1 )      
       printf("Client closed the connection");
    else
       printf("Message sent!");
    ssharish
    Well that's what I was doing actually, and I also thought that if I can't receive data anymore (=an error occurred) I close the connection.

  13. #13
    Registered User
    Join Date
    Oct 2007
    Posts
    242
    I don't want to open a new thread so I'll just keep posting on this one.
    ---
    2 problems:
    1. I can't bind this way to my address: (assume 999.999.999.999 is my ip)
    Code:
    METHOD 1:
    inet_aton("999.999.999.999", &server.sin_addr);
    METHOD 2:
    server.sin_addr.s_addr = inet_addr("999.999.999.999")
    (Scroll down for the rest of my question)
    2. Working code:
    Code:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <unistd.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #define PACKETSIZE 100
    #define MAXCONNECTIONS 5
    #define PORT 10008
    
    int recvalldata(int fd, char *buffer);
    int sendalldata(int fd, char *data);
    
    int main(void)
    {
    	// variable declarations
    	int sockfd, newfd, loopbetweenDesc;
    	struct sockaddr_in server, client;
    	fd_set mainSet, current;
    	
    	// initializing the lists
    	FD_ZERO(&mainSet);
    	FD_ZERO(&current);
    	
    	if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) // creating a TCP socket
    	{
    		perror("socket");
    		exit(1);
    	}
    	
    	// configuration
    	server.sin_family = AF_INET;
    	server.sin_addr.s_addr = htonl(INADDR_ANY);
    	server.sin_port = htons(PORT);
    	memset(&server.sin_zero, '\0', 8);
    	
    	if(bind(sockfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1) // binding to the given port
    	{
    		perror("bind");
    		exit(1);
    	}
    	
    	if(listen(sockfd, MAXCONNECTIONS) == -1) // listening only to MAXCONNECTIONS connections
    	{
    		perror("listen");
    		exit(1);
    	}
    	
    	loopbetweenDesc = sockfd;
    	FD_SET(sockfd, &mainSet); // adding the server to our streams list
    	while(1)
    	{
    		int recvNum = 0, i = 0;
    		current = mainSet; // current will be changed (and might not) when calling select, so we are using main to hold all of ourr streams
    																					// so every loop we can try and go over all of our streams
    		if(select(loopbetweenDesc + 1, &current, NULL, NULL, NULL) == -1)
    		{
    			perror("select");
    			exit(1);
    		}
    		
    		for(; i <= loopbetweenDesc; i++) // looping between our connections
    		{
    			if(FD_ISSET(i, &current)) // if a connection is ready
    			{
    				char talker[PACKETSIZE/2], buffer[PACKETSIZE], data[PACKETSIZE + (PACKETSIZE/2)];
    				bzero(&talker, PACKETSIZE/2); // holds `__IP__ says`
    				bzero(&buffer, PACKETSIZE); // client's message
    				// note: data (which holds talker + buffer) is not bzeroed because strcat will automatically add the null-terminator
    				if(sockfd == i) // if our server is ready to accept a new connection
    				{
    					size_t size = sizeof(client);
    					if( (newfd = accept(sockfd, (struct sockaddr *)&client, &size)) == -1)
    						perror("accept");
    					else
    					{
    						FD_SET(newfd, &mainSet); // add the new connection to our list
    						FD_SET(newfd, &current); // we want to greet him for entering the server
    						if(newfd > loopbetweenDesc) // if it is bigger, swap because we do not want to miss this connection later in our loop
    							loopbetweenDesc = newfd;
    							
    						char userJoinedMsg[PACKETSIZE];
    						bzero(&userJoinedMsg, PACKETSIZE);
    						sprintf(userJoinedMsg, "&#37;s has joined the server.\n", inet_ntoa(client.sin_addr));
    						printf("%s\n", userJoinedMsg); // informing the server
    						int h = 0;
    						for(; h <= loopbetweenDesc; h++) // informing the others that i has joined
    						{
    							if(FD_ISSET(h, &mainSet) && h != sockfd)
    							{
    								if(newfd == h)
    								{
    									const char *greeting = "Hello and welcome to the server.\n";
    									if(send(h, greeting, strlen(greeting), 0) == -1)
    										perror("send");
    								}
    								else
    								{
    									if(send(h, userJoinedMsg, strlen(userJoinedMsg), 0) == -1)
    										perror("send");
    								}
    							}
    						}
    					}
    				}
    				else
    				{
    					size_t peer_size = sizeof(struct sockaddr);
    					getpeername(i, (struct sockaddr *)&client, &peer_size);
    					sprintf(talker, "%s says: ", inet_ntoa(client.sin_addr));
    					if( (recvNum = recv(i, buffer, PACKETSIZE, 0)) == -1)
    						perror("recv");
    					else if(recvNum == 0) // if client has closed the connection
    					{
    						int k =0;
    						shutdown(i, 2);
    						FD_CLR(i, &mainSet);
    						FD_CLR(i, &current); // we want to inform them that i left
    						char userLeftMsg[PACKETSIZE];
    						bzero(&userLeftMsg, PACKETSIZE);
    						size_t peer_size_left = sizeof(struct sockaddr);
    						getpeername(i, (struct sockaddr *)&client, &peer_size_left);
    						sprintf(userLeftMsg, "%s has left the server.", inet_ntoa(client.sin_addr));
    						printf("%s\n", userLeftMsg); // informing the server 
    						
    						for(; k <= loopbetweenDesc; k++) // informing the others that i has left
    							if(FD_ISSET(k, &current) && k != sockfd)
    								if(send(k, userLeftMsg, strlen(userLeftMsg), 0) == -1)
    									perror("send");
    					}
    					else // if recv was successful
    					{
    						bzero(&data, PACKETSIZE + (PACKETSIZE / 2));
    						strcat(data, talker);
    						strcat(data, buffer); // appending talker and buffer to data
    						printf("%s\n", data);
    						int k = 0;
    						for(; k <= loopbetweenDesc; k++)
    						{
    							if(i == k) // the sender (i) does not need to receive his own message
    								continue;
    							if(FD_ISSET(k, &mainSet) && k != sockfd)
    								if(send(k, data, strlen(data), 0) == -1)
    									perror("send");
    						}
    					}
    				}
    			}
    		}
    	}
    	return 0;
    }
    If I use:
    Code:
    server.sin_addr.s_addr = INADDR_ANY;
    the client cannot connect to me through telnet (telnet MYIPADDRESS PORT)
    it gives:
    Code:
    telnet: Unable to connect to remote host: Connection refused
    only if I try to connect to 127.0.0.1 (localhost) I succeed.

    EDIT 2 (I deleted unnecessary stuff that I wrote): I've found the solution!! Apparently, I can't write to sockfd, and this is done basically so I just gotta place some conditions...anyways, anyone knows why I can't write to sockfd?
    Last edited by eXeCuTeR; 07-07-2008 at 08:05 PM.

  14. #14
    Registered User
    Join Date
    Oct 2007
    Posts
    242
    Sorry for the triple-posting but I really need the answer as soon as possible.
    Well,
    1.
    METHOD 1:
    inet_aton("999.999.999.999", &server.sin_addr);
    METHOD 2:
    server.sin_addr.s_addr = inet_addr("999.999.999.999")

    both of these cannot assign my address and when I use:
    server.sin_addr.s_addr = INADDR_ANY;
    I can connect only with 127.0.0.1 (telnet 127.0.0.1 PORT) and not with any other IP address, for example, telnet 999.999.999.999 PORT won't work (if 999.999.999.999 is my IP address of course)

    and my second question is why can't I write to sockfd (the "main" socket which waits for new connections)?

    Thanks.

  15. #15
    Registered User
    Join Date
    Oct 2007
    Posts
    242
    Well, here is the chat guys with nicknames, times and stuff:
    Horray:
    Code:
    /*
     * 						Console Chat V0.1, 
     * 			written by eXeCuTeR <executerx[at]gmail[dot]com>, 9/7/08
    */ 
    
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <unistd.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <time.h>
    
    #define PORT 10020
    #define PACKETSIZE 100
    #define MAXCONNECTIONS 10
    #define USERNAMELENGTH 20
    #define STRING 50
    
    typedef struct
    {
    	char *ipaddress;
    	char name[USERNAMELENGTH];
    } userInfo;
    
    char *usernamebyip(userInfo *users, struct sockaddr_in *client);
    void getcurrenttime(char *str, char *newstr);
    void cutNewLine(char *str);
    
    int main(void)
    {
    	// variable declarations
    	int sockfd, newfd, loopbetweenDesc, userIndexer = 0;
    	struct sockaddr_in server, client;
    	userInfo users[MAXCONNECTIONS + 10];
    	fd_set mainSet, current;
    	const char *nickReq = "Enter your nickname: ";
    	
    	if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) // creating a TCP socket
    	{
    		perror("socket");
    		exit(1);
    	}
    	
    	// configuration
    	server.sin_family = AF_INET;
    	server.sin_addr.s_addr = htonl(INADDR_ANY);
    	server.sin_port = htons(PORT);
    	memset(&server.sin_zero, '\0', 8);
    	
    	if(bind(sockfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1) // binding to the given port
    	{
    		perror("bind");
    		exit(1);
    	}
    	
    	if(listen(sockfd, MAXCONNECTIONS) == -1) // listening only to MAXCONNECTIONS connections
    	{
    		perror("listen");
    		exit(1);
    	}
    	time_t now = time(0);
    	printf("CC Server is running, &#37;s", ctime(&now));
    	
    	loopbetweenDesc = sockfd;
    	
    	// initializing the lists
    	FD_ZERO(&mainSet);
    	FD_ZERO(&current);
    	
    	FD_SET(sockfd, &mainSet); // adding the server to our streams list
    	while(1)
    	{
    		int recvNum = 0, i = 0;
    		current = mainSet; // current will be changed (and might not) when calling select, so we are using main to hold all of ourr streams
    																					// so every loop we can try and go over all of our streams
    		if(select(loopbetweenDesc + 1, &current, NULL, NULL, NULL) == -1)
    		{
    			perror("select");
    			exit(1);
    		}
    		
    		for(; i <= loopbetweenDesc; i++) // looping between our connections
    		{
    			if(FD_ISSET(i, &current)) // if a connection is ready
    			{
    				time_t current_time = time(0);
    				char *time = malloc(10);
    				getcurrenttime(ctime(&current_time), time);
    				char talker[PACKETSIZE/2], buffer[PACKETSIZE], data[PACKETSIZE + (PACKETSIZE/2)];
    				bzero(&buffer, PACKETSIZE); // client's message
    				// note: data (which holds talker + buffer) is not bzeroed because strcat will automatically add the null-terminator
    				if(sockfd == i) // if our server is ready to accept a new connection
    				{
    					size_t size = sizeof(client);
    					if( (newfd = accept(sockfd, (struct sockaddr *)&client, &size)) == -1)
    						perror("accept");
    					else
    					{
    						FD_SET(newfd, &mainSet); // add the new connection to our list
    						FD_SET(newfd, &current); // we want to greet him for entering the server
    						
    						bzero(users[userIndexer].name, USERNAMELENGTH);
    						users[userIndexer].ipaddress = inet_ntoa(client.sin_addr);
    						
    						do
    						{
    							if(send(newfd, (char *)nickReq, strlen(nickReq) + 1, 0) == -1)
    								perror("send");
    							else
    							{
    								int tempBytesNick = 0;
    								if( (tempBytesNick = recv(newfd, users[userIndexer].name, USERNAMELENGTH, 0)) == -1)
    									perror("recv");
    							}
    						} while(strlen(users[userIndexer].name) <= 2);
    						cutNewLine(users[userIndexer].name);
    						if(newfd > loopbetweenDesc) // if it is bigger, swap because we do not want to miss this connection later in our loop
    							loopbetweenDesc = newfd;
    							
    						char userJoinedMsg[PACKETSIZE];
    						bzero(&userJoinedMsg, PACKETSIZE);
    						sprintf(userJoinedMsg, "\n%s <%s, %s> has joined the server.\n", users[userIndexer].name, inet_ntoa(client.sin_addr), time);
    						printf("%s", userJoinedMsg); // informing the server
    						int h = 0;
    						for(; h <= loopbetweenDesc; h++) // informing the others that i has joined
    						{
    							if(FD_ISSET(h, &mainSet) && h != sockfd)
    							{
    								if(newfd == h)
    								{
    									char greeting[STRING/2];
    									sprintf(greeting, "Hello %s and welcome to the server.\n", users[userIndexer].name);
    									if(send(h, greeting, strlen(greeting), 0) == -1)
    										perror("send");
    								}
    								else
    								{
    									if(send(h, userJoinedMsg, strlen(userJoinedMsg), 0) == -1)
    										perror("send");
    								}
    							}
    						}
    						userIndexer++;
    					}
    				}
    				else
    				{
    					if( (recvNum = recv(i, buffer, PACKETSIZE, 0)) == -1)
    						perror("recv");
    					else if(recvNum == 0) // if client has closed the connection
    					{
    						int k =0;
    						shutdown(i, 2);
    						FD_CLR(i, &mainSet);
    						FD_CLR(i, &current); // we want to inform them that i left
    						char userLeftMsg[PACKETSIZE];
    						bzero(&userLeftMsg, PACKETSIZE);
    						size_t peer_size_left = sizeof(struct sockaddr);
    						getpeername(i, (struct sockaddr *)&client, &peer_size_left);
    						sprintf(userLeftMsg, "%s <%s, %s> has left the server.\n", usernamebyip((userInfo *)&users, &client), inet_ntoa(client.sin_addr), time);
    						printf("%s", userLeftMsg); // informing the server
    						for(; k <= loopbetweenDesc; k++) // informing the others that i has left
    							if(FD_ISSET(k, &current) && k != sockfd)
    								if(send(k, userLeftMsg, strlen(userLeftMsg), 0) == -1)
    									perror("send");
    					}
    					else // if recv was successful
    					{
    						bzero(&talker, PACKETSIZE/2); // holds __IP__ says
    						size_t peer_size = sizeof(struct sockaddr);
    						getpeername(i, (struct sockaddr *)&client, &peer_size);
    						sprintf(talker, "%s says <%s>: ", usernamebyip((userInfo *)&users, &client), time);
    						bzero(&data, PACKETSIZE + (PACKETSIZE / 2));
    						strcat(data, talker);
    						strcat(data, buffer); // appending talker and buffer to data
    						printf("%s", data);
    						int u = 0;
    						for(; u <= loopbetweenDesc; u++)
    						{
    							if(FD_ISSET(u, &mainSet) && u != sockfd)
    							{
    								if(u == i) // the sender (i) does not need to receive his own message
    									continue;
    								else
    								{
    									if(send(u, data, strlen(data), 0) == -1)
    										perror("send");
    								}
    							}
    						}
    					}
    				}
    				free(time);
    			}
    		}
    	}
    	return 0;
    }
    
    char *usernamebyip(userInfo *users, struct sockaddr_in *client)
    {
    	char *seekAddress = inet_ntoa(client->sin_addr);
    	int i = 0;
    	for(; i < MAXCONNECTIONS * 2; i++)
    		if(strcmp(users[i].ipaddress, seekAddress) == 0)
    			return users[i].name;
    	return "Unknown";
    }
    
    void getcurrenttime(char *str, char *newstr)
    {
    	int index = 0, i = 11;
    	for(; i < 19; i++)
    		newstr[index++] = str[i];
    		
    	newstr[index] = '\0';
    }
    
    void cutNewLine(char *str)
    {
    	int i = 0;
    	for(; i < strlen(str); i++)
    	{
    		if(str[i] == '\r')
    		{
    			str[i] = '\0';
    			break;
    		}
    	}
    }

Page 1 of 2 12 LastLast
Popular pages Recent additions subscribe to a feed

Similar Threads

  1. glibc detected - error
    By mr_m4x in forum C Programming
    Replies: 2
    Last Post: 03-15-2009, 10:29 AM
  2. *** glibc detected *** a.out: realloc()
    By msshapira in forum C Programming
    Replies: 9
    Last Post: 01-27-2009, 08:49 AM
  3. Replies: 1
    Last Post: 09-19-2008, 01:21 AM
  4. Replies: 7
    Last Post: 11-26-2007, 12:11 PM
  5. *** glibc detected *** free(): invalid next size
    By icebabe in forum C Programming
    Replies: 2
    Last Post: 05-24-2006, 12:09 PM

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21