gcc использует термины «архитектура» для обозначения «набора команд» конкретного ЦП, а «цель» охватывает комбинацию ЦП и архитектуры, наряду с другими переменными, такими как ABI, libc, endian-ness и т. д. (возможно, включая «голый металл»). Типичный компилятор имеет ограниченный набор целевых комбинаций (вероятно, один ABI, одно семейство процессоров, но, возможно, 32- и 64-разрядные). Кросс-компилятор обычно означает либо компилятор с целью, отличной от системы, на которой он работает, либо с несколькими целями или ABI (см. Также это ).
Являются ли двоичные файлы переносимыми на разные архитектуры ЦП?
В общем нет. Двоичный файл в обычных терминах - это нативный объектный код для конкретного процессора или семейства процессоров. Но есть несколько случаев, когда они могут быть от умеренно до высокой переносимости:
- одна архитектура является надмножеством другой (обычно двоичные файлы x86 предназначены для i386 или i686, а не для самой последней и лучшей x86, например
-march=core2
)
- одна архитектура обеспечивает нативную эмуляцию или трансляцию другой (вы, возможно, слышали о Crusoe ) или предоставляет совместимые сопроцессоры (например, PS2 )
- ОС и среда поддержки поддерживают multiarch (например, возможность запуска 32-битных двоичных файлов x86 на x86_64) или делают VM / JIT бесшовным (Android, использующий Dalvik или ART )
- есть поддержка "толстых" двоичных файлов, которые по существу содержат дублирующийся код для каждой поддерживаемой архитектуры
Если вам как-то удастся решить эту проблему, другая проблема переносимых бинарных версий бесчисленных версий библиотек (glibc, которую я смотрю на вас) будет представлена сама собой. (Большинство встроенных систем избавляет вас от этой конкретной проблемы, по крайней мере.)
Если вы еще этого не сделали, сейчас самое время бежать gcc -dumpspecs
и gcc --target-help
посмотреть, с чем вы столкнулись .
Жирные двоичные файлы имеют различные недостатки , но все же имеют потенциальное использование ( EFI ).
Однако в других ответах отсутствуют еще два соображения: ELF и интерпретатор ELF, а также поддержка ядром Linux произвольных двоичных форматов . Я не буду вдаваться в подробности о двоичных файлах или байт-коде для нереальных процессоров здесь, хотя их можно рассматривать как «родные» и выполнять двоичные файлы Java или скомпилированные байт-коды Python , такие двоичные файлы не зависят от аппаратной архитектуры (но вместо этого зависят на соответствующей версии виртуальной машины, которая в конечном итоге запускает собственный двоичный файл).
Любая современная система Linux будет использовать двоичные файлы ELF (технические подробности в этом PDF- файле ), в случае динамических двоичных файлов ELF ядро отвечает за загрузку изображения в память, но это работа «интерпретатора», установленного в ELF. Заголовки, чтобы сделать тяжелую работу. Обычно это включает в себя обеспечение доступности всех зависимых динамических библиотек (с помощью раздела «Динамический», в котором перечислены библиотеки и некоторые другие структуры, в которых перечислены необходимые символы), - но это почти уровень общего назначения.
$ file /bin/ls
/bin/ls: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses \
shared libs), stripped
$ readelf -p .interp /bin/ls
String dump of section '.interp':
[ 0] /lib/ld-linux.so.2
( /lib/ld-linux.so.2
также является двоичным файлом ELF, не имеет интерпретатора и является собственным двоичным кодом.)
Проблема с ELF заключается в том, что заголовок в двоичном ( readelf -h /bin/ls
) помечает его для конкретной архитектуры, класса (32- или 64-разрядного), порядкового номера и ABI («универсальные» двоичные файлы Apple используют альтернативный двоичный формат Mach-O). вместо этого, который решает эту проблему, он возник на NextSTEP). Это означает, что исполняемый файл ELF должен соответствовать системе, в которой он будет запущен. Один escape-штриховкой является интерпретатор, это может быть любой исполняемый файл (включая тот, который извлекает или отображает специфичные для архитектуры подразделы исходного двоичного файла и вызывает их), но вы все еще ограничены типом (ами) ELF, которые ваша система разрешит запускать , (У FreeBSD есть интересный способ обработки файлов ELF в Linux , он brandelf
изменяет поле ELF ABI.)
В Linux есть поддержка (использование binfmt_misc
) Mach-O , там есть пример, который показывает, как создать и запустить толстый (32- и 64-битный) двоичный файл. Разветвление ресурсов / ADS , как это было изначально сделано на Mac, могло бы стать обходным путем, но ни одна нативная файловая система Linux не поддерживает это.
Более или менее то же самое относится к модулям ядра, .ko
файлы также являются ELF (хотя они не имеют установленного интерпретатора). В этом случае есть дополнительный слой, который использует ядро version ( uname -r
) в пути поиска, что теоретически можно сделать вместо этого в ELF с версионированием, но при некоторой сложности и небольшом выигрыше, я подозреваю.
Как отмечалось в другом месте, Linux изначально не поддерживает толстые двоичные файлы, но существует активный толстый двоичный проект: FatELF . Это было в течение многих лет , оно никогда не было интегрировано в стандартное ядро частично из-за (в настоящее время истекшего) проблем с патентами. В настоящее время требуется поддержка как ядра, так и набора инструментов. Это не использует binfmt_misc
подход, это обходит проблемы заголовка ELF и учитывает также толстые модули ядра.
- Если у меня есть приложение, скомпилированное для запуска на «цели x86, версия ОС Linux xyz», могу ли я просто запустить тот же скомпилированный двоичный файл на другой системе «цель ARM, версия ОС Linux xyz»?
Не с ELF, это не позволит вам сделать это.
- Если вышеприведенное неверно, единственный способ - получить исходный код приложения для перестройки / перекомпиляции с использованием соответствующей цепочки инструментов, например, arm-linux-gnueabi?
Простой ответ - да. (Сложные ответы включают эмуляцию, промежуточные представления, трансляторы и JIT; за исключением случая «понижения версии» двоичного файла i686 для использования только кодов операций i386, они, вероятно, здесь не интересны, а исправления ABI потенциально сложны, как перевод собственного кода. )
- Точно так же, если у меня есть загружаемый модуль ядра (драйвер устройства), который работает на «цели x86, версия ОС Linux, xyz», могу ли я просто загрузить / использовать тот же скомпилированный файл .ko на другой системе «цель ARM, версия ОС Linux xyz»? ?
Нет, ELF не позволит тебе сделать это.
- Если вышеприведенное неверно, единственный способ - получить исходный код драйвера для перекомпиляции / перекомпиляции с использованием соответствующей цепочки инструментов, например, arm-linux-gnueabi?
Простой ответ - да. Я считаю, что FatELF позволяет вам создавать .ko
мультиархитектуру, но в какой-то момент необходимо создать двоичную версию для каждой поддерживаемой архитектуры. Вещи, для которых требуются модули ядра, часто поставляются с исходным кодом и собираются по мере необходимости, например, VirtualBox делает это.
Это уже длинный бессвязный ответ, есть только еще один обходной путь. В ядро уже встроена виртуальная машина, хотя и выделенная: виртуальная машина BPF, которая используется для сопоставления пакетов. Человекочитаемый фильтр "host foo, а не port 22") компилируется в байт-код, и фильтр пакетов ядра выполняет его . Новый eBPF предназначен не только для пакетов, теоретически код VM может переноситься через любой современный linux, и llvm поддерживает его, но по соображениям безопасности он, вероятно, не подойдет ни для чего, кроме административных правил.
Теперь, в зависимости от того, насколько вы щедры на определение двоичного исполняемого файла, вы можете (ab) использовать его binfmt_misc
для реализации полной поддержки двоичного кода с помощью сценария оболочки и ZIP-файлов в качестве формата контейнера:
#!/bin/bash
name=$1
prog=${1/*\//} # basename
prog=${prog/.woz/} # remove extension
root=/mnt/tmpfs
root=$(TMPDIR= mktemp -d -p ${root} woz.XXXXXX)
shift # drop argv[0], keep other args
arch=$(uname -m) # i686
uname_s=$(uname -s) # Linux
glibc=$(getconf GNU_LIBC_VERSION) # glibc 2.17
glibc=${glibc// /-} # s/ /-/g
# test that "foo.woz" can unzip, and test "foo" is executable
unzip -tqq "$1" && {
unzip -q -o -j -d ${root} "$1" "${arch}/${uname_s}/${glibc}/*"
test -x ${root}/$prog && (
export LD_LIBRARY_PATH="${root}:${LD_LIBRARY_PATH}"
#readlink -f "${root}/${prog}" # for the curious
exec -a "${name}" "${root}/${prog}" "$@"
)
rc=$?
#rm -rf -- "${root}/${prog}" # for the brave
exit $rc
}
Назовите это «wozbin», и настройте его следующим образом:
mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc
printf ":%s:%s:%s:%s:%s:%s:%s" \
"woz" "E" "" "woz" "" "/path/to/wozbin" "" > /proc/sys/fs/binfmt_misc/register
Это регистрирует .woz
файлы в ядре, wozbin
вместо этого вызывается сценарий с его первым аргументом, установленным на путь вызываемого .woz
файла.
Чтобы получить переносимый (толстый) .woz
файл, просто создайте test.woz
ZIP-файл с иерархией каталогов так:
i686/
\- Linux/
\- glibc-2.12/
armv6l/
\- Linux/
\- glibc-2.17/
В каждом каталоге arch / OS / libc (произвольный выбор) размещайте специфичные для архитектуры test
двоичные .so
файлы и такие компоненты, как файлы. Когда вы вызываете его, требуемый подкаталог извлекается в файловую систему tmpfs в памяти ( /mnt/tmpfs
здесь) и вызывается.