UNIXwork

Ein paar Features des GNU-Buildsystem

2017-02-08 15:59:12.0

Software, die das GNU-Buildsystem verwendet, lässt sich mit ./configure gefolgt von make und make install kompilieren und installieren. Mit diesem Artikel möchte ich auf ein paar Features dieses Buildsystems hinweisen.

Build-Verzeichnis

Anstatt das configure-Script im Root-Verzeichnis der Software auszuführen, geht dies auch von einem anderen aus. Dies hat den Vorteil, dass alle erstellten Dateien in einem anderen Verzeichnis landen, also z.B.:

mkdir build
cd build
../configure
make

Nicht nur alle von configure erstellten Dateien landen dann im build-Verzeichnis, sondern auch alle Objekt-Dateien und was sonst noch so durch make erstellt wird.

config.site

Ein weiteres interessante Feature ist, dass das configure-Script am Anfang die Datei prefix/share/config.site lädt. Diese kann normale Shell-Kommandos enthalten. Anstatt also immer per Hand die gewünschten Umgebungsvariablen zu setzen, oder Dinge wie das libdir anzupassen, kann man diese Einstellungen über die config.site erledigen. Siehe Setting Site Defaults.

Make Targets

Alle kompilierten und andere durch make erstellte Dateien entfernt man mit make clean. Mit make distclean kann man auch alle durch configure erstellte Dateien löschen. Desweiteren gibt es noch make maintainer-clean, das alle reproduzierbaren Dateien löscht, bis auf das configure script und andere Dateien, die für configure erforderlich sind. Auch interessant ist make check, was Tests für die Software ausführt (falls welche vorhanden sind).

Desweiteren möchte ich noch mal meinen Artikel über den DESTDIR-Parameter bei make install erwähnen.

Autor: Olaf | 0 Kommentare | Tags: shell, gnu, make

RPM Fusion für CentOS 7

2017-02-03 14:55:15.0

Als Fedora-User war RPM Fusion mein Repository der Wahl für Grafiktreiber, Codecs und andere Software, die nicht im offiziellen Fedora-Repo vorhanden war. Nach meinem Wechsel auf EL7 (erst Scientific Linux, dann CentOS) musste ich mir eine neue Quelle dafür suchen, denn RPM Fusion hat EL7 nicht unterstützt. Doch jetzt, mehr als 2 Jahre nach dem Erscheinen von RHEL7, ist RPM Fusion auch für EL7 verfügbar. Aber noch nicht so ganz, denn die Infrastruktur besteht zwar bereits, die Anzahl der Pakete ist hingegen sehr überschaubar. Das dürfte sich aber in den nächsten Wochen, Monaten oder Jahrhunderten hoffentlich ändern.

Okay, genug gelästert. Ich danke allen Beteiligten für ihre Arbeit.

Autor: Olaf | 0 Kommentare | Tags: rhel, el7, centos

Neue Solaris und SPARC Roadmap

2017-01-21 10:59:59.0

Oracle hat eine neue Roadmap für SPARC und Solaris veröffentlicht, und wie erwartet kommt dort kein Solaris 12 mehr vor. Stattdessen setzt Oracle auf ein Continuous Delivery Modell, so dass neue Features direkt in Solaris 11 landen.

Das ist zwar einerseits ein gutes Konzept, andererseits will Oracle sicherlich auch Entwicklungskosten drücken. Es bleibt abzuwarten ob das nächste Solaris 11 Release die Neuerungen erhält, die Oracle bisher für Solaris 12 schon angekündigt hatte.

Autor: Olaf | 0 Kommentare | Tags: solaris, oracle

Executable Memory und Intel XED

2016-12-24 11:09:47.0

Ich hab mich schon öfter gefragt, was man alles tun muss, um einen JIT-Compiler zu schreiben. Oder anders gefragt, wie kann man zur Laufzeit Maschinencode generieren und ausführen.

Zunächst einmal benötigt man Speicher, der es überhaupt erlaubt, dass davon Code ausgeführt werden kann. Wenn man mit malloc Speicher alloziert, ist dieser nicht ausführbar. Intern verwendet malloc mmap und das kann auch einfach direkt genutzt werden. Dabei kann man direkt die Zugriffsrechte für den Speicher festlegen. Man könnte sie allerdings auch im nachhinein mit mprotect ändern. Mit mmap werden auch Dateien in den Speicher gemapped, durch das Flag MAP_ANONYMOUS liefert der Kernel aber ganz ohne Datei den gewünschten Speicher.

void *execmem = mmap(NULL, len, PROT_EXEC | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);

