Operazioni I/O
Stream
#include <stdio.h> // header in cui sono dichiarate:
// - stdout
// - stderr
// - stdin
int main
() {
fprintf
(stdout,
"ciao\n");
// scrive sullo stream relativo a standard output
printf("ciao\n");
// Stesso effetto della fprintf con stdout
// Standard C11 N1570:
// The printf function is equivalent to fprintf with the argument stdout interposed before the arguments to printf.
// https://port70.net/~nsz/c/c11/n1570.html#7.21.6.3p2
fprintf
(stderr,
"errore!!!\n");
// scrive sullo stream relativo allo standard error
return 0;
}
> gcc -m32 example-stream.c -o example-stream
> ./example-stream
ciao
ciao
errore!!!
> ./example-stream 1> standard_output.txt
errore!!!
> ./example-stream 2> standard_error.txt
ciao
ciao
File Descriptors
#include <unistd.h> // write, STDOUT_FILENO
#include <string.h> // strlen
#include <stdio.h> // printf, perror
#include <stdlib.h> // exit
#include <errno.h> // errno
int main
() {
ssize_t scritti;
char* testo =
"Hello World\n";
// costante STDOUT_FILENO == 1
scritti = write
(STDOUT_FILENO, testo, strlen
(testo
));
if (scritti ==
-1) { // errore della write, vedere: man 2 write
exit
(1);
}
printf("%lu\n", scritti
);
return 0;
}
#include <unistd.h> // write, STDOUT_FILENO
#include <string.h> // strlen
#include <stdio.h> // printf, perror
#include <stdlib.h> // exit
#include <errno.h> // errno
int main
() {
ssize_t scritti;
char* testo =
"Hello World\n";
scritti = write
(176677, testo, strlen
(testo
));
// file descriptor volutamente non valido per indurre un errore nella write
if (scritti ==
-1) {
perror
("Errore in write");
// perror permette di stampare una stringa che descrive l'errore
// prende come argomento un ulteriore stringa che viene concatenata alla descrizione dell'errore
// in questo caso emettera sullo stderr: "Errore in write: Bad file descriptor"
// dove ": Bad file descriptor" è stato aggiunto da perror in coda
// alla stringa fornita come argomento
exit
(1);
}
printf("%lu\n", scritti
);
return 0;
}
#include <unistd.h> // write, STDOUT_FILENO
#include <string.h> // strlen
#include <stdio.h> // printf, perror
#include <stdlib.h> // exit
#include <errno.h> // errno
int main
() {
ssize_t scritti;
char* testo =
"Hello World\n";
scritti = write
(176677, testo, strlen
(testo
));
if (scritti ==
-1) {
// valutiamo la causa dell'errore e
// reagiamo diversamente in base all'errore
switch (errno
) {
case EBADF:
// uno dei possibile errori di write. Vedere: man 2 write, sezioni RETURN VALUE e ERRORS
fprintf
(stderr,
"Descrittore errato\n");
// stampiamo semplicemente una stringa sullo stderr ma potremmo fare qualcosa di piu' utile
break;
default:
perror
("Errore in write");
}
exit
(1);
}
printf("%lu\n", scritti
);
return 0;
}
Tracciamento invocazione system call
#include <stdio.h>
int main
() {
printf("hello\n");
// printf utilizza internamente gli stream, che a loro volta sono implementati utilizzando i file descriptor
return 0;
}
Per tracciare le system call invocate da un eseguibile in Linux possiamo utilizzare il tool
strace:
> strace ./hello 2> dump_syscall.txt
hello
L'output di
strace è emesso sullo standard error.
Contenuto di
dump_syscall.txt:
execve("./hello", ["./hello"], [/* 85 vars */]) = 0
brk(NULL) = 0x21d1000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fddc6825000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=211509, ...}) = 0
mmap(NULL, 211509, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fddc67f1000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\t\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0
mmap(NULL, 3971488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fddc6238000
mprotect(0x7fddc63f8000, 2097152, PROT_NONE) = 0
mmap(0x7fddc65f8000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c0000) = 0x7fddc65f8000
mmap(0x7fddc65fe000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fddc65fe000
close(3) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fddc67f0000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fddc67ef000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fddc67ee000
arch_prctl(ARCH_SET_FS, 0x7fddc67ef700) = 0
mprotect(0x7fddc65f8000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ) = 0
mprotect(0x7fddc6827000, 4096, PROT_READ) = 0
munmap(0x7fddc67f1000, 211509) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 5), ...}) = 0
brk(NULL) = 0x21d1000
brk(0x21f2000) = 0x21f2000
write(1, "hello\n", 6) = 6
exit_group(0) = ?
+++ exited with 0 +++
Effettivamente
printf invoca la system call
write:
Image credits to:
shichao.io
Invocazione di una system call
Diverse funzioni definite da POSIX sono semplicemente composte da codice che innesca l'invocazione di una system call. Il meccanismo di invocazione di una system call è specifico per l'architettura e per il kernel in uso. Il meccanismo di invocazione non è stabilito dallo standard POSIX. Per scrivere codice portabile POSIX dobbiamo sempre utilizzare le API POSIX: sarranno quest'ultime a gestire l'invocazione della system call in base alla piattaforma utilizzata.
Invocazione di una system call in x86 Linux
Il meccanismo di invocazione di una system call in Linux è specificato in
man syscall, sezione
Architecture calling conventions.
Le system call implementate da Linux sono elencate in un header del kernel:
> cat /usr/src/linux-headers-$(uname -r)/arch/x86/include/generated/uapi/asm/unistd_32.h
[...]
#define __NR_write 4
[...]
La system call Linux relativa alla
write ha l'id numerico
4. Gli argomenti presi dalla system call write sono gli stessi presi dalla funzione POSIX
write, vedere
man 2 write.
// Non usiamo la funzione POSIX write ma implementiamo noi una
// nostra funzione che invoca la system call linux che implementa il task della write
int mywrite(int fd, const void *buf, int count);
int main() {
mywrite(1, "Hello\n", 6);
return 0;
}
# syscall numbers:
# cat /usr/src/linux-headers-$(uname -r)/arch/x86/include/generated/uapi/asm/unistd_32.h
# ssize_t write(int fd, const void *buf, size_t count)
# eax ebx ecx edx
.globl mywrite
mywrite:
pushl %ebx
movl $4, %eax # syscall write ha id numerico 4
# #define __NR_write 4 in /usr/src/linux-headers-$(uname -r)/
# arch/x86/include/generated/uapi/asm/unistd_32.h
movl 8