Recuperare la command line, e non solo
Quando un programma elf viene lanciato eredita una serie di informazioni depositate come dword sullo stack. Queste informazioni riguardano sia la command line, completa di tutti gli argomenti, sia le variabili d'ambiente associate. La situazione dello stack, all'inizio dell'esecuzione, è schematizzata nella figura seguente.
argC | integer: Numero di argomenti (C=N+1) |
arg0 | pointer: Nome del programma |
arg1 | pointers: Argomenti command line |
arg2 | |
.... | |
argN | |
null | integer: Terminatore argomenti |
env0 | pointers: Variabili d'ambiente |
env1 | |
.... | |
envM | |
null | integer: Terminatore variabili |
L'esempio seguente estrae dallo stack tutto il contenuto schematizzato nella tabella precedente e lo visualizza sul terminale. La conversione del contatore è effettuata per semplicità , nell'ipotesi che il suo valore sia minore di 10, aggiungendo semplicemente una costante opportuna.
;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; commline_1.asm ; Visualizza command line e variabili d'ambiente. ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; compilare con nasm+ld %define STDOUT 1 %define SYSCALL_EXIT 1 %define SYSCALL_WRITE 4 global _start ;necessario per il linker ld section .data ; Sezione dati inizializzati msg1 db 0Dh,0Ah,"Numero di argomenti: " argc dd 0 db " incluso il nome del programma.",0Dh,0Ah MSG1 equ $-msg1 msg2 db 0Dh,0Ah,"Elenco argomenti: ",0Dh,0Ah MSG2 equ $-msg2 msg3 db 0Dh,0Ah,"Elenco variabili d'ambiente: ",0Dh,0Ah MSG3 equ $-msg3 crlf db 0Dh,0Ah xflag dd 0 section .text _start: ;:::::::::::::::::::::::::::::::::::::::: Visualizza Numero degli Argomenti pop ecx ; Estrae il contatore degli argomenti add ecx,20202030h ; Conversione in ASCII (Ipotesi N<10) mov [argc],ecx ; Inserimento nel corpo di msg1 mov eax, SYSCALL_WRITE ; write function mov ebx, STDOUT ; Arg1: file descriptor mov ecx, msg1 mov edx, MSG1 int 80h ; syscall kernel to write ;:::::::::::::::::::::::::::::::::::::::; Visualizza Messaggio Primo Elenco mov eax, SYSCALL_WRITE ; write function mov ebx, STDOUT ; Arg1: file descriptor mov ecx, msg2 mov edx, MSG2 int 80h ; syscall kernel to write ;:::::::::::::::::::::::::::::::::::::::: Ciclo principale estrazione/visualizzazione ; utilizzato sia per gli argomenti sia per ; le variabili d'ambiente xloop: pop ecx ; Estrae un puntatore or ecx,ecx ; Verifica se null jz exit1 ; Se SI: va a exit1 ; Se NO: prosegue mov esi,ecx ; Utilizza Puntatore esi per usare la lodsb xor edx,edx ; Azzera contatore lunghezza (edx) dec edx strlen: inc edx ; calcola la lunghezza di msg lodsb or al,al jnz strlen ; Visualizza msg mov eax, SYSCALL_WRITE ; write function mov ebx, STDOUT ; Arg1: file descriptor int 80h ; syscall kernel to write call newln ; Aggiunge un newline jmp short xloop ; Ricicla ;:::::::::::::::::::::::::::::::::::::::: Controllo per il secondo giro exit1: mov eax,[xflag] inc eax mov [xflag],eax cmp eax,2 je exit ;:::::::::::::::::::::::::::::::::::::::: Visualizza Messaggio Secondo Elenco mov eax, SYSCALL_WRITE ; write function mov ebx, STDOUT ; Arg1: file descriptor mov ecx, msg3 mov edx, MSG3 int 80h ; syscall kernel to write jmp short xloop ; Rientra Secondo giro ;:::::::::::::::::::::::::::::::::::::::: Uscita exit: mov EBX, 0 ; exit code, normal=0 mov EAX, SYSCALL_EXIT ; exit function int 80h ; syscall kernel to take over ;:::::::::::::::::::::::::::::::::::::::: Routine NewLine newln: mov eax, SYSCALL_WRITE ; write function mov ebx, STDOUT ; Arg1: file descriptor mov ecx, crlf mov edx, 2 int 80h ; syscall kernel to write ret ; EOF ::::::::::::::::::::::::::::::::::::::::::::::::::::::
Poichè le variabili d'ambiente sono molte è opportuno avviare il programma con:
./commline_1 ... ... ... |more
in modo da potere scorrere agevolmente tutto l'output. Si noterà , tra l'altro, che l'ultima voce delle variabili d'ambiente contiene ancora il nome del programma, che era già presente come arg0 nella lista degli argomenti.
Talvolta è necessario, invece, avere dei puntatori diretti ad alcuni punti speciali dello stack, senza dovere essere costretti a scaricarlo tutto, ecco alcuni suggerimenti:
pop eax ; Lettura contatore degli argomenti pop esi ; Il reg. esi punta il primo degli argomenti oppure pop eax ; Lettura contatore degli argomenti mov esi,[esp+eax*4] ; Il reg. esi punta l'ultimo degli argomenti oppure pop eax ; Lettura contatore degli argomenti mov esi,[esp+(eax+1)*4] ; Il reg. esi punta la prima var. d'ambiente
E' evidente che queste istruzioni devono essere usate proprio all'inizio del programma, prima di avere alterato lo stato dello stack.
L'esempio precedente ha, secondo me, l'inconveniente di alterare la situazione dello stack in quanto c'è una palese quantità di istruzioni di pop non equilibrate o da altrettante istruzioni di push o da una opportuna modifica del valore del registro esp. A tal proposito, c'è una scuola di pensiero che vuole che il programmatore, all'uscita del programma, lasci il puntatore di stack con lo stesso valore trovato all'inizio.
Quella che segue è una variante breve dell'esercizio precedente che illustra questo secondo modo di agire, per semplicità tratta soltanto gli argomenti della command line.
;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; commline_2.asm ; Visualizza la command line ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; compilare con nasm+gcc %define STDOUT 1 %define SYSCALL_WRITE 4 global main extern printf section .data msg1 db 0Dh,0Ah,"Numero di argomenti: " argc dd 0 db " incluso il nome del programma.",0Dh,0Ah MSG1 equ $-msg1 format: db '%s', 10, 0 ; Stringa di formato section .text main: ;:::::::::::::::::::::::::::::::::::::::: mov ecx, [esp+4] ; Lettura di argC push ecx ; Salvataggio ecx add ecx,20202030h ; Conversione in ASCII (Ipotesi N<10) mov [argc],ecx ; Inserimento nel corpo di msg1 mov eax, SYSCALL_WRITE ; write function mov ebx, STDOUT ; Arg1: file descriptor mov ecx, msg1 mov edx, MSG1 int 80h ; syscall kernel to write pop ecx ; Ripristino ecx mov edx, [esp+8] ; Lettura puntatore di arg0 ;:::::::::::::::::::::::::::::::::::::::: Ciclo di lavoro xloop: push ecx ; Salvataggio registri usati da printf push edx ; ;........................................ push dword [edx] ; Passa puntatore stringa push dword format ; Passa stringa formato call printf ; Visualizza argomento add esp, 8 ; Rimozione parametri dallo stack ;........................................ pop edx ; Ripristino registri pop ecx ; add edx, 4 ; Modifica puntatore ad arg successivo dec ecx ; Decrementa contatore jnz xloop ; Ricicla ret ;EOF :::::::::::::::::::::::::::::::::::::::::::::::::::
Abbiamo usato il linker gcc in quanto si è preferito, per semplicità , effettuare l'output tramite la printf; si noti, a questo proposito, l'uso della stringa di formato dentro il segmento .data per concorrere al formato della printf. Il puntatore di stack esp, alla fine del programma, rimane inalterato.
La lunghezza effettiva della vita è data dal numero di giorni diversi che un individuo riesce a vivere. Quelli uguali non contano.
L. de Crescenzo