Warum erstellt GCC ein gemeinsames Objekt anstelle einer ausführbaren Binärdatei gemäß Datei?

Lesezeit: 9 Minuten

Benutzeravatar von Luke Smith
Lukas Smith

Ich habe eine Bibliothek, die ich baue. Alle meine Objekte werden nacheinander kompiliert und verknüpft, wenn ich eines der folgenden ausführe:
ar rcs lib/libryftts.a $^

gcc -shared $^ -o lib/libryftts.so

in meinem Makefile. Ich bin auch in der Lage, sie erfolgreich in zu installieren /usr/local/lib
Wenn ich die Datei mit nm teste, sind alle Funktionen da. Mein Problem ist, dass wenn ich laufe gcc testing/test.c -lryftts -o test && file ./test oder gcc testing/test.c lib/libryftts.a -o test && file ./test
es sagt:

test: ELF 64-bit LSB shared object Anstatt von test: ELF 64-bit LSB executable wie ich es erwarten würde. Was mache ich falsch?

  • Ist dein test.c enthält main?

    – Eugen Sch.

    29. Dezember 2015 um 21:48 Uhr

  • @EugenSch. Ja, es enthält main.

    – Lukas Smith

    29. Dezember 2015 um 21:50 Uhr

  • Hört sich komisch an. Aber wahrscheinlich brauchen Sie mehr Informationen, um das Problem zu diagnostizieren. Schlagen Sie vor, dass Sie einen minimalen Testfall erstellen und den genauen Code und die Build-Befehle posten, die zur Erzeugung des Problems verwendet wurden. Außerdem möchten Sie vielleicht die letzte Kompilierungszeile mit ausführen gcc -v um genau zu sehen, welche Befehle und Optionen gcc tatsächlich verwendet.

    – Kaylum

    29. Dezember 2015 um 21:58 Uhr


  • Verwandte: Absolute 32-Bit-Adressen sind in x86-64-Linux nicht mehr zulässig?

    – Peter Cordes

    10. Oktober 2017 um 9:06 Uhr

Was mache ich falsch?

Nichts.

Es hört sich so an, als ob Ihr GCC zum Erstellen konfiguriert ist -pie Binärdateien standardmäßig. Diese Binärdateien wirklich sind gemeinsam genutzte Bibliotheken (vom Typ ET_DYN), außer dass sie genau wie eine normale ausführbare Datei ausgeführt werden.

Sie sollten also einfach Ihre Binärdatei ausführen und sich (falls sie funktioniert) keine Gedanken darüber machen.

Oder Sie könnten Ihre Binärdatei mit verknüpfen gcc -no-pie ... und das sollte eine nicht-PIE ausführbarer Typ ET_EXECwofür file werde sagen ELF 64-bit LSB executable.

  • Beim Laufen gcc ... -v Ich habe -pie in der Konfiguration gesehen. Dies hat behoben, was ich gesehen habe. Danke für die Information!

    – Lukas Smith

    30. Dezember 2015 um 17:22 Uhr

  • Für jeden (wie mich), der mit kompiliert Code::Blöcke – Sie müssen hinzufügen -no-pie zu Linker-Einstellungen → Andere Linker-Optionen, nicht Compiler-Einstellungen → Andere Compiler-Optionen wie Sie vielleicht erwarten – letzteres fügt hinzu -no-pie beim Kompilieren der einzelnen C-Dateien, aber nicht beim Erstellen der endgültigen ausführbaren Datei.

    – GoBusto

    21. Dezember 2016 um 12:50 Uhr

  • Wenn Sie verwenden werden -no-piesollten Sie auch verwenden -fno-pie um eine etwas effizientere Code-Generierung zu erhalten. (siehe stackoverflow.com/questions/43367427/…)

    – Peter Cordes

    10. Oktober 2017 um 9:06 Uhr

  • @dylan Du müsstest deinen eigenen Compiler bauen. Wenn du configure es, nicht angeben --enable-default-pie und du bist fertig.

    – Angestellter Russe

    6. Dezember 2017 um 6:32 Uhr

  • ET_DYN ist wirklich nur ein Trick, um den Lader dazu zu bringen, es an einer nicht absoluten Adresse zu laden.

    – SS Anne

    10. Januar 2020 um 1:03 Uhr

Ciro Santilli Benutzeravatar von OurBigBook.com
Ciro Santilli OurBigBook.com

file 5.36 sagt es deutlich

file 5.36 gibt tatsächlich deutlich aus, ob die ausführbare Datei PIE ist oder nicht, wie unter: https://unix.stackexchange.com/questions/89211/how-to-test-ob-a-linux-binary-was-compiled-as-position-independent-code/435038#435038

Beispielsweise wird eine ausführbare PIE-Datei wie folgt angezeigt:

main.out: ELF 64-Bit LSB Pie ausführbar, x86-64, Version 1 (SYSV), dynamisch gelinkt, nicht entfernt

und ein Nicht-PIE als:

main.out: ELF 64-Bit LSB ausführbar, x86-64, Version 1 (SYSV), statisch gelinkt, nicht entfernt

Die Funktion wurde in 5.33 eingeführt, hat aber nur eine einfache Funktion chmod +x überprüfen. Davor wurde nur gedruckt shared object für TORTE.

In 5.34 sollte man anfangen, die spezialisierteren zu prüfen DF_1_PIE ELF-Metadaten, aber aufgrund eines Fehlers in der Implementierung beim Commit 9109a696f3289ba00eaa222fd432755ec4287e28 es hat tatsächlich Dinge kaputt gemacht und ausführbare GCC PIE-Dateien als angezeigt shared objects.

Der Fehler wurde in 5.36 beim Commit behoben 03084b161cf888b5286dbbcd964c31ccad4f64d9.

Der Fehler ist insbesondere in Ubuntu 18.10 vorhanden, das hat file 5.34.

Es manifestiert sich nicht beim Verknüpfen von Assemblycode mit ld -pie wegen einem Zufall.

Die Aufschlüsselung des Quellcodes wird im “file 5.36 Quellcodeanalyse” Abschnitt dieser Antwort.

Der Linux-Kernel 5.0 bestimmt, ob ASLR basierend auf verwendet werden kann ET_DYN

Die eigentliche Ursache der file “Verwirrung” besteht darin, dass sowohl ausführbare PIE-Dateien als auch gemeinsam genutzte Bibliotheken positionsunabhängig sind und an randomisierten Speicherorten platziert werden können.

Bei fs/binfmt_elf.c Der Kernel akzeptiert nur diese beiden Arten von ELF-Dateien:

/* First of all, some simple consistency checks */
if (interp_elf_ex->e_type != ET_EXEC &&
        interp_elf_ex->e_type != ET_DYN)
        goto out;

Dann nur für ET_DYN setzt es das load_bias zu etwas, das nicht Null ist. Das load_bias ist dann das, was den ELF-Offset bestimmt: Wie wird die Adresse des Textabschnitts einer ausführbaren PIE-Datei unter Linux bestimmt?

/*
 * If we are loading ET_EXEC or we have already performed
 * the ET_DYN load_addr calculations, proceed normally.
 */
