Как правильно использовать простой скрипт компоновщика? Исполняемый файл получает SIGKILL при запуске - PullRequest
19 голосов
/ 25 августа 2011

Я пытаюсь понять более глубокие процессы компоновки и сценарии компоновщика ... глядя на документацию binutils, я нашел простую реализацию сценария компоновщика, которую я улучшил, добавив несколько команд:

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

ENTRY(mymain)

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

Моя программа очень простая:

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

Теперь я попытался создать исполняемый файл:

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

Но если я пытаюсь запустить prog, он получает SIGKILL во время запуска. Я знаю, что когда программа компилируется и связывается с командой:

gcc prog.c -o prog

конечный исполняемый файл является продуктом других объектных файлов, таких как crt1.o, crti.o и crtn.o, но как насчет моего случая? Как правильно использовать скрипты этого компоновщика?

Ответы [ 2 ]

22 голосов
/ 25 августа 2011

Я подозреваю, что ваш код работает нормально, и в конце возникают проблемы: что вы ожидаете, что произойдет после a++?

mymain() простообычная функция C, которая будет пытаться вернуться к своему вызывающему.

Но вы установили ее в качестве точки входа ELF, которая говорит загрузчику ELF перейти к ней после загрузки сегментов программы вв нужном месте - и он не ожидает, что вы вернетесь.

Эти "другие объектные файлы, такие как crt1.o, crti.o и crtn.o" обычно обрабатывают это для программ на Си.Точка входа ELF для программы на C не является main() - вместо этого это оболочка, которая устанавливает подходящую среду для main() (например, установка аргументов argc и argv в стеке или в регистрах,в зависимости от платформы), вызывает main() (с ожиданием того, что он может вернуться), а затем вызывает системный вызов exit (с кодом возврата от main()).


[Обновить следующие комментарии:]

Когда я пробую ваш пример с gdb, я вижу, что он действительно не возвращается после mymain(): после установки точки останова на mymain, изатем, следуя инструкциям, я вижу, что он выполняет инкремент, а затем возникает проблема в эпилоге функции:

$ 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

По крайней мере, для i386 загрузчик ELF устанавливает разумный стек перед входом взагруженный код, так что вы можете установить точку входа ELF в функцию C и получить разумное поведение;однако, как я упоминал выше, вы должны самостоятельно обработать выход из процесса.И если вы не используете среду выполнения C, вам лучше не использовать библиотеки, зависящие от среды выполнения C.

Так что вот пример этого, используя ваш оригинальный скрипт компоновщика - но скод C изменен, чтобы инициализировать a известным значением и вызвать системный вызов exit (с использованием встроенной сборки) с конечным значением a в качестве кода выхода.(Примечание: я только что понял, что вы не сказали точно, какую платформу вы используете; я предполагаю, что Linux здесь.)

$ 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
$ 
2 голосов
/ 28 июля 2018

да, чтобы работать в Linux, нам нужно изменить файл .lds

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