Quasi tutti i programmatori hanno una conoscenza più o meno approfondita di cosa siano e come funzionino gli argomenti da linea di comando e le variabili di ambiente.
int main(int argc, char *argv[], char *envp[]) {}
Dal classico punto di ingresso del nostro programma si evince come argv e envp siano array di puntatori a stringhe (terminate dal classico carattere di fine stringa) i cui elementi puntano, rispettivamente, ad argomenti da linea di comando e variabili d'ambiente.
Entrambi questi array si trovano all'inizio dello stack riservato dal kernel per il thread principale del processo ed entrambi, come da specifica ABI, devono terminare con un puntatore nullo. Nel caso dell'array di puntatori agli argomenti da linea di comando, ad esempio, sarà argv[argc] = 0, vedi fig. sotto all'indirizzo 8 + 8 * argc + %rsp. Stessa cosa per le variabili d'ambiente, vedi sopra la regione di memoria riservata ad envp.
Insieme a questi due array ne esiste un altro, l'Auxiliary Vector, meno conosciuto (per via del suo uso e del tipo di informazioni che contiene) ma non meno importante. Quello che distingue maggiormente quest'ultimo array ed i primi due e che argomenti da linea di comando e variabili di ambiente trasmettono informazioni da un programma (ad es. la shell) ad un altro (la nostra applicazione) mentre l'Auxiliary Vector trasmette informazioni dal sistema operativo ad un programma.
La sua presenza si rende necessaria perché il linker dinamico ha bisogno di andare a recuperare informazioni per esso vitali ai fini di rilocazione per tutti gli oggetti rilocabili caricati dal loader.
Questa figura rappresenta l'organizzazione dello stack nel momento in cui viene effettuata la chiamata a _start(), prima che venga effettuata la chiamata alla main(). Guardandola si capiscono molte cose interessanti. Innanzitutto rende evidente come argv ed envp (o meglio, i loro elementi) facciano riferimento alla zona dello stack denominata Blocco informazioni, che contiene le vere e proprie stringhe contenenti argomenti da linea di comando e variabili d'ambiente per il processo corrente.
Ma, soprattutto, questa figura ci dice come possiamo avere accesso alle informazioni degll'Auxiliary Vector. Prima di vedere come, è utile una breve descrizione della struttura e dei vari tipi di elementi (entry) che l'Auxiliary Vector contiene.
typedef struct { uint64_t a_type; union { uint64_t a_val; } a_un; } Elf64_auxv_t;
Sopra si può vedere la struttura che rappresenta un elemento (entry) dell'Auxiliary Vector. Il membro a_type identifica il tipo dell'elemento mentre il membro a_val indica un valore che cambia di significato a seconda del tipo indicato da a_type. Come descritto dalla figura sopra, la dimensione di ogni entry è di 16 byte, ma con l'ultima entry (l'elemento Null) di soli 8 byte. Sotto è riportata una tabella che indica alcuni valori di a_type ed i relativi significati di a_val. Per una descrizione completa e dettagliata si veda [2].
Nome Valore a_type Significato a_val
AT_NULL 0 /* Irrilevante */
AT_IGNORE 1 /* Irrilevante */
AT_EXECFD 2 /* Descrittore del file del programma */
AT_PHDR 3 /* Indirizzo del Program Header caricato in memoria */
AT_PHENT 4 /* Dimensione di una entry del Program Header */
AT_PHNUM 5 /* Numero di Program Header */
AT_PAGESZ 6 /* Dimensione di una pagina di sistema */
AT_BASE 7 /* Indirizzo di base dell'interprete */
AT_FLAGS 8 /* Flags */
AT_ENTRY 9 /* Indirizzo dell'Entry point del programma */
AT_NOTELF 10 /* Un valore diverso da zero indica che il programma non è nel formato ELF */
AT_UID 11 /* User ID reale del processo */
AT_EUID 12 /* User ID effettivo del processo */
AT_GID 13 /* Group ID reale del processo */
AT_EGID 14 /* Group ID effettivo del processo */
AT_PLATFORM 15 /* Indirizzo a stringa contenente nome della piattaforma */
AT_CLKTCK 17 /* Frequenza di times() */
Ora non rimane che descrivere come poter accedere agli elementi contenuti nell'Auxiliary Vector. In effetti ci sono vari modi per farlo. Il primo sfrutta il fatto che, nonostante le specifiche ABI non definiscano alcun ordinamento particolare (per Auxiliary Vector, argomenti da linea di comando e variabili d'ambiente), in tutte le implementazioni Unix-like la disposizione è quella vista nella figura, con l'Auxiliary Vector posizionato subito dopo il puntatore nullo di envp. Per accedere all'Auxiliary Vector basterà quindi iterare su envp fino al puntatore nullo e avanzare ancora di 8 byte.
#include <stdio.h> #include <elf.h> int main(int argc, char* argv[], char* envp[]) { Elf64_auxv_t *auxv; while (*envp++ != NULL); /* auxv->a_type = AT_NULL indica la fine dell'Auxiliary Vector */ for (auxv = (Elf64_auxv_t *)envp; auxv->a_type != AT_NULL; auxv++) { if (auxv->a_type == AT_PLATFORM) printf("AT_PLATFORM: %s\n", (char*)auxv->a_un.a_val); } return 0; }
Un secondo modo, di più alto livello, prevede l'uso della funzione getauxval() che ritorna il valore del tipo di entry che gli viene passato come parametro.
#include <stdio.h> #include <sys/auxv.h> int main() { unsigned long value = getauxval(AT_PLATFORM); printf("AT_PLATFORM: %s\n", (char*)value); return 0; }
Si può usare, volendo, anche la variabile d'ambiente LD_SHOW_AUXV per mostrare, da riga di comando, l'Auxiliary Vector di un eseguibile.
export LD_SHOW_AUXV=true
./my_app
AT_SYSINFO_EHDR: 0x7ffe63100000
AT_HWCAP: bfebfbff
AT_PAGESZ: 4096
AT_CLKTCK: 100
AT_PHDR: 0x400040
AT_PHENT: 56
AT_PHNUM: 9
AT_BASE: 0x7f5ead1ca000
AT_FLAGS: 0x0
AT_ENTRY: 0x4003e0
AT_UID: 1000
AT_EUID: 1000
AT_GID: 1000
AT_EGID: 1000
AT_SECURE: 0
AT_RANDOM: 0x7ffe6300b719
AT_EXECFN: ./my_app
AT_PLATFORM: x86_64
Un altro modo, sempre da riga di comando, è quello di leggere il file /proc/PID/auxv. Dove PID è il process ID del processo. Questo metodo prevede, ovviamente, che si abbiano i permessi per leggere il file. Non si può usare il semplice comando cat poichè i valori decimali rappresentati da a_type e a_val sono di 8 byte. Leggere un carattere alla volta non avrebbe senso. In quest'ultimo caso, però, l'output non è molto user-friendly. Bisogna fare affidamento ai valori numerici di a_type piuttosto che alle relative descrizioni.
pidof firefox
8354
od -t d8 /proc/8354/auxv
0000000 33 140736229810176
0000020 16 3219913727
0000040 6 4096
0000060 17 100
0000100 3 94241942745152
0000120 4 56
0000140 5 10
0000160 7 140642528149504
0000200 8 0
0000220 9 94241942763760
0000240 11 1000
0000260 12 1000
0000300 13 1000
0000320 14 1000
0000340 23 0
0000360 25 140736229772969
0000400 31 140736229781471
0000420 15 140736229772985
0000440 0 0
Riferimenti:
[1] http://articles.manugarg.com/aboutelfauxiliaryvectors
[2] https://www.uclibc.org/docs/psABI-x86_64.pdf
[3] https://lwn.net/Articles/519085/
Nessun commento:
Posta un commento