Segmentation fault: Problemas ao trabalhar com alocação dinâmica e estruturas de endereço Internet [

1. Segmentation fault: Problemas ao trabalhar com alocação dinâmica e estruturas de endereço Internet [

97-3048-567-XS32
97-3048-567-XS32

(usa Outra)

Enviado em 03/07/2021 - 10:58h

Já analisei o código abaixo com o valgrind (na verdade não sei utilizar isso ainda direito), mas ainda não entendi qual erro de alocação específico que está levando ao programa dar segmentation fault em tempo de execução.

Segue o código:

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <stdbool.h>

#include <netdb.h>
#include <unistd.h>
#include <sys/un.h>
#include <sys/times.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define MAXCONN 3
#define SNDTIMEO 5

union sockaddr_any{

struct sockaddr generic;
struct sockaddr_in in;
struct sockaddr_in6 in6;
struct sockaddr_un un;
};

static bool set_sock_opts(int sockfd, size_t sndtimeo, bool only_ipv6) {

const int optval = 1;
struct timeval tm = {sndtimeo, 0};

if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0 ) {

return false;
}

if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tm, sizeof(tm)) < 0 ) {

return false;
}

if (only_ipv6 == true) {

if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &optval, sizeof(optval)) < 0 ) {

return false;
}
}

return true;
}

static int make_server_socket(int addr_family, const char *service, bool only_ipv6, size_t sndtimeo, size_t somaxconn) {

int sockfd, errcode;
struct addrinfo hints, *result = NULL, *rp = NULL;

memset(&hints, 0, sizeof(hints));

hints.ai_flags = AI_PASSIVE;
hints.ai_family = addr_family;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;

if ((errcode = getaddrinfo(NULL, service, &hints, &result)) < 0 ) {

fprintf(stderr, "make_server_socket() -> getaddrinfo() -> ERROR: %s\n", gai_strerror(errcode));

exit(EXIT_FAILURE);
}

for (rp = result; rp != NULL; rp = rp->ai_next) {

if ((sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) < 0 ) {

continue;
}

if ((set_sock_opts(sockfd, sndtimeo, only_ipv6)) != true ) {

close(sockfd);

continue;
}

if ((bind(sockfd, rp->ai_addr, rp->ai_addrlen)) == 0 ) {

break;
}

close(sockfd);
}

if (!rp) {

fprintf(stderr, "make_server_socket() -> socket()/bind() -> ERROR: %s\n", strerror(errno));

freeaddrinfo(result);

exit(EXIT_FAILURE);
}

freeaddrinfo(result);

if (listen(sockfd, somaxconn) < 0 ) {

fprintf(stderr, "make_server_socket() -> listen() -> ERROR: %s\n", strerror(errno));

close(sockfd);

exit(EXIT_FAILURE);
}

return sockfd;
}

static bool check_addr(int addr_family, const union sockaddr_any *addr, void **cache, size_t cache_size) {

if (addr_family == AF_UNIX) {

for (size_t i = 0; i < cache_size; i++) {

struct sockaddr_un *tmp = cache[i];

if (memcmp(addr->un.sun_path, tmp->sun_path, sizeof(tmp->sun_path)) == 0 ) {

return true;
}
}

} else if (addr_family == AF_INET) {

for (size_t i = 0; i < cache_size; i++) {

struct sockaddr_in *tmp = cache[i];

if (memcmp(&addr->in.sin_addr.s_addr, &tmp->sin_addr.s_addr, sizeof(tmp->sin_addr.s_addr)) == 0 ) {

return true;
}
}

} else if (addr_family == AF_INET6) {

for (size_t i = 0; i < cache_size; i++) {

struct sockaddr_in6 *tmp = cache[i];

if (memcmp(&addr->in6.sin6_addr.s6_addr, &tmp->sin6_addr.s6_addr, sizeof(tmp->sin6_addr.s6_addr)) == 0 ) {

return true;
}
}

} else {

for (size_t i = 0; i < cache_size; i++) {

struct sockaddr *tmp = cache[i];

if (memcmp(&addr->generic.sa_data, &tmp->sa_data, sizeof(tmp->sa_data)) == 0 ) {

return true;
}
}
}

return false;
}