Jetzt haben wir ausführbaren Speicher. Wenn dort Instructions reingeladen werden, können diese ausgeführt werden. Mein erster Versuch war einfach mit memcpy von einem Function-Pointer dort ein paar Bytes rein zu kopieren. Das hat zwar funktioniert, auch wenn ich nicht wusste wie viele Bytes die Funktion eigentlich groß ist und ich einfach eine größere Menge kopiert habe, aber das wäre für diesen ohnehin schon etwas hackigen Blogartikel etwas zu unsauber.

Glücklicherweise hat Intel kürzlich eine Bibliothek für das decoden und encoden von x86-Maschinencode veröffentlicht. Damit ist es mir gelungen nur die Instructions der Funktion zu kopieren.

Um den Maschinencode zu decoden braucht man erstmal einen Pointer auf den Code, in meinem Fall einen Funktions-Pointer.

const xed_uint8_t *inst = (const xed_uint8_t*)func;

Einen Befehl decoden macht folgender Code:

xed_decoded_inst_t dec;
xed_error_enum_t error;

// init xed_decoded_inst_t struct
memset(&dec, '\0', sizeof(xed_decoded_inst_t));
xed_decoded_inst_set_mode(&dec, XED_MACHINE_MODE_LONG_64, XED_ADDRESS_WIDTH_64b);

// decode instruction
error = xed_decode(&dec, inst, 15);

Dies liest 15 Bytes (die Maximalgröße einer Instruction) und dekodiert die Instruction. Wie groß diese dann ist, kann mit xed_decoded_inst_get_length abgefragt werden. Mit der Länge kann man dann zur nächsten Instruction springen.

Man kann diese auch als Assembler-String formatieren:

// print instruction
xed_format_context(XED_SYNTAX_ATT, &dec, buffer, 1024, 0, 0, 0);
printf("%s\n", buffer);

Hier ist das fertige Beispielprogramm. Kompiliere ich das ohne Optimierung und führe es aus ist die Ausgabe:

$ ./x86dec 
copy function code:

pushq  %rbp
mov %rsp, %rbp
movl  %edi, -0x4(%rbp)
movl  %esi, -0x8(%rbp)
movl  -0x8(%rbp), %eax
movl  -0x4(%rbp), %edx
add %edx, %eax
popq  %rbp
retq  

execute new code:

f(10, 50) = 60

Das ganze erfüllt jetzt natürlich keinen Zweck. Spannend wird es erst, wenn man eigenen Code generiert und den dann ausführt.

Was man noch erwähnen sollte ist, dass CPUs separate Data- und Instruction-Caches haben. Glücklicherweise muss man sich bei x86-CPUs keine Sorgen darüber machen, da dort erkannt wird, wenn Speicher modifiziert wird, der gerade auch im Instruction-Cache ist. Hingegen bei RISC-Architekturen, z.B. ARM, muss meistens der Instruction-Cache manuell aktualisiert werden.

Siehe auch: Self-Modifying Code and the Cache on Intel x86

Autor: Olaf | 0 Kommentare | Tags: c, x86, asm, jit

C: Attribute von allen Dateien im Verzeichnis

2016-12-23 21:06:16.0

Wenn man ein Verzeichnis liest und von allen enthaltenen Dateien die Extended Attributes erhalten will, gibt es zwei Möglichkeiten:

  1. Man fügt zum Verzeichnispfad den Dateinamen hinzu und nutzt den neu erhaltenen Pfad mit den Syscalls listxattr oder getxattr.
  2. Mit dem Filedescriptor des Verzeichnisses und openat öffnet man die Dateien und nutzt dann flistxattr und getxattr.

Ich hab mich gefragt was schneller ist. Dazu habe ich ein kleines Testprogramm geschrieben. Dieses kann mit unterschiedlichen Preprocessor-Optionen kompiliert werden. So habe ich 4 Testprogramme erstellt. Für getxattr und fgetxattr jeweils ein Programm, das ein Attribut liest und eines das 32 Attribute liest.

Bei einem Verzeichnis mit 128.000 Dateien hab ich folgende Werte erhalten:

getxattr:1  getxattr:32  fgetattr:1  fgetattr:32
------------------------------------------------
246100055   654704421    456172044   749849574
230183311   663632162    457183706   769223423
247109480   654775136    440397212   743349119

Die Datei erst zu öffnen um dann fgetxattr zu nutzen ist also langsamer. Erst als ich das Programm so modifiziert habe, dass es mehrere hundert Attribute liest, war es etwas schneller. Das ist jedoch ein eher unrealistisches Szenario. Allerdings war das ganze generell sehr schnell, so dass es eigentlich egal ist, welche Methode man anwendet.

Autor: Olaf | 0 Kommentare | Tags: linux, c, xattr, benchmark
Weiter