Als jemand der auf Windows (bzw. DOS) mit Programmieren angefangen hat empfand ich unter POSIX-Systemen immer das dauernde gepipe (und damit verbunden: geparse) am schlimmsten. Und wenn mir das jetzt ernsthaft irgendwer als gut verkaufen will darf er sich den Sourcecode des RIPE-whois angucken. (den Teil mit der GPG-Anbindung)
Quasi als Windows-Nachwirkung benutze ich sehr gern libdl und eine handvoll .so-Dateien. Nur hat man da wiederum das Problem, dass fehlerhafte Module die ganze Applikation zum Absturz bringen können. Unter Windows kann man COM-Objekte in separaten Adressräumen laufen lassen, und wenn das SEGVed kracht halt das COM-Objekt weg, aber nicht die ganze Anwendung. Ich dachte sehr lange, unter GNU/Linux müsste man die selbe Methodik nachbauen - also Threads benutzen - bis mir auf einer Zugfahrt vor paar Wochen die Idee zu dem Code hier gekommen ist:
static int callmod(int op)
{
__label__ out;
struct sigaction new, oldsegv, oldbus;
int rv = 0;
void sigh(int sig, siginfo_t *si, void *info)
{
fprintf(stderr, "sig%d accessing %p in module\n",
sig, si->si_addr);
rv = 0xDEADBEEF;
goto out;
}
new.sa_sigaction = sigh;
new.sa_flags = SA_SIGINFO | SA_RESETHAND | SA_NODEFER;
sigemptyset(&new.sa_mask);
sigaction(SIGSEGV, &new, &oldsegv);
rv = fn(op);
out:
sigaction(SIGSEGV, &oldsegv, NULL);
return rv;
}
(Wer das auf Anhieb verstanden hat braucht den Rest nicht mehr zu lesen.)
Dass das ganze überhaupt geht, ermöglicht etwas gcc-Magic - verschachtelte Funktionen (void sigh(...)) gibt es leider in ANSI-/ISO-C nicht. Der Trick ist, dass gcc aus solchen Funktionen in die enthaltende Funktion herausspringen kann, von wo aus wir dann normal weiterarbeiten können.
Zur Umsetzung des ganzen verwendet gcc Trampolines, denen grsecurity-Benutzer vielleicht schon begegnet sind. Als Trampoline wird hier Code bezeichnet, der auf den Stack geschoben wird sobald man die Adresse der inneren Funktion haben will.
Die Problematik ist hier, dass die innere Funktion nicht direkt per Adresse des Codes aufgerufen werden kann. Zum Aufruf der Funktion benötigt man nicht wie normalerweise eine Information - die Adresse der Funktion - sondern zwei Informationen: ihre Adresse und die Adresse des Stack-Frames der äusseren Funktion. Es müssen also zwei Informationen in eine gequetscht werden. Das geschieht, indem man zur Laufzeit die Adresse des Stackframes zusammen mit dem Aufruf der eigentlichen Funktion auf den Stack schiebt, und dann die Adresse dieses Fetzchens übergibt.
Die Adresse des Stack-Frames der äusseren Funktion wird im Normalfall genutzt, um von der inneren Funktion auf lokale Variablen der äusseren Funktion zuzugreifen, also im Codebeispiel die Zuweisung von 0xDEADBEEF an rv. In diesem Fall wird sie jedoch für etwas anderes benutzt: als "Save-Point" nach dem Absturz. Falls nun also irgendwo weiter unten im Code eine SEGV auftritt, kann man den Stack der aufgerufenen Funktionen abwerfen und zum definierten Punkt (__label__ out) mit intaktem Stack zurückkehren.
mov eax, offset sigh lea rcx, [rsp+496] lea rdx, [rsp+524] mov word [rdx], 0bb41h mov dword [rdx+2], eax mov word [rdx+6], 0ba49h mov qword [rdx+8], rcx mov word [rdx+16], 0ff49h mov byte [rdx+18], 0e3h und als Ergebnis hat man auf dem Stack: 41 bb ???????? mov r11, 0x???????? # sigh 49 ba ???????????????? mov r10, 0x???????????????? # frame 49 ff e3 jmp [r11]
Ein Allheilmittel ist das hier allerdings nicht. Sollte ein Stack Overflow passieren oder globale Variablen verändert werden, ist der "Save-Point" wertlos...

Hackish C
2006年01月15日 (日曜日) 21時13分Daß man solche häßlichen Hacks in C braucht, ist traurig. In Common Lisp kann die gleiche Funktionalität portabel und performant (keine Syscalls!) so erreicht werden (verschiedenste Variationen sind möglich):
(defun callmod (fun &rest args) (ignore-errors (apply fun args)))So braucht man auch keine Tabelle mit den möglichen Operationen, etc...