Переносимы ли двоичные файлы для разных архитектур ЦП?

Модуль ParseWords, поставляемый с Perl, довольно элегантно покрывает это. Пример ниже.

$ perl -MText::ParseWords -nE '@a=quotewords ",",1,$_;say $a[0],",",$a[1]' <test.txt
"col1","col2"
"col4","col5,subtext"
$
16
21.08.2018, 02:16
8 ответов

Нет. Двоичные файлы должны быть (пере) скомпилированы для целевой архитектуры, а Linux «из коробки» не предлагает ничего похожего на толстые двоичные файлы . Причина в том, что код скомпилирован в машинный код для конкретной архитектуры, а машинный код сильно различается между большинством семейств процессоров (например, ARM и x86 сильно различаются).

РЕДАКТИРОВАТЬ: стоит отметить, что некоторые архитектуры предлагают уровни обратной совместимости (и даже реже совместимости с другими архитектурами); на 64-битных процессорах обычно имеется обратная совместимость с 32-битными выпусками (но помните: ваши зависимые библиотеки также должны быть 32-битными, включая вашу стандартную библиотеку C, если только вы не связываете статически). Также стоит упомянуть Itanium , где можно было запускать код x86 (только 32-разрядный), хотя и очень медленно; низкая скорость выполнения кода x86 была, по крайней мере, одной из причин, по которой он не был очень успешным на рынке.

