Кросскомпиляция під ARM

Досить давно хотів освоїти сабж, але всі були інші, більш пріоритетні справи. І ось настала черга кросскомпиляции.

В цьому пості будуть описані:

  1. Інструменти
  2. Елементарна технологія кросскомпиляции
  3. І, власне, HOW2
Кому це цікаво, прошу під кат.

Вступна
Одне з перспективних напрямків у сучасному IT це IoT. Розвивається цей напрямок досить швидко, весь час виходять всякі круті штуки (типу кросівок з вбудованим трекером або кросівки, які можуть вказувати напрямок, куди йти (спеціально для незрячих людей)). Основна маса цих пристроїв представляють собою щось типу «блютуз лампочки», але частина являє собою складні процесорні системи, які збирають дані і керують цим величезним розмаїттям всяких розумних штучок. Ці складні системи, як правило, являють собою одноплатні комп'ютери, такі як Raspberry Pi, Odroid, Orange Pi і т. п. На них запускається Linux і пишеться прикладної софт. В основному, використовують скриптові мови і Java. Але бувають програми, коли необхідна висока продуктивність, і тут, природно, потрібні C і C++. Наприклад, може знадобитися додати щось специфічне в ядро або, як можна швидше, вирахувати ШПФ. Ось тут-то і потрібна кросскомпиляция.

Якщо проект не дуже великий, то його можна збирати і налагоджувати прямо на цільовій платформі. А якщо проект досить великий, то компіляція на цільовій платформі буде скрутна через тимчасових витрат. Наприклад, спробуйте зібрати Boost на Raspberry Pi. Думаю, очікування збірки буде тривалим, а якщо ще і помилки які спливуть, то це може зайняти дуже багато часу.

Тому краще збирати на хості. У моєму випадку, це i5 з 4ГБ ОЗУ, Fedora 24.

Інструменти
Для кросскомпиляции під ARM потрібні toolchain і емулятор платформи або реальна цільова платформа.

Оскільки мене цікавить компіляція для ARM, то буде використовуватися і відповідний toolchain.

Toolchain'и для ARM діляться на кілька типів. Основні це noneabi, eabi та eabihf.

  • noneabi — це toolchain для компіляції проекту працює в bare metal.
  • eabi — це toolchain для компіляції проекту працює в будь-якій ОС. У моєму випадку, це Linux.
  • eabihf — це майже те ж саме, що і eabi, з різницею в реалізації операцій над числами з плаваючою точко. hf — розшифровується як hard float, тобто апаратна реалізація обчислень з плаваючою точкою.
Спершу я намагався використовувати toolchain'и, які лежать в репах Fedora 24. Але був неприємно здивований цим:

[gazpar@localhost ~]$ dnf info gcc-c++-arm-linux-gnu
Last metadata expiration check: 3 days, 22:18:36 ago on Tue Jan 10 21:18:07 2017.
Installed Packages
Name : gcc-c++-arm-linux-gnu
Arch : x86_64
Epoch : 0
Версія : 6.1.1
Release : 2.fc24
Size : M 18
Repo : @System
From repo : updates
Summary : Cross-build binary utilities for arm-linux-gnu
URL : http://gcc.gnu.org
Ліцензія : GPLv3+ and GPLv3+ with exceptions and GPLv2+ with exceptions and LGPLv2+ and BSD
Description : Cross-build GNU C++ compiler.
: 
: Only the compiler is provided; not libstdc++. Support for cross-building
: user space programs is not currently as provided that would massively multiply
: the number of packages.

Пошукавши, натрапив на toolchain від компанії Linaro. І він мене цілком влаштував.

Другий інструмент — це QEMU. Я буду використовувати його, тому що мій Odroid-C1+ поліг смертю хоробрих (нагнувся контролер SD карти). Але я таки встиг з ним трохи попрацювати, що не може не радувати.

