Lesen eines Registerwerts in eine C-Variable [duplicate]

Lesezeit: 12 Minuten

Brians Benutzeravatar
Brian

Ich erinnere mich, dass ich eine Möglichkeit gesehen habe, die erweiterte gcc-Inline-Assemblierung zu verwenden, um einen Registerwert zu lesen und ihn in einer C-Variablen zu speichern.

Ich kann mich jedoch für mein ganzes Leben nicht daran erinnern, wie man die asm-Anweisung bildet.

  • Und woher wissen Sie, was sich in EBX befindet, wenn Ihr C-Code ausgeführt wird?

    – Gulden

    22. Januar 2010 um 17:45 Uhr

  • Sie können nicht wissen, welchen Wert der vom Compiler generierte Code in einem Register gespeichert hat, wenn Sie inline asm Anweisung ausgeführt wird, daher ist der Wert normalerweise bedeutungslos, und Sie wären viel besser dran, einen Debugger zu verwenden, um sich die Registerwerte anzusehen, wenn Sie an einem Haltepunkt angehalten werden. Es könnte für einen Stapelzeiger sinnvoll sein, aber es gibt ihn __builtin_frame_address(0) um eine Stack-Adresse zu erhalten (und IIRC, bewirken, dass diese Funktion einen vollen Stack-Frame erstellt, selbst wenn -fomit-frame-pointer aktiviert ist, wie es standardmäßig auf x86 ist.)

    – Peter Cordes

    17. März 2018 um 9:54 Uhr


  • Als Duplikat eines neuen Q&A schließen, weil die Die am höchsten bewertete Antwort hier ist veraltet (mit Klirren unterbrochen, von GCC nicht unterstützt). (Zumindest in einfachen Fällen funktioniert es immer noch mit GCC.) Eine asm-Anweisung, die a mov %%reg, %0 zu einem "=r"(var) Ausgabe ist auch sicher, diese Antwort ist in Ordnung.

    – Peter Cordes

    7. Juni 2021 um 4:21 Uhr


Benutzeravatar von ephemient
vergänglich

Anmerkung der Redaktion: Diese Art der Verwendung einer lokalen Register-ASM-Variablen ist jetzt von GCC als “nicht unterstützt” dokumentiert. Es funktioniert normalerweise immer noch auf GCC, bricht aber mit Clang ab. (Diese Formulierung in der Dokumentation wurde hinzugefügt, nachdem diese Antwort veröffentlicht wurde, denke ich.)

Die variable Version mit globalen festen Registern hat einen hohen Leistungsaufwand für 32-Bit-x86, das nur 7 GP-Integer-Register hat (den Stapelzeiger nicht mitgezählt). Dies würde das auf 6 reduzieren. Ziehen Sie dies nur in Betracht, wenn Sie eine globale Variable haben, die Ihr gesamter Code stark verwendet.


Gehen Sie in eine andere Richtung als andere Antworten, da ich mir nicht sicher bin, was Sie wollen.

GCC-Handbuch § 5.40 Variablen in spezifizierten Registern

register int *foo asm ("a5");

Hier a5 ist der Name des Registers, das verwendet werden soll…

Natürlich ist der Registername CPU-abhängig, aber das ist kein Problem, da bestimmte Register meistens mit expliziten Assembler-Anweisungen nützlich sind (siehe Erweiterte Asm). Beides erfordert im Allgemeinen, dass Sie Ihr Programm entsprechend dem CPU-Typ konditionalisieren.

Durch die Definition einer solchen Registervariablen wird das Register nicht reserviert; sie bleibt für andere Verwendungen dort verfügbar, wo die Flusssteuerung bestimmt, dass der Wert der Variablen nicht live ist.

GCC-Handbuch § 3.18 Optionen für Konventionen zur Codegenerierung

-ffixed-Reg

Behandeln Sie das genannte Register Reg als festes Register; generierter Code sollte niemals darauf verweisen (außer vielleicht als Stapelzeiger, Rahmenzeiger oder in einer anderen festen Rolle).

Dies kann Richards Antwort auf einfachere Weise replizieren,

int main() {
    register int i asm("ebx");
    return i + 1;
}

obwohl das ziemlich bedeutungslos ist, da man ja keine ahnung hat was drin ist ebx registrieren.

Wenn Sie diese beiden kombinieren, kompilieren Sie diese mit gcc -ffixed-ebx,

#include <stdio.h>
register int counter asm("ebx");
void check(int n) {
    if (!(n % 2 && n % 3 && n % 5)) counter++;
}
int main() {
    int i;
    counter = 0;
    for (i = 1; i <= 100; i++) check(i);
    printf("%d Hamming numbers between 1 and 100\n", counter);
    return 0;
}

