#assembly #compilation #linker #riscv #crt
Вопрос:
Вот код Hello World на языке Си:
// a.c
#include <stdio.h>
int main() {
printf("Hello worldn");
return 0;
}
Я компилирую его как gcc a.c
, который производит a.out
, как ожидалось, и ./a.out
печатает Hello world
… как ожидалось.
Теперь , если я выполню компиляцию и свяжусь отдельно: gcc -c a.c; ld -lc a.o
, он запустит a.out
созданное, как ./a.out
только я получу сообщение:
bash: ./a.out: No such file or directory
Я погуглил эту ошибку, и, похоже, это происходит, когда исполняемый файл создается 32-разрядным ELF, а архитектура машины 64-разрядная.
Я использую 64-разрядную машину и запускаю file a.out
ее:
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), not stripped
Почему это происходит?
Редактировать:
Выход из uname -m
$ uname -m
x86_64
Выход из ldd a.out
$ ldd a.out
linux-vdso.so.1 => (0x00007ffeeedfb000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa13a7b8000)
/lib64/ld-linux-x86-64.so.2 (0x00007fa13abab000)
gcc a.c
производит a.out
то, что работает правильно.
Комментарии:
1. 64-разрядная машина не обязательно означает 64-разрядную ОС. Каков результат
uname -m
?2. Что
ldd a.out
производит? Что происходит, когда вы это делаетеgcc a.c
?3. Добавлены выходные данные в качестве правок к вопросу
4. вы можете выполнить: gcc-v a.c и проверить этап связи (нужно ли вам связываться с объектом запуска, обычно crt0.o)
5. Почему вы не хотите, чтобы gcc занимался связыванием?
Ответ №1:
ld -lc a.o
В этой командной строке есть несколько ошибок:
- В общем случае код пользовательского уровня никогда не следует использовать
ld
напрямую и всегда использовать соответствующий интерфейс компилятора (gcc
здесь) для выполнения ссылки.Как вы обнаружили, командная строка ссылки, которая
gcc
создает, довольно сложна, и командная строка, которую вы приняли в ответе Джоан Эстебан, неверна.Если вы хотите увидеть фактическую команду link, просмотрите вывод из
gcc -v a.o
.Также обратите внимание, что команда link значительно меняется, когда вы меняете
gcc
команду незначительно (например, некоторые операционные системы требуют другогоcrt1.o
в зависимости от того, связываете ли вы многопоточный исполняемый файл или нет), а командная строка всегда зависит от операционной системы (что является еще одной причиной никогда не использоватьld
напрямую). - Библиотеки должны следовать за объектными файлами в командной строке. Так
ld -lc a.o
никогда не бывает правильно, и всегда должно быть (вариант)ld a.o -lc
. Объяснение.
Комментарии:
1. пояснение заархивированная ссылка web.archive.org/web/20180627210132/http://webpages.charter.net/…
Ответ №2:
Свяжите динамические исполняемые файлы с gcc foo.o
(чтобы использовать правильные пути для CRT и libc, а также интерпретатор динамического компоновщика / ELF ld-linux-x86-64.so.2
).
Или gcc -nostartfiles foo.o
для libc , но не для CRT _start
, если у вас есть рукописный _start
(Для статических исполняемых файлов без libc или CRT вы можете использовать ld
напрямую или gcc -nostdlib -static
.)
gcc -v foo.o
покажет вам фактические пути GCC, используемые в вашей системе.
Другие ответы касаются только того, как избежать этого 1, а не фактического вопроса о том, что произошло.
gcc -c a.c; ld -lc a.o
Команды, которые вы дали, дают довольно очевидное предупреждение:
ld: warning: cannot find entry symbol _start; defaulting to 0000000000400260
Поэтому, даже если этот файл можно было бы выполнить, он, вероятно, сразу же рухнет. Смотрите ответ @EmployedRussian для объяснения того, что вы должны были сделать.
Вопрос о том, почему он даже не может быть выполнен, по-прежнему интересен:
$ strace ./a.out
execve("./a.out", ["./a.out"], [/* 72 vars */]) = -1 ENOENT (No such file or directory)
execve(2)
возвращает ENOENT, потому что он не может найти интерпретатор (из которого я понял file
и так далее, см. Ниже). Вы получите ту же ошибку при попытке запустить файл, который начинался с
#!/usr/non-existant-path/bin/bash
Как вы обнаружили, обычная причина этого сообщения об ошибке заключается в запуске двоичного файла ELF в системе без установленного правильного динамического компоновщика и динамических библиотек (например, 64-разрядная система без установленной поддержки 32 бит). В вашем случае это связано с тем, что вы использовали неверную команду link и создали динамический исполняемый файл с неверным путем интерпретатора.
Я на Ubuntu 15.10, где GNU file
версии 5.22 сообщает:
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld64.so.1, not stripped
/lib/ld64.so.1
В моей системе его нет. ldd
вывод сбивает с толку, потому ldd
что использует интерпретатор ELF по умолчанию, а не тот, который указан в двоичном файле.
$ ldd a.out
linux-vdso.so.1 => (0x00007ffc18d2b000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0e0a79f000)
/lib/ld64.so.1 => /lib64/ld-linux-x86-64.so.2 (0x0000559dbc9d2000)
Таким образом, предполагается, что интерпретатор времени выполнения в двоичном файле разрешен к тому ldd
, который сам использовался, я полагаю.
Ваш ldd
вывод, вероятно, тоже из старой версии, так как он просто отображается /lib64/ld-linux-x86-64.so.2
для этой строки. Не делать неверных предположений, вероятно, лучше для такого странного случая, как этот, но это не поможет вам увидеть, что ваш двоичный файл имеет странный путь интерпретатора.
readelf -l a.out
будет декодировать заголовки ELF для вас, включая путь интерпретатора. (Спасибо комментарию @EmployedRussian за указание на это.)
Комментарии:
1. Стандартный способ узнать, что такое интерпретатор для данного двоичного файла, — это
readelf -l a.out | grep interpreter
Ответ №3:
Используйте это:
ld -o a.out -dynamic-linker /lib/ld-linux.so.2 /usr/lib/crt1.o /usr/lib/crti.o -lc c.o /usr/lib/crtn.o
Комментарии:
1. Во-первых,
crt1.o
,crti.o
, иcrtn.o
присутствуют/usr/lib/x86_64-linux-gnu/
на моей машине. После изменения этого и запускаld
, а затемa.out
, я получаю ошибку:bash: ./a.out: Accessing a corrupted shared library
2. @pratyaksh Вам также необходимо адаптировать компоновщик
/lib64/ld-linux-x86-64.so.2
.3. @piggs_boson: Это хорошая причина для того, чтобы связывать динамические исполняемые файлы с
gcc c.o
илиgcc -nostartfiles c.o
вместоld
вручную — ваш дистрибутив настроит его так, чтобы он использовал правильные пути для вашей системы . Используйтеgcc -v
, чтобы узнать, какие эти пути находятся в вашей системе, а не у кого-то другого в Интернете.