web-dev-qa-db-de.com

Wie verwende ich ein einfaches Linker-Skript richtig? Ausführbare Datei erhält SIGKILL, wenn sie ausgeführt wird

Ich versuche, tiefer gehende Verknüpfungsprozesse und Verknüpfungsskripte zu verstehen. Auf der Suche nach binutils doc habe ich eine einfache Implementierung von Verknüpfungsskripten gefunden, die ich durch Hinzufügen einiger Befehle verbessert habe:

OUTPUT_FORMAT("elf32-i386", "elf32-i386",
          "elf32-i386")
OUTPUT_Arch(i386)

ENTRY(mymain)

SECTIONS
{
   . = 0x10000;
   .text : { *(.text) }
   . = 0x8000000;
   .data : { *(.data) }
   .bss : { *(.bss) }
}

Mein Programm ist ein sehr einfaches Programm:

void mymain(void)
{
  int a;
  a++;
}

Jetzt habe ich versucht, eine ausführbare Datei zu erstellen:

gcc -c main.c
ld -o prog -T my_script.lds main.o

Aber wenn ich versuche, prog auszuführen, erhält es eine SIGKILL während des Starts. Ich weiß, dass, wenn ein Programm kompiliert und mit dem Befehl verknüpft wird:

gcc prog.c -o prog

die endgültige ausführbare Datei ist das Produkt auch aus anderen Objektdateien wie crt1.o, crti.o und crtn.o, aber was ist mit meinem Fall? Was ist der richtige Weg, um diese Linker-Skripte zu verwenden?

19
MirkoBanchi

Ich vermute, dass Ihr Code einwandfrei läuft und am Ende in Schwierigkeiten gerät: Was erwarten Sie nach dem a++?

mymain() ist nur eine gewöhnliche C-Funktion, die versucht, zu ihrem Aufrufer zurückzukehren.

Sie haben es jedoch als ELF-Einstiegspunkt festgelegt, der den ELF-Loader anweist, dorthin zu springen, sobald die Programmsegmente an der richtigen Stelle geladen wurden - und keine Rückkehr von Ihnen erwartet.

Diese "anderen Objektdateien wie crt1.o, crti.o und crtn.o" behandeln normalerweise dieses Zeug für C-Programme. Der ELF-Einstiegspunkt für ein C-Programm ist nicht main() - stattdessen ist es ein Wrapper, der eine geeignete Umgebung für main() einrichtet (z. B. das Einrichten der Argumente argc und argv auf dem Stack oder in Registern, je nach Plattform, ruft main() auf) ( mit der Erwartung, dass es zurückkehren kann) und ruft dann den Systemaufruf exit auf (mit dem Rückkehrcode von main()).


[Folgende Kommentare aktualisieren:]

Wenn ich Ihr Beispiel mit gdb versuche, sehe ich, dass es tatsächlich fehlschlägt, wenn ich von mymain() zurückkomme: Nachdem ich einen Haltepunkt für mymain gesetzt und dann die Anweisungen durchgegangen habe, sehe ich, dass es das Inkrement ausführt und dann im Funktionsepilog in Schwierigkeiten gerät:

$ gcc -g -c main.c
$ ld -o prog -T my_script.lds main.o
$ gdb ./prog
...
(gdb) b mymain
Breakpoint 1 at 0x10006: file main.c, line 4.
(gdb) r
Starting program: /tmp/prog 

Breakpoint 1, mymain () at main.c:4
4         a++;
(gdb) display/i $pc
1: x/i $pc
0x10006 <mymain+6>:     addl   $0x1,-0x4(%ebp)
(gdb) si
5       }
1: x/i $pc
0x1000a <mymain+10>:    leave  
(gdb) si
Cannot access memory at address 0x4
(gdb) si
0x00000001 in ?? ()
1: x/i $pc
Disabling display 1 to avoid infinite recursion.
0x1:    Cannot access memory at address 0x1
(gdb) q

Zumindest für i386 richtet der ELF-Loader vor der Eingabe des geladenen Codes einen sinnvollen Stack ein, sodass Sie can den ELF-Einstiegspunkt auf eine C-Funktion setzen und ein angemessenes Verhalten erzielen; Wie oben erwähnt, müssen Sie jedoch einen sauberen Prozess-Exit selbst durchführen. Und wenn Sie nicht die C-Laufzeit verwenden, sollten Sie auch keine Bibliotheken verwenden, die von der C-Laufzeit abhängig sind.

Hier ist ein Beispiel dafür, bei dem Sie Ihr ursprüngliches Linkerskript verwenden - der C-Code wurde jedoch geändert, um a auf einen bekannten Wert zu initialisieren, und ein Systemaufruf exit (mit Inline-Assembly) mit dem Endwert a als Exit-Code wird aufgerufen. (Hinweis: Ich habe gerade festgestellt, dass Sie nicht genau angegeben haben, welche Plattform Sie verwenden. Ich gehe hier von Linux aus.)

$ cat main2.c
void mymain(void)
{
  int a = 42;
  a++;
  asm volatile("mov $1,%%eax; mov %0,%%ebx; int $0x80" : : "r"(a) : "%eax" );
}
$ gcc -c main2.c
$ ld -o prog2 -T my_script.lds main2.o
$ ./prog2 ; echo $?
43
$ 
22

ja, um unter Linux zu laufen, müssen wir die .lds-Datei ändern

SECTIONS
{
   . = 0x8048000;
   .text : { *(.text) 
}
0
sharath kumar