2015-03-16 4 views
11

Während ich versuchte, alten Code wieder funktionsfähig zu machen (https://github.com/chaos4ever/chaos/blob/master/libraries/system/system_calls.h#L387, FWIW), entdeckte ich, dass einige der Semantiken von gcc sich in den letzten 10-15 Jahren auf ziemlich subtile, aber immer noch gefährliche Weise verändert haben ...: PGCC/x86 inline asm: Wie sagt man gcc, dass der Inline-Assembly-Abschnitt% esp ändert?

Der Code verwendet, um gut mit älteren Versionen von gcc, wie 2.95 zu arbeiten. Wie auch immer, hier ist der Code:

static inline return_type system_call_service_get(const char *protocol_name, service_parameter_type *service_parameter, 
    tag_type *identification) 
{ 
    return_type return_value; 

    asm volatile("pushl %2\n" 
       "pushl %3\n" 
       "pushl %4\n" 
       "lcall %5, $0" 
       : "=a" (return_value), 
        "=g" (*service_parameter) 
       : "g" (identification), 
        "g" (service_parameter), 
        "g" (protocol_name), 
        "n" (SYSTEM_CALL_SERVICE_GET << 3)); 

    return return_value; 
} 

Das Problem mit dem Code oben ist, dass gcc (4.7 in meinem Fall) dies an folgendem asm-Code kompiliert (AT & T-Syntax):

# 392 "../system/system_calls.h" 1 
pushl 68(%esp) # This pointer (%esp + 0x68) is valid when the inline asm is entered. 
pushl %eax 
pushl 48(%esp) # ...but this one is not (%esp + 0x48), since two dwords have now been pushed onto the stack, so %esp is not what the compiler expects it to be 
lcall $456, $0 

# Restoration of %esp at this point is done in the called method (i.e. lret $12) 

Das Problem: Die Variablen (identification und protocol_name) sind im aufrufenden Kontext auf dem Stapel. So gcc (mit Optimierungen stellte sich heraus, unsicher, wenn es darauf ankommt) wird nur die Werte von dort bekommen und es in den Inline-ASM-Abschnitt übergeben. Aber seit ich Sachen auf den Stapel drängen, werden die Offsets, die gcc berechnen, im dritten Aufruf um 8 deaktiviert(). :)

Das brauchte eine lange Zeit um herauszufinden, es war nicht alles offensichtlich für mich zuerst.

Der einfachste Weg, um dies zu erreichen, ist natürlich die Verwendung der r Eingabebedingung, um sicherzustellen, dass der Wert stattdessen in einem Register ist. Aber gibt es einen anderen, besseren Weg? Eine naheliegende Möglichkeit wäre natürlich, die gesamte Systemaufrufschnittstelle neu zu schreiben, um nicht erst einmal Sachen auf den Stapel zu schieben (und stattdessen Register wie zB Linux zu verwenden), aber das ist kein Refactoring, ich fühle mich heute Abend ...

Gibt es eine Möglichkeit, gcc Inline asm zu sagen, dass "der Stapel flüchtig" ist? Wie geht es euch in der Vergangenheit?


aktualisieren später am selben Abend: Ich habe ein relevantes gcc ML Gewinde (https://gcc.gnu.org/ml/gcc-help/2011-06/msg00206.html) gefunden, aber es schien nicht zu helfen. Es scheint wie %esp in der Clobber-Liste sollte machen es Offsets von %ebp statt tun, aber es funktioniert nicht und ich vermute, die -O2 -fomit-frame-pointer hat hier eine Wirkung. Ich habe beide Flags aktiviert.

+0

IIRC auf die clobber Liste „cc“ und/oder „Speicher“ und fügte hinzu macht dies. Das Hinzufügen von volatile zu asm() verhindert manchmal eine Überoptimierung des Compilers. – technosaurus

+0

Wie wäre es mit 'Push 8 +% 4'? –

+2

Übrigens: [% rsp in der Clobber-Liste wird stillschweigend ignoriert] (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52813). –

Antwort

2

Was funktioniert und was nicht:

  1. Ich versuchte Weglassen. Kein Effekt überhaupt. Ich schloss %esp, esp und sp in der list of clobbers ein.

  2. Ich versuchte und -O3 weglassen. Dies erzeugt tatsächlich Code, der funktioniert, da er sich auf %ebp statt %esp stützt.

    pushl 16(%ebp) 
    pushl 12(%ebp) 
    pushl 8(%ebp) 
    lcall $456, $0 
    
  3. Ich habe versucht, mit nur -O3 mit und nicht in meinem Befehlszeile angegeben. Erzeugt schlechten, defekten Code (beruht auf %esp, der innerhalb des gesamten Assembly Blocks konstant ist, d.h.kein Stapelrahmen).

  4. Ich versuchte mit überspringen und nur -O2 verwenden. Fehlerhafter Code, kein Stapelrahmen.

  5. Ich habe versucht, nur mit -O1. Fehlerhafter Code, kein Stapelrahmen.

  6. Ich habe versucht, cc als Clobber hinzuzufügen. Nein kann, macht überhaupt keinen Unterschied.

  7. Ich habe versucht, die Eingabe-Constraints zu ri zu ändern, geben Sie den Eingang & Ausgangscode unten. Das funktioniert natürlich funktioniert aber ist etwas weniger elegant als ich gehofft hatte. Dann wieder, perfect is the enemy of good also vielleicht muss ich damit für jetzt leben.

Eingang C-Code:

static inline return_type system_call_service_get(const char *protocol_name, service_parameter_type *service_parameter, 
    tag_type *identification) 
{ 
    return_type return_value; 

    asm volatile("pushl %2\n" 
       "pushl %3\n" 
       "pushl %4\n" 
       "lcall %5, $0" 
       : "=a" (return_value), 
        "=g" (*service_parameter) 
       : "ri" (identification), 
        "ri" (service_parameter), 
        "ri" (protocol_name), 
        "n" (SYSTEM_CALL_SERVICE_GET << 3)); 

    return return_value; 
} 

Code Output nh. Wie man sehen kann, Register anstatt verwendet, die immer sicher sein sollte (aber vielleicht etwas weniger performant, da der Compiler Sachen zu bewegen, hat sich um):

#APP 
# 392 "../system/system_calls.h" 1 
pushl %esi 
pushl %eax 
pushl %ebx 
lcall $456, $0