static void show_addr(int addr_family, union sockaddr_any addr) {

char buff[INET6_ADDRSTRLEN];

if (addr_family == AF_UNIX) {

printf("%s\n", addr.un.sun_path);

} else if (addr_family == AF_INET) {

if (inet_ntop(addr_family, &addr.in.sin_addr.s_addr, buff, sizeof(buff)) < 0 ) {

fprintf(stderr, "show_addr() -> inet_ntop() -> ERROR: %s\n", strerror(errno));

} else {

printf("%s\n", buff);
}

} else if (addr_family == AF_INET6) {

if (inet_ntop(addr_family, &addr.in6.sin6_addr.s6_addr, buff, sizeof(buff)) < 0 ) {

fprintf(stderr, "show_addr() -> inet_ntop() -> ERROR: %s\n", strerror(errno));

} else {

printf("%s\n", buff);
}

} else {

fprintf(stderr, "show_addr() -> ERROR: Unexpected socket type\n");
}
}

static bool add_new_table_addr(void **cache, size_t *cache_size, int addr_family, union sockaddr_any *addr) {

socklen_t addrlen;

switch(addr_family) {

case AF_UNIX:

addrlen = sizeof(addr->un);

break;

case AF_INET:

addrlen = sizeof(addr->in);

break;

case AF_INET6:

addrlen = sizeof(addr->in6);

break;

default:

addrlen = sizeof(addr->generic);
}

void *new_addr = malloc(addrlen);

if (!new_addr) {

fprintf(stderr, "ERROR: Memory allocation for %u bytes failed\nadd_new_table_addr() -> malloc() -> ERROR: %s",
addrlen, strerror(errno));

return false;
}

void **new_ptr = realloc(cache, (1 + *cache_size) * sizeof(*cache));

if (!new_ptr) {

fprintf(stderr, "ERROR: Memory allocation for %zu bytes failed\nadd_new_table_addr() -> malloc() -> ERROR: %s",
(1 + *cache_size) * sizeof(*cache), strerror(errno));

free(new_addr);

return false;
}

memcpy(new_addr, addr, addrlen);
cache = new_ptr;
cache[*cache_size] = new_addr;
++*cache_size;

return true;
}

static void free_table(void **cache, size_t cache_size) {

for (size_t i = 0; i < cache_size; i++) {

free(cache[i]);
}

free(cache);
}

static void search(char *argv[], size_t somaxconn, size_t sndtimeo, size_t maxconn) {

int addr_family;
bool only_ipv6 = false;

if (strcmp(argv[1], "-u") == 0 ) {

addr_family = AF_UNIX;

} else if (strcmp(argv[1], "-4") == 0 ) {

addr_family = AF_INET;

} else if (strcmp(argv[1], "-6") == 0 ) {

addr_family = AF_INET6;

only_ipv6 = true;

} else {

addr_family = AF_INET6;
}

int sockfd = make_server_socket(addr_family, argv[2], only_ipv6, sndtimeo, somaxconn);
size_t cache_size = 0;
void **cache = NULL;
char err_msg[] = "Internal server error!", msg_1[] = "Welcome!", msg_2[] = "Get out!";

while (cache_size != maxconn) {

union sockaddr_any client_addr;
socklen_t addrlen = sizeof(client_addr);
int cli_sockfd = accept(sockfd, &client_addr.generic, &addrlen);

if (cli_sockfd < 0 ) {

fprintf(stderr, "search() -> accept() -> ERROR: %s\n", strerror(errno));

} else {

if (check_addr(addr_family, &client_addr, cache, cache_size) != true ) {

if (add_new_table_addr(cache, &cache_size, addr_family, &client_addr) != true ) {

if (send(cli_sockfd, err_msg, sizeof(err_msg), 0) < 0 ) {

fprintf(stderr, "search() -> send() -> ERROR: %s\n", strerror(errno));
}

} else {

show_addr(addr_family, client_addr);

if (send(cli_sockfd, msg_1, sizeof(msg_1), 0) < 0 ) {

fprintf(stderr, "search() -> send() -> ERROR: %s\n", strerror(errno));
}
}

} else {

if (send(cli_sockfd, msg_2, sizeof(msg_2), 0) < 0 ) {

fprintf(stderr, "search() -> send() -> ERROR: %s\n", strerror(errno));
}
}

close(cli_sockfd);
}
}

close(sockfd);

free_table(cache, cache_size);
}