Sie können sicherstellen, dass eine C-Variable sich immer in einem Register für einen schnellen Zugriff befindet und auch nicht durch anderen generierten Code überlastet wird. (Praktisch, ebx ist callee-save unter den üblichen x86-Aufrufkonventionen, also selbst wenn es durch Aufrufe anderer Funktionen, die ohne kompiliert wurden, überlastet wird -ffixed-*es sollte auch wiederhergestellt werden.)

Auf der anderen Seite ist dies definitiv nicht portabel und normalerweise auch kein Leistungsvorteil, da Sie die Freiheit des Compilers einschränken.

  • Zitat aus der aktuelle Dokumente Beschreibung lokaler Register Die einzige unterstützte Verwendung für diese Funktion ist die Angabe von Registern für Eingabe- und Ausgabeoperanden beim Aufruf von Extended asm. Also setzen i innerhalb von main() wird so nicht unterstützt. Und um Ihren Punkt zu betonen: x86 hat nur eine begrenzte Anzahl von Registern. Das Entfernen einer von der allgemeinen Verwendung über eine globale Registervariable kann andere kritische Teile Ihres Codes verlangsamen. Einige Diskussionen hier.

    – David Wohlferd

    17. März 2018 um 10:25 Uhr


  • Ausweis höchst empfehlen nicht Verwenden einer globalen Registervariablen, außer vielleicht in einer .c Datei mit einer Funktion als Hack. Erwarten Sie erhebliche Leistungseinbußen, insbesondere auf 32-Bit-x86.

    – Peter Cordes

    17. März 2018 um 10:45 Uhr

Benutzeravatar von Richard Pennington
Richard Pennington

Hier ist eine Möglichkeit, ebx zu bekommen:

int main()
{
    int i;
    asm("\t movl %%ebx,%0" : "=r"(i));
    return i + 1;
}

Das Ergebnis:

main:
    subl    $4, %esp
    #APP
             movl %ebx,%eax
    #NO_APP
    incl    %eax
    addl    $4, %esp
    ret


Bearbeiten:

Das „=r“(i) ist eine Ausgabebeschränkung, die dem Compiler mitteilt, dass die erste Ausgabe (%0) ein Register ist, das in die Variable „i“ platziert werden sollte. Auf dieser Optimierungsstufe (-O5) wird die Variable i nie gespeichert, sondern im eax-Register gehalten, das auch das Rückgabewertregister ist.

  • Ich würde die verwenden =rm Einschränkung statt =r. Der Optimierer des Compilers wird versuchen, den besten Pfad auszuwählen. Wenn sich der Inline-Assembler zufällig in einer Registermangelsituation befindet =r kann es dazu zwingen, weniger als optimalen Code zu generieren. =rm würde dem Optimierer die Möglichkeit geben, eine Speicherreferenz zu verwenden, wenn dies zufällig die beste Wahl wäre. In diesem einfachen Beispiel wird es kein Problem sein, aber wenn sich der Code in einer komplexeren Situation befindet, könnte es vorteilhaft sein, dem Compiler Optionen zu geben.

    – Michael Petsch

    6. Juni 2016 um 12:57 Uhr


  • @MichaelPetch Wie wäre es mit “=b” und einer leeren Vorlagenzeichenfolge?

    – David Wohlferd

    17. März 2018 um 10:28 Uhr

  • Beachten Sie, dass Clang normalerweise Speicher auswählt, wenn Sie es verwenden "=rm", auch wenn es tatsächlich den Wert in einem Register benötigt. Es wird am Ende gespeichert und neu geladen. Dies ist eine seit langem verpasste Optimierung in clangs Inline-ASM-Unterstützung. Verwenden "=b"(i) sollte auch funktionieren, indem Sie dem Compiler nur mitteilen, dass der EBX den Wert von enthält i nach der asm-Anweisung. Du möchtest vielleicht asm volatile Wenn Sie dies an mehr als einer Stelle verwenden, kann der Compiler sonst davon ausgehen, dass die asm-Anweisung immer dieselbe Ausgabe erzeugt (weil die Eingabe immer dieselbe ist: die leere Menge von Eingaben.)

    – Peter Cordes

    7. Juni 2021 um 4:25 Uhr

  • -O5 Optimierung ? Ich habe gelesen, O3 ist das Maximum?

    – Ilan Schemaul

    7. Mai um 14:25 Uhr

Ich weiß nichts über gcc, aber in VS geht das so:

int data = 0;   
__asm
{
    mov ebx, 30
    mov data, ebx
}
cout<<data;

Im Wesentlichen habe ich die Daten verschoben ebx zu deiner Variable data.

  • x86 natürlich nur. Die Compiler von Microsoft für x64 und Itanium unterstützen keine Inline-Assemblierung.

    – vergänglich

    22. Januar 2010 um 4:52 Uhr

  • Ich denke, die Assembly wird in mov ebx, 30 mov dword ptr übersetzt[data]ebx

    – Sridarschan

    13. Februar 2012 um 5:13 Uhr


  • Warum nicht einfach mov data, 30 ?

    Benutzer2742371

    28. Februar 2014 um 15:06 Uhr

