Monday, September 3, 2007, 08:51 AM -
Programming
The program crashes with a bad segmentation fault.
You try hard looking at the code and run over it many times and dont see the error so easily.
So you attach your favorite debugger and step through it and it does not crash.For a developer, this scenario is very irritating. You become almost helpless when the tools that are supposed to detect flaws in your code dont.
There are many reasons why a program does not crash in your debugger. Today I will demonstrate with an example one of the "N" reasons why this could happen.
A little basics:
A Segmentation fault (SIGSEGV) occurs when a program attempts to access a memory location that it is not allowed to access, or attempts to access a memory location in a way that is not allowed.
It is possible that you or your debugger performed certain actions that has made a memory location readable or writable. Once this happens, then the code which used to seg-fault will run smoothly user the process space of the debugger. When you start a debugger like gdb, you have the ability to read data from any memory that you can access, provided you as a user has permission to do so.
The program you wrote must not have set permissions to access certain memory areas and may be trying to read from that memory.
Our example today is based on two processes, one writer and the other reader and they use POSIX shared memory to pass a message to the other.
The writer creates a shared memory segment. If it exists, it deletes it and creates a new segment. It then attaches to it, write a few bytes of data and detaches.
The reader attaches to the shared memory and tries to read the data and print. it then detaches and destroys the segment.
Thats all our program does. Below is the code straight from my build-box. You can simple copy and compile using the Makefile below. Discussion follows below ....
Code:------------------------------------------------------------
/* myshm.h */
#ifndef SHM_H
#define SHM_H
#define MY_SHARED_MEM_NAME "/my_shared_mem_name"
#define MY_SHARED_MEM_SIZE 1024 //1 kb
#define READER 0
#define WRITER 1
void* Attach(int rw_flag);
void Detach();
void Destroy();
void Write();
void Read();
#endif
----------------------------------------------------------
/* myshm.c */#include <stdio.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include "myshm.h"
void* mem_start = NULL;
void* Attach(int rw_flag) {
int ret = 0;
int fd = 0;
int shmExists = 0;
if (rw_flag == READER) {
fd = shm_open(MY_SHARED_MEM_NAME, O_RDWR ,S_IRWXU | S_IRWXG);
if (fd == -1) {
printf("Shared memory does not
exist..\n");
return NULL;
}
}
if (rw_flag == WRITER) {
fd = shm_open(MY_SHARED_MEM_NAME, O_RDWR | O_CREAT | O_EXCL, S_IRWXU | S_IRWXG);
if ((fd == -1) && (errno == EEXIST)) {
Destroy();
fd = shm_open(MY_SHARED_MEM_NAME,
O_RDWR | O_CREAT,
S_IRWXU | S_IRWXG);
if (fd == -1) {
return NULL;
}
}
} else if (fd == -1) {
return NULL;
}
if (rw_flag == WRITER) {
ret = ftruncate(fd, MY_SHARED_MEM_SIZE);
if (ret == -1) {
return NULL;
}
}
mem_start = mmap(0, MY_SHARED_MEM_SIZE, PROT_WRITE, MAP_SHARED, fd, 0);
if (mem_start == MAP_FAILED) {
return NULL;
}
ret = close(fd);
if (ret == -1) {
return NULL;
}
printf("Attached to shared memory at:%p\n",mem_start);
return mem_start;
}
void Detach() {
printf("Detaching...\n");
munmap(mem_start, MY_SHARED_MEM_SIZE);
return;
}
void Destroy() {
printf("Destroying shared memory..\n");
shm_unlink(MY_SHARED_MEM_NAME);
}
void Write() {
printf("Writing into shared memory..\n");
char* data = "NETOTTO";
memcpy(mem_start, (void*)data, 8);
return;
}
void Read() {
printf("Reading from shared memory..\n");
char data[8];
memcpy(data, mem_start, 8);
printf("Data is: %s\n", data);
return;
}
-----------------------------------------------------------
/* Writer.c */#include <stdio.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
#include "myshm.h"
int main() {
int ret = 0;
if (Attach(WRITER) == NULL) {
perror("Error attaching to shared memory\n");
exit(1);
}
Write();
Detach();
return 0;
}
------------------------------------------------------------
/* reader.c */#include <stdio.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
#include "myshm.h"
int main() {
int ret = 0;
if (Attach(READER) == NULL) {
perror("Error attaching to shared memory\n");
return(1);
}
Read();
Detach();
Destroy();
return 0;
}
-------------------------------------------------------------
/* Makefile */
default: all
all: writer reader
writer: myshm.o writer.o
g++ -g myshm.o writer.o -lrt -o writer
reader: myshm.o reader.o
g++ -g myshm.o reader.o -lrt -o reader
myshm.o: myshm.h myshm.c
gcc -c -g myshm.c
reader.o: reader.c
gcc -c -g reader.c
writer.o: writer.c
gcc -c -g writer.c
clean:
rm -f *.o reader writer
-----------------------------------------------------------
This is what happens when you run the writer followed by the reader.
[root@shuvavmrhel ]#
makegcc -c -g myshm.c
gcc -c -g writer.c
g++ -g myshm.o writer.o -lrt -o writer
gcc -c -g reader.c
g++ -g myshm.o reader.o -lrt -o reader
[root@shuvavmrhel ]#
./writer Attached to shared memory at:0xb7fff000
Writing into shared memory..
Detaching...
[root@shuvavmrhel ]#
./reader Attached to shared memory at:0xb7fff000
Reading from shared memory..
Segmentation fault[root@shuvavmrhel ]#
The core dump file would suggest that the segmentation error in question is coming from the function Read():myshm.c at the memcpy() statement as shown below:
void Read() {
printf("Reading from shared memory..\n");
char data[8];
memcpy(data, mem_start, 8); printf("Data is: %s\n", data);
return;
}
It is obvious that for some reason we are not allowed to read from the memory pointed by mem_start. If you run gdb and reach this line and try to access the memory pointed to by mem_start, it would display as follows:
81 memcpy(data, mem_start, 8);
(gdb)
x/2 mem_start0xb7fff000: 0x4f54454e 0x004f5454
(gdb) n
82 printf("Data is: %s\n", data);
(gdb) n
Data is: NETOTTO
84 }
You can now proceed with the gdb session. The memcpy works and we can see the data string "NETOTTO" printed for us.
No segmentation fault.
This is because when the reader memory mapped the shared memory into the process space, it did so with only the PROT_WRITE flag and not PROT_READ flag.
mem_start = mmap(0, MY_SHARED_MEM_SIZE, PROT_WRITE, MAP_SHARED, fd, 0);
The correct statement should have been
mem_start = mmap(0, MY_SHARED_MEM_SIZE,
PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
In gdb however, since we accessed the memory using the x/2 gdb-command, gdb acquired the read permissions and effectively the rest of our program which is running in the same space got the read permission for that memory segment. So it does not fail.
If we dont use the x/2 command in gdb, we would get the fault in gdb too.
This is a very important thing that I have learned and I am writing it here so that I dont waste another half a day scratching my head to figure out what went wrong.
Thoughts?