int main(int argc, char *argv[]) {

if (argc == 1) {

printf(" *** ERROR: No arguments! ***\n");
printf("Usage: %s [OPTION] [PORT/SERVICE] [SOMAXCONN] [SNDTIMEO] [MAXCONN]\n", argv[0]);
printf(" -u Use process comunication\n");
printf(" -4 Use IPv4\n");
printf(" -6 Use IPv6\n");
printf(" -M Use IPv4 and IPv6 (IPv4-Mapped IPv6 Adress)\n");
printf(" -h Show this page\n");

exit(EXIT_FAILURE);
}

int opt = getopt(argc, argv, "u46Mh");

if (strlen(argv[1]) > 2 ) {

printf("*** Invalid value for [OPTION] ***\n");
printf("Usage: %s [OPTION] [PORT/SERVICE] [SOMAXCONN] [SNDTIMEO] [MAXCONN]\n", argv[0]);
printf(" -u Use process comunication\n");
printf(" -4 Use IPv4\n");
printf(" -6 Use IPv6\n");
printf(" -M Use IPv4 and IPv6 (IPv4-Mapped IPv6 Adress)\n");
printf(" -h Show this page\n");

} else if (opt == 'h' || opt == '?') {

printf("Usage: %s [OPTION] [PORT/SERVICE] [SOMAXCONN] [SNDTIMEO] [MAXCONN]\n", argv[0]);
printf(" -u Use process comunication\n");
printf(" -4 Use IPv4\n");
printf(" -6 Use IPv6\n");
printf(" -M Use IPv4 and IPv6 (IPv4-Mapped IPv6 Adress)\n");
printf(" -h Show this page\n");

} else if ((argc > 6) || (argc < 6)) {

printf("*** Invalid arguments! ***\n");
printf("Usage: %s [OPTION] [PORT/SERVICE] [SOMAXCONN] [SNDTIMEO] [MAXCONN]\n", argv[0]);
printf(" -u Use process comunication\n");
printf(" -4 Use IPv4\n");
printf(" -6 Use IPv6\n");
printf(" -M Use IPv4 and IPv6 (IPv4-Mapped IPv6 Adress)\n");
printf(" -h Show this page\n");

} else {

size_t somaxconn = strtoull(argv[3], NULL, 10);
size_t sndtimeo = strtoull(argv[4], NULL, 10);
size_t maxconn = strtoull(argv[5], NULL, 10);

if (somaxconn > SOMAXCONN) {

printf("*** ERROR: The value passed for [SOMAXCONN] is too long! ***\n");
printf("The maximum allowed value is %d\n", SOMAXCONN);

} else if (sndtimeo > SNDTIMEO) {

printf("*** ERROR: The value passed for [SNDTIMEO] is too long! ***\n");
printf("The maximum allowed value is %d\n", SNDTIMEO);

} else if (maxconn > MAXCONN) {

printf("*** ERROR: The value passed for [MAXCONN] is too long! ***\n");
printf("The maximum allowed value is %d\n", MAXCONN);

} else {

search(argv, somaxconn, sndtimeo, maxconn);
}
}

return EXIT_SUCCESS;
}


Tempo de execução com o valgrind:


