Ho scoperto solo di recente che vim puo’ fare da editor anche per i binari.
Il buon vecchio:
sim@idrogeno:~/script/gcc/tmp$ cat hw.c #includeint main(int argc, char **argv) { printf("Hello world\n"); return 0; }
post compilazione appare così:
sim@idrogeno:~/script/gcc/tmp$ head -n1 hw ELF>à@@À@@%"@@@@@À@@@@¤¤ ¨¨`¨▒ ÐÐ`Ð` @@ Påtdèè@è@$$Qåt/lib64/ld-linux-x86-64.so.2GN ▒ÂÀ__gmon_start__libc.so.6puts__libc_start_mainGLIBC_2.2.5u▒i 1`HèkèúèåHÃÿ5Ê ÿ%Ì @ÿ%Ê héàÿÿÿÿ% héÐÿÿÿ1íIÑ^HâHäðPTIÇÀð@HÇÁ@HÇÇÌ@èÇÿÿÿôHHY HÀtÿÐHÃUHåSH=p uK¸À`Hj H-¸`HÁøHXÿH9Ús%HBHE ÿŸ`H7 H9ÚrâÆ# H[ÉÃfff.UH= Håt▒¸HÀt¿È`IÃÉAÿã@ÉÃUHåHì}üHuð¿Ü@èÛþÿÿ¸ÉÃóÃfffff.Hl$ØL|$øH- L= Ld$àLl$èLt$ðH\$ÐHì8L)ýAþIõHÁýIÔèSþÿÿHít1Û@LâLîD÷AÿßHÃH9ërêH \Hl$Ld$▒Ll$ Lt$(L|$0HÄ8ÃUHåSH Høÿt»¨`DHÿÐHHøÿuñH[ÉÃHè_þÿÿHÃHello worlc$äþÿÿÿÿÿx▒ÿÿÿzRx zRx Ì@ AC þÿÿ$4þÿÿJf@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ È@@@õþÿoh@è@@ @ sim@idrogeno:~/script/gcc/tmp$
Bene. Ogni tanto capita di avere a che fare con spazzatura illeggibile.
Ora, se apriamo il binario con vim -b hw avvertiamo l'editor che sta per aprire un binario, quindi che stia attento.
Il codice è comunque ancora illeggibile.
Per rendere piu' chiaro l'hex interno usiamo l'opzione :set display=uhex che visualizza i vari valori byte per byte.
Dovremmo vedere qualcosa tipo:
<7f>ELF<02><01><01><00><00><00> [...]
Per essere in grado di lavorare sul serio sul codice piu’ comodamente è necessario convertire il testo (o meglio, fare un hexdump) ulteriormente con un programma unix che si chiama xxd (man) attraverso l’opzione :%!xxd (modifica tutto il file [%] usando il comando esterno [!] xxd). Avremo quindi:
sim@idrogeno:~/script/gcc/tmp$ head hw.xxd 0000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............ 0000010: 0200 3e00 0100 0000 e003 4000 0000 0000 ..>.......@..... 0000020: 4000 0000 0000 0000 c010 0000 0000 0000 @............... 0000030: 0000 0000 4000 3800 0800 4000 2500 2200 ....@.8...@.%.". 0000040: 0600 0000 0500 0000 4000 0000 0000 0000 ........@....... 0000050: 4000 4000 0000 0000 4000 4000 0000 0000 @.@.....@.@..... 0000060: c001 0000 0000 0000 c001 0000 0000 0000 ................ 0000070: 0800 0000 0000 0000 0300 0000 0400 0000 ................ 0000080: 0002 0000 0000 0000 0002 4000 0000 0000 ..........@..... 0000090: 0002 4000 0000 0000 1c00 0000 0000 0000 ..@.............
Ora possiamo modificare il programma con vim andando a toccare i codici hex nella parte centrale (la parte letterale è irrilevante).
Posizionandoci su un carattere nella parte letterale e utilizzando il comando ga possiamo vedere che vim ci annuncia la codifica in hex corrispondente (e, ovviamente, presente nella parte centrale nel caso di caratteri).
E’ fondamentale, finite le modifiche al codice, reversare il dump con :%!xxd -r e poi salvare l’eseguibile con vim come normale.
Funzionozze per interrogare MySQL con il C.
La lista completa delle funzioni sul sito di MySQL.
Prototipo di mysql_init(), per allocare memoria e inizializzare l’oggetto MYSQL.
proto: MYSQL *mysql_init(MYSQL *mysql)
Dopo aver inizializzato l’oggetto si puo’ creare la connessione al db con mysql_real_connect().
proto: MYSQL *mysql_real_connect(MYSQL *mysql, const char *host,
const char *user, const char *passwd, const char *db,
unsigned int port, const char *unix_socket, unsigned long client_flag)
Esempio: mysql_real_connect(&mysql, host, user, pass, db_name, 0, NULL, 0)
La query si chiama col prototipo che segue inserendo la stringa stmt_str e la lunghezza della stessa (es.: con strlen()). Se ritorna zero, la query è avvenuta.
proto: int mysql_real_query(MYSQL *mysql, const char *stmt_str,
unsigned long length)
L’analisi del risultato è un po’ macchinosa.
Il risultato della query viene incamerato grazie alla funzione mysql_store_result() che incamera i dati nella struct MYSQL_RES.
proto: MYSQL_RES *mysql_store_result(MYSQL *mysql)
Esempio:
MYSQL mysql; MYSQL_RES *result; [ blah blah blah... ] result = mysql_store_result(&mysql)
Notare che result rappresenta il risultato della query, sia esso
- Nullo perchè la query non è andata a buon fine
- Con dati causati da un SELECT o uno SHOW.
- Con risultato ma senza dati causato magari da un INSERT (che non spamma righe ma un risultato di righe modificate).
La struct MYSQL_RES di result puo’ venire esplorata con varie funzioni.
La funzione principale è probabilmente mysql_fetch_row(result) che estrae le singole righe dal result set.
proto: MYSQL_ROW mysql_fetch_row(MYSQL_RES *result)
Il numero di valori nella riga (i campi restituiti) è dato da mysql_num_fields(result) e mysql_num_rows() conta le righe restituite.
proto: unsigned int mysql_num_fields(MYSQL_RES *result) proto: my_ulonglong mysql_num_rows(MYSQL_RES *result)
Questo dovrebbe bastare per “navigare” i risultati della SELECT query (un bell’esempio per ciclare i risultati della query lo si trova in fondo alla pagina del fetch).
Per chiudere le operazioni, usare mysql_free_result(result) per liberare la memoria allocata per il result set e mysql_close(&mysql).
Aggiuntine.
mysql_fetch_result(result) parsa i campi.
proto: MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *result)
num_fields = mysql_num_fields(result);
fields = mysql_fetch_fields(result);
for(i = 0; i < num_fields; i++)
{
printf("Field %u is %s\n", i, fields[i].name);
}
MySQL è the world’s most popular open source database.
C è un (il?) linguaggio di programmazione sviluppato da D. Ritchie per implementare Unix OS.
L’API è l’application programming interface.
Il file /usr/include/mysql/mysql.h è l’header che contiene classi, funzioni e variabili per mysql (#include <mysql/mysql.h>).
Il manualazzo delle API MySQL.
MYSQL è la struttura per maneggiare la connessione al database (le si associa una variabile, e ne si richiama l’indirizzo con l’operatore unario & ogni qual volta serva – cioè sempre).
typedef struct st_mysql
{
[... blah blah blah ...]
char *host,*user,*passwd,*unix_socket,*server_version,*host_info,*info;
char *db;
struct charset_info_st *charset;
MYSQL_FIELD *fields;
MEM_ROOT field_alloc;
my_ulonglong affected_rows;
[... blah blah blah ...]
my_bool free_me; /* If free in mysql_close */
my_bool reconnect; /* set to 1 if automatic reconnect */
/*
Points to boolean flag in MYSQL_RES or MYSQL_STMT. We set this flag
from mysql_stmt_close if close had to cancel result set of this object.
*/
my_bool *unbuffered_fetch_owner;
[... blah blah blah ...]
} MYSQL;
MYSQL_RES è la struttura rappresentante il risutlato di una query che ritorna delle righe (es. SELECT).
typedef struct st_mysql_res {
my_ulonglong row_count;
MYSQL_FIELD *fields;
MYSQL_DATA *data;
MYSQL_ROWS *data_cursor;
unsigned long *lengths; /* column lengths of current row */
MYSQL *handle; /* for unbuffered reads */
MEM_ROOT field_alloc;
unsigned int field_count, current_field;
MYSQL_ROW row; /* If unbuffered read */
MYSQL_ROW current_row; /* buffer to current row */
my_bool eof; /* Used by mysql_fetch_row */
/* mysql_stmt_close() had to cancel this result */
my_bool unbuffered_fetch_cancelled;
const struct st_mysql_methods *methods;
} MYSQL_RES;
MYSQL_ROW è un array che rappresenta una riga di dati.
typedef char **MYSQL_ROW; /* return data as array of strings */
Ci metto pure il MYSQL_FIELD, visto che viene considerato dalla struct RES.
typedef struct st_mysql_field {
char *name; /* Name of column */
char *org_name; /* Original column name, if an alias */
char *table; /* Table of column if column was a field */
char *org_table; /* Org table name, if table was an alias */
char *db; /* Database for table */
char *catalog; /* Catalog for table */
char *def; /* Default value (set by mysql_list_fields) */
[... blah blah blah ...]
enum enum_field_types type; /* Type of field. See mysql_com.h for types */
} MYSQL_FIELD;
Dopo aver creato il socket, averlo bind’ato e messo in ascolto, nonche’ averlo connesso lato client, le nostre due macchine possono comunicare grazie ai comandi send() e recv():
int send(int sockd, void *buf, int len, int opt)
int recv(int sockd, void *buf, int len, int opt)
Il parametro len indica la lunghezza del buffer (dei dati da mandare o ricevere) e opt sono le opzioni (spesso 0).
Tutto quanto ho descritto finora è perfetto per la gestione TCP.
Nel caso del protocollo UDP non c’e’ connessione, quindi si possono evitare connect(), listen() e accept().
La comunicazione avviene fra i socket di client e server (con socket bind’ato) grazie a funzione come sendto e recvfrom – il kernel riceve i pacchetti datagram e li riceve sul processo in ascolto sulla porta a cui sono diretti oppure li scarta (con conseguente messaggio ICMP “port unreachable” al client) se non c’e’ alcun processo in ascolto.
ssize_t sendto(int sockd, const void *buf, size_t len,
int flags, const struct sockaddr *to, socklen_t tolen)
Flags è un parametro di opzioni che va di default a 0, to e tolen sono rispettivamnete l’indirizzo di destinazione e la sua lunghezza.
ssize_t recvfrom(int sockd, const void *buf,
size_t len, int flags, const struct sockaddr *from,
socklen_t *fromlen)
(Si possono mettere from e fromlen a NULL se non si è interessati a sapere i dati dell’indirizzo di origine.)
Dopo aver creato un socket e creato una struttura coi dati dell’host necessari, dobbiamo associare le due cose grazie alla funzione bind().
La funzione bind viene utilizzata dal server per associare il socket ad un ip ed una porta; un client non usa bind perche’ il suo indirizzo viene determinato dall’interfaccia usata, e la porta la decide il kernel scegliendo la prima disponibile.
Il prototipo di bind è:
int bind(int sockd, const struct sockaddr *serv_addr, socklen_t addrlen)
dove viene considerato il socket descriptor, la struttura dell’indirizzo del server, e la dimensione dell’indirizzo.
L’indirizzo puo’ essere inserito liberamente, oppure si possono utilizzare alcune costanti predefinite:
- INADDR_ANY per indicare qualunque indirizzo (0.0.0.0)
- INADDR_BROADCAST
- INADDR_LOOPBACK (127.0.0.1)
Dopo il bind’aggio del socket, si deve indicare la server di rimanere in ascolto con listen() e di accettare con accept() eventuali connessioni.
int listen(int sockd, int backlog)
int accept(int sockd, struct sockaddr *addr,
socklen_t *addrlen)
Nel caso di accept, l’indirizzo e la dimensione relativa sono del client che invia la richiesta di connessione (nel caso ci servissero i dati per loggare i contatti).
La connessione TCP fra client e server si stabilisce grazie alla funzione connect() dopo aver creato il socket-lato-client:
int connect(int sockd, const struct sockaddr *serv_addr,
socklen_t addrlen)
Forse non tutti sanno che l’ordine di lettura dei byte non è fissato (wiki).
Le diverse architetture dei calcolatori hanno un modo differente di leggere dati di dimensione superiore al byte: la cosa fondamentale è che puo’ cambiare l’ordine in cui diversi byte consecutivi vengono memorizzati.
Big endian è il caso in cui le posizioni di memoria sono occupate a partire dal byte piu’ significativo (a sinistra) (Mac, per esempio, usa questo modo).
Little endian è il caso in cui le posizioni di memoria partono dal byte piu’ a destra (Intel).
Esempio:
Se vogliamo memorizzare la stringa ABCD, e la localizziamo all’indirizzo di memoria 100, in big endian avremo A al 100, B al 101, C al 102, D al 103; mentre in little endian avremo D al 100, C al 101, B al 102 e A al 103.
Ovviamente questo problema diventa immenso quando si inseriesce come stringa un numero (ad esempio una porta): 1234 puo’ diventare 4321 con effetti assai diversi.
Il formato di rete dei byte, o network byte order, è big endian; per questo è necessario convertire i valori numerici degli ip e delle porte in big endian – per fare questo ci vengono in soccorso le funzioni htonl (per i long int) e htons (per gli short). Ad esempio nel definire la struttura dell’indirizzo abbiamo:
struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_port = htons(port); sa.sin_addr.s_addr = htonl(INADDR_ANY);
In generale un socket di rete è un tubo tra due processi (ie: client – server) col quale ciascuno dei due processi puo’ interagire (leggere, scrivere, ecc.).
Il prototipo per creare un socket in C è il seguente:
int socket (int domain, int type, int prot)
Il parametro domain indica la famiglia da usare (AF_INET, AF_INET6, AF_UNIX), il type definisce la comunicazione (tcp, upd: SOCK_STREAM, SOCK_DGRAM) e protocol definisce un particolare protocollo (generalmente vale 0).
Il socket da solo non vale niente (allocca le strutture necessarie nel kernel); è quindi necessario fornire gli indirizzi secondo strutture di dati prestabilite (in sys/socket.h):
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
}
La struttura è volutamente generica: le funzioni che usano gli indirizzi hanno nel prototipo un puntatore alla struttura, e necessitano di un casting (del puntatore) per farsi capire dal protocollo voluto.
Gli indirizzi IPv4 seguono la seguente struttura:
struct sockaddr_in
{
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
}
e la struttura in_addr (che contiene l’IPv4 di 32 bit) risulta:
struct in_addr
{
u_int32_t s_addr;
}
Le strutture IPv6 e per indirizzi locali hanno alcuni campi specifici.