custom malloc, segmentation fault - c

I'm doing a custom malloc. I did a very simple one but now I'm trying to merge and split blocks in order to improve the efficiency of calls to sbrk(). when I try to execute a custom program with not many mallocs it works perfectly. But as soon as I try more mallocs or for example the command ls after some successful allocations, it ends giving a weird segmentation fault (core dumped) when calling the split function.
Any help or hint would be greatly appreciated.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include "struct.h"
static p_meta_data first_element = NULL;
static p_meta_data last_element = NULL;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
#define ALIGN8(x) (((((x)-1)>>3)<<3)+8)
#define MAGIC 0x87654321
void *malloc(size_t size_bytes);
p_meta_data search_available_space(size_t size_bytes);
p_meta_data request_space(size_t size_bytes);
p_meta_data merge(p_meta_data meta_data1, p_meta_data meta_data2);
void split(p_meta_data meta_data, size_t size_bytes);
void free(void *ptr);
void *calloc(size_t num_bytes, size_t num_blocs);
void *realloc(void *ptr, size_t size_bytes);
p_meta_data search_available_space(size_t size_bytes) {
p_meta_data current = first_element;
while (current && !(current->available && current->size_bytes >= size_bytes)){
fprintf(stderr, " %zu libre %d\n", current->size_bytes, current->available);
current = current->next;
}
if (current == NULL) {
fprintf(stderr, "null\n" );
} else {
fprintf(stderr, "%zu libre %d\n", current->size_bytes, current->available);
}
return current;
}
p_meta_data request_space(size_t size_bytes) {
if (size_bytes < 122880) {
size_bytes = 122880;
fprintf(stderr, "request %zu\n", size_bytes);
}
p_meta_data meta_data;
meta_data = (void *)sbrk(0);
if (sbrk(SIZE_META_DATA + size_bytes) == (void *)-1)
return (NULL);
meta_data->size_bytes = size_bytes;
meta_data->available = 0;
meta_data->magic = MAGIC;
meta_data->next = NULL;
meta_data->previous = NULL;
return meta_data;
}
p_meta_data merge(p_meta_data meta_data1, p_meta_data meta_data2) {
if (!meta_data1 || !meta_data2) {
return NULL;
}
meta_data1->size_bytes = meta_data1->size_bytes + SIZE_META_DATA + meta_data2->size_bytes;
meta_data1->next = meta_data2->next;
if (last_element == meta_data2) {
fprintf(stderr, "gleich\n");
last_element = meta_data1;
}
meta_data2 = NULL;
return meta_data1;
}
void free(void *ptr) {
p_meta_data meta_data;
if (!ptr)
return;
pthread_mutex_lock(&mutex);
meta_data = (p_meta_data)(ptr - SIZE_META_DATA);
if (meta_data->magic != MAGIC) {
fprintf(stderr, "ERROR free: value of magic not valid\n");
exit(1);
}
meta_data->available = 1;
fprintf(stderr, "Free at %x: %zu bytes\n", meta_data, meta_data->size_bytes);
p_meta_data meta_data_prev, meta_data_next;
meta_data_prev = meta_data->previous;
meta_data_next = meta_data->next;
if (meta_data_prev && meta_data_prev->available) {
meta_data = merge(meta_data_prev, meta_data);
}
if (meta_data_next && meta_data_next->available) {
meta_data = merge(meta_data, meta_data_next);
}
pthread_mutex_unlock(&mutex);
}
void split(p_meta_data meta_data, size_t size_bytes) {
if (!meta_data) {
fprintf(stderr, "no deberia entrar\n");
return;
}
p_meta_data meta_data2;
size_t offset = SIZE_META_DATA + size_bytes;
meta_data2 = (p_meta_data)(meta_data + offset);
fprintf(stderr, "size of metadata %d", meta_data->size_bytes - size_bytes - SIZE_META_DATA);
meta_data2->size_bytes = meta_data->size_bytes - size_bytes - SIZE_META_DATA;
meta_data2->available = 1;
meta_data2->magic = MAGIC;
meta_data2->previous = meta_data;
meta_data2->next = meta_data->next;
if (meta_data == last_element) {
last_element = meta_data2;
}
meta_data->size_bytes = size_bytes;
meta_data->next = meta_data2;
return;
}
void *malloc(size_t size_bytes) {
void *p, *ptr;
p_meta_data meta_data;
if (size_bytes <= 0) {
return NULL;
}
size_bytes = ALIGN8(size_bytes);
fprintf(stderr, "Malloc %zu bytes\n", size_bytes);
// Bloquegem perque nomes hi pugui entrar un fil
pthread_mutex_lock(&mutex);
meta_data = search_available_space(size_bytes);
if (meta_data) { // free block found
fprintf(stderr, "FREE BLOCK FOUND---------------------------------------------------\n");
meta_data->available = 0; //reservamos el bloque
} else { // no free block found
meta_data = request_space(size_bytes); //pedimos más espacio del sistema
if (!meta_data) //si meta_data es NULL (es decir, sbrk ha fallado)
return (NULL);
if (last_element) // we add the new block after the last element of the list
last_element->next = meta_data;
meta_data->previous = last_element;
last_element = meta_data;
if (first_element == NULL) // Is this the first element ?
first_element = meta_data;
}
fprintf(stderr, "die differenz %zu\n", meta_data->size_bytes - size_bytes);
if ((meta_data->size_bytes - size_bytes) > 12288) {
split(meta_data, size_bytes);
fprintf(stderr,"call split\n");
}
p = (void *)meta_data;
// Desbloquegem aqui perque altres fils puguin entrar
// a la funcio
pthread_mutex_unlock(&mutex);
// Retornem a l'usuari l'espai que podra fer servir.
ptr = p + SIZE_META_DATA; //p es puntero al inicio de meta_data, y ptr es el puntero al inicio del bloque de datos en sí (justo después de los metadatos)
return ptr;
}
void *calloc(size_t num_bytes, size_t num_blocs) {
size_t mem_to_get = num_bytes * num_blocs;
void *ptr = malloc(mem_to_get);
if (ptr == NULL) {
return ptr;
} else {
memset(ptr, 0, mem_to_get);
return ptr;
}
}
void *realloc(void *ptr, size_t size_bytes) {
fprintf(stderr, "realloc\n");
if (ptr == NULL) {
return malloc(size_bytes);
} else {
p_meta_data inic_bloc = (p_meta_data )(ptr - SIZE_META_DATA);
if (inic_bloc->size_bytes >= size_bytes) {
return ptr;
} else {
void *new_p = malloc(size_bytes);
memcpy(new_p, ptr, inic_bloc->size_bytes);
inic_bloc->available = 1;
return new_p;
}
}
}
where struct.h is:
#include <stddef.h>
#include <unistd.h>
#define SIZE_META_DATA sizeof(struct m_meta_data)
typedef struct m_meta_data *p_meta_data;
/* This structure has a size multiple of 8 */
struct m_meta_data {
size_t size_bytes;
int available;
int magic;
p_meta_data next;
p_meta_data previous;
};