Benutzeravatar von R Samuel Klatchko
R. Samuel Klatschko

Dadurch wird das Stapelzeigerregister in die sp-Variable verschoben.

intptr_t sp;
asm ("movl %%esp, %0" : "=r" (sp) );

Ersetzen Sie einfach ‘esp’ durch das tatsächliche Register, an dem Sie interessiert sind (aber achten Sie darauf, das %%) nicht zu verlieren, und ‘sp’ durch Ihre Variable.

Aus den GCC-Dokumenten selbst: http://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html

Benutzeravatar von Mahdi Mohammadi
Mahdi Mohammadi

#include <stdio.h>

void gav(){
        //rgv_t argv = get();
        register unsigned long long i asm("rax");
        register unsigned long long ii asm("rbx");
        printf("I`m gav - first arguman is: %s - 2th arguman is: %s\n", (char *)i, (char *)ii);
}

int main(void)
{
    char *test = "I`m main";
    char *test1 = "I`m main2";
    printf("0x%llx\n", (unsigned long long)&gav);
    asm("call %P0" : :"i"((unsigned long long)&gav), "a"(test), "b"(test1));
    return 0;
}

Benutzeravatar von Peter Cordes
Peter Kordes

Sie können nicht wissen, welchen Wert der vom Compiler generierte Code in einem Register gespeichert hat, wenn Sie inline asm Anweisung ausgeführt wird, daher ist der Wert normalerweise bedeutungslos, und Sie wären viel besser dran, einen Debugger zu verwenden, um sich die Registerwerte anzusehen, wenn Sie an einem Haltepunkt angehalten werden.

Davon abgesehen, wenn Sie diese seltsame Aufgabe erledigen, können Sie es genauso gut effizient erledigen.

Auf einigen Zielen (wie x86) können Sie bestimmte Registerausgabebeschränkungen verwenden, um den Compiler zu informieren die registrieren, in dem ein Ausgang sein wird. Verwenden Sie eine Ausgabebeschränkung für bestimmte Register mit einer leeren asm-Vorlage (null Anweisungen), um dem Compiler mitzuteilen, dass sich Ihre asm-Anweisung nicht um diesen Registerwert bei der Eingabe kümmert, aber danach befindet sich die angegebene C-Variable in diesem Register.

#include <stdint.h>

int foo() {
    uint64_t rax_value;           // type width determines register size
    asm("" : "=a"(rax_value));  // =letter determines which register (or partial reg)

    uint32_t ebx_value;
    asm("" : "=b"(ebx_value));

    uint16_t si_value;
    asm("" : "=S"(si_value) );

    uint8_t sil_value;  // x86-64 required to use the low 8 of a reg other than a-d
       // With -m32:  error: unsupported size for integer register
    asm("# Hi mom, my output constraint picked %0" : "=S"(sil_value) );

    return sil_value + ebx_value;
}

Kompiliert mit clang5.0 auf Godbolt für x86-64. Beachten Sie, dass die 2 unbenutzten Ausgabewerte wegoptimiert werden, nein #APP / #NO_APP vom Compiler generierte Asm-Kommentar-Paare (die den Assembler aus / in den Fast-Parsing-Modus schalten oder zumindest daran gewöhnen, wenn das keine Sache mehr ist). Dies liegt daran, dass ich es nicht verwendet habe asm volatileund sie haben einen Ausgabeoperanden, also sind sie nicht implizit volatile.

foo():                                # @foo()
# BB#0:
    push    rbx
    #APP
    #NO_APP
    #DEBUG_VALUE: foo:ebx_value <- %EBX
    #APP
    # Hi mom, my output constraint picked %sil
    #NO_APP
    #DEBUG_VALUE: foo:sil_value <- %SIL
    movzx   eax, sil
    add     eax, ebx
    pop     rbx
    ret
                                    # -- End function
                                    # DW_AT_GNU_pubnames
                                    # DW_AT_external

Beachten Sie den vom Compiler generierten Code, um zwei Ausgaben direkt aus den angegebenen Registern zusammenzufügen. Beachten Sie auch das Push/Pop von RBX, da RBX ein anruferhaltenes Register in der Aufrufkonvention von x86-64 System V ist. (Und im Grunde alle 32- und 64-Bit-x86-Aufrufkonventionen). Aber wir haben dem Compiler gesagt, dass unsere asm-Anweisung dort einen Wert schreibt. (Die Verwendung einer leeren asm-Anweisung ist eine Art Hack; es gibt keine Syntax, um dem Compiler direkt mitzuteilen, dass wir nur ein Register lesen möchten, da Sie, wie gesagt, nicht wissen, was der Compiler mit den Registern gemacht hat, wenn Ihre asm-Anweisung lautet eingefügt.)