$ valgrind --leak-check=full ./server -M 9009 4096 5 3
==10626== Memcheck, a memory error detector
==10626== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==10626== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==10626== Command: ./server -M 9009 4096 5 3
==10626==
::1
==10626== Invalid read of size 8
==10626== at 0x1098ED: check_addr (in /home/xubuntu/Documents/server)
==10626== by 0x109E2E: search (in /home/xubuntu/Documents/server)
==10626== by 0x10A2FA: main (in /home/xubuntu/Documents/server)
==10626== Address 0x0 is not stack'd, malloc'd or (recently) free'd
==10626==
==10626==
==10626== Process terminating with default action of signal 11 (SIGSEGV)
==10626== Access not within mapped region at address 0x0
==10626== at 0x1098ED: check_addr (in /home/xubuntu/Documents/server)
==10626== by 0x109E2E: search (in /home/xubuntu/Documents/server)
==10626== by 0x10A2FA: main (in /home/xubuntu/Documents/server)
==10626== If you believe this happened as a result of a stack
==10626== overflow in your program's main thread (unlikely but
==10626== possible), you can try to increase the size of the
==10626== main thread stack using the --main-stacksize= flag.
==10626== The main thread stack size used in this run was 8388608.
==10626==
==10626== HEAP SUMMARY:
==10626== in use at exit: 36 bytes in 2 blocks
==10626== total heap usage: 4 allocs, 2 frees, 1,136 bytes allocated
==10626==
==10626== 36 (8 direct, 28 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 2
==10626== at 0x483B723: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==10626== by 0x483E017: realloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==10626== by 0x109B1B: add_new_table_addr (in /home/xubuntu/Documents/server)
==10626== by 0x109E5C: search (in /home/xubuntu/Documents/server)
==10626== by 0x10A2FA: main (in /home/xubuntu/Documents/server)
==10626==
==10626== LEAK SUMMARY:
==10626== definitely lost: 8 bytes in 1 blocks
==10626== indirectly lost: 28 bytes in 1 blocks
==10626== possibly lost: 0 bytes in 0 blocks
==10626== still reachable: 0 bytes in 0 blocks
==10626== suppressed: 0 bytes in 0 blocks
==10626==
==10626== For lists of detected and suppressed errors, rerun with: -s
==10626== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
Segmentation fault (core dumped)


Afinal, o que esse programa faz?

É basicamente uma aplicação servidora escrita usando a API de sockets do Linux que tem como objetivo enviar uma simples mensagem para o cliente informando se ele se conectou pela primeira vez ("Welcome!") ou se ele está realizando uma "reconexão" ("Get out!").

Na verdade não é a primeira vez que escrevo algo do tipo. Algumas semanas atrás eu fiz exatamente o mesmo, porém não utilizei union. Segue o código para uma melhor análise:


#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <stdbool.h>

#include <netdb.h>
#include <unistd.h>
#include <sys/un.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define SNDTIMEO 5
#define MAXCONN 10

static bool set_sock_opts(int sockfd, bool only_ipv6) {

bool ok = false;
const int optval = 1;
struct timeval timeout = {SNDTIMEO, 0};

do {

if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) != 0 ) {

break;
}

if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) != 0 ) {

break;
}

if (only_ipv6 == true) {

if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &optval, sizeof(optval)) != 0 ) {

break;
}
}

ok = true;

} while (false);

return ok;
}

static int make_server_socket(int af, const char *service, bool only_ipv6, size_t somaxconn) {

int sockfd, ecode;
struct addrinfo hints, *result = NULL, *rp = NULL;

memset(&hints, 0, sizeof(hints));

hints.ai_flags = AI_PASSIVE;
hints.ai_family = af;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;

if ((ecode = getaddrinfo(NULL, service, &hints, &result)) != 0 ) {

fprintf(stderr, "make_server_socket() -> getaddrinfo() -> ERROR: %s\n", gai_strerror(ecode));

exit(EXIT_FAILURE);
}

for (rp = result; rp != NULL; rp = rp->ai_next) {

if ((sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) < 0 ) {

continue;
}

if (set_sock_opts(sockfd, only_ipv6) != true ) {

close(sockfd);

continue;
}

if (bind(sockfd, rp->ai_addr, rp->ai_addrlen) != -1 ) {

break;
}

close(sockfd);
}

if (rp == NULL) {

fprintf(stderr, "make_server_socket() -> socket()/bind() -> ERROR: %s\n", strerror(errno));

freeaddrinfo(rp);

exit(EXIT_FAILURE);
}

freeaddrinfo(rp);

if (listen(sockfd, somaxconn) < 0 ) {

fprintf(stderr, "make_server_socket() -> listen() -> ERROR: %s\n", strerror(errno));

close(sockfd);

exit(EXIT_FAILURE);
}

return sockfd;
}

static bool check_addr(int af, void *addr, void **addr_table, size_t count) {

bool found = false;

if (af == AF_UNIX) {

struct sockaddr_un *un = addr;

for (size_t i = 0; i < count; i++) {

struct sockaddr_un *tmp = addr_table[i];

if (memcmp(un->sun_path, tmp->sun_path, sizeof(un->sun_path)) == 0 ) {

found = true;

break;
}
}

} else if (af == AF_INET) {

struct sockaddr_in *in = addr;

for (size_t i = 0; i < count; i++) {

struct sockaddr_in *tmp = addr_table[i];

if (memcmp(&in->sin_addr.s_addr, &tmp->sin_addr.s_addr, sizeof(in->sin_addr.s_addr)) == 0 ) {

found = true;

break;
}
}

} else {

struct sockaddr_in6 *in6 = addr;

for(size_t i = 0; i < count; i++) {

struct sockaddr_in6 *tmp = addr_table[i];

if (memcmp(&in6->sin6_addr.s6_addr, &tmp->sin6_addr.s6_addr, sizeof(in6->sin6_addr.s6_addr)) == 0 ) {

found = true;

break;
}
}
}

return found;
}

