Difference between revisions of "Esperimenti 2021 settembre 30"
Jump to navigation
Jump to search
(Created page with "La history è stata modificata per rendere replicabili gli esperimenti (per quanto possibile) <pre> # hello_world.c e' il solito hello world gcc -o hello_world hello_world.c ...") |
(No difference)
|
Revision as of 18:37, 30 September 2021
La history è stata modificata per rendere replicabili gli esperimenti (per quanto possibile)
# hello_world.c e' il solito hello world
gcc -o hello_world hello_world.c
./hello_world
# funziona ... ma gcc non e' il compilatore, invoca l'intera toolchain
# preprocessore - compilatore - ottimizzatore - assemlatore - linker
# cosi' si vedono i passi
gcc -v -o hello_world hello_world.c
# ma i comandi hanno tantissimi parametri non si capisce
# la regola e' che gcc puo' interrompere la catena di tool con un parametro.
# -E esegue solo il preprocessore (e manda l'output in stdout)
$ gcc -E hello_world.c
# -S genera i ferma dopo il compiletore e l'ottimizzatore, quindi produce assembler
$ gcc -S hello_world.c
#ecco l'assembler
$ cat hello_world.s
# -c non chiama il linker quindi genera il file oggetto .o
$ gcc -c hello_world.c
# cosi' si vedono i tipi dei file
$ file hello_world*
# per completare il percorso di compilazioone a partire da un risultato intermedio
basta dare in input un file col suffisso giusto
# questo genera l'eseguibile a partire dal sogente assembler
$ gcc hello_world.s
# funziona
$ ./a.out
# cancelliamo a.out
$ rm a.out
# questo chiama solo in linker:
$ gcc hello_world.o
$ ./a.out
######## proviamo a fare a meno della libreria
42.c contiene:
int main() {
return 42;
}
########
$ gcc -o 42 42.c
$ ./42
#funziona ma sembra non far nulla
$ echo $?
# ecco dove e' finito, viene assegnato a una variabile della shell
# aggiungiamo 2 righe a 42.c
int main() {
int i;
for (i = 0; i< 1000000000; i++)
;
return 42;
}
$ gcc -o 42 42.c
$ ./42
# ora aspetta un po'.
$ gcc -S 42.c
$ cat 42.s
# si vede nell'assembler il loop di attesa
$ ls
# chiediamo all'ottimizzatore di eliminare il codice inutile.
$ gcc -O2 -o 42 42.c
$ ./42
# ora e' velocissimo
$ gcc -O2 -S 42.c
$ cat 42.s
#infatti nell'assembler e'sparito il loop
$ gcc -O2 -S 42.c
$ cat 42.s
# se vogliamo che il compilatore mantengai il loop anche ottimizzando il codice dobbiamo
# dire che non puo' fare assunzioni sulla variabile i che puo' essere modificata da agenti
# esterni, fuori dalla portata del codice generato dal compialtore... quindi la variabile i
# deve essere definita: "volatile int i:". Se lo si fa:
$ gcc -O2 -S 42.c
$ cat 42.s
# ora il loop e' rimasto (ma altre parti con codice inutile sarebbero state ottimizzate,
# quindi spetta al programmatore dire al compilatore cosa puo' essere ottimizzato e cosa no.
$ gcc 42.c
$ a.out
# l'attesa c'e'
# ora pero' vi faccio vedere che la libreria c'e' anche se non la usiamo.
# compiliamo in modo statico (senza librerie dinamiche
$ gcc -static 42.c
$ ls -l a.out
# gia' la stazza fa presagire che non c'e' solo un loop e un return.
# con nm si vedono i simboli contenuti
$ nm a.out
# infatti ce ne sono tanatissimi compresi printf e scanf che non abbiamo utilizzato.
# se proviamo a compilare e linkare "a mano"
$ gcc -c 42.c
$ ld 42.o
#l'errore che otteniamo e' che manca "_start". Per Linux il progrmma principale e' _start
# e non main... la libreria C fra le altre cose impleemnta uno start che chiama main e
# quando main ritorna il controllo chiama la system call exit (infatti i processi
# non muoiono mai di morte naturale, o vengono uccisi o si suicidano).
# ho preparato un esempiodi codice start in assembler:
$ cat > crt0r.S
# o in altro modo generiamo il file crt0r.S col contenuto seguente
---------------
.text
.globl _start
_start: # _start is the entry point known to the linker
xor %ebp, %ebp # effectively RBP := 0, mark the end of stack frames
mov (%rsp), %edi # get argc from the stack (implicitly zero-extended to 64-bit)
lea 8(%rsp), %rsi # take the address of argv from the stack
lea 16(%rsp,%rdi,8), %rdx # take the address of envp from the stack
xor %eax, %eax # per ABI and compatibility with icc
call main # %edi, %rsi, %rdx are the three args (of which first two are C standard) to main
xor %rdi, %rdi # clean rdi
mov %eax, %edi # copy eax to edi (syscall first arg)
mov $60, %eax # exit
syscall
---------------
# cancelliamo a.out
$ rm a.out
# usiamo l'assemblatore per generare l'oggetto
$ as -o crt0r.o crt0r.S
$ ld -o a.out crt0r.o 42.o
$ ./a.out
$ echo $?
# sembra funzionare
#ora la libc non c'e'
$ nm a.out
# start definiscce _start ma ha necessita' (U-ndefined) main
$ nm crt0r.o
# e in 42.o c'e' main
$ nm 42.o
# ora facciamo un aprova col file hw6 che contiene:
--------------------
long mystrlen(char *s) { // lunghezza di una stringa
long len;
for (len = 0; *s != 0; len++)
s++;
return len;
}
long mywrite(char *s) { //stampa una stringa
long addr = (long) s; //addr e' l'indirizzo della stringa
long len = mystrlen(s); // e len e' la lunghezza
register long r_syscallno asm("rax") = 1; // mette nel registro rax il numero della syscall 1=write
// l'elenco completo in /usr/include/x86_64-linux-gnu/asm/unistd_64.h
// mutatis mutandis se usare un'altra architetttura di processore
register long r_arg1 asm("rdi") = 1; // da "man 2 write" si vede che il 1' paramentro e' il fd. 1 e' stdout
register long r_arg2 asm("rsi") = addr; // secondo parametro l'indirizzo del buffer da mettere in output
register long r_arg3 asm("rdx") = len; // terzo parametro e' la lunghezza
register long r_retvalue asm("rax"); // chiamo r_retvalue il registro rax, dove le syscall mettono il valore di rit.
asm("syscall"); // genera la trap per "svegliare" il kernel
return r_retvalue; // restituisco il valore di ritorno
}
void myexit(long value) { // termina il chiamante
register long r_syscallno asm("rax") = 60; // la ssytem call 60 e' _exit
register long r_arg1 asm("rdi") = value; // ed ha un solo parametro: il valore di ritorno
asm("syscall");
}
void _start(void) {
long retvalue;
retvalue = mywrite("hello world\n"); // stampa la famosa stringa
myexit(retvalue); // e resituisci come exit status il valore di ritorno della write
// e' il numero dei caratteri stampati (o il codice di erroe in negativo).
}
$ gcc -c hw6.c
$ ld hw6.o
# ora non e' servito nulla
$ ./a.out
$ echo $?
# e funziona
# ora facciamo un altro esperimento. scriviamo questo programma che chiamiano noSO.c:
-----------------
typedef unsigned char uint8_t;
#define DDRC (*(volatile uint8_t *) 0x27)
#define PORTC (*(volatile uint8_t *) 0x28)
int main(void)
{
volatile int i;
DDRC |= 1 << 5;
for (;;) {
for (i=0; i < 32767; i++)
;
PORTC ^= 1 << 5;
}
}
-----------------
Convertendo interi (0x27 e' il numero 27 esadecimale, cioè 39) in puntatori
accediamo alle periferiche (memoria o altro) "sparando" sul bus indirizzi ciò che pi ci piace.
# proviamo a compilarlo
$ gcc noSO.c
$ ./a.out
# il risultato ' segmentation fault, perché gli indirizzi non sono legali per un processo in esecuzione in linux.
# compiliamolo per l'architetture avr atmel/atmega: (occorre il compilatore)
$ avr-gcc -mmcu=atmega168 -Os -o noSO noSO.c
# l'eseguibile ottenurto non e' per intel:
$ file noSO
# generiamo il file .hex (perché l'utility di trasferimento lo vuole così)
$ avr-objcopy -O ihex noSO noSO.hex
# ... carichiamo il file sul microcontrollore atmega168
$ avrdude -p m168 -c stk500v2 -P /dev/ttyUSB0 -U flash:w:noSO.hex
# (occorre il chip, il programmatore ed u circuito quale quello descirtto qui:
# http://tuxgraphics.org/electronics/200904/avr-c-programming.shtml
# il led lampeggia. potere del software.