Here are some remarks about your code:
it is confusing for the reader to hide pointers behind typedefs. Why not define m_meta_data as a typedef for struct m_meta_data and use m_meta_data * everywhere?
are you sure sbrk() is properly defined? The cast (void *)sbrk(0) seems to indicate otherwise. sbrk() is declared in <unistd.h> on POSIX systems.
BUG in split(), the computation meta_data2 = (p_meta_data)(meta_data + offset); is incorrect. It should be:
meta_data2 = (p_meta_data)((unsigned char *)meta_data + offset);
you should define strdup() and strndup() as the definitions from the C library might not call your redefined malloc():
char *strdup(const char *s) {
size_t len = strlen(s);
char *p = malloc(len + 1);
if (p) {
memcpy(p, s, len + 1);
}
return p;
}
char *strndup(const char *s, size_t n) {
size_t len;
char *p;
for (len = 0; len < n && s[n]; len++)
continue;
if ((p = malloc(len + 1)) != NULL) {
memcpy(p, s, len);
p[len] = '\0';
}
return p;
}
blocks allocated with malloc() should be aligned on 16-byte boundaries on 64-bit intel systems. As a matter of fact, the m_meta_data structure has a size of 32 bytes on 64-bit systems, but 20 bytes on 32-bit systems. You should adjust your m_meta_data structure for 32-bit systems.
you should check for overflow in size_t mem_to_get = num_bytes * num_blocs;
you should not rely on void * arithmetics, it is a gcc extension. Write this instead:
p_meta_data inic_bloc = (p_meta_data)ptr - 1;
in realloc(), when extending the size of the block, you just make the original block available but you do not coalesce it with adjacent blocks as you do in free(). You might just call free(ptr), especially since modifying inic_bloc->available = 1; without getting the lock seems risky.
you should check meta_data->available in free() and realloc() to detect invalid calls and prevent arena corruption.
in malloc(), you forget to release the lock in case of allocation failure.
calling fprintf while the lock is set is risky: if fprintf calls malloc, you would get a deadlock. You might assume that printing to stderr does not call malloc() because stderr is unbuffered, but you are taking chances.
when allocating a new block with sbrk(), you should use sbrk(0) after the allocation to determine the actual size made available as it may have been rounded up to a multiple of PAGE_SIZE.
you should split blocks if (meta_data->size_bytes - size_bytes) > SIZE_META_DATA. The current test is far too loose.

Related

shared array with mmap is not visible between different processes