static void show_addr(int af, void *addr) {

char buff[INET6_ADDRSTRLEN];

if (af == AF_UNIX) {

struct sockaddr_un *un = addr;

printf("%s\n", un->sun_path);

} else if (af == AF_INET) {

struct sockaddr_in *in = addr;

if (inet_ntop(af, &in->sin_addr.s_addr, buff, INET_ADDRSTRLEN) < 0 ) {

fprintf(stderr, "show_addr() -> inet_ntop() -> ERROR: %s\n", strerror(errno));

} else {

printf("%s\n", buff);
}

} else {

struct sockaddr_in6 *in6 = addr;

if (inet_ntop(af, &in6->sin6_addr.s6_addr, buff, INET6_ADDRSTRLEN) < 0 ) {

fprintf(stderr, "show_addr() -> inet_ntop() -> ERROR: %s\n", strerror(errno));

} else {

printf("%s\n", buff);
}
}
}

static void **realloc_addr_table(void **addr_table, size_t count, socklen_t addrlen) {

void **tmp = realloc(addr_table, count * sizeof(*addr_table));

if (tmp == NULL) {

fprintf(stderr, "ERROR: Memory allocation for %zu bytes failed\nrealloc_addr_table() -> realloc() -> ERROR: %s\n",
count * sizeof(*addr_table), strerror(errno));

} else {

tmp[count-1] = malloc(addrlen);

if (tmp == NULL) {

fprintf(stderr, "ERROR: Memory allocation for %u bytes failed\nrealloc_addr_table -> realloc() -> ERROR: %s\n",
addrlen, strerror(errno));
}
}

return tmp;
}

static void free_addr_table(void **addr_table, size_t count) {

for (size_t i = 0; i < count; i++) {

free(addr_table[i]);
}

free(addr_table);
}

static void search(char **argv, size_t somaxconn, size_t maxconn) {

int af;
bool only_ipv6 = false;
socklen_t addrlen;

if (strcmp(argv[1], "-u") == 0 ) {

af = AF_UNIX;

addrlen = sizeof(struct sockaddr_un);

} else if (strcmp(argv[1], "-4") == 0 ) {

af = AF_INET;

addrlen = sizeof(struct sockaddr_in);

} else if (strcmp(argv[1], "-6") == 0 ) {

af = AF_INET6;

only_ipv6 = true;

addrlen = sizeof(struct sockaddr_in6);

} else {

af = AF_INET6;

addrlen = sizeof(struct sockaddr_in6);
}

size_t i = 0;
int sockfd = make_server_socket(af, argv[2], only_ipv6, somaxconn);
void *addr = malloc(addrlen);
void **addr_table = NULL;

const char msg_1[] = "Welcome!\n", err_msg[] = "Internal server error!", msg_2[] = "Get out!\n";

while (i < maxconn) {

int cli_sockfd;

if ((cli_sockfd = accept(sockfd, addr, &addrlen)) < 0 ) {

fprintf(stderr, "search() -> accept() -> ERROR: %s\n", strerror(errno));

} else {

if (check_addr(af, addr, addr_table, i) == false ) {

void **tmp = realloc_addr_table(addr_table, i+1, addrlen);

if (!tmp) {

send(cli_sockfd, err_msg, sizeof(err_msg), 0);

} else {

i++;

addr_table = tmp;

memcpy(addr_table[i-1], addr, addrlen);

show_addr(af, addr);

send(cli_sockfd, msg_1, sizeof(msg_1), 0);
}

}else{

send(cli_sockfd, msg_2, sizeof(msg_2), 0);
}

close(cli_sockfd);
}
}

free(addr);

free_addr_table(addr_table, i);

close(sockfd);
}

