Dieser Artikel wurde als Artikel zum 8. Tag von [OthloTech Adventskalender 2017] geschrieben (https://qiita.com/advent-calendar/2017/othlotech).
Ich bin seit gestern an zwei aufeinander folgenden Tagen dafür verantwortlich. Gestern ging es um Mobile, aber heute möchte ich den Kernel in C-Sprache ändern und ansprechen. Insbesondere wird Hello World nur mit den im Betriebssystem definierten Systemaufrufen ausgeführt.
__ Zielgruppe __
** Artikelinhalt **
** Betriebsumgebung **
Schreiben wir Hello World, die mit C am einfachsten zu sein scheint. Es ist etwas, das viele Leute geschrieben haben, wie zum Beispiel das Lernen in der Schule.
Hello.c
#include<stdio.h>
int main(){
printf("Hello World!\n");
return 0;
}
Ich habe es im Druckformat geschrieben. Sie können es einfach anzeigen, indem Sie eine Zeichenfolge angeben. Diese Funktion namens printf funktioniert jedoch nur, wenn stdio.h enthalten ist. Es ist eine sogenannte magische. Die Bedienung im Inneren wird allein dadurch nicht gut verstanden.
Lassen Sie uns kompilieren und sehen, wie die ausführbare Datei aussieht. Da es mit gdb debuggt, wird die Option -g hinzugefügt, und der dynamische Link ist faul, daher wird -static hinzugefügt.
$ gcc Hello.c -static -g
$ gdb -q a.out
(gdb) break main
(gdb) layout asm
(gdb) run
(gdb) si
(gdb) si
...
Ich habe versucht, so zu treten, wie es ist, aber es endet nicht leicht. ~~ Ich bin bis zum Ende nicht motiviert ~~ Ich bin mir nicht sicher, wie es funktioniert, also drücke ich strace, einen Befehl, der Systemaufrufe auflistet.
$ 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 +++
Es scheint, dass das Gedächtnis irgendwie gesichert ist und verschiedene Dinge getan werden. Ich bin jedoch der Meinung, dass beim Schreiben eine Zeichenfolge angezeigt werden kann. Als nächstes werde ich dies verwenden, um eine Zeichenfolge auszugeben.
Als ich nach der Schreibfunktion suchte, wurde sie wie folgt definiert. (/usr/include/unistd.h)
extern ssize_t write (int __fd, const void *__buf, size_t __n) __wur;
fd ist ein Dateideskriptor. Dieses Mal ist es 1, da es an die Standardausgabe ausgegeben wird.
buf ist der Ausgabeinhalt und n ist die Anzahl der Zeichen. Es scheint unpraktisch zu sein als printf, aber es scheint, dass die Unannehmlichkeit nicht bequem ist, so dass es mehr Dinge gibt, die nicht verwendet werden ()
Lassen Sie uns dies einschließen.
Write.c
#include<unistd.h>
int main(){
const void *string = "Hello Write!\n";
write(1, string, 13);
return 0;
}
Ich habe versucht, eine Zeichenfolge in der Standardausgabe anzuzeigen. Lassen Sie es auf die gleiche Weise funktionieren.
$ gcc Write.c -static -g
$ gdb -q a.out
(gdb) break main
(gdb) layout asm
(gdb) run
(gdb) si
(gdb) si
...
Dieses Mal kam ich relativ schnell zur Anzeige der Zeichen.
Anscheinend wird es beim Aufruf von syscall angezeigt.
Aber ich mag #include <unistd.h>
nicht.
Wenn eine Zeichenkette von syscall angezeigt wird, sollten Sie sie festlegen und syscall aufrufen. Daher habe ich untersucht, wie syscall verwendet wird.
Anscheinend von den von der 64-Bit-CPU verwendeten Registern
rax = 1 (gibt an, dass es sich um einen Schreibsystemaufruf handelt) rdi = 1 (Dateideskriptor, dh 1 für Standardausgabe) rsi = (erste Adresse der Zeichenfolge) (Anzeigezeichenfolge angeben) rdx = (Länge der Zeichenfolge) (Anzahl der Zeichen)
Es scheint, dass die Zeichenfolge angezeigt wird, wenn syscall als aufgerufen wird. Als nächstes rufen wir syscall direkt vom Assembler aus auf. Dann können Sie sich von der verhassten Unistd.h verabschieden.
Ich beziehe mich auf Folgendes http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/
Klicken Sie hier für CPU-Register https://software.intel.com/en-us/articles/introduction-to-x64-assembly
Beim Aufrufen in C-Sprache scheint es, dass die obigen Spezifikationen nur erfüllt werden können, wenn mindestens die Zeichenfolge und die Zeichenlänge angegeben sind. Daher möchte ich das Argument angeben. Wenn Sie das Argument mit einem 64-Bit-Assembler verwenden, scheint es wie folgt zu funktionieren.
rdi erstes argument rsi zweites Argument rdi 3. Argument rcx 4. Argument r8 5. Argument r9 6. Argument
Referenz http://p.booklog.jp/book/34047/page/613687
Da es diesmal zwei Argumente gibt, werden wir rdi und rsi verwenden. Speichern Sie dann die erste Adresse der Zeichenfolge in rsi, die Anzahl der Zeichen in rdx und 1 in rax und rdi. Basierend auf dem oben Gesagten habe ich einen Assembler für Nasm geschrieben. Der Funktionsname ist Hallo.
syscall.asm
bits 64
global hello
hello:
mov rdx, rsi
mov rsi, rdi
mov rax, 1
mov rdi, 1
syscall
ret
Ich habe ein C-Sprachprogramm mit den vom Assembler erstellten Funktionen geschrieben.
main.c
void hello(char *string, int len);
int main (){
char *string = "Hello Asm!\n";
hello(string, 11);
return 0;
}
Endlich ist das Include weg !!! In der Sprache C wird der Hallo-Prototyp deklariert und die Funktion ausgeführt. Lassen Sie es uns kompilieren. Dieses Mal wird eine Objektdatei generiert und verknüpft, um mehrere Dateien zu verbinden.
$ nasm -f elf64 -o syscall.o syscall.asm
$ gcc -c main.c
$ gcc main.o syscall.o
$ ./a.out
Hello Asm!
Ich konnte die Zeichenfolge, die ich ausgeben möchte, sicher ausgeben!
In nasm wird -f elf64
angegeben, um die Objektdatei für 64bit auszugeben.
Hello World (vorläufig) wurde nur durch Systemaufrufe vom Betriebssystem erstellt, ohne die Bibliothek sicher zu verwenden!
Natürlich ist es ein rudimentärer Anfang, aber ich habe das Gefühl, ein wenig über die Verwendung des Betriebssystems gelernt zu haben.
Eine Startroutine, die beim Aufrufen einer Funktion Hauptargumente verarbeitet und am Ende zurückgibt, wird aus der Bibliothek aufgerufen. Ich habe einen Kommentar erhalten.
Vorerst werde ich das Programm veröffentlichen, das nicht die Startroutine verwendet, die ich erhalten habe. Ich werde es erneut aktualisieren, wenn ich es in mich selbst einfügen kann.
$ 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!
$
Wie war das? ~~ Ich hoffe du verstehst wie hochklassig C Sprache ist. ~~ Ich würde mich sehr freuen, wenn Sie diesen Artikel aufrufen und den Spaß und die Tiefe von Systemaufrufen erkennen könnten. Es gibt nur wenige Leute in OthloTech, die Low Layer machen, also hoffe ich persönlich, dass es in Zukunft zunehmen wird lol Dann habt alle ein gutes Hack-Leben!
Recommended Posts