I'm trying to write an implementation of malloc and free and a shared stack (that gets commands to push pop and top from clients connecting to server).
For some reason when I open 2 clients the memory of the stack is not visible to both of them. (top of the stack is different).
I'll mention that when I implemented this exercise with threads and sbrk it worked perfectly so I suspect it has something to do with how I use the mmap function.
This is the malloc function:
#include<stddef.h>
# include "funcs.hpp"
#include <sys/mman.h>
#include <stdlib.h>
char* memory= (char*)mmap(NULL, sizeof(char)*40000, PROT_READ|PROT_WRITE , MAP_SHARED | MAP_ANONYMOUS, -1, 0);
struct block* mata_data_list=(struct block*)memory;
void initialize(){
mata_data_list->size= 40000 - sizeof(struct block);
mata_data_list->is_available=1;
mata_data_list->next_meta_data=NULL;
}
void split(struct block *old, size_t size){
struct block *new_block=(struct block*)((int*)old + size + sizeof(struct block));
new_block->size= (old->size) - size - sizeof(struct block);
new_block->is_available=1;
new_block->next_meta_data=old->next_meta_data;
old->size=size;
old->is_available=0;
old->next_meta_data=new_block;
}
void *malloc(size_t number_of_bytes){
struct block *curr;
void *result;
if(!(mata_data_list->size)){
initialize();
}
curr=mata_data_list;
while((((curr->size) < number_of_bytes) || ((curr->is_available) == 0)) && (curr->next_meta_data != NULL)){
curr=curr->next_meta_data;
}
if((curr->size) == number_of_bytes){
curr->is_available=0;
result=(void*)(++curr);
return result;
}
else if((curr->size)>(number_of_bytes + sizeof(struct block))){
split(curr, number_of_bytes);
result=(void*)(++curr);
return result;
}
else{
result=NULL;
return result;
}
}
void merge(){
struct block *curr;
curr=mata_data_list;
while((curr->next_meta_data) != NULL){
if((curr->is_available) && (curr->next_meta_data->is_available)){
curr->size+= (curr->next_meta_data->size) + sizeof(struct block);
curr->next_meta_data=curr->next_meta_data->next_meta_data;
}
curr=curr->next_meta_data;
}
}
void free(void* ptr) throw(){
if(((void*)memory<=ptr)&&(ptr<=(void*)(memory+40000))){
struct block* curr=(struct block*)ptr;
--curr;
curr->is_available=1;
merge();
}
}
void *calloc(size_t nitems, size_t size){
void* ptr = malloc(nitems*size);
memset(ptr, 0, nitems*size);
return ptr;
}
This is the relevant server code:
if (!fork()) { // this is the child process
int pid = getpid();
handle_stack(pid, new_fd);
}

How to detect if a block of memory already freed