int main(int argc, char **argv) {

if (argc == 1) {

printf(" *** ERROR: No arguments! ***\n");
printf("Usage: %s [OPTION] [PORT/SERVICE] [SOMAXCONN] [MAXCONN]\n", argv[0]);
printf(" -u Use process comunication\n");
printf(" -4 Use IPv4\n");
printf(" -6 Use IPv6\n");
printf(" -M Use IPv4 and IPv6 (IPv4-Mapped IPv6 Adress)\n");
printf(" -h Show this page\n");

exit(EXIT_FAILURE);
}

int opt = getopt(argc, argv, "u46Mh");

if (strlen(argv[1]) > 2 ) {

printf("*** Invalid value for [OPTION]! ***\n");
printf("Usage: %s [OPTION] [PORT/SERVICE] [SOMAXCONN] [MAXCONN]\n", argv[0]);
printf(" -u Use process comunication\n");
printf(" -4 Use IPv4\n");
printf(" -6 Use IPv6\n");
printf(" -M Use IPv4 and IPv6 (IPv4-Mapped IPv6 Adress)\n");
printf(" -h Show this page\n");

} else if (opt == 'h' || opt == '?') {

printf("Usage: %s [OPTION] [PORT/SERVICE] [SOMAXCONN] [MAXCONN]\n", argv[0]);
printf(" -u Use process comunication\n");
printf(" -4 Use IPv4\n");
printf(" -6 Use IPv6\n");
printf(" -M Use IPv4 and IPv6 (IPv4-Mapped IPv6 Adress)\n");
printf(" -h Show this page\n");

} else if ((argc < 5) || (argc > 5)) {

printf("*** Invalid arguments! ***\n");
printf("Usage: %s [OPTION] [PORT/SERVICE] [SOMAXCONN] [MAXCONN]\n", argv[0]);
printf(" -u Use process comunication\n");
printf(" -4 Use IPv4\n");
printf(" -6 Use IPv6\n");
printf(" -M Use IPv4 and IPv6 (IPv4-Mapped IPv6 Adress)\n");
printf(" -h Show this page\n");

} else {

size_t somaxconn = strtoull(argv[3], NULL, 10);
size_t maxconn = strtoull(argv[4], NULL, 10);

if (somaxconn > SOMAXCONN) {

printf("*** ERROR: The value passed for [SOMAXCONN] is too long! ***\n");
printf("The maximum allowed value is %d\n", SOMAXCONN);

} else if (maxconn > MAXCONN) {

printf("*** ERROR: The value passed for [MAXCONN] is too long! ***\n");
printf("The maximum allowed value is %d\n", MAXCONN);

} else {

search(argv, somaxconn, maxconn);
}
}

return EXIT_SUCCESS;
}


Tempo de execução com o valgrind:


$ gcc -Wall server_1.c -o server
$ valgrind --leak-check=full ./server -M 9009 4096 3
==6549== Memcheck, a memory error detector
==6549== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==6549== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==6549== Command: ./server -M 9009 4096 3
==6549==
::ffff:127.0.0.1
::1
::ffff:192.168.50.2
==6549==
==6549== HEAP SUMMARY:
==6549== in use at exit: 0 bytes in 0 blocks
==6549== total heap usage: 9 allocs, 9 frees, 1,260 bytes allocated
==6549==
==6549== All heap blocks were freed -- no leaks are possible
==6549==
==6549== For lists of detected and suppressed errors, rerun with: -s
==6549== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)


------------------------------------------------------

* OBS: Estou utilizando o netcat como aplicação cliente.


  


2. MELHOR RESPOSTA

Paulo
paulo1205

(usa Ubuntu)

Enviado em 04/07/2021 - 01:20h

Vamos ver a saída do valgrind.
==10626== Invalid read of size 8
==10626== at 0x1098ED: check_addr (in /home/xubuntu/Documents/server)
==10626== by 0x109E2E: search (in /home/xubuntu/Documents/server)
==10626== by 0x10A2FA: main (in /home/xubuntu/Documents/server)
==10626== Address 0x0 is not stack'd, malloc'd or (recently) free'd


Você tentou ler 8 bytes (1ª linha) do endereço 0 (“0x0” na 5ª linha, indicando que recebeu um ponteiro nulo) dentro da função check_addr(), que foi chamada por search(), que foi chamada por main() (linhas 3 a 5).

