martedì 25 aprile 2017

Uno sguardo (indiscreto) all'Auxiliary Vector

L'ambiente di riferimento usato per questo articolo è quello Unix-like ed il formato degli eseguibili è quello ELF.
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 argcchar *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 argccharargv[], charenvp[])
{
 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