I already know that there is no way to know if a pointer target still a valid allocation of it's already freed, so I'm trying to use pointer to pointer to solve this but it didn't work.
My objective is simply making print_block() detect if block pointer is Null or not.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void free_block(u_int8_t **_block) {
// Dereference
u_int8_t *block = *_block;
// Info
printf("free_block()\t-> %p Point To %p \n", &block, block);
// Free Block
free(block);
block = NULL;
}
void print_block(u_int8_t **_block) {
// Dereference
u_int8_t *block = *_block;
// Detectc if this block is freed
// This is the objective of this code
if(block == NULL) {
printf("print_block()\t-> %p Is Null.\n", block);
return;
}
// Info
printf("print_block()\t-> %p Point To %p -> ", &block, block);
// Print byte by byte
u_int8_t *p = block;
for(int i = 0; i < 3; i++) {
printf("0x%02X ", *(u_int8_t *)p);
p++;
}
printf("\n");
}
int main(void) {
// Allocat a block in the memory
u_int8_t *block = malloc(3 * sizeof(u_int8_t));
// Set all to zeros
memset(block, 0x00, 3);
// Info
printf("Main()\t\t\t-> %p Point To %p \n", &block, block);
// Print the block content
print_block(&block);
// Free the block
free_block(&block);
// Print the block content gain
// This shold print Null because
// we freed the block.
print_block(&block);
return 0;
}
Result
Main() -> 0x7fffd549cc58 Point To 0xfa42a0
print_block() -> 0x7fffd549cc28 Point To 0xfa42a0 -> 0x00 0x00 0x00
free_block() -> 0x7fffd549cc60 Point To 0xfa42a0
print_block() -> 0x7fffd549cc28 Point To 0xfa42a0 -> 0xA4 0x0F 0x00
First, you need to be aware that the function free(p) will just flag a block of memory to be available for any new allocation, that's it, nothing else; While your pointer p still Valid, and you can Read and Write using it even if the block is already freed.
About your question "How to detect if a block of memory is already freed?" the short answer in C is there is no standard way. But you can write your own pointer-tracker to detect if a block of memory is already freed, which is not hard to do, this is an example:
void *ptr_list[64];
int ptr_position = 0;
bool ptr_exist(void *p) {
if(p == NULL)
return false;
for(int i = 0; i < ptr_position; i++) {
if(ptr_list[i] == p)
return true;
}
return false;
}
void ptr_add(void *p) {
if(p == NULL)
return;
if(!ptr_exist(p)) {
for(int i = 0; i < ptr_position; i++) {
if(ptr_list[i] == NULL) {
ptr_list[i] = p;
return;
}
}
ptr_list[ptr_position] = p;
ptr_position++;
}
}
void ptr_free(void **p) {
if(*p == NULL)
return;
for(int i = 0; i < ptr_position; i++) {
if(ptr_list[i] == *p) {
ptr_list[i] = NULL;
}
}
for(int i = ptr_position; i >= 0; i--) {
if(ptr_list[i] == NULL) {
ptr_position = i;
break;
}
}
free(*p);
*p = NULL;
}
To use it, simply after you allocate a block of memory add your pointer to the tracker using ptr_add(), and when you want to free it, use ptr_free(). Finally you can check at any moment from any thread if this block of memory still valid or not using ptr_exist().
Full code :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
// -- Pointers Tracker --
void *ptr_list[64];
int ptr_position = 0;
bool ptr_exist(void *p) {
if(p == NULL)
return false;
for(int i = 0; i < ptr_position; i++) {
if(ptr_list[i] == p)
return true;
}
return false;
}
void ptr_add(void *p) {
if(p == NULL)
return;
if(!ptr_exist(p)) {
for(int i = 0; i < ptr_position; i++) {
if(ptr_list[i] == NULL) {
ptr_list[i] = p;
return;
}
}
ptr_list[ptr_position] = p;
ptr_position++;
}
}
void ptr_free(void **p) {
if(*p == NULL)
return;
for(int i = 0; i < ptr_position; i++) {
if(ptr_list[i] == *p) {
ptr_list[i] = NULL;
}
}
for(int i = ptr_position; i >= 0; i--) {
if(ptr_list[i] == NULL) {
ptr_position = i;
break;
}
}
free(*p);
*p = NULL;
}
// ----------------------
void free_block(u_int8_t *block) {
// Info
printf("free_block()\t-> %p Point To %p \n", &block, block);
// Free Block
// free(block);
// block = NULL;
ptr_free((void *)&block);
}
void print_block(u_int8_t *block) {
// Detectc if this block is freed
// This is the objective of this code
if(!ptr_exist(block)) {
printf("print_block()\t-> %p Is Null.\n", block);
return;
}
// Info
printf("print_block()\t-> %p Point To %p -> ", &block, block);
// Print byte by byte
u_int8_t *p = block;
for(int i = 0; i < 3; i++) {
printf("0x%02X ", *(u_int8_t *)p);
p++;
}
printf("\n");
}
int main(void) {
// Allocat a block in the memory
u_int8_t *block = malloc(3 * sizeof(u_int8_t));
// Add it to the tracker
ptr_add((void *)block);
// Set all to zeros
memset(block, 0x00, 3);
// Info
printf("Main()\t\t\t-> %p Point To %p \n", &block, block);
// Print the block content
print_block(block);
// Free the block
free_block(block);
// Print the block content gain
// This shold print Null because
// we freed the block.
print_block(block);
return 0;
}
You can use a Block struct and a bit of discipline.
As an example
typedef struct
{
size_t size;
uint8_t fill;
uint8_t* data;
} Block;
Block* free_block(Block*);
Block* get_block(size_t size, uint8_t fill_value);
void print_block(Block* block, const char* title);
and
make the functions operate on pointers to Block, encapsulating the data
make free_block() return NULL to ease the task of invalidating the block pointer
test the block address inside print_block()
use an optional title in print_block()
main as an example
int main(void)
{
const int test_size = 32;
Block* block = get_block(test_size, 0); // zero-filled
printf(
"Main()\t\t\t-> Pointer at %p points to data at "
"%p\n",
&block, block->data);
print_block(block, "\nBlock contents:");
block = free_block(block);
print_block(block, "\nafter free()");
return 0;
}
Maybe you find this way easier to read.
output of main as above
Main() -> Pointer at 00CFF754 points to data at 00E96C70
Block contents
print_block()-> valid block data of size 32 at 00E96C70
fill char is 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
after free()
print_block() -> block is null.
complete code
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct
{
size_t size;
uint8_t fill;
uint8_t* data;
} Block;
Block* free_block(Block*);
Block* get_block(size_t size, uint8_t fill_value);
void print_block(Block* block, const char* title);
int main(void)
{
const int test_size = 32;
Block* block = get_block(test_size, 0); // 3 bytes, zero filled
printf(
"Main()\t\t\t-> Pointer at %p points to data at "
"%p\n",
&block, block->data);
print_block(block, "\nBlock contents:");
block = free_block(block);
print_block(block, "\nafter free()");
return 0;
}
Block* free_block(Block* block)
{
if (block == NULL) return NULL;
if (block->data == NULL) return NULL;
free(block->data); // data is free
free(block);
return NULL;
}
Block* get_block(size_t size, uint8_t fill)
{
Block* block = malloc(sizeof(Block));
if (block == NULL) return NULL;
block->data = malloc(size * sizeof(uint8_t));
if (block->data == NULL) return NULL;
memset(block->data, fill, size);
block->fill = fill;
block->size = size;
return block;
}
void print_block(Block* block, char* title)
{
if (title != NULL) printf("%s\n", title);
if (block == NULL)
{
printf("print_block()\t-> block is null.\n");
return;
}
printf(
"print_block()->\tvalid block data of size %d at "
"%p\n\t\tfill char is %02X\n\n",
block->size, block->data, block->fill);
// Print byte by byte
const int nc = 16; // 16 bytes per line
size_t j = 1;
for (size_t i = 0; i < block->size; i += 1)
{
printf("%02X ", block->data[i]);
if (j >= nc)
printf("\n"), j = 1;
else
j += 1;
}
if ( j != 1 ) printf("\n");
return;
}
There is no standard way to tell if a pointer is valid, nor if it points to an allocated object nor if it was freed already.
Yet it is possible to design a framework on top of the allocation functions to keep track of all allocations and deallocations and maintain a list of valid pointers. Looking up a pointer value in this list will tell you if the pointer points to a valid allocated block and possibly what size was requested from the heap.
Here is a simplistic implementation:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void track_add_pointer(void *p, size_t size);
int track_find_pointer(void *p, size_t *sizep);
void track_remove_pointer(void *p);
void *tmalloc(size_t size) {
void *p = (malloc)(size);
if (p) {
track_add_pointer(p, size);
}
return p;
}
void *tcalloc(size_t size, size_t nmemb) {
void *p = (calloc)(size, nmemb);
if (p) {
track_add_pointer(p, size * nmemb);
}
return p;
}
void *trealloc(void *p, size_t size) {
if (p) {
void *newp = NULL;
if (!track_find_pointer(p, NULL)) {
fprintf(stderr, "trealloc: invalid pointer %p\n", p);
} else {
if (size == 0) {
(free)(p);
} else {
newp = (realloc)(p, size);
}
if (newp != p) {
if (p) track_remove_pointer(p);
if (newp) track_add_pointer(newp);
}
}
return newp;
} else {
return tmalloc(size);
}
}
void tfree(void *p) {
if (p) {
if (!track_find_pointer(p, NULL)) {
fprintf(stderr, "tfree: invalid pointer %p\n", p);
} else {
(free)(p);
track_remove_pointer(p);
}
}
}
char *tstrdup(const char *s) {
char *p = NULL;
if (s) {
size_t len = strlen(s);
p = tmalloc(len + 1);
if (p) {
memcpy(p, s, len + 1);
}
} else {
fprintf(stderr, "tstrdup: null pointer\n");
}
return p;
}
char *tstrndup(const char *s, size_t n) {
char *p = NULL;
if (s) {
size_t len = strnlen(s, n);
p = tmalloc(len + 1);
if (p) {
memcpy(p, s, len);
p[len] = '\0';
}
} else {
fprintf(stderr, "tstrndup: null pointer\n");
}
return p;
}