Após olhar o conteúdo de check_addr(), ficou claro para mim que a causa raiz não está nela, mas vem de quem a chamou, já que todos os ponteiros que são usados dentro dela são provenientes dos parâmetros que a função recebe.

Antes de prosseguir com a análise, porém, permita-me fazer um comentário sobre check_addr(): o primeiro argumento da função é redundante, já que a família de endereços pode — e teoricamente deveria — ser examinada a partir do socket, que você também fornece à função.

A causa raiz está na função add_new_table_addr() (lembra que, quando eu a postei no outro tópico, eu avisei que não a havia testado? pois é... ela tinha um erro) e na forma como ela é invocada. Dado que o cache de endereços de clientes é um array de ponteiros (porque cada elemento pode ser alocado com tamanho diferente para diferentes tipos de sockets) e que o próprio cache também é dinamicamente alocado, então esse cache tem de ser um ponteiro para ponteiros.

Como a função add_new_table_addr() precisa de alterar o tamanho desse cache, então o parâmetro correspondente precisa de mais um nível de ponteiros para que o cache seja passado por referência (na verdade, que seja passada uma referência para o cache). Do jeito como eu fiz originalmente, o que a função recebe é uma cópia do valor de cache (que é o endereço do primeiro elemento), e o que é realocado dentro da função é a cópia recebida.

Para corrigir, a função teria de ter a seguinte forma.
static bool add_new_table_addr(void ***cache, size_t *cache_size, union sockaddr_any *addr) {
socklen_t addrlen;
switch(addr->family) {
case AF_UNIX: addrlen = sizeof addr->un; break;
case AF_INET: addrlen = sizeof addr->in; break;
case AF_INET6: addrlen = sizeof addr->in6; break;
default: addrlen = sizeof addr->generic;
}
void *new_addr = malloc(addrlen);
if (!new_addr) {
fprintf(
stderr,
"ERROR: Memory allocation for %u bytes failed\n"
"add_new_table_addr() -> malloc() -> ERROR: %s",
addrlen, strerror(errno)
);
return false;
}
void *new_ptr = realloc(*cache, (1 + *cache_size) * sizeof **cache);
if (!new_ptr) {
fprintf(stderr, "ERROR: Memory allocation for %zu bytes failed\nadd_new_table_addr() -> malloc() -> ERROR: %s", (1 + *cache_size) * sizeof(*cache), strerror(errno));
free(new_addr);
return false;
}
memcpy(new_addr, addr, addrlen);
*cache = new_ptr;
(*cache)[*cache_size] = new_addr;
++*cache_size;

return true;
}

Para chamar a função corrigida, é preciso usar “&cache” em vez de apenas “cache”.


... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)

3. Re: Segmentation fault: Problemas ao trabalhar com alocação dinâmica e estruturas de endereço Internet [

Paulo
paulo1205

(usa Ubuntu)

Enviado em 03/07/2021 - 18:36h

Ainda não consegui analisar o código que você colocou acima, mas acho importante pontuar que nem sempre um SIGSEGV está ligado a problema de alocação. Qualquer tentativa de usar um endereço que esteja fora dos espaços reservados para o programa ou de fazer acessos indevidos (por exemplo, escrever em memória marcada como apenas para leitura) pode produzir esse sinal. Dependendo de como e onde o acesso foi tentado, o valgrind pode ser mais ou menos eficiente.


... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)


4. Resolvido!

97-3048-567-XS32
97-3048-567-XS32

(usa Outra)

Enviado em 05/07/2021 - 05:22h


Antes de prosseguir com a análise, porém, permita-me fazer um comentário sobre check_addr(): o primeiro argumento da função é redundante, já que a família de endereços pode — e teoricamente deveria — ser examinada a partir do socket, que você também fornece à função.


Fiz um teste aqui e aparetemente isso também pode ser aplicado para as funções show_addr() e add_new_table_addr()

show_addr()


