ARM 交叉编译

发布于:2025-09-07 ⋅ 阅读:(15) ⋅ 点赞:(0)

我一直想精通这个主题,但总有其他更重要的事情要做。现在是时候进行交叉编译了。

这篇文章将描述:
 

  1. 工具
  2. 基本交叉编译技术
  3. 事实上,如何做


如果有人感兴趣的话,请继续阅读。

介绍


物联网是现代IT领域中一个蓬勃发展的领域。这个领域发展迅速,各种酷炫的东西层出不穷(例如内置追踪器的运动鞋,或者可以指示方向的运动鞋(尤其适合盲人))。这些设备大部分类似于“蓝牙灯泡”,但其余的则是复杂的处理器系统,用于收集数据并控制种类繁多的智能设备。这些复杂的系统通常由单板计算机构成,例如Raspberry Pi、Odroid、Orange Pi等。Linux系统在其上运行,并编写应用软件。通常使用脚本语言和Java。但有些应用程序对性能要求很高,自然需要C和C++。例如,您可能需要在内核中添加一些特定功能,或者需要尽快计算FFT。这时就需要交叉编译了。

如果项目规模不大,可以直接在目标平台上编译和调试。如果项目规模足够大,那么在目标平台上进行编译将会非常困难,因为时间成本较高。例如,尝试在 Raspberry Pi 上构建 Boost。我认为构建过程会耗费很长时间,如果出现一些错误,构建过程可能会耗费更多时间。

因此,最好在主机上构建。我的主机是 i5 处理器,4GB 内存,Fedora 24 系统。
 

工具


ARM 的交叉编译需要一个工具链和一个平台模拟器(或者一个真实的目标平台)。

由于我对 ARM 的编译很感兴趣,所以会使用相应的工具链。

工具链分为几种类型或三元组。一个三元组通常由三部分组成:目标处理器、供应商和操作系统,供应商通常会被省略。
 

  • *-none-eabi是用于编译裸机项目的工具链。
  • *eabi是一个用于编译可在任何操作系统上运行的项目的工具链。就我而言,它是 Linux。
  • *eabihf与eabi几乎相同,不同之处在于浮点函数的 ABI 实现。hf代表浮点


以上内容适用于 gcc 及其基于它的工具链。

起初,我尝试使用 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
Version     : 6.1.1
Release     : 2.fc24
Size        : 18 M
Repo        : @System
From repo   : updates
Summary     : Cross-build binary utilities for arm-linux-gnu
URL         : http://gcc.gnu.org
License     : 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 provided as that would massively multiply
            : the number of packages.


经过一番搜索,我偶然发现了 Linaro 的工具链,它非常适合我。

第二个工具是QEMU。我会用它,因为我的 Odroid-C1+ 坏了(SD 卡控制器坏了)。不过我还是设法用了一下,这真是个好消息。
 

基本交叉编译技术


其实它没什么特别的。它只是把工具链当作编译器使用。标准库也包含在工具链中。

它看起来是这样的:
 

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


您可以在 gnu 网站的相应部分中查看工具链具有哪些密钥。
 

如何做


首先,你需要在你感兴趣的平台上运行模拟。我决定模拟 Cortex-A9。

当然,首先你需要获取 QEMU。我从标准的 Fedora 仓库安装了它。

接下来,我们创建一个硬盘映像,用于安装 Debian。
 

qemu-img create -f raw armdisk.img 8G
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 Id Type
armdisk.img1 *        2048   499711   497664  243M 83 Linux
armdisk.img2        499712 15958015 15458304  7.4G 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 `warranty'. 
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(char const *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


 



实际上,这就是交叉编译。


网站公告

今日签到

点亮在社区的每一天
去签到