Will memory not freed cause segmentation fault in C?

I've just encountered a very strange bug. I was doing unit-test for a simple function as below.
UPDATE: Thanks #Bodo, here's the minimal working example. You can simply compile and run tokenizer.c.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
/* ============================= BOOL =============================== */
#ifndef _BOOL_
#define _BOOL_
typedef enum {
true, false
} bool;
#endif // _BOOL_
/* ============================= STACK =============================== */
#ifndef _STACK_
#define _STACK_
typedef void (*stack_freefn)(void *elemAddr);
typedef struct {
size_t size; // number of element allowed
int ite; // point to the current last element
size_t elemSize; // size of each element (how many bytes)
void *elems; // stockage of elements
stack_freefn freefn; // free memory allocated for each element if necessary
} stack;
/* constructor */
void new_stack(stack *s, const size_t size, const size_t elemSize, stack_freefn freefn) {
s->size = size;
s->ite = 0;
s->elemSize = elemSize;
s->elems = malloc(size * elemSize);
s->freefn = freefn;
}
/* free memory */
void dispose_stack(stack *s) {
if (s->freefn != NULL) {
while (s->ite > 0) {
void *elemAddr = (char *)s->elems + --s->ite * s->elemSize;
s->freefn(elemAddr);
}
}
free(s->elems);
s->elems = NULL;
}
/* push one new element on the top */
void push_stack(stack *s, const void *value, const size_t elemSize) {
if (s->ite == s->size) {
s->size *= 2;
s->elems = realloc(s->elems, s->size * s->elemSize);
}
void *elemAddr = (char *)s->elems + s->elemSize * s->ite++;
memcpy(elemAddr, value, s->elemSize);
}
/* pop our the element on the top */
void pop_stack(stack *s, void *res) {
if (s->ite > 0) {
void *elemAddr = (char *)s->elems + ((s->ite - 1) * s->elemSize);
memcpy(res, elemAddr, s->elemSize);
s->ite--;
}
}
void clear_stack(stack *s) {
if (s->freefn != NULL) {
while (s->ite > 0) {
void *elemAddr = (char *)s->elems + --s->ite * s->elemSize;
s->freefn(elemAddr);
}
} else {
s->ite = 0;
}
}
size_t stack_size(stack *s) {
return s->ite;
}
#endif // _STACK_
/* ============================= VECTOR =============================== */
#ifndef _VECTOR_
#define _VECTOR_
typedef int (*VectorCompareFunction)(const void *elemAddr1, const void *elemAddr2);
typedef void (*VectorFreeFunction)(void *elemAddr);
typedef struct {
int elemSize; //how many byte for each element
int elemNum; //number of current element in vector
int capacity; //maximum number of element vector can hold
void *elems; //pointer to data memory
VectorFreeFunction freefn; //pointer to the function used to free each element
} vector;
/**
* Reallocate a new memory of twice of original size
* return 1 if reallocation success, otherwise return -1.
*/
static void DoubleMemory(vector *v) {
void *tmp = realloc(v->elems, v->capacity * v->elemSize * 2);
assert(tmp != NULL);
v->elems = tmp;
v->capacity *= 2;
}
/**
* Constructor
*/
void VectorNew(vector *v, int elemSize, VectorFreeFunction freefn, int initialAllocation) {
v->elems = malloc(initialAllocation * elemSize);
assert(v->elems != NULL);
v->elemSize = elemSize;
v->elemNum = 0;
v->capacity = initialAllocation;
v->freefn = freefn;
}
/**
* Frees up all the memory of the specified vector.
*/
void VectorDispose(vector *v) {
if (v->freefn != NULL) {
for (; v->elemNum > 0; v->elemNum--) {
void *elemAddr = (char *)v->elems + (v->elemNum - 1) * v->elemSize;
v->freefn(elemAddr);
}
}
free(v->elems);
v->elems = NULL;
}
/**
* Returns the logical length of the vector.
*/
int VectorLength(const vector *v) {
return v->elemNum;
}
/**
* Appends a new element to the end of the specified vector.
*/
void VectorAppend(vector *v, const void *elemAddr) {
/* double size if neccessary */
if (v->elemNum == v->capacity) DoubleMemory(v);
memcpy((char *)v->elems + v->elemNum * v->elemSize, elemAddr, v->elemSize);
v->elemNum++;
}
/**
* Search the specified vector for an element whose contents match the element passed as the key.
*/
int VectorSearch(const vector *v, const void *key, VectorCompareFunction searchfn, int startIndex, bool isSorted) {
assert(key && searchfn);
if (v->elemNum == 0) return -1;
assert(startIndex >= 0 && startIndex < v->elemNum);
if (isSorted == true) {
/* binary search */
void *startAddr = (char *)v->elems + startIndex * v->elemSize;
int size = v->elemNum - startIndex;
void *resAddr = bsearch(key, startAddr, size, v->elemSize, searchfn);
return (resAddr != NULL)? ((char *)resAddr - (char *)v->elems) / v->elemSize : -1;
} else {
/* linear search */
for (int i = 0; i < v->elemNum; i++) {
if (searchfn((char *)v->elems + i * v->elemSize, key) == 0) {
return i;
}
}
return -1;
}
}
#endif // _VECTOR_
/* ============================= TOKENIZER =============================== */
/**
* Dump current string into vector as a new word.
* Strings are null-terminated.
*/
static void dumpstack(stack *s, vector *v) {
size_t len = stack_size(s);
char *word = (char *)malloc((len + 1) * sizeof(char)); // +1 for null-terminator
for (int i = len - 1; i >= 0; i--) {
pop_stack(s, word + i * sizeof(char));
}
word[len] = '\0';
VectorAppend(v, &word);
clear_stack(s);
}
static const size_t kTokenStackDefaultSize = 64;
static void tokenize(vector *words, char *stream) {
stack s;
new_stack(&s, kTokenStackDefaultSize, sizeof(char), NULL);
size_t len = strlen(stream);
bool begin = false;
char c;
for (int i = 0; i < len; i++) {
c = stream[i];
/* =============================== My printf() is here ============================== */
// printf("char c = [%c]\n", c);
/* =============================== My printf() is here ============================== */
if (isalpha(c) || isdigit(c)) {
if (begin == false) begin = true;
char lower = tolower(c);
push_stack(&s, &lower, sizeof(char));
} else if (c == '-') {
if (begin == true) { // case: covid-19
push_stack(&s, &c, sizeof(char));
} else {
if (i < len - 1 && isdigit(stream[i + 1])) { // case: -9
begin = true;
push_stack(&s, &c, sizeof(char));
} else {
if (begin == true) {
dumpstack(&s, words);
begin = false;
}
}
}
} else if (c == '.' && begin == true) { // case: 3.14
if (isdigit(stream[i - 1])) {
push_stack(&s, &c, sizeof(char));
} else {
if (begin == true) {
dumpstack(&s, words);
begin = false;
}
}
} else {
if (begin == true) {
dumpstack(&s, words);
begin = false;
}
}
}
if (begin == true) {
dumpstack(&s, words);
begin = false;
}
dispose_stack(&s);
}
/* ============================= UNIT-TEST =============================== */
/**
* HashSetFreeFunction<char *>
*/
static void freestr(void *elemAddr) {
char *str = *(char **)elemAddr;
free(str);
}
/**
* HashSetCompareFunction<char *>
*/
static int compstr(const void *elemAddr1, const void *elemAddr2) {
char *str1 = *(char **)elemAddr1;
char *str2 = *(char **)elemAddr2;
return strcmp(str1, str2);
}
static void test_tokenize(void) {
printf("Testing Tokenizer.c::tokenize() ...\n");
char *sentence = "Covid-19: Top adviser warns France at 'emergency' virus moment - BBC News\nPi = 3.14\n-1 is negative.";
vector words;
VectorNew(&words, sizeof(char *), freestr, 256);
tokenize(&words, sentence);
char *musthave[] = {"covid-19", "top", "3.14", "-1"};
char *musthavenot[] = {"-", "1"};
assert(VectorLength(&words) == 16);
for (int i = 0; i < sizeof(musthave)/sizeof(char *); i++) {
assert(VectorSearch(&words, &musthave[i], compstr, 0, false) != -1);
}
for (int i = 0; i < sizeof(musthavenot)/sizeof(char *); i++) {
assert(VectorSearch(&words, &musthavenot[i], compstr, 0, false) == -1);
}
VectorDispose(&words);
printf("[ALL PASS]\n");
}
int main(void) {
test_tokenize();
}
I've got segmentation fault at first.
[1] 4685 segmentation fault testtokenizer
But when I add a printf() to debug, the segmentation fault was gone and the test passed. After comment out the printf, the function still works. I was so confused.
Just recall that before this test, I tested some memory dispose function, and perhaps had left some unfreed blocks in memory. Will that be the reason for fleeting segmentation fault? Thx bros.
UPDATE:
Now I can't even reproduce this bug myself. tokenizer.c above can pass the unit-test. I thought it might caused by makefile prerequisite rules. gcc didn't re-compile some object files when source code is changed.
Thanks #Steve Summit, you make it clear that unfreed memory will not cause segmentation fault.
Thanks #schwern for code review, it's really helpful.
But when I add a printf() to debug, the segmentation fault was gone and the test passed. After comment out the printf, the function still works. I was so confused.
They call it undefined behavior, because its behavior is undefined. Seemingly unrelated operations might nudge things just a bit to make the code "work" but they're only tangentially related to the problem.
I tested some memory dispose function, and perhaps had left some unfreed blocks in memory. Will that be the reason for fleeting segmentation fault?
No. It does mean the memory is unreferencable and "leaked". The memory will be freed to the operating system when the process exits.
The problem must lie elsewhere. Without seeing your whole program we can't say for sure, but two fishy things stand out.
You're defining a fixed sized stack, but you're pushing onto it an indeterminate number of times. Unless push_stack has protection against this, you will walk off your allocated memory.
You're storing references to variables on the stack. lower, c
char lower = tolower(c);
push_stack(&s, &lower, sizeof(char));
Once lower goes out of scope it will automatically be freed and the memory reused. &lower is invalid once tokenize returns. This seems to be fine if your stack only lasts the length of the function, but it's worth noting.
And it's possible new_stack, push_stack, or dumpstack are doing something incorrect.