Елементарна технологія кросскомпиляции
Власне, нічого незвичайного в цьому немає. Просто використовується toolchain в ролі компілятора. А стандартні бібліотеки поставляються разом з toolchain'ом.

Виглядає це так:

CC := g++
TOOLCHAIN := arm-linux-gnueabihf-
PT :=
CFL := -Wextra -std=c++11
TPATH := /home/gazpar/toolchain/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabihf/bin/
LPATH := /home/gazpar/toolchain/sysroot-glibc-linaro-2.21-2016.05-arm-linux-gnueabihf/
ARCH := -march=armv7-a -mcpu=cortex-a5 --sysroot=$(LPATH)

all: slc.cpp
$(CC) $(CFL) -o eval slc.cpp

cross: slc.cpp
$(TPATH)$(TOOLCHAIN)$(CC) $(CFL) $(ARCH) slc.cpp -o acalc -static

clear:
rm -f *.o
rm -f eval

Які ключі у toolchain'а можна подивитися на сайті gnu, у відповідному розділі.

HOW2
Для початку потрібно запустити емуляцію з цікавить платформою. Я вирішив съэмулировать Cortex-A9.

Після декількох невдалих спроб натрапив на how2, який виявився цілком осудним, на мій погляд.

Ну спершу, само собою, потрібно мати QEMU. Встановив я його з стандартних репов Fedor'и.

Далі створюємо образ жорсткого диска, на який буде встановлений Debian.

qemu-img create -f raw armdisk.img 8G

этой ссылке скачав vmlinuz і initrd і запустив їх у емуляції.

qemu-system-arm -m 1024M -sd armdisk.img \
-M vexpress-a9 -cpu cortex-a9 \
-kernel vmlinuz-3.2.0-4-vexpress -initrd initrd.gz \
-append "root=/dev/ram" -no-reboot \
-net user,hostfwd=tcp::10022-:22 -net nic

Далі просто встановлюємо Debian на наш образ жорсткого диска (у мене пішло ~1.5 години).

Після установки потрібно вийняти з образу жорсткого диска vmlinuz і initrd. Робив я це з опису звідси.

Спершу дізнаємося зміщення, де розташований розділ з потрібними нам файлами:

[gazpar@localhost work]$ fdisk -l armdisk.img
Disk armdisk.img: 8 GiB, 8589934592 bytes, 16777216 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x000e5fe1

Device Boot Start End Sectors Size Type Id
armdisk.img1 * 2048 499711 497664 243M 83 Linux
armdisk.img2 499712 15958015 15458304 7.4 G 83 Linux
armdisk.img3 15960062 16775167 815106 398M 5 Extended
armdisk.img5 15960064 16775167 815104 398M 82 Linux swap / Solaris

Розрахуємо зміщення:

