2010-08-12 5 views
8

Wenn Sie eine C/C++ Funktion von Inline-Assembler anrufen möchten, können Sie etwas tun:Direktanruf GCC Inline-Montage mit

void callee() {} 
void caller() 
{ 
    asm("call *%0" : : "r"(callee)); 
} 

GCC wird dann Code emittieren, die wie folgt aussieht:

movl $callee, %eax 
call *%eax 

Dies kann problematisch sein, da der indirekte Aufruf die Pipeline auf älteren CPUs zerstört.

Da die Adresse callee schließlich eine Konstante ist, kann man sich vorstellen, dass es möglich wäre, die i Einschränkung zu verwenden. Zitiert aus der GCC Online docs:

`i'

An immediate integer operand (one with constant value) is allowed. This includes symbolic constants whose values will be known only at assembly time or later.

Wenn ich versuche, es zu benutzen, wie folgt aus:

asm("call %0" : : "i"(callee)); 

ich die folgende Fehlermeldung aus dem Assembler erhalten:

Error: suffix or operands invalid for `call'

Dies liegt daran, GCC sendet den Code

call $callee 

Statt

call callee 

Also meine Frage ist, ob es möglich ist, GCC Ausgang der korrekten call zu machen.

+1

Sind Sie sicher, dass der indirekte Aufruf die Pipeline zerstört? Haben Sie ein Benchmarking durchgeführt? Mein Verständnis war, dass in den alten Tagen auf x86 (pre-i686) indirekte Anrufe sehr schlecht waren (ich erinnere mich, dass sie auf meinem K6 10-100 mal langsamer waren), aber heutzutage sind CPUs schlauer und können mit ihnen gut umgehen . Machen Sie also ein paar Tests, bevor Sie voreilige Schlüsse ziehen! –

+0

@R ..: Du hast Recht: Wenn ich das auf einer realen CPU benchze, macht es keinen Unterschied. Ich benutze meinen Code jedoch in qemu und es scheint dort einen Unterschied zu machen (ca. 20% mehr Zyklen/Anruf). – Job

+0

Dann würde ich einfach bei der Art und Weise bleiben, wie Sie es tun, mit dem indirekten Anruf. Dies ermöglicht es gcc, den richtigen Code für PIC/PIE-Bibliotheken/ausführbare Dateien zu erzeugen, ohne dass Sie spezielle Hacks einfügen müssen, um diese Dinge zu handhaben. –

Antwort

10

bekam ich die answer aus Mailingliste GCC:

asm("call %P0" : : "i"(callee)); 

Jetzt muss ich herausfinden, was %P0 bedeutet eigentlich, weil es eine nicht dokumentierte Funktion ...

bearbeiten zu sein scheint: Nach dem Blick auf den GCC-Quellcode ist nicht genau klar, was der Code P vor einer Einschränkung bedeutet. Aber unter anderem verhindert es, dass GCC $ vor konstante Werte setzt. Genau das brauche ich in diesem Fall.

+0

Siehe auch https://stackoverflow.com/questions/37639993/is-this-assembly-function-call-safe-complete für ** die Gefahren der Verwendung von 'call' von Inline-Asm **. Es ist im Grunde unmöglich, zuverlässig für x86-64 System V (Linux) zu arbeiten, weil Sie dem Compiler nicht sagen können, dass Sie die rote Zone überlisten wollen. Es ist generell eine schlechte Idee. Siehe https://stackoverflow.com/questions/7984209/calling-a-function-in-gcc-inline-assembly. –

0

Wie wäre es?

asm volatile ("call $callee"); 

da Sie den Namen des Symbols sowieso wissen.

Edit: Der Punkt, den ich machen wollte, ist, dass Sie nicht gcc Konventionen für asm Argumente hier durchlaufen müssen, aber dass Sie nur Textverarbeitung tun müssen, um die Aufgabe zu erledigen.

+0