making malloc function doesn't work correctly?

Hi i wrote a simple malloc and free function. The idea is basically from "The C Programming language". Now i have the problem like if I malloc 3 times and i free it in the same order. My allocp is near to the end but the whole memory is free. I give you my example now. My problem is with the 3 frees. Like when i free 4, 3, 2 its ok because you free the allocp down in the correct order, but i don't know how to solve my problem.
Any ideas?
Thanks for your help :)
#include "mystdlib.h"
#include <stdio.h>
#define ALLOCSIZE 1048576 /* Groeße des verfuegbaren Speichers */
static char allocbuf[ALLOCSIZE]; /* Speicher fuer mymalloc */
static char *allocp = allocbuf; /* next free position */
void *mymalloc(size_t size) {
if(allocp + size <= allocbuf + ALLOCSIZE) {
allocp += size;
return (allocp - size);
}
else {
return NULL;
}
}
void myfree(void *ptr) {
if(ptr >= (void*)allocbuf && ptr < (void*)allocbuf + ALLOCSIZE) {
allocp = ptr;
}
}
int main(){
char *buf1 = mymalloc(900000);
if (buf1 == NULL) {
printf("Error 404, allocated memory not found...\n");
return -1;
}
myfree(buf1);
char *buf2 = mymalloc(500000);
char *buf3 = mymalloc(400000);
char *buf4 = mymalloc(300000);
if (buf2 == NULL || buf3 == NULL) {
printf("Fix your mymalloc!\n");
return -1;
}
if (buf4 == NULL) {
printf("This was supposed to happen. Very good!\n");
} else {
printf("Nope. That's wrong. Very wrong.\n");
return -1;
}
myfree(buf2);
myfree(buf3);
myfree(buf4);
char *buf5 = mymalloc(900000);
if (buf5 == NULL) {
printf("You got so far, but your error free journey ends here. Because an error occured.\n");
return -1;
}
char *buf6 = myrealloc(buf5, 500000);
myfree(buf6);
printf("Congrats, you passed all tests. Your malloc and free seem to work\n");
}

