UNIXwork

File Descriptor zwischen Prozessen austauschen

05. März 2017

Es ist möglich, dass mehrere Prozesse Zugriff auf die gleichen File-Deskriptoren haben. Beispielsweise werden bei einem Fork alle File-Deskriptoren kopiert. Der Kind-Prozess kann dann auf die selben Dateien oder Sockets zugreifen.

Man kann auch nach einem Fork oder mit nicht-verwandten Prozessen File-Deskriptoren austauschen. Dies geht über Unix Domain Sockets mit den Funktionen sendmsg und recvmsg.

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

Die Nachricht vom Typ struct msghdr hat so einige Felder, die man erstmal ausfüllen muss.

struct msghdr {
	void         *msg_name;       /* optional address */
	socklen_t     msg_namelen;    /* size of address */
	struct iovec *msg_iov;        /* scatter/gather array */
	size_t        msg_iovlen;     /* # elements in msg_iov */
	void         *msg_control;    /* ancillary data, see below */
	size_t        msg_controllen; /* ancillary data buffer len */
	int           msg_flags;      /* flags on received message */
 };

Das Feld msg_name wird hier nicht benötigt, daher setzen wir msg_name und msg_namelen auf 0.

struct msghdr msg;
msg.msg_name = NULL;
msg.msg_namelen = 0;

Die eigentlichen Daten überträgt man mit dem Feld msg_iov, ein Array an Buffern, wie man es von writev kennt. Man erstellt ein Array mit struct iovec Elementen und gibt jeweils einen Pointer auf die Daten und die Länge an. Es reicht natürlich, nur einen Buffer anzugeben.

struct iovec iov[1] ;
iov[0].iov_base = (void*)buf;
iov[0].iov_len = len;
	 
msg.msg_iov = iov;
msg.msg_iovlen = 1;

Jetzt wollen wir noch einen Filedescriptor übertragen. Für solche (und andere) Zwecke gibt es das Feld msg_control für folgende struct:

struct cmsghdr {
	size_t cmsg_len;    /* Data byte count, including header
	                       (type is socklen_t in POSIX) */
	int    cmsg_level;  /* Originating protocol */
	int    cmsg_type;   /* Protocol-specific type */
	/* followed by
	   unsigned char cmsg_data[]; */
};

Nach den Feldern der struct müssen also noch zusätzliche Daten im Speicher liegen. Für den Fall, dass man Filedeskriptoren übertragen möchte, müssen ein oder mehrere Integer folgen. Es empfiehlt sich zunächst einen statischen Buffer mit der entsprechenden Größe anzulegen. Hierfür gibt es das Makro CMSG_SPACE, was die Größe des Buffers liefert.

char cmsg_buf[CMSG_SPACE(sizeof(int))];

Anschließend setzen wir die Felder der struct.

struct cmsghdr *cmsg = (struct cmsghdr*)cmsg_buf;
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS; // indicates fd payload data

Jetzt fehlt nur noch der Filedescriptor. Dieser muss, wie schon erwähnt, hinter der struct liegen. Mit dem Makro CMSG_DATA erhält man einen Pointer auf die Payload-Data.

int *data = (int*)CMSG_DATA(cmsg);
*data = fd;

Jetzt setzen wir nur noch msg_control und msg_controllen, sowie msg_flags. Danach kann sendmsg aufgerufen werden.

msg.msg_control = cmsg;
msg.msg_controllen = CMSG_LEN(sizeof(int));
	
msg.msg_flags = 0;
   
sendmsg(sock, &msg, 0);

Um einen Filedescriptor zu empfangen nutzt man recvmsg, was in der Benutzung ähnlich ist. Dort kommt die selbe struct für die Message zum Einsatz und man muss die Felder auch genauso setzen.

Ich hab ein kleines Beispielprogramm geschrieben, welches nach einem Fork eine Datei im Elternprozess öffnet und anschließend den Filedescriptor dieser Datei an den Kindprozess sendet. Das Kind empfängt dann den Filedescriptor und schreibt etwas in diese Datei.

Autor: Olaf | 0 Kommentare | Tags: unix, c, socket, ipc

Executable Memory und Intel XED

24. Dezember 2016

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

23. Dezember 2016

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

Linkdump

22. Dezember 2016
Autor: Olaf | 0 Kommentare | Tags: links, c, web, unix

Gnome Dark-Theme-Fenster ohne GTK

17. Dezember 2016

Gnome 3 hat das nette Feature, dass ein GTK-Theme eine light und eine darkVariante hat, und einzelne GTK-Anwendungen können sich aussuchen, welche Theme-Variante sie nutzen wollen. Nutzt eine Anwendung jedoch nicht GTK, so gibt es trotzdem eine Möglichkeit zumindestens einen dunklen Fenster-Rahmen zu erhalten.

Welche Farbe ein Fenster hat, hängt nur von einer X-Property ab. Starten wir einfach mal xprop und klicken auf ein dark-theme-Fenster, dann gibt das Programm alle Properties aus. Darunter findet sich dann auch diese Zeile:

_GTK_THEME_VARIANT(UTF8_STRING) = "dark"

Man kann mit xprop auch Properties setzen:

xprop -f _GTK_THEME_VARIANT 8u -set _GTK_THEME_VARIANT "dark" 

Klickt man dann auf ein Fenster, müsste seine Titelleiste das dark theme nutzen.

Um das gleiche in C mit der xlib zu machen, reicht folgender Code:

void set_dark_theme(Display *dp, Window window) {
	Atom atom = XInternAtom(dp, "_GTK_THEME_VARIANT", False);
	Atom type = XInternAtom(dp, "UTF8_STRING", False);
	XChangeProperty(
	        dp, 
	        window, 
	        atom,
	        type,
	        8,
	        PropModeReplace,
	        (const unsigned char*)"dark",
	        4);
}
Autor: Olaf | 0 Kommentare | Tags: x11, gnome, c
Zurück Weiter