Nun, das wäre mein letzter Ausweg (es sollte 'asm (" Call callee ") sein;', obwohl). Das Problem ist, dass Sie den Namen des verstümmelten Symbols benötigen würden. Dies ist kein großes Problem in C, sondern ein PITA in C++. – Job

+0

Dies sollte 'asm (" call callee ") sein;', aber in C++ wäre das etwas wie 'asm (" call _ZL4callv ");' –

+4

Dies funktioniert aus verschiedenen Gründen nicht. Ein, verschiedene Betriebssysteme (auf der gleichen Hardware) haben unterschiedliche Konventionen für die Zuordnung von Symbolnamen (die häufigste Kuriosität ist das Voranstellen eines Unterstrichs). Und zweitens, es sei denn, gcc wird irgendwie gesagt, dass der asm die Funktion verwendet, es kann sehr wohl niemals eine eigenständige aufrufbare Version der Funktion weglassen. Zum Beispiel kann es überall eingefügt werden, wo es gebraucht wird, oder es kann es ganz weglassen, wenn es nie aus C-Code aufgerufen wird. Drei, es gibt wahrscheinlich Probleme mit PIC und PIE-Code ... –

0

Der Trick ist Zeichenfolge literale Verkettung. Bevor beginnt GCC eine wirkliche Bedeutung aus dem Code zu bekommen versuchen, wird es neben Stringliterale, verketten so, obwohl Montage Saiten sind nicht das gleiche wie andere Zeichenfolgen in Ihrem Programm verwenden, sollten sie verkettet werden, wenn Sie das tun:

#define ASM_CALL(X) asm("\t call " X "\n") 


int main(void) { 
    ASM_CALL("my_function"); 
    return 0; 
} 

Da Sie GCC verwenden könnten Sie auch tun

#define ASM_CALL(X) asm("\t call " #X "\n") 

int main(void) { 
    ASM_CALL(my_function); 
    return 0; 
} 

Wenn Sie nicht bereits wissen, sollten Sie sich bewusst sein, dass die Dinge von Inline-Assembler-Aufruf sehr schwierig ist. Wenn der Compiler eigene Aufrufe an andere Funktionen generiert, enthält er Code zum Einrichten und Wiederherstellen von Dingen vor und nach dem Aufruf. Es weiß jedoch nicht, dass es das für Ihren Anruf tun sollte. Sie müssen entweder das selbst hinzufügen (sehr schwierig, um richtig zu machen und können mit einem Compiler-Upgrade oder Kompilierungsflags brechen) oder sicherstellen, dass Ihre Funktion so geschrieben ist, dass sie anscheinend keine Register oder Bedingungen geändert hat Stack (oder Variable darauf).

bearbeiten dies funktioniert nur für C-Funktionsnamen - nicht C++, wie sie gemangelt sind.

+1

Nein, das funktioniert überhaupt nicht.Erstens ist das '$' völlig falsch für den Aufruf, zweitens will er nicht "my_function" in der asm-Zeichenfolge, sondern den ** entstellten Namen, den C++ generiert **. –

+0

Ich habe nicht gesehen, dass dies auch für C++ war. In diesem Fall ist es wahrscheinlich, dass dies nicht funktioniert, wenn der Funktionsname ohne viele Ringe überladen ist. Ich habe gerade das "$" von einem anderen Post kopiert, da ich mich nicht daran erinnern konnte, dass die x86-ASM-Syntax nicht vorhanden ist. Fixing that ... – nategoose

0

Wenn Sie zu erzeugen 32-Bit-Code (zB -m32 gcc-Option), gibt die folgende asm inline einen direkten Aufruf:

asm ("call %0" :: "m" (callee)); 
1

Vielleicht hier etwas fehlt mir, aber

extern "C" void callee(void) 
{ 

} 

void caller(void) 
{ 
    asm("call callee\n"); 
} 

sollte gut funktionieren. Sie benötigen extern "C", damit der Name nicht auf C++ - Benennungsregeln basiert.