Cet article est écrit comme l'article du 8ème jour du Calendrier de l'Avent d'OthloTech 2017.
J'en suis responsable pendant deux jours consécutifs depuis hier. Hier, c'était sur le mobile, mais aujourd'hui j'aimerais changer et aborder le noyau en langage C. Plus précisément, Hello World sera exécuté en utilisant uniquement les appels système définis dans le système d'exploitation.
__ Public cible __
** Contenu de l'article **
Environnement d'exploitation
Écrivons Hello World, qui semble être le langage C le plus simple. C'est quelque chose que beaucoup de gens ont écrit, comme étudier à l'école.
Hello.c
#include<stdio.h>
int main(){
printf("Hello World!\n");
return 0;
}
Je l'ai écrit en utilisant le format d'impression. Vous pouvez facilement l'afficher en spécifiant simplement une chaîne de caractères. Cependant, cette fonction appelée printf ne fonctionnera que si stdio.h est inclus. C'est un soi-disant magique. L'opération à l'intérieur n'est pas bien comprise par cela seul.
Compilons et voyons à quoi ressemble l'exécutable. Comme il est débogué avec gdb, j'ai ajouté l'option -g, et comme le lien dynamique est paresseux, j'ai ajouté -static.
$ gcc Hello.c -static -g
$ gdb -q a.out
(gdb) break main
(gdb) layout asm
(gdb) run
(gdb) si
(gdb) si
...
J'ai essayé de marcher comme ça, mais ça ne se termine pas facilement. ~~ Je ne suis motivé qu'à la fin ~~ Je ne sais pas comment cela fonctionne, alors j'ai frappé strace, une commande qui énumère les appels système.
$ strace ./a.out
~/hello
execve("./a.out", ["./a.out"], 0x7ffdd8a3ad60 /* 43 vars */) = 0
brk(NULL) = 0x1182000
brk(0x11831c0) = 0x11831c0
arch_prctl(ARCH_SET_FS, 0x1182880) = 0
uname({sysname="Linux", nodename="Juju-62q", ...}) = 0
readlink("/proc/self/exe", "/home/(username)/hello/a.out", 4096) = 23
brk(0x11a41c0) = 0x11a41c0
brk(0x11a5000) = 0x11a5000
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
write(1, "Hello World\n", 12Hello World
) = 12
exit_group(0) = ?
+++ exited with 0 +++
Il semble que d'une manière ou d'une autre, la mémoire est sécurisée et que diverses choses sont faites. Cependant, je pense que l'écriture peut afficher une chaîne de caractères. Ensuite, je vais l'utiliser pour générer une chaîne de caractères.
Lorsque j'ai recherché la fonction d'écriture, elle a été définie comme suit. (/usr/include/unistd.h)
extern ssize_t write (int __fd, const void *__buf, size_t __n) __wur;
fd est un descripteur de fichier. Cette fois, ce sera 1 car il est émis vers la sortie standard.
buf est le contenu de sortie et n est le nombre de caractères. Cela semble gênant que printf, mais il semble que l'inconvénient ne soit pas pratique, donc il y a plus de choses qui ne sont pas utilisées ()
Incluons ceci.
Write.c
#include<unistd.h>
int main(){
const void *string = "Hello Write!\n";
write(1, string, 13);
return 0;
}
J'ai essayé d'afficher une chaîne de caractères dans la sortie standard. Faisons en sorte que cela fonctionne de la même manière.
$ gcc Write.c -static -g
$ gdb -q a.out
(gdb) break main
(gdb) layout asm
(gdb) run
(gdb) si
(gdb) si
...
Cette fois, je suis arrivé à l'affichage des caractères relativement rapidement.
Apparemment, il est affiché lors de l'appel de syscall.
Mais je n'aime pas #include <unistd.h>
.
Si une chaîne de caractères est affichée par syscall, il semble que vous deviez la définir et appeler syscall, j'ai donc étudié comment utiliser syscall.
Apparemment, des registres utilisés par le processeur 64 bits
rax = 1 (en spécifiant qu'il s'agit d'un appel système d'écriture) rdi = 1 (descripteur de fichier, c'est-à-dire 1 pour la sortie standard) rsi = (première adresse de la chaîne de caractères) (spécifier la chaîne de caractères d'affichage) rdx = (longueur de la chaîne) (nombre de caractères)
Il semble que la chaîne de caractères s'affiche lorsque syscall est appelé en tant que. Ensuite, appelons syscall directement depuis l'assembleur. Ensuite, vous pouvez dire au revoir à unistd.h détesté.
Je me réfère à ce qui suit http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/
Cliquez ici pour les registres CPU https://software.intel.com/en-us/articles/introduction-to-x64-assembly
Lors d'un appel en langage C, il semble que les spécifications ci-dessus ne peuvent être satisfaites que si au moins la chaîne de caractères et la longueur des caractères sont spécifiées, je voudrais donc spécifier l'argument. Lors de l'utilisation de l'argument avec un assembleur 64 bits, il semble que cela se passe comme suit.
rdi premier argument rsi deuxième argument rdi 3ème argument rcx 4ème argument r8 5ème argument r9 6ème argument
référence http://p.booklog.jp/book/34047/page/613687
Puisqu'il y a deux arguments cette fois, nous utiliserons rdi et rsi. Ensuite, stockez la première adresse de la chaîne de caractères dans rsi, le nombre de caractères dans rdx et 1 dans rax et rdi. Sur la base de ce qui précède, j'ai écrit un assembleur pour nasm. Le nom de la fonction est bonjour.
syscall.asm
bits 64
global hello
hello:
mov rdx, rsi
mov rsi, rdi
mov rax, 1
mov rdi, 1
syscall
ret
J'ai écrit un programme en langage C en utilisant les fonctions créées par l'assembleur.
main.c
void hello(char *string, int len);
int main (){
char *string = "Hello Asm!\n";
hello(string, 11);
return 0;
}
Enfin l'inclusion est partie !!! En langage C, hello prototype est déclaré et la fonction est exécutée. Compilons-le. Cette fois, un fichier objet est généré et lié pour connecter plusieurs fichiers.
$ nasm -f elf64 -o syscall.o syscall.asm
$ gcc -c main.c
$ gcc main.o syscall.o
$ ./a.out
Hello Asm!
J'ai pu sortir la chaîne de caractères que je veux sortir en toute sécurité!
Dans nasm, -f elf64
est spécifié pour afficher le fichier objet pour 64 bits.
Hello World (provisoire) a été créé uniquement par des appels système depuis le système d'exploitation sans utiliser la bibliothèque en toute sécurité!
Bien sûr, c'est un début rudimentaire, mais j'ai l'impression d'avoir appris un peu comment utiliser le système d'exploitation.
Une routine de démarrage qui traite les principaux arguments lors de l'appel d'une fonction et retourne à la fin est appelée à partir de la bibliothèque. J'ai reçu un commentaire.
Pour le moment, je posterai le programme qui n'utilise pas la routine de démarrage que j'ai reçue. Je le mettrai à jour à nouveau si je peux le mettre moi-même.
$ cat -n main.c
1 void hello(const char*, int);
2 void exit(int) __attribute__((noreturn));
3
4 int main(void){
5 const char* string = "Hello Asm!\n";
6 hello(string, __builtin_strlen(string));
7 exit(0);
8 }
$ cat -n syscall.asm
1 bits 64
2
3 global hello
4
5 hello:
6 mov rdx, rsi
7 mov esi, edi
8 mov eax, 1
9 mov edi, 1
10 syscall
11 ret
12
13 global exit
14
15 exit:
16 mov esi, edi
17 mov eax, 60
18 syscall
$ cat -n makefile
1 target:
2 nasm -f elf64 -o syscall.o syscall.asm
3 gcc -O2 -Wall -Wextra main.c syscall.o -nostdlib -static -Wl,-Map=main.map -Wl,-emain
$ make
nasm -f elf64 -o syscall.o syscall.asm
gcc -O2 -Wall -Wextra main.c syscall.o -nostdlib -static -Wl,-Map=main.map -Wl,-emain
$ ls -l a.out
-rwxrwxrwx 1 user user 1504 Dec 9 00:00 a.out
$ ./a.out
Hello Asm!
$
Comment était-ce? ~~ J'espère que vous comprenez à quel point le langage C est haut de gamme. ~~ Je serais très heureux si vous pouviez appeler cet article et réaliser le plaisir et la profondeur des appels système. Il y a peu de gens dans OthloTech qui font de la couche basse, donc j'espère personnellement que cela augmentera à l'avenir lol Alors tout le monde, bonne vie!
Recommended Posts