static void show_addr(union sockaddr_any addr) {

char buff[INET6_ADDRSTRLEN];

if (addr.generic.sa_family == AF_UNIX) {

printf("%s\n", addr.un.sun_path);

} else if (addr.generic.sa_family == AF_INET) {

if (inet_ntop(addr.generic.sa_family, &addr.in.sin_addr.s_addr, buff, sizeof(buff)) < 0 ) {

fprintf(stderr, "show_addr() -> inet_ntop() -> ERROR: %s\n", strerror(errno));

} else {

printf("%s\n", buff);
}

} else if (addr.generic.sa_family == AF_INET6) {

if (inet_ntop(addr.generic.sa_family, &addr.in6.sin6_addr.s6_addr, buff, sizeof(buff)) < 0 ) {

fprintf(stderr, "show_addr() -> inet_ntop() -> ERROR: %s\n", strerror(errno));

} else {

printf("%s\n", buff);
}

} else {

fprintf(stderr, "show_addr() -> ERROR: Unexpected socket type\n");
}
}

add_new_table_addr()


static bool add_new_table_addr(void ***cache, size_t *cache_size, union sockaddr_any *addr) {

socklen_t addrlen;

switch(addr->generic.sa_family) {

case AF_UNIX:

addrlen = sizeof(addr->un);

break;

case AF_INET:

addrlen = sizeof(addr->in);

break;

case AF_INET6:

addrlen = sizeof(addr->in6);

break;

default:

addrlen = sizeof(addr->generic);
}

void *new_addr = malloc(addrlen);

if (!new_addr) {

fprintf(stderr, "ERROR: Memory allocation for %u bytes failed\nadd_new_table_addr() -> malloc() -> ERROR: %s",
addrlen, strerror(errno));

return false;
}

void *new_ptr = realloc(*cache, (1 + *cache_size) * sizeof(**cache));

if (!new_ptr) {

fprintf(stderr, "ERROR: Memory allocation for %zu bytes failed\nadd_new_table_addr() -> malloc() -> ERROR: %s",
(1 + *cache_size) * sizeof(**cache), strerror(errno));

free(new_addr);

return false;
}

memcpy(new_addr, addr, addrlen);
*cache = new_ptr;
(*cache)[*cache_size] = new_addr;
++*cache_size;

return true;
}

Valeu pela dica de otimização!


A causa raiz está na função add_new_table_addr() (lembra que, quando eu a postei no outro tópico, eu avisei que não a havia testado? pois é... ela tinha um erro) e na forma como ela é invocada. Dado que o cache de endereços de clientes é um array de ponteiros (porque cada elemento pode ser alocado com tamanho diferente para diferentes tipos de sockets) e que o próprio cache também é dinamicamente alocado, então esse cache tem de ser um ponteiro para ponteiros.

Como a função add_new_table_addr() precisa de alterar o tamanho desse cache, então o parâmetro correspondente precisa de mais um nível de ponteiros para que o cache seja passado por referência (na verdade, que seja passada uma referência para o cache). Do jeito como eu fiz originalmente, o que a função recebe é uma cópia do valor de cache (que é o endereço do primeiro elemento), e o que é realocado dentro da função é a cópia recebida.


Funcionou da forma como você sugeriu. Valeu!


$ gcc -Wall server_3.c -o server
$ valgrind -s --leak-check=full ./server -M 9009 4096 5 3==6707== Memcheck, a memory error detector
==6707== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==6707== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
==6707== Command: ./server -M 9009 4096 5 3
==6707==
::ffff:192.168.0.106
::ffff:127.0.0.1
::1
==6707==
==6707== HEAP SUMMARY:
==6707== in use at exit: 0 bytes in 0 blocks
==6707== total heap usage: 8 allocs, 8 frees, 1,232 bytes allocated
==6707==
==6707== All heap blocks were freed -- no leaks are possible
==6707==
==6707== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
$

Hehehe... acho que vou demorar um tempo para entender direito o conceito de "ponteiro para ponteiros", mas inicialmente acho que já peguei a ideia... ou quase.



void *new_ptr = realloc(*cache, (1 + *cache_size) * sizeof **cache);

if (!new_ptr) {
fprintf(stderr, "ERROR: Memory allocation for %zu bytes failed\nadd_new_table_addr() -> malloc() -> ERROR: %s", (1 + *cache_size) * sizeof(*cache), strerror(errno));
free(new_addr);
return false;
}

Para chamar a função corrigida, é preciso usar "&cache" em vez de apenas "cache".


Só um detalhe para caso outro junior passe por aqui futuramente: altere "(1 + *cache_size) * sizeof(*cache)" dentro do último fprintf() em add_new_table_addr() para "(1 + *cache_size) * sizeof(**cache)"






Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts