2015-12-11 6 views
8

Ich arbeite an einer setjmp/longjmp benutzerdefinierte Implementierung für x86-64 Systeme, die den gesamten Kontext der CPU speichert (nämlich alle xmm, FPU-Stack, etc; nicht nur Callee-save-Register). Dies wird direkt in Assembly geschrieben.x86_64: zwingen gcc, Argumente auf dem Stapel zu übergeben

Der Code funktioniert in minimalen Beispielen (wenn er direkt von einer Baugruppenquelle aufgerufen wird). Das Problem tritt auf, wenn es mit C-Code verwendet wird, aufgrund der Art und Weise, wie Parameter an die Funktionen des Homebrew setjmp/longjmp übergeben werden. Tatsächlich schreibt SysV ABI für x64_64-Systeme vor, dass Argumente über Register weitergegeben werden sollten (wenn sie höchstens 6 sind). Die Unterschrift meiner Funktionen sind:

long long set_jmp(exec_context_t *env);
__attribute__ ((__noreturn__)) void long_jmp(exec_context_t *env, long long val);

Natürlich kann dies nicht wie arbeiten. In der Tat, wenn ich set_jmp eingeben, rdi und rsi wurden bereits geklopft, um einen Zeiger auf env und val zu halten. Gleiches gilt für long_jmp gegenüber rdi.

Gibt es eine Möglichkeit, GCC, z. indem man sich auf irgendein Attribut verlässt, um das Argument zu zwingen, über den Stapel zu gehen? Dies wäre viel eleganter als das Wrapping set_jmp und long_jmp mit einigen definieren, die manuell die clobbered Register auf Stapel, so dass sie später abrufen.

+1

Dies würde die PCS/ABI brechen. Sie nähern sich von der falschen Seite. Ihr Assembler-Code muss dem ABI folgen. Am besten verwenden Sie C-Funktionen mit Inline-Assembler entweder direkt oder als Wrapper für Ihren eigentlichen Code. Auf diese Weise können Sie festlegen, welche Code-Clobber gespeichert werden sollen und wie Sie gcc speichern/wiederherstellen können. – Olaf

+2

'setjmp' muss nicht' rdi' und 'rsi' speichern - wie du sagst, sie sind in' setjmp' geplätschert, also warum sollten sie gespeichert werden? Tatsächlich müssen nur die Register, die als "callee-saved" (d. H. Rbp, ebx, r12, r13, r14, r15 und natürlich rsp) markiert sind, beibehalten werden. – fuz

+0

Ich stimme zu, dass ich die ABI nicht respektiere, aber dafür gibt es einen Grund. Alles funktioniert gut mit 'setjmp' und' longjmp', denn was nicht von 'setjmp' gespeichert wird, wird tatsächlich vom Aufrufer gespeichert, falls es benötigt wird, da es sich um Register zum Speichern der Aufrufer handelt. In meiner Anwendung habe ich eine Interaktion mit dem Linux-Kernel, der bei einem bestimmten Interrupt das Steuerelement an einen anderen Teil des Codes auf Benutzerebene zurückgibt, der wiederum 'setjmp' aufruft. Durch diese Konstruktion weiß der ursprünglich ausführende Code nicht, dass "setjmp" aufgerufen wird, und daher sollten Register zum Speichern von Anrufern ebenfalls gespeichert werden. – ilpelle

Antwort

2

Sie können das Überschreiben der Register vermeiden, indem Sie die Funktion über Inline-Assembly aufrufen.

#include <stdio.h> 

static void foo(void) 
{ 
     int i; 
     asm volatile ("mov 16(%%rbp), %0" : "=g" (i)); 
     printf("%d\n", i); 
} 

#define foo(x) ({ int _i = (x); \ 
     asm ("push %0\ncall %P1\nadd $8, %%rsp\n" : : "g"(_i), "i"(foo)); }) 

int main(int argc, char *argv[]) 
{ 
     foo(argc-1); 
     return 0; 
} 

Hier wird eine Ganzzahl auf den Stack geschoben und die Funktion foo aufgerufen. foo stellt den Wert in seiner lokalen Variablen i zur Verfügung. Nach der Rückkehr wird der Stapelzeiger auf seinen ursprünglichen Wert zurückgesetzt.

+1

Dies wird natürlich schrecklich brechen, wenn die aufrufende Funktion 'main()' verwendet '% rbp' nicht als Rahmenzeiger, sondern verwendet es als zusätzliches Ganzzahlregister oder verwendet es überhaupt nicht, um den Push/Pop-Overhead zu vermeiden. Letzteres ist der Fall für diesen Code, wenn Sie mit "-O2" kompilieren. –

+0

@NateEldredge: Es ist nur 'foo', das schlecht entworfen ist, aber es ist nur ein Platzhalter für sein Beispiel. Das Makro zum Generieren eines benutzerdefinierten ABI-Anrufs sieht gut aus. Das OP würde eine benutzerdefinierte "setjmp" -Implementierung verwenden, die von Hand in ASM geschrieben wurde und die keine dieser Annahmen treffen würde. Es würde damit beginnen, einige Register zu drücken, um einige Scratch-Regs zu erhalten, und dann den Zustand des Anrufers zu speichern. IDK, wenn 'xsave' aus dem User-Space gut funktioniert. –

Verwandte Themen