Der Compiler behandelt Ihre asm-Anweisung so, als wäre sie tatsächlich vorhanden schrieb dieses Register, wenn es also den Wert für später benötigt, hat es ihn in ein anderes Register kopiert (oder in den Speicher verschüttet), wenn Ihre asm-Anweisung “läuft”.


Das andere x86-Registereinschränkungen sind b (bl/bx/ebx/rbx), c (…/rcx), d (…/rdx), S (sil/si/esi/rsi), D (…/rdi). Es gibt keine spezielle Einschränkung für bpl/bp/ebp/rbp, obwohl es bei Funktionen ohne Frame-Zeiger nicht besonders ist. (Vielleicht, weil die Verwendung dazu führen würde, dass Ihr Code nicht mit Compiler wird -fno-omit-frame-pointer.)

Sie können verwenden register uint64_t rbp_var asm ("rbp")in welchem ​​Fall asm("" : "=r" (rbp_var)); garantiert, dass die "=r" Einschränkung wird auswählen rbp. Ähnlich für r8-r15, die auch keine expliziten Einschränkungen haben. Auf einigen Architekturen wie ARM sind asm-register-Variablen die einzige Möglichkeit, anzugeben, welches Register Sie für asm-Eingabe-/Ausgabeeinschränkungen verwenden möchten. (Und beachte das asm Einschränkungen sind die nur unterstützte Nutzung von register asm Variablen; Es gibt keine Garantie dafür, dass sich der Wert der Variablen zu einem anderen Zeitpunkt in diesem Register befindet.


Nichts hindert den Compiler daran, diese asm-Anweisungen an beliebiger Stelle innerhalb einer Funktion (oder übergeordneten Funktionen nach dem Inlining) zu platzieren.. Sie haben also keine Kontrolle darüber wo Sie tasten den Wert eines Registers ab. asm volatile kann einige Umordnungen vermeiden, aber vielleicht nur in Bezug auf andere volatile Zugriffe. Sie könnten das vom Compiler generierte asm überprüfen, um zu sehen, ob Sie das bekommen haben, was Sie wollten, aber beachten Sie, dass es zufällig gewesen sein könnte und später kaputt gehen könnte.

Sie können eine asm-Anweisung in der Abhängigkeitskette für etwas anderes platzieren, um zu steuern, wo der Compiler sie platziert. Verwenden ein "+rm" Einschränkung, um dem Compiler mitzuteilen, dass er eine andere Variable ändert, die tatsächlich für etwas verwendet wird, das nicht wegoptimiert wird.

uint32_t ebx_value;
asm("" : "=b"(ebx_value), "+rm"(some_used_variable) );

wo some_used_variable kann ein Rückgabewert von einer Funktion sein und (nach einiger Verarbeitung) als Argument an eine andere Funktion übergeben werden. Oder in einer Schleife berechnet und als Rückgabewert der Funktion zurückgegeben. In diesem Fall kommt die asm-Anweisung garantiert irgendwann nach dem Ende der Schleife und vor jedem Code, der vom späteren Wert dieser Variablen abhängt.

Dies wird jedoch Optimierungen wie die Konstantenausbreitung für diese Variable zunichte machen. https://gcc.gnu.org/wiki/DontUseInlineAsm. Der Compiler kann nicht davon ausgehen irgendetwas über den Ausgabewert; es überprüft nicht, dass die asm Anweisung hat null Anweisungen.


Dies funktioniert nicht für einige Register, die Sie mit gcc nicht als Ausgabeoperanden oder Clobber verwenden können, z. B. den Stapelzeiger.

Das Einlesen des Werts in eine C-Variable kann jedoch für einen Stapelzeiger sinnvoll sein, wenn Ihr Programm etwas Besonderes mit Stapeln macht.

Als Alternative zu inline-asm gibt es __builtin_frame_address(0) um eine Stapeladresse zu erhalten. (Aber IIRC sorgt dafür, dass diese Funktion einen vollständigen Stack-Frame erstellt, selbst wenn -fomit-frame-pointer aktiviert ist, wie es standardmäßig auf x86 ist.)

In vielen Funktionen ist dies jedoch nahezu kostenlos (und das Erstellen eines Stapelrahmens kann für die Codegröße gut sein, da kleinere Adressierungsmodi für RBP-relativen als für RSP-relativen Zugriff auf lokale Variablen vorhanden sind).

Verwendung einer mov Unterricht in einem asm Aussage würde natürlich auch funktionieren.

1408340cookie-checkLesen eines Registerwerts in eine C-Variable [duplicate]

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy