Nei titoli e nei testi troverete qualche rimando cinematografico (eh si, sono un cinefilo). Se non vi interessano fate finta di non vederli, già che non sono fondamentali per la comprensione dei post...

Di questo blog mando avanti anche una versione in Spagnolo (e, normalmente, i post sono disponibili un po' in ritardo rispetto alla versione in Italiano...). Potete trovarlo su El arte de la programación en C. Buona lettura.

sabato 15 settembre 2018

Lo chiamavano Jeeg OpenSSL
come scrivere TCP Server e Client con OpenSSL in C - pt.1

Alessia: Ma che pure te c'hai a'spadalata?
Enzo/Jeeg: A spada che?
Ok, due notizie, una buona e una cattiva. La cattiva è che, esattamente come Enzo/Jeeg del gran Lo chiamavano Jeeg Robot, non c'abbiamo a'spadalata. La buona è che, per proteggere la privacy delle nostre comunicazioni ci abbiamo OpenSSL. Quindi possiamo stare (ragionevolmente) tranquilli.
...Ma che pure te c'hai er OpenSSL?...
L'argomento di oggi è molto interessante e oscuro (come quello dei due antichi post introduttivi che trovate qui e qui, ricordate?). Facciamo mente locale: l'obbiettivo è scrivere un TCP Server e un TCP Client minimali che usino OpenSSL (quindi con crittografia dei dati, chiavi, bla, bla, bla...). Se devono essere minimali dovranno, per forza, somigliare ai due esempi già presentati (o meglio, il mio obbiettivo è quello). Quindi, dopo aver letto l'articolo, vi invito a fare una semplice comparazione tra i sorgenti di riferimento e quelli nuovi (ad esempio copiandoli su file e usando un bel comparatore tipo Meld). Noterete che la somiglianza strutturale è notevole, i file sono parzialmente sovrapponibili e le differenze sono facilmente isolabili.

Ed ora bando alle ciance: cominciamo con il codice del Server:
#include "../libmyssl/myssl.h"
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <openssl/err.h>

int main(int argc, char *argv[])
{
    // test argomenti
    if (argc != 2) {
        // errore args
        printf("%s: numero argomenti errato\n", argv[0]);
        printf("uso: %s port [i.e.: %s 8888]\n", argv[0], argv[0]);
        return EXIT_FAILURE;
    }

    // crea un socket
    int my_socket;
    if ((my_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
        // errore socket()
        fprintf(stderr, "%s: non posso creare il socket (%s)\n", argv[0], strerror(errno));
        return EXIT_FAILURE;
    }

    // prepara la struttura sockaddr_in per questo server
    struct sockaddr_in server;              // (locale) server socket info
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;            // set address family
    server.sin_addr.s_addr = INADDR_ANY;    // set server address per qualunque interfaccia
    server.sin_port = htons(atoi(argv[1])); // set port number del server

    // assegnazione di un indirizzo al socket creato
    if (bind(my_socket, (struct sockaddr *)&server, sizeof(server)) < 0) {
        // errore bind()
        fprintf(stderr, "%s: errore bind (%s)", argv[0], strerror(errno));
        close(my_socket);
        return EXIT_FAILURE;
    }

    // start ascolto con una coda di max BACKLOG connessioni
    if (listen(my_socket, BACKLOG) < 0) {
        // errore listen()
        fprintf(stderr, "%s: errore listen (%s)\n", argv[0], strerror(errno));
        close(my_socket);
        return EXIT_FAILURE;
    }

    // accetta connessioni da un client entrante
    printf("%s: attesa connessioni entranti...\n", argv[0]);
    socklen_t socksize = sizeof(struct sockaddr_in);
    struct sockaddr_in client;          // (remote) client socket info
    int client_sock;
    if ((client_sock = accept(my_socket, (struct sockaddr *)&client, &socksize)) < 0) {
        // errore accept()
        fprintf(stderr, "%s: errore accept (%s)\n", argv[0], strerror(errno));
        close(my_socket);
        return EXIT_FAILURE;
    }

    // chiude il socket non più in uso
    close(my_socket);

    // preparazione per la SSL_accept(): crea un openssl context
    SSL_CTX *ctx;
    int error;
    if ((ctx = sslCreateCtx(SSL_SERVER, &error)) == NULL || error < 0) {
        // errore sslCreateCtx()
        fprintf(stderr, "%s: openssl errore creando il contesto SSL_CTX\n", argv[0]);
        ERR_print_errors_fp(stderr);
        sslClose(NULL, my_socket, ctx, false);
        return EXIT_FAILURE;
    }

    // preparazione per la SSL_accept(): crea la struttura dati corrispondente del openssl context
    SSL *ssl;
    if ((ssl = SSL_new(ctx)) == NULL) {
        // errore SSL_new()
        fprintf(stderr, "%s: openssl errore creando la struttura SSL\n", argv[0]);
        ERR_print_errors_fp(stderr);
        sslClose(ssl, client_sock, ctx, false);
        return EXIT_FAILURE;
    }

    // preparazione per la SSL_accept(): associa la struttura SSL con il socket corrente
    if (SSL_set_fd(ssl, client_sock) == 0) {
        // errore SSL_set_fd()
        fprintf(stderr, "%s: openssl errore SSL_set_fd\n", argv[0]);
        ERR_print_errors_fp(stderr);
        sslClose(ssl, client_sock, ctx, false);
        return EXIT_FAILURE;
    }

    // accetta una connessione OpenSSL con SSL_accept()
    int rc;
    if ((rc = sslFunc(SSL_accept, ssl)) != 1) {
        // errore sslAccept()
        fprintf(stderr, "%s: openssl errore accept (%d)\n", argv[0], SSL_get_error(ssl, rc));
        ERR_print_errors_fp(stderr);
        sslClose(ssl, client_sock, ctx, false);
        return EXIT_FAILURE;
    }

    // loop di ricezione messaggi dal client
    char client_msg[MYBUFSIZE];
    int recv_size;
    while ((recv_size = sslRead(ssl, client_msg, MYBUFSIZE)) > 0 ) {
        // send messaggio di ritorno al client
        printf("%s: ricevuto messaggio dal sock %d: %s\n", argv[0], client_sock, client_msg);
        char server_msg[MYBUFSIZE];
        sprintf(server_msg, "mi hai scritto: %s", client_msg);
        if (sslWrite(ssl, server_msg, strlen(server_msg)) < 0) {
            // errore sslWrite()
            fprintf(stderr, "%s: errore send (%d)\n", argv[0], SSL_get_error(ssl, recv_size));
            ERR_print_errors_fp(stderr);
            sslClose(ssl, client_sock, ctx, true);
            return EXIT_FAILURE;
        }

        // clear buffer
        memset(client_msg, 0, MYBUFSIZE);
    }

    // loop terminato: test motivo
    if (recv_size < 0) {
        // errore sslRead()
        fprintf(stderr, "%s: errore recv (%d)\n", argv[0], SSL_get_error(ssl, recv_size));
        ERR_print_errors_fp(stderr);
        sslClose(ssl, client_sock, ctx, true);
        return EXIT_FAILURE;
    }
    else if (recv_size == 0) {
        // Ok: il client si è disconnesso
        printf("%s: client disconnesso\n", argv[0]);
    }

    // esco con Ok
    sslClose(ssl, client_sock, ctx, true);
    return EXIT_SUCCESS;
}
Credo che il codice sia abbastanza chiaro e ben commentato, quindi è il caso di soffermarsi solo sulle differenze rispetto al sorgente del TCP Server di riferimento (quello del vecchio post). Le uniche varianti significative sono:
  1. una fase di accept aggiuntiva per OpenSSL dopo la fase di accept classica. Questa fase include la creazione degli oggetti OpenSSL veri e propri: un contesto (di tipo SSL_CTX*) che la libreria OpenSSL usa per gestire tutti i meccanismi di funzionamento interni, e una struttura dati (di tipo SSL*) che è un po' l'equivalente OpenSSL del descrittore di file (di tipo int) della libreria Berkeley Socket (quello creato con la chiamata a socket() un po' più sopra). Notare che con la chiamata SSL_set_fd() la liberia OpenSSL riesce ad associare il descrittore di file socket (int client_sock) con la struttura dati OpenSSL (SSL* ssl).   
  2. dal punto 1 in avanti, visto che il descrittore di file è stato sostituito da un pointer SSL, le chiamate classiche di Berkeley Socket recv(), send() e close() vengono sostituite da delle misteriose chiamate sslRead(), sslWrite() e sslClose(), che poi tanto misteriose non sono, visti i nomi.
Tutto qua per il Server, veramente poco, no? E ora possiamo passare al codice del Client. Eccolo!
#include "../libmyssl/myssl.h"
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <openssl/err.h>

int main(int argc, char *argv[])
{
    // test argomenti
    if (argc != 3) {
        // errore args
        printf("%s: numero argomenti errato\n", argv[0]);
        printf("uso: %s host port [i.e.: %s 127.0.0.1 8888]\n", argv[0], argv[0]);
        return EXIT_FAILURE;
    }

    // crea un socket
    int my_socket;
    if ((my_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
        // errore socket()
        fprintf(stderr, "%s: non posso creare il socket (%s)\n", argv[0], strerror(errno));
        return EXIT_FAILURE;
    }

    // prepara la struttura sockaddr_in per il server remoto
    struct sockaddr_in server;                      // (remoto) server socket info
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;                    // set address family
    server.sin_addr.s_addr = inet_addr(argv[1]);    // set server address
    server.sin_port = htons(atoi(argv[2]));         // set port number del server

    // connessione al server remoto
    if (connect(my_socket, (struct sockaddr *)&server, sizeof(server)) < 0) {
        // errore connect()
        fprintf(stderr, "%s: errore connect (%s)\n", argv[0], strerror(errno));
        close(my_socket);
        return EXIT_FAILURE;
    }

    // preparazione per la SSL_connect(): crea un openssl context
    SSL_CTX *ctx;
    int error;
    if ((ctx = sslCreateCtx(SSL_CLIENT, &error)) == NULL || error < 0) {
        // errore sslCreateCtx()
        fprintf(stderr, "%s: openssl errore creando il contesto SSL_CTX\n", argv[0]);
        ERR_print_errors_fp(stderr);
        sslClose(NULL, my_socket, ctx, false);
        return EXIT_FAILURE;
    }

    // preparazione per la SSL_connect(): crea la struttura dati corrispondente del openssl context
    SSL *ssl;
    if ((ssl = SSL_new(ctx)) == NULL) {
        // errore SSL_new()
        fprintf(stderr, "%s: openssl errore creando la struttura SSL\n", argv[0]);
        ERR_print_errors_fp(stderr);
        sslClose(ssl, my_socket, ctx, false);
        return EXIT_FAILURE;
    }

    // preparazione per la SSL_connect(): associa la struttura SSL con il socket corrente
    if (SSL_set_fd(ssl, my_socket) == 0) {
        // errore SSL_set_fd()
        fprintf(stderr, "%s: openssl errore SSL_set_fd\n", argv[0]);
        ERR_print_errors_fp(stderr);
        sslClose(ssl, my_socket, ctx, false);
        return EXIT_FAILURE;
    }

    // avvia una connessione OpenSSL con SSL_connect()
    int rc;
    if ((rc = sslFunc(SSL_connect, ssl)) != 1) {
        // errore sslConnect()
        fprintf(stderr, "%s: openssl errore connect (%d)\n", argv[0], SSL_get_error(ssl, rc));
        ERR_print_errors_fp(stderr);
        sslClose(ssl, my_socket, ctx, false);
        return EXIT_FAILURE;
    }

    // loop di comunicazione col server remoto
    for (;;) {
        // compone messaggio per il server remoto
        char my_msg[MYBUFSIZE];
        printf("Scrivi un messaggio per il Server remoto: ");
        scanf("%s", my_msg);

        // send messaggio al server remoto
        if ((rc = sslWrite(ssl, my_msg, strlen(my_msg))) < 0) {
            // errore sslWrite()
            fprintf(stderr, "%s: errore send (%d)\n", argv[0], SSL_get_error(ssl, rc));
            ERR_print_errors_fp(stderr);
            sslClose(ssl, my_socket, ctx, true);
            return EXIT_FAILURE;
        }

        // riceve una risposta dal server remoto
        memset(my_msg, 0, MYBUFSIZE);
        if ((rc = sslRead(ssl, my_msg, MYBUFSIZE)) < 0) {
            // errore sslRead()
            fprintf(stderr, "%s: errore recv (%d)\n", argv[0], SSL_get_error(ssl, rc));
            ERR_print_errors_fp(stderr);
            sslClose(ssl, my_socket, ctx, true);
            return EXIT_FAILURE;
        }

        // mostra la risposta
        printf("%s: risposta Server: %s\n", argv[0], my_msg);
    }

    // esco con Ok
    sslClose(ssl, my_socket, ctx, true);
    return EXIT_SUCCESS;
}
Anche il Client è, evidentemente, molto simile al riferimento, e le uniche varianti significative sono:
  1. una fase di connect aggiuntiva per OpenSSL dopo la fase di connect classica. Questa fase include la creazione degli oggetti OpenSSL veri e propri già visti qua sopra nel punto 1 del Server. Notare anche qui che con la chiamata SSL_set_fd() la liberia OpenSSL riesce ad associare il descrittore di file socket (int my_socket) con la struttura dati OpenSSL (SSL* ssl).
  2. dal punto 1 in avanti le chiamate classiche di Berkeley Socket recv(), send() e close() sono sostituite, anche in questo caso, da delle misteriose chiamate sslRead(), sslWrite() e sslClose() che poi, ripeto, tanto misteriose non sono, visti i nomi.

Tutto qua per il Client, di nuovo veramente poco, no?

Riepilogando, sembra veramente facile: creiamo contesti e strutture dati OpenSSL, aggiungiamo le fasi OpenSSL aggiuntive (accept per il Server e connect per il Client), sostituiamo le chiamate classiche con quelle misteriose et voilá! missione compiuta!

Ma no, ci deve essere un trucco, non può essere così facile... cerchiamo nel manuale di OpenSSL come funzionano le funzioni misteriose... ma non esistono! Cosa è successo? Ecco, non voglio tenervi sulle spine e passo subito a spiegare l'arcano: il trucco è evidenziato dalla linea #include "../libmyssl/myssl.h" con cui si include l'header file della libreria speciale che ho scritto per sviluppare questi Server e Client: la libreria (che ha l'originalissimo nome di libmyssl) ci fornisce dei wrapper per alcune delle funzioni tipiche di OpenSSL. E non sono dei semplici wrapper per rinominare le funzioni e offrire, magari, una lista di argomenti più facile da usare: no, sono dei wrapper abbastanza complessi, alcuni dei quali fanno molta elaborazione interna, e questo perché OpenSSL ha un funzionamento abbastanza complesso e anche l'uso non è proprio immediato.

Ma niente paura! Tanto per cominciare il primo obbiettivo l'abbiamo già raggiunto: scrivere un Server e un Client minimali che abbiano un aspetto il più possibile simile a quello dei modelli Berkeley Socket. E per forzare questo abbiamo spostato un po' di complessità in una nuova libreria (unica per Server e Client) scritta ad-hoc. Quindi che cosa ci resta da fare? Solo il secondo passo, capire come funziona la nuova libreria (e visto che l'ho scritta io penso di poter dare sufficienti dritte per raggiungere lo scopo...). Ma questo lo vedremo nella seconda parte del post...

Ciao e al prossimo post!

venerdì 10 agosto 2018

Per qualche strlen in più
come ottimizzare la strlen in C - pt.2

col. Mortimer: Che succede ragazzo?
Il Monco: Niente vecchio... non mi tornavano i conti. Me ne mancava uno.
Ok, siamo pronti per la seconda parte di Per qualche dollaro in più (oops... ma non era Per qualche strlen in più?). Come giustamente diceva Il Monco, un buon programmatore fa sempre bene i conti, quindi in questo post analizzeremo i risultati di un benchmark che ho scritto ad hoc per questo scopo. (...si lo so, avevo anche promesso una sorpresa. Ci sarà...).
...niente vecchio... la strlen è un po' lenta oggi...
Allora, nel benchmark confronteremo i risultati (di velocità, ovviamente) delle quattro versioni di strlen() viste nello scorso post, e cioè: K&R, FreeBSD, glibc, e musl libc e il nostro punto di riferimento sarà, evidentemente, la strlen() di default del sistema, anche perché uno dei nostri obbiettivi è scoprire cosa usa di default Linux, che è, al solito, il nostro sistema di riferimento. Vediamo il codice del benchmark:
#include <stdint.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <string.h>

// prototipi locali
size_t strlenKaR(const char *s);
size_t strlenFreeBSD(const char *str);
size_t strlenGlibc(const char *str);
size_t strlenMusl(const char *s);

int main(int argc, char* argv[])
{
    static char s[1000000000];
    clock_t t_start;
    size_t  result;
    clock_t t_end;
    double  t_passed;
    int i;

    // riempio la stringa di test
    for (i = 0; i < sizeof(s); i++) {
        if (i % 2)
            s[i] = 'a';
        else
            s[i] = 'u';
    }
    s[i - 1] = 0; // terminatore

    // esegue con strlen() di default e calcola tempo
    t_start  = clock();
    result   = strlen(s);
    t_end    = clock();
    t_passed = ((double)(t_end - t_start)) / CLOCKS_PER_SEC;
    printf("strlen(s)        = %zu - Tempo trascorso: %f secondi\n", result, t_passed);

    // esegue con strlenKaR() e calcola tempo
    t_start  = clock();
    result   = strlenKaR(s);
    t_end    = clock();
    t_passed = ((double)(t_end - t_start)) / CLOCKS_PER_SEC;
    printf("strlenKaR(s)     = %zu - Tempo trascorso: %f secondi\n", result, t_passed);

    // esegue con strlenFreeBSD() e calcola tempo
    t_start  = clock();
    result   = strlenFreeBSD(s);
    t_end    = clock();
    t_passed = ((double)(t_end - t_start)) / CLOCKS_PER_SEC;
    printf("strlenFreeBSD(s) = %zu - Tempo trascorso: %f secondi\n", result, t_passed);

    // esegue con strlenGlibc() e calcola tempo
    t_start  = clock();
    result   = strlenGlibc(s);
    t_end    = clock();
    t_passed = ((double)(t_end - t_start)) / CLOCKS_PER_SEC;
    printf("strlenGlibc(s)   = %zu - Tempo trascorso: %f secondi\n", result, t_passed);

    // esegue con strlenMusl() e calcola tempo
    t_start  = clock();
    result   = strlenMusl(s);
    t_end    = clock();
    t_passed = ((double)(t_end - t_start)) / CLOCKS_PER_SEC;
    printf("strlenMusl(s)    = %zu - Tempo trascorso: %f secondi\n", result, t_passed);

    return 0;
}
Come avrete notato, abbiamo creato una stringa enorme (e se no che test era?) e l'abbiamo riempita di una sequenza di "au" (...beh, si poteva riempire di qualsiasi cosa, ma quel giorno mi ero svegliato con un "au" in testa che non vi dico...). Abbiamo aggiunto il dovuto terminatore di stringa e chiamato in sequenza le varie strlen() usando un metodo affidabile (con clock()) per verificare il tempo reale di attività (un metodo che abbiamo già usato in un vecchio post). Le strlen() sono, ovviamente, quelle dello scorso post (i codici li trovate li), ribattezzate come strlenKaR(), strlenFreeBSD(), strlenGlibc() e strlenMusl(), per distinguerle dall'unica funzione non locale, che è la strlen() di libreria. Come si nota per la versione K&R ho modificato il prototipo (e la definizione) per uniformarla alla strlen() standard.

Vediamo ora i risultati del benchmark con compilazione normale (-O0, che è il default e si può anche omettere) e con una notevole ottimizzazione (-O2, che è quella che si usa normalmente nei casi reali):

usando gcc -O0
aldo@mylinux:~/blogtest$ ./strlens 
strlen(s)        = 999999999 - Tempo trascorso: 0.054814 secondi
strlenKaR(s)     = 999999999 - Tempo trascorso: 2.173237 secondi
strlenFreeBSD(s) = 999999999 - Tempo trascorso: 0.308145 secondi
strlenGlibc(s)   = 999999999 - Tempo trascorso: 0.252466 secondi
strlenMusl(s)    = 999999999 - Tempo trascorso: 0.304617 secondi

usando gcc -O2
aldo@mylinux:~/blogtest$ ./strlens 
strlen(s)        = 999999999 - Tempo trascorso: 0.164509 secondi
strlenKaR(s)     = 999999999 - Tempo trascorso: 0.395466 secondi
strlenFreeBSD(s) = 999999999 - Tempo trascorso: 0.091599 secondi
strlenGlibc(s)   = 999999999 - Tempo trascorso: 0.102967 secondi
strlenMusl(s)    = 999999999 - Tempo trascorso: 0.091931 secondi
I risultati sono veramente interessanti. La strlenKaR() è, come ci aspettavamo, molto più lenta delle versioni che usano l'algoritmo descritto in ZeroInWord e, anche compilando con -O2, è più lenta delle altre compilate con -O0, anche se, comunque, il divario tra le versioni ottimizzate è minore di quello tra le non ottimizzate. Ripeto, siamo in un caso limite: la stringa misurata è veramente enorme, e con stringhe "normali" la differenza di prestazioni sarebbe molto più piccola, comunque è evidente che le versioni con algoritmo speciale vanno meglio e sono, giustamente, da preferire, specialmente ricordando che "le super-ottimizzazioni sono sicuramente raccomandabili per le piccole funzioni di uso frequente" (...e questa è una mia auto-citazione dal mio ultimo post...). Le tre funzioni strlenFreeBSD(), strlenGlibc e strlenMusl() hanno prestazioni ottime ed analoghe, visto che usano lo stesso algoritmo, e la versione glibc prevale leggermente compilando con -O0, mentre che compilando con -O2 la migliore è la musl, con la FreeBSD a ruota.

E veniamo alla parte sorprendente: pare che la strlen() di default è nettamente più veloce di tutte le altre compilando con -O0 mentre perde il vantaggio compilando con -O2. Quindi, almeno senza ottimizzazioni, sul mio sistema Linux la strlen() di default non è quella della glibc (come uno si aspetterebbe) ma è qualcos'altro. E cosa è, allora? Proviamo a commentare la linea "#include <string.h>" e a ricompilare, e vediamo cosa ci dice il compilatore...
aldo@mylinux:~/blogtest$ gcc -c -O0 strlens.c -o strlens.o
strlens.c: In function 'main':
strlens.c:35:16: warning: implicit declaration of function 'strlen'
     result   = strlen(s);
                ^
strlens.c:35:16: warning: incompatible implicit declaration of built-in function 'strlen'
strlens.c:35:16: note: include '<string.h>' or provide a declaration of 'strlen'
Ecco, pare che la strlen() di default sia una funzione implicita del compilatore gcc, quindi non si usa la versione della libreria e, del resto "...in alcuni casi queste funzioni possono essere addirittura inlineate e/o scritte in assembler..." (...e questa è un'altra mia auto-citazione dal mio ultimo post...). Per confermare tutto questo ho cercato una versione in assembler della strlen() e l'ho trovata qui su stackoverflow. Ho modificato leggermente Il codice (la originale non restituiva correttamente il risultato) e ho modificato il benchmark per usarla. Il codice è il seguente:
#include <immintrin.h>

// una possibile implementazione in assembler della strlen() implicita in gcc
size_t strlenAsm(const char* src)
{
    size_t result = 0;
    unsigned int tmp1;
    __m128i zero = _mm_setzero_si128(), vectmp;

    // A pointer-increment may perform better than an indexed addressing mode
    asm(
        "\n.Lloop:\n\t"
            "movdqu   (%[src], %[res]), %[vectmp]\n\t" // result reg is used as the loop counter
            "pcmpeqb  %[zerovec], %[vectmp]\n\t"
            "pmovmskb %[vectmp], %[itmp]\n\t"
            "add      $0x10, %[res]\n\t"
            "test     %[itmp], %[itmp]\n\t"
            "jz  .Lloop\n\t"

        "bsf %[itmp], %[itmp]\n\t"
        "add %q[itmp], %q[res]\n\t"                // q modifier to get quadword register.
        : [res] "+r"(result), [vectmp] "=&x" (vectmp), [itmp] "=&r" (tmp1)
        : [zerovec] "x" (zero)  // There might already be a zeroed vector reg when inlining
            , [src] "r" (src)
            , [dummy] "m" (*(const char (*)[])src) // this reads the whole object, however
                                                   // long gcc thinks it is not needed
                                                   // because of the dummy input
    );

    return result - tmp1 - 1;
}
e i risultati diventano così (con -O0, e con -O2 il risultato della strlenAsm() non cambia visto che, essendo in assembler, viene compilata a parte e senza ottimizzazioni, come si può notare nelle linee successive):

usando gcc -O0
aldo@mylinux:~/blogtest$ gcc -c strlenasm.c -o strlenasm.o
aldo@mylinux:~/blogtest$ gcc -c -O0 strlens.c -o strlens.o
aldo@mylinux:~/blogtest$ gcc strlens.o strlenasm.o -o strlens
aldo@mylinux:~/blogtest$ ./strlens 
strlen(s)        = 999999999 - Tempo trascorso: 0.054087 secondi
strlenKaR(s)     = 999999999 - Tempo trascorso: 2.163937 secondi
strlenFreeBSD(s) = 999999999 - Tempo trascorso: 0.306909 secondi
strlenGlibc(s)   = 999999999 - Tempo trascorso: 0.251383 secondi
strlenMusl(s)    = 999999999 - Tempo trascorso: 0.305544 secondi
strlenAsm(s)     = 999999999 - Tempo trascorso: 0.061378 secondi
Ecco, il risultato canta: la versione implicita di gcc è scritta in assembler (e ripeto: non è affatto una scelta stranissima per piccole funzioni di libreria di uso frequente). Resta il mistero del perché compilando con -O2 (riguardare i risultati più sopra) la strlen() di default sembra non essere più la versione implicita, visto che le sue prestazioni peggiorano. Questo è un mistero che mi riprometto di risolvere prima o poi.

(...ma, visto che siamo ad Agosto, sicuramente durante le vacanze me ne dimenticherò, di solito mentre sono in spiaggia penso a ben altro...)

Ciao e al prossimo post!