[gazpar@localhost work]$ bc
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `гарантія'. 
512*2048
1048576

Тепер з цього зміщення примонтируем потрібний нам розділ.

[gazpar@localhost work]$ sudo mount -o loop,offset=1048576 armdisk.img qemu-mnt/

[gazpar@localhost work]$ ls -la qemu-mnt/
total 5174
drwxr-xr-x. 3 root root 1024 Jan 14 09:30 .
drwxrwxr-x. 19 gazpar gazpar 4096 Jan 14 10:35 ..
-rw-r--r--. 1 root root 79252 Jan 1 01:13 config-3.2.0-4-vexpress
lrwxrwxrwx. 1 root root 27 Jan 14 08:47 initrd.img -> initrd.img-3.2.0-4-vexpress
-rw-r--r--. 1 root root 1991475 Jan 14 09:30 initrd.img-3.2.0-4-vexpress
drwxr-xr-x. 2 root root 12288 Jan 14 08:30 lost+found
-rw-r--r--. 1 root root 1130676 Jan 1 01:13 System.map-3.2.0-4-vexpress
lrwxrwxrwx. 1 root root 24 Jan 14 08:47 vmlinuz -> vmlinuz-3.2.0-4-vexpress
-rw-r--r--. 1 root root 2051760 Jan 1 01:13 vmlinuz-3.2.0-4-vexpress

Копіюємо файли vmlinuz і initrd і размонтируем жорсткий диск.

[gazpar@localhost work]$ sudo umount qemu-mnt/

Тепер можна запустити емуляцію.

qemu-system-arm -m 1024M -M vexpress-a9 \
-kernel vmlinuz -initrd initrd.img \
-append "root=/dev/mmcblk0p2" \
-sd armdisk.img \
-net user,hostfwd=tcp::10022-:22 -net nic

І ось заповітне запрошення:



Тепер з хоста по SSH можна подцепиться до симуляції.

[gazpar@localhost work]$ ssh -p10022 arm@localhost
arm@debian:~$ 
arm@debian:~$ uname -a
Linux debian 3.2.0-4-vexpress #1 SMP Debian 3.2.84-1 armv7l GNU/Linux

Тепер можна й зібрати програмку. За Makefile'у ясно, що буде калькулятор. Простенький.

#include < iostream>
#include < string>
#include < vector>


// Function to check input expression
bool checkExpression(std::string exp){
for(uint i=0; i<exp.length(); i++){
char c = exp[i];
if(c < '(' || c > '9' || c == '\"){
if(c != '') return false;
}
}
return true;
}


// Template function to evaluate atomic expression
template < class T>
T eval(int a, int b, const char op){
switch(op){
case '+':{
return a+b;
}
case '-':{
return a-b;
}
case '*':{
return a*b;
}
case '/':{
return a/b;
}
default: throw("atomEval: Undefined math operation");

}
};

// Function to evaluate expression without brackets
template < class T>
std::string evalExpWithoutBrackets(std::string exp){

std::vector<T> operands;
std::vector<char> operations;

const uint explen = exp.length();

// Allocating arguments and operations without ordering
for(uint shift=0, position = 0; shift<explen; shift++){

// This check need for situation when we didn't allocate last argument
if(shift == explen-1){
std::string expWithoutBrackets;
expWithoutBrackets.assign(exp, position, explen - position + 1);
operands.push_back((T) std::stod(expWithoutBrackets));
}

if( exp[shift] == '+' || exp[shift] == '-' || exp[shift] == '*' || exp[shift] == '/'){
std::string expTemp;
expTemp.assign(exp, position, shift-position);
operands.push_back((T) std::stod(expTemp));

operations.push_back(exp[shift]);

std::string tempExp;
position = shift+1;
for(shift++; shift<explen; shift++){
if( exp[shift] == '+' || exp[shift] == '-' || exp[shift] == '*' || exp[shift] == '/' ){
tempExp.assign(exp, position, shift-position);
operations.push_back(exp[shift]);
break;
}
if(shift == explen-1){
tempExp.assign(exp, position, explen - position);
}
}
operands.push_back((T)std::stod(tempExp));
position = shift+1;
}
}

// Calculator

std::vector<uint> evalOrder; // Order of operations
uint highPriority = 0, lowPriority = 0;

// Ordering operations

// First of all we need operations with high priority
for(uint i=0; i < operations.size(); i++){
if(operations[i] == '*' || operations[i] == '/'){
evalOrder.push_back(i);
highPriority++;
}
}

// Now we need to order low priority operations
for(uint i=0; i < operations.size(); i++){
if(operations[i] == '-' || operations[i] == '+'){
evalOrder.push_back(i);
lowPriority++;
}
}

// Evaluating epression by order
for(uint i=0; i < evalOrder.size(); i++){
T rexp = (T)NULL;

try{
rexp = eval<T>(operands[evalOrder[i]], operands[evalOrder[i]+1], operations[evalOrder[i]]);
}
catch(const char *er){
std::cout << er << std::endl;
}

// Erasing operations and operands, because operands[evalOrder[i]] and operands[evalOrder[i]+1]
// became single argument after completing operations[evalOrder[i]] operation
if(evalOrder[i] < operands.size()-1){
operands.erase(operands.begin()+evalOrder[i]+1);
operations.erase(operations.begin()+evalOrder[i]);
}
// Recallculating order
for(uint j = i; j < evalOrder.size(); j++){
if(evalOrder[j] > evalOrder[i]) evalOrder[j]--;
}
// Storing result of eval<T>
operands[evalOrder[i]] = rexp;
}

return std::to_string(operands[0]);
}

template < class T>
std::string evalExpression(std::string exp){
uint open = 0, close = 0;
for(uint i=0; i<exp.length(); i++){
if(exp[i] == '(') open++;
else if(exp[i] == ')') close++;
}
if(open != close)
return (std::string)"error: Expression have uncoupled brackets";

// Divide expression to the blocks if there are any brackets
for(uint closeBracketPosition=0; closeBracketPosition<exp.length(); closeBracketPosition++){
if(exp[closeBracketPosition] == ')'){
uint openBracketPosition = closeBracketPosition;
while(openBracketPosition--){
if(exp[openBracketPosition] == '('){
std::string expWithoutBrackets; 
expWithoutBrackets.assign(exp, openBracketPosition + 1, closeBracketPosition - openBracketPosition - 1);

std::string atomExpResult = evalExpression<T>(expWithoutBrackets);

std::string leftPartExp, rightPartExp;

leftPartExp.assign(exp, 0, openBracketPosition);
rightPartExp.assign(exp, closeBracketPosition + 1, exp.length() - closeBracketPosition);

return evalExpression<T>( leftPartExp + atomExpResult + rightPartExp);
}
}
}
}
return evalExpWithoutBrackets<T>(exp);;
}

int main(int argc, char **argv){
std::string evalexp(argv[1]);

// Check input expression for unhandling symbols
if(!checkExpression(evalexp)) return -1;

// Clear expression from spaces
for(uint i=0 ; i < evalexp.length(); i++){
if(evalexp[i] == ' '){
evalexp.erase(evalexp.begin() + i);
if(i > 0) i--;
}
}
std::cout << "Evaluating expression is: \"" << evalexp << "\"" << std::endl;

std::cout << "Result is: " << evalExpression<int>(evalexp) << std::endl;
return 0;
}

Збираємо на хості виконуваний файл.

[gazpar@localhost slcalc]$ make cross
/home/gazpar/toolchain/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-g++ -Wextra -std=c++11 -march=armv7-a -mcpu=cortex-a5 --sysroot=/home/gazpar/toolchain/sysroot-glibc-linaro-2.21-2016.05-arm-linux-gnueabihf/ slc.cpp -o acalc -static
[gazpar@localhost slcalc]$ ls -la
drwxrwxr-x. 2 gazpar gazpar 4096 Jan 15 16:35 .
drwxrwxr-x. 7 gazpar gazpar 4096 Aug 15 07:56 ..
-rwxrwxr-x. 1 gazpar gazpar 9704352 Jan 15 16:35 acalc
-rwxrwxrwx. 1 gazpar gazpar 59 Jan 10 22:04 .directory
-rwxrwxrwx. 1 gazpar gazpar 469 Jan 14 11:14 Makefile
-rwxrwxrwx. 1 gazpar gazpar 4951 Jan 13 21:15 slc.cpp

Зазначу, що простіше зібрати з ключем -static, якщо немає особливого бажання вдаватися до плотських утіх з бібліотеками на цільовій платформі.

Копіюємо виконуваний файл на таргет і перевіряємо.

[gazpar@localhost slcalc]$ scp -P 10022 acalc arm@localhost:/home/arm/acalc

arm@debian:~$ ./acalc 12*13-11*(21-3)
Evaluating expression is: "12*13-11*(21-3)"
Result is: -42



Власне, ось така вона, ця кросскомпиляция.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.