Имейте в виду, что вы по-прежнему не можете использовать двоичные файлы, скомпилированные с новыми инструкциями на старых процессорах, даже в режимах совместимости (например, вы не можете использовать AVX в 32-битном двоичном формате на процессорах Nehalem x86 ; ЦП просто не поддерживает это.

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

Для получения информации о кроссе -компилировать двоичные файлы (так что вам не нужно иметь набор инструментов на целевом устройстве ARM), см. исчерпывающий ответ Grochmal ниже.

43
27.01.2020, 19:47

Не только двоичные файлы не переносятся между x86 и ARM, существуют разные варианты ARM .

На практике вы, вероятно, встретите ARMv6 против ARMv7. Raspberry Pi 1 - это ARMv6, более поздние версии - ARMv7. Таким образом, можно скомпилировать код для более поздних версий, который не работает на Pi 1.

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

(Управление версиями ARM сбивает с толку, но если перед числом стоит буква V, это говорит об архитектуре набора инструкций (ISA). Если нет, то это номер модели, такой как «Cortex M0» или «ARM926EJS». Номера моделей не имеют ничего общего с номерами ISA.)

7
27.01.2020, 19:47

Обратите внимание, что в крайнем случае (например, когда у вас нет исходного кода) вы можете запускать двоичные файлы на другой архитектуре с помощью эмуляторов, таких как qemu , dosbox или exagear . Некоторые эмуляторы предназначены для эмуляции систем, отличных от Linux (например, dosbox предназначен для запуска программ MS-DOS, и существует множество эмуляторов для популярных игровых консолей). Эмуляция имеет значительные накладные расходы на производительность: эмулируемые программы работают в 2-10 раз медленнее, чем их собственные аналоги.

Если вам нужно запустить модули ядра на чужом ЦП, вам придется эмулировать всю ОС, включая ядро, для той же архитектуры. AFAIK невозможно запустить чужой код внутри ядра Linux.

9
27.01.2020, 19:47

gcc использует термины «архитектура» для обозначения «набора инструкций» конкретного ЦП, а «цель» охватывает комбинацию ЦП и архитектуры, а также другие переменные, такие как ABI, libc, endian-ness и другие (возможно, включая "голый металл"). Типичный компилятор имеет ограниченный набор целевых комбинаций (возможно, один ABI, одно семейство процессоров, но, возможно, и 32-, и 64-разрядные). Кросс-компилятор обычно означает компилятор с целью, отличной от системы, в которой он работает, или компилятор с несколькими целями или ABI (см. Также this ).

Переносимы ли двоичные файлы на разные архитектуры ЦП?

В общем, нет. Бинарный код в общепринятых терминах - это собственный объектный код для конкретного ЦП или семейства ЦП. Но есть несколько случаев, когда они могут быть умеренно или очень переносимыми:

  • одна архитектура является надмножеством другой (обычно двоичные файлы x86 нацелены на i386 или i686, а не на последнюю и лучшую x86, например -march = core2 )
  • одна архитектура обеспечивает собственную эмуляцию или трансляцию другой (вы, возможно, слышали о Crusoe ) или предоставляет совместимые сопроцессоры (например, PS2 )
  • Поддержка ОС и среды выполнения , мультиархитектура (например,возможность запускать 32-разрядные двоичные файлы x86 на x86_64) или сделать виртуальную машину / 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 (универсальный "толстые двоичные файлы вместо этого используют альтернативный двоичный формат Mach-O , который решает эту проблему, это возникло на NextSTEP). Это означает, что исполняемый файл ELF должен соответствовать системе, в которой он должен быть запущен. . Один аварийный люк - это интерпретатор, это может быть любой исполняемый файл (в том числе тот, который извлекает или отображает специфичные для архитектуры подразделы исходного двоичного файла и вызывает их), , но вы все еще ограничены Тип (ы) ELF, который ваша система позволит запускать. (Во FreeBSD есть интересный способ обработки файлов Linux ELF , его brandelf изменяет поле ELF ABI.)

Есть (с использованием binfmt_misc ) поддержки Mach-O в Linux , там есть пример, который показывает вам, как создать и запустить толстый (32- и 64-битный) двоичный файл. Форки ресурсов / ADS , как это было изначально сделано на Mac, могут быть обходным решением, но никакая собственная файловая система Linux не поддерживает это.

Более или менее то же самое относится к модулям ядра, .ko файлы также являются ELF (хотя у них нет набора интерпретаторов). В этом случае есть дополнительный уровень, который использует версию ядра ( uname -r ) в пути поиска, что теоретически можно было бы сделать вместо этого в ELF с управлением версиями, но я подозреваю, что с некоторой сложностью и небольшим выигрышем.

Как отмечалось в другом месте, Linux изначально не поддерживает толстые двоичные файлы, но есть активный проект жирного двоичного кода: FatELF .Он существует уже много лет , он никогда не был интегрирован в стандартное ядро ​​отчасти из-за проблем с патентом (срок действия которого истек). В настоящее время требуется поддержка как ядра, так и инструментальной цепочки. {{1} } Он не использует подход binfmt_misc , это устраняет проблемы с заголовком ELF и позволяет использовать расширенные модули ядра.

  1. Если у меня есть приложение, скомпилированное для работы на «цели x86, версия ОС Linux xyz», могу ли я просто запустить тот же скомпилированный двоичный файл в другой системе «цель ARM, версия ОС Linux xyz»?

Нет с ELF это не позволит вам сделать это.

  1. Если вышесказанное не соответствует действительности, единственный способ - заставить исходный код приложения перекомпилировать / перекомпилировать с помощью соответствующей инструментальной цепочки, например, arm-linux-gnueabi?

Простой ответ - да. (Сложные ответы включают эмуляцию, промежуточные представления, трансляторы и JIT; за исключением случая «понижения» двоичного кода i686 для использования только кодов операций i386, они, вероятно, здесь не интересны, а исправления ABI потенциально так же

  1. Точно так же, если у меня есть загружаемый модуль ядра (драйвер устройства), который работает на «цель x86, версия ОС Linux xyz», могу ли я просто загрузить / использовать тот же скомпилированный .ko на другая система 'ARM target, linux OS version xyz'?

Нет, ELF не позволит вам это сделать.

  1. Если вышесказанное неверно, единственный способ - заставить исходный код драйвера перекомпилировать / перекомпилировать с помощью соответствующей инструментальной цепочки, например, arm-linux-gnueabi?

Простой ответ - да. Я считаю, что FatELF позволяет построить .ko , который является многоархитектурным, но в какой-то момент необходимо создать двоичную версию для каждой поддерживаемой архитектуры. Вещи, для которых требуются модули ядра, часто идут с исходным кодом и собираются по мере необходимости, например VirtualBox делает это.

Это уже длинный бессвязный ответ, остался только один обходной путь. Ядро уже имеет встроенную виртуальную машину, хотя и выделенную: виртуальная машина BPF , которая используется для сопоставления пакетов. Удобочитаемый фильтр «host foo, а не порт 22») компилируется в байт-код, и фильтр пакетов ядра выполняет его . Новый eBPF предназначен не только для пакетов, теоретически код виртуальной машины переносится на любой современный 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 , просто создайте тест .woz ZIP-файл с иерархией каталогов таким образом:

i686/ 
    \- Linux/
            \- glibc-2.12/
armv6l/
    \- Linux/
            \- glibc-2.17/

В каждом каталоге arch / OS / libc (произвольный выбор) поместите архитектурно-зависимый тестовый двоичный файл и такие компоненты, как .so файлов. Когда вы вызываете его, необходимый подкаталог извлекается в файловую систему в памяти tmpfs (здесь / mnt / tmpfs ) и вызывается.

2
27.01.2020, 19:47

Этот вопрос можно переформулировать в более знакомой обстановке. По аналогии:

«У меня есть программа Ruby, которую я хочу запустить, но на моей платформе есть только интерпретатор Python. Могу ли я использовать интерпретатор Python для запуска моей программы Ruby, или мне нужно переписать свою программу на Python? "

Архитектура набора команд (" цель ") - это язык -" машинный язык "- и разные процессоры реализуют разные языки. Таким образом, запрос к процессору ARM для запуска двоичного файла Intel очень похож на попытку запустить программу Ruby с использованием интерпретатора Python.

3
27.01.2020, 19:47

Элизабет Майерс права, каждая архитектура требует скомпилированного двоичного кода для рассматриваемой архитектуры. Для создания двоичных файлов для архитектуры, отличной от вашей системы, вам понадобится кросс-компилятор .


В большинстве случаев вам нужно скомпилировать кросс-компилятор. У меня есть опыт работы только с gcc (но я считаю, что llvm и другие компиляторы имеют похожие параметры). Кросс-компилятор gcc достигается добавлением - target в конфигурацию:

./configure --build=i686-arch-linux-gnu --target=arm-none-linux-gnueabi

Вам необходимо скомпилировать gcc , glibc и binutils с этими параметрами (и предоставляют заголовки ядра на целевой машине).

На практике это значительно сложнее, и в разных системах возникают разные ошибки сборки.

Существует несколько руководств о том, как скомпилировать набор инструментов GNU, но я рекомендую Linux From Scratch , который постоянно поддерживается и очень хорошо объясняет, что делают представленные команды.

Другой вариант - это начальная компиляция кросс-компилятора. Благодаря борьбе компиляторов кросс-компиляторов для разных архитектур на разных архитектурах был создан crossstool-ng .Он дает начальную загрузку по цепочке инструментов, необходимую для создания кросс-компилятора.

crossstool-ng поддерживает несколько целевых триплетов на разных архитектурах, в основном это программа начальной загрузки, в которой люди посвящают свое время решению проблем, возникающих во время компиляции цепочки инструментов кросс-компилятора.


Некоторые дистрибутивы предоставляют кросс-компиляторы в виде пакетов:

Другими словами, проверьте, что в вашем дистрибутиве доступно с точки зрения кросс-компиляторов. Если в вашем дистрибутиве нет кросс-компилятора для ваших нужд, вы всегда можете скомпилировать его самостоятельно.

Ссылки:


Примечание о модулях ядра

Если вы компилируете кросс-компилятор вручную, у вас есть все необходимое для компиляции модулей ядра. Это потому, что вам нужны заголовки ядра для компиляции glibc .

Но, если вы используете кросс-компилятор, предоставленный вашим дистрибутивом, вам потребуются заголовки ядра, которое работает на целевой машине.

16
27.01.2020, 19:47

Вам всегда нужно нацеливать на платформу. В простейшем случае целевой ЦП напрямую запускает код, скомпилированный в двоичном формате (это примерно соответствует исполняемым файлам COM MS DOS). Давайте рассмотрим две разные платформы, которые я только что придумал - Armistice и Intellio. В обоих случаях у нас будет простая программа hello world, выводящая на экран 42. Я также предполагаю, что вы используете многоплатформенный язык, не зависящий от платформы, поэтому исходный код одинаков для обоих:

Print(42)

В Armistice у вас есть простой драйвер устройства, который заботится о печати чисел , поэтому все, что вам нужно сделать, это вывести в порт. На нашем переносимом ассемблере это будет соответствовать примерно так:

out 1234h, 42

Однако или в системе Intellio такой вещи нет, поэтому она должна проходить через другие уровни:

mov a, 10h
mov c, 42
int 13h

Ой, у нас уже есть существенная разница между двумя , еще до того, как мы перейдем к машинному коду! Это примерно соответствует разнице между Linux и MS DOS или IBM PC и X-Box (даже если оба могут использовать один и тот же процессор).

Но это то, для чего нужны ОС. Предположим, у нас есть HAL, который гарантирует, что все различные аппаратные конфигурации обрабатываются одинаково на уровне приложения - в основном, мы будем использовать подход Intellio даже в Armistice, и наш код «переносимой сборки» будет таким же. Это используется как современными Unix-подобными системами, так и Windows, часто даже во встроенных сценариях. Хорошо - теперь у нас может быть один и тот же действительно переносимый ассемблерный код как для Armistice, так и для Intellio.Но как насчет двоичных файлов?

Как мы и предполагали, ЦП должен выполнять двоичные файлы напрямую. Давайте посмотрим на первую строку нашего кода, mov a, 10h , в Intellio:

20 10

Ой. Оказывается, mov a, constant настолько популярен, что у него есть собственная инструкция со своим собственным кодом операции. Как с этим справляется «Перемирие»?

36 01 00 10

Хмм. Есть код операции для mov.reg.imm , поэтому нам нужен еще один аргумент для выбора регистра, которому мы назначаем. А константа всегда представляет собой 2-байтовое слово в нотации с прямым порядком байтов - именно так было спроектировано Armistice, на самом деле, все инструкции в Armistice имеют длину 4 байта, без исключений.

А теперь представьте, что вы запускаете двоичный файл из Intellio в режиме перемирия: ЦП начинает декодирование инструкции, находит код операции 20h . По перемирию это соответствует, скажем, инструкциям и.imm.reg . Он пытается прочитать 2-байтовую константу слова (которая читает 10XX , уже проблема), а затем номер регистра (еще один XX ). Мы выполняем неправильную инструкцию с неправильными аргументами. И что еще хуже, следующая инструкция будет полной подделкой, потому что мы на самом деле съели другую инструкцию, думая, что это данные.

У приложения нет шансов на работу, и, скорее всего, оно выйдет из строя или почти сразу же зависнет.

Это не означает, что исполняемый файл всегда должен сообщать, что он работает на Intellio или Armistice. Вам просто нужно определить платформу, которая не зависит от ЦП (например, bash в Unix) или как ЦП, так и ОС (например, Java или .NET, а в настоящее время даже вроде JavaScript).В этом случае приложение может использовать один исполняемый файл для всех различных ЦП и ОС, в то время как в целевой системе есть какое-то приложение или служба (которая напрямую нацелена на правильный ЦП и / или ОС), которая переводит независимый от платформы код во что-то ЦП действительно может выполнять. Это может или не может повлиять на производительность, стоимость или возможности.

Процессоры обычно бывают семействами. Например, все процессоры из семейства x86 имеют общий набор инструкций, которые закодированы точно таким же образом, поэтому каждый процессор x86 может запускать любую программу x86, если он не пытается использовать какие-либо расширения (например, операции с плавающей запятой или векторные операции). На x86 наиболее распространенными сегодня примерами являются, конечно же, Intel и AMD. Atmel - известная компания, разрабатывающая процессоры семейства ARM, довольно популярных для встраиваемых устройств. Например, у Apple также есть собственные процессоры ARM.

Но ARM совершенно несовместима с x86 - у них очень разные требования к дизайну и очень мало общего. Инструкции имеют совершенно разные коды операций, они декодируются по-разному, адреса памяти обрабатываются по-разному ... Возможно, можно создать двоичный файл, который будет работать как на процессоре x86, так и на процессоре ARM, используя некоторые безопасные операции для различать эти два и переходить к двум совершенно разным наборам инструкций, но это по-прежнему означает, что у вас есть отдельные инструкции для обеих версий, с помощью только загрузчика, который выбирает правильный набор во время выполнения.

7
27.01.2020, 19:47

berry boot, решит некоторые из ваших проблем.. но это не решает проблему, как работать на arm hf, нормальный/регулярный дистрибутив Linux для x86 -32/64bit.

Я думаю, это должно быть встроено в isolinux (bootloader linux на usb )какой-нибудь живой конвертер что может распознать обычный дистрибутив и конвертировать в райд/лайв в hf.

Почему? Потому что, если каждый Linux может быть преобразован с помощью загрузки berry для работы на руке -hf, чтобы он мог встроить механизм загрузки bery в isolinux, который мы загружаем, используя, например, каждый или встроенный загрузочный диск Ubuntu.

0
27.01.2020, 19:47

Теги

Похожие вопросы