if (loc->elf_ex.e_type == ET_EXEC || load_addr_set) {
        elf_flags |= elf_fixed;
} else if (loc->elf_ex.e_type == ET_DYN) {
        /*
         * This logic is run once for the first LOAD Program
         * Header for ET_DYN binaries to calculate the
         * randomization (load_bias) for all the LOAD
         * Program Headers, and to calculate the entire
         * size of the ELF mapping (total_size). (Note that
         * load_addr_set is set to true later once the
         * initial mapping is performed.)
         *
         * There are effectively two types of ET_DYN
         * binaries: programs (i.e. PIE: ET_DYN with INTERP)
         * and loaders (ET_DYN without INTERP, since they
         * _are_ the ELF interpreter). The loaders must
         * be loaded away from programs since the program
         * may otherwise collide with the loader (especially
         * for ET_EXEC which does not have a randomized
         * position). For example to handle invocations of
         * "./ld.so someprog" to test out a new version of
         * the loader, the subsequent program that the
         * loader loads must avoid the loader itself, so
         * they cannot share the same load range. Sufficient
         * room for the brk must be allocated with the
         * loader as well, since brk must be available with
         * the loader.
         *
         * Therefore, programs are loaded offset from
         * ELF_ET_DYN_BASE and loaders are loaded into the
         * independently randomized mmap region (0 load_bias
         * without MAP_FIXED).
         */
        if (elf_interpreter) {
                load_bias = ELF_ET_DYN_BASE;
                if (current->flags & PF_RANDOMIZE)
                        load_bias += arch_mmap_rnd();
                elf_flags |= elf_fixed;
        } else
                load_bias = 0;

Ich bestätige dies experimentell unter: Was ist die Option -fPIE für positionsunabhängige ausführbare Dateien in gcc und ld?

file 5.36 Verhaltenszusammenbruch

Nach dem Studium wie file Werke aus seiner Quelle. Wir werden daraus schließen:

  • wenn Elf32_Ehdr.e_type == ET_EXEC
    • drucken executable
  • sonst wenn Elf32_Ehdr.e_type == ET_DYN
    • wenn DT_FLAGS_1 dynamischer Abschnittseintrag vorhanden ist
      • wenn DF_1_PIE ist eingelassen DT_FLAGS_1:
        • drucken pie executable
      • anders
        • drucken shared object
    • anders
      • ob Datei von Benutzer, Gruppe oder anderen ausführbar ist
        • drucken pie executable
      • anders
        • drucken shared object

Und hier sind einige Experimente, die das bestätigen:

Executable generation        ELF type  DT_FLAGS_1  DF_1_PIE  chdmod +x      file 5.36
---------------------------  --------  ----------  --------  -------------- --------------
gcc -fpie -pie               ET_DYN    y           y         y              pie executable
gcc -fno-pie -no-pie         ET_EXEC   n           n         y              executable
gcc -shared                  ET_DYN    n           n         y              pie executable
gcc -shared                  ET_DYN    n           n         n              shared object
ld                           ET_EXEC   n           n         y              executable
ld -pie --dynamic-linker     ET_DYN    y           y         y              pie executable
ld -pie --no-dynamic-linker  ET_DYN    y           y         y              pie executable

Getestet in Ubuntu 18.10, GCC 8.2.0, Binutils 2.31.1.

Das vollständige Testbeispiel für jede Art von Experiment ist beschrieben unter:

ELF type und DF_1_PIE werden jeweils bestimmt mit:

readelf --file-header main.out | grep Type
readelf --dynamic     main.out | grep FLAGS_1

file 5.36 Quellcodeanalyse

Die zu analysierende Schlüsseldatei ist Magie/Magdir/Elf.

Dieses magische Format bestimmt Dateitypen nur in Abhängigkeit von den Werten der Bytes an festen Positionen.

Das Format selbst ist dokumentiert unter:

man 5 magic

An dieser Stelle sollten Sie also die folgenden Dokumente griffbereit haben:

Gegen Ende der Datei sehen wir:

0       string          \177ELF         ELF
!:strength *2
>4      byte            0               invalid class
>4      byte            1               32-bit
>4      byte            2               64-bit
>5      byte            0               invalid byte order
>5      byte            1               LSB
>>0     use             elf-le
>5      byte            2               MSB
>>0     use             \^elf-le

\177ELF sind die 4 magischen Bytes am Anfang jeder ELF-Datei. \177 ist das Oktal für 0x7F.

Dann durch den Vergleich mit der Elf32_Ehdr struct aus dem Standard sehen wir, dass Byte 4 (das 5. Byte, das erste nach der magischen Kennung) die ELF-Klasse bestimmt:

e_ident[EI_CLASSELFCLASS]

und einige der möglichen Werte sind:

ELFCLASS32 1
ELFCLASS64 2

Im file Quelle dann haben wir:

1 32-bit
2 64-bit

und 32-bit und 64-bit sind die Saiten, die file gibt auf stdout aus!

Also suchen wir jetzt shared object in dieser Datei, und wir werden geführt zu:

0       name            elf-le
>16     leshort         0               no file type,
!:mime  application/octet-stream
>16     leshort         1               relocatable,
!:mime  application/x-object
>16     leshort         2               executable,
!:mime  application/x-executable
>16     leshort         3               ${x?pie executable:shared object},

Also das elf-le ist eine Art Kennung, die im vorherigen Teil des Codes enthalten ist.

Byte 16 ist genau der ELF-Typ:

Elf32_Ehdr.e_type

und einige seiner Werte sind:

ET_EXEC 2
ET_DYN  3

Deswegen, ET_EXEC wird immer als gedruckt executable.

ET_DYN hat aber zwei möglichkeiten je nach ${x:

  • pie executable
  • shared object

${x fragt: ist die Datei von Benutzer, Gruppe oder anderen ausführbar oder nicht? Wenn ja, zeigen pie executableanders shared object.

Diese Erweiterung erfolgt in der varexpand Funktion ein src/softmagic.c:

static int
varexpand(struct magic_set *ms, char *buf, size_t len, const char *str)
{
    [...]
            case 'x':
                    if (ms->mode & 0111) {
                            ptr = t;
                            l = et - t;
                    } else {
                            ptr = e;
                            l = ee - e;
                    }
                    break;

Es gibt jedoch noch einen Hack! Im src/readelf.c Funktion dodynamicwenn die DT_FLAGS_1 flags Eintrag des dynamischen Abschnitts (PT_DYNAMIC) vorhanden ist, dann die Berechtigungen in st->mode werden durch das Vorhandensein oder Fehlen von außer Kraft gesetzt DF_1_PIE Flagge:

case DT_FLAGS_1:
        if (xdh_val & DF_1_PIE)
                ms->mode |= 0111;
        else
                ms->mode &= ~0111;
        break;

Der Fehler in 5.34 ist, dass der ursprüngliche Code wie folgt geschrieben wurde:

    if (xdh_val == DF_1_PIE)

was bedeutet, dass wenn ein anderes Flag gesetzt wurde, was GCC standardmäßig durchführt DF_1_NOWdie ausführbare Datei wurde als angezeigt shared object.

Das DT_FLAGS_1 Der Eintrag flags ist im ELF-Standard nicht beschrieben, daher muss es sich um eine Binutils-Erweiterung handeln.

Dieses Flag hat im Linux-Kernel 5.0 oder glibc 2.27 keine Verwendung, daher scheine ich nur informativ zu sein, um anzuzeigen, ob eine Datei PIE ist oder nicht.

1402550cookie-checkWarum erstellt GCC ein gemeinsames Objekt anstelle einer ausführbaren Binärdatei gemäß Datei?

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

Privacy policy