Подтвердить что ты не робот

Как правильно использовать простой компоновщик script? Исполняемый файл получает SIGKILL при запуске

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

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, но как насчет моего дела? Каков правильный способ использования сценариев компоновщика?

4b9b3361

Ответ 1

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

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

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

Эти "другие объектные файлы, такие как crt1.o, crti.o и crtn.o" обычно обрабатывают этот материал для программ на C. Точка входа 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.

Итак, вот пример этого, используя ваш исходный компоновщик script, но с кодом 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
$