linux kernel module memory checker

I'm developing a kernel memory checker to find memory leaks in kernel space.
I have two functions profile_vmalloc and profile_vfree, profile_vmalloc uses vmalloc to allocate memory and adds memory allocated info to a list, profile_vfree frees the allocated memory and removes that info from the list. How to replace vmalloc and vfree so that when I compile and run my module any kernel module allocating and freeing memory will use my functions ?
How do I use sysfs to create a read only file that will allow the user to see a summary of memory leaks?
This is what my code looks like so far
// Linux Kernel headers
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/config.h>
#include <linux/vmalloc.h>
#define FILE_NAME_LENGTH 256
#define vmalloc(size) profile_malloc (block_size, __FILE__, __LINE__)
#define vfree(mem_ref) profile_free(mem_ref)
struct _MEM_INFO
{
const void *address;
size_t size;
char file_name[FILE_NAME_LENGTH];
size_t length;
};
typedef struct _MEM_INFO MEM_INFO;
struct _MEM_LEAK {
MEM_INFO mem_info;
struct _MEM_LEAK * next;
};
typedef struct _MEM_LEAK MEM_LEAK;
#undef vmalloc
#undef vfree
static MEM_PROFILER_LIST * ptr_start = NULL;
static MEM_PROFILER_LIST * ptr_next = NULL;
/*
* adds allocated memory info. into the list
*
*/
void add(MEM_INFO alloc_info)
{
MEM_PROFILER_LIST * mem_leak_info = NULL;
mem_leak_info = (MEM_PROFILER_LIST *) malloc (sizeof(MEM_PROFILER_LIST));
mem_leak_info->mem_info.address = alloc_info.address;
mem_leak_info->mem_info.size = alloc_info.size;
strcpy(mem_leak_info->mem_info.file_name, alloc_info.file_name);
mem_leak_info->mem_info.line = alloc_info.line;
mem_leak_info->next = NULL;
if (ptr_start == NULL)
{
ptr_start = mem_leak_info;
ptr_next = ptr_start;
}
else {
ptr_next->next = mem_leak_info;
ptr_next = ptr_next->next;
}
}
/*
* remove memory info. from the list
*
*/
void erase(unsigned pos)
{
unsigned int i = 0;
MEM_PROFILER_LIST * alloc_info, * temp;
if(pos == 0)
{
MEM_PROFILER_LIST * temp = ptr_start;
ptr_start = ptr_start->next;
free(temp);
}
else
{
for(i = 0, alloc_info = ptr_start; i < pos;
alloc_info = alloc_info->next, ++i)
{
if(pos == i + 1)
{
temp = alloc_info->next;
alloc_info->next = temp->next;
free(temp);
break;
}
}
}
}
/*
* deletes all the elements from the list
*/
void clear()
{
MEM_PROFILER_LIST * temp = ptr_start;
MEM_PROFILER_LIST * alloc_info = ptr_start;
while(alloc_info != NULL)
{
alloc_info = alloc_info->next;
free(temp);
temp = alloc_info;
}
}
/*
* profile of vmalloc
*/
void * profile_vmalloc (unsigned int size, const char * file, unsigned int line)
{
void * ptr = vmalloc (size);
if (ptr != NULL)
{
add_mem_info(ptr, size, file, line);
}
return ptr;
}
/*
* profile of free
*/
void profile_free(void * mem_ref)
{
remove_mem_info(mem_ref);
free(mem_ref);
}
/*
* gets the allocated memory info and adds it to a list
*
*/
void add_mem_info (void * mem_ref, unsigned int size, const char * file, unsigned int line)
{
MEM_INFO mem_alloc_info;
/* fill up the structure with all info */
memset( &mem_alloc_info, 0, sizeof ( mem_alloc_info ) );
mem_alloc_info.address = mem_ref;
mem_alloc_info.size = size;
strncpy(mem_alloc_info.file_name, file, FILE_NAME_LENGTH);
mem_alloc_info.line = line;
/* add the above info to a list */
add(mem_alloc_info);
}
/*
* if the allocated memory info is part of the list, removes it
*
*/
void remove_mem_info (void * mem_ref)
{
unsigned int i;
MEM_PROFILER_LIST * leak_info = ptr_start;
/* check if allocate memory is in our list */
for(i = 0; leak_info != NULL; ++i, leak_info = leak_info->next)
{
if ( leak_info->mem_info.address == mem_ref )
{
erase ( i );
break;
}
}
}
/*
* writes a memory leak summary to a file
*/
void mem_leak_summary(void)
{
unsigned int i;
MEM_PROFILER_LIST * mem_output;
FILE * fp_write = fopen (SUMMARY_FILE, "wt");
char info[1024];
if(fp_write != NULL)
{
fwrite(info, (strlen(info) + 1) , 1, fp_write);
sprintf(info, "%s\n", "-----------------------------------");
fwrite(info, (strlen(info) + 1) , 1, fp_write);
for(mem_output= ptr_start; mem_output!= NULL; mem_output= mem_output->next)
{
sprintf(info, "address : %d\n", leak_info->mem_output.address);
fwrite(info, (strlen(info) + 1) , 1, fp_write);
sprintf(info, "size : %d bytes\n", leak_info->mem_output.size);
fwrite(info, (strlen(info) + 1) , 1, fp_write);
sprintf(info, "line : %d\n", leak_info->mem_output.line);
fwrite(info, (strlen(info) + 1) , 1, fp_write);
sprintf(info, "%s\n", "-----------------------------------");
fwrite(info, (strlen(info) + 1) , 1, fp_write);
}
}
clear();
}
static int __init profiler_init(void)
{
return 0;
}
static void __exit profiler_cleanup(void)
{
printk("profiler module uninstalled\n");
}
module_init(profiler_init);
module_exit(profiler_cleanup);
MODULE_LICENSE("GPL");
How to replace vmalloc and vfree so
that when I compile and run my module any kernel module allocating and
freeing memory will use my functions ?
Step 1. In the files that call vmalloc & vfree, define macros:
#define vmalloc(x) profile_vmalloc(x)
#define vfree(x) profile_vfree(x)
This will ensure that your functions are called.
Step 2: In your definition of the profile_* functions, do you accounting and call the actual vmalloc/vfree functions.
You are already doing the step 2, so you just need to take care of step 1.

Resources