QEMU:模拟 ARM 大端字节序运行环境

news2024/9/21 10:23:41

文章目录

  • 1. 前言
  • 2. ARM 大小端模拟测试
    • 2.1 裸机模拟测试
      • 2.1.1 大端模拟测试
      • 2.1.2 小端模拟测试
    • 2.2 用户空间模拟测试
      • 2.2.1 大端模拟测试
      • 2.2.2 小端模拟测试
    • 2.3 结论
  • 3. 参考链接

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. ARM 大小端模拟测试

本文通过 VMware + Ubuntu + QEMU 进行测试验证,有需要的读者可以先行构建测试环境。

2.1 裸机模拟测试

2.1.1 大端模拟测试

测试程序 endian_test_system_assign.S 汇编代码:

	.text
	
	.global _start
	
_start:
	@ setup SP pointer
	mov sp, #0x60000000
	add sp, sp, #12
	
	@ u16 = 0x1234
	movw r0, #0x1234
	strh r0, [sp, #-6]
	
	@ u8 = u16
	ldrh r1, [sp, #-6]
	strb r1, [sp, #-7]
	
	@ read u8
	mov r3, #0
	ldrb r3, [sp, #-7]
	
	@ read u16
	mov r4, #0
	ldrh r4, [sp, #-6]

	@ read u32
	mov r5, #0
	ldr r5, [sp, #-8]

1:
	b 1b

这段汇编的代码的核心逻辑,是将一个 u16 类型强制赋值给一个 u8,然后读取 u8 的值,看 u8 的值是 u16高 8-bit 还是低 8-bit,即:

// ? 将代码编译为大端程序后,在 ARM 大端模式机器上运行,u8_var 的值是 0x34,还是 0x12 ?
u16 u16_var = 0x1234;
u8 u8_var = u16_var;

先下载支持大端编译的 ARM 交叉编译器

https://releases.linaro.org/components/toolchain/binaries/latest-7/armeb-eabi/

然后编译:

$ armeb-eabi-gcc -nostdlib -g -march=armv7-a -mbig-endian -o endian_test_system_assign.elf endian_test_system_assign.S
$ file endian_test_system_assign.elf
endian_test_system_assign.elf: ELF 32-bit MSB executable, ARM, EABI5 BE8 version 1 (SYSV), statically linked, BuildID[sha1]=7e5a4b0f93d2b1d66514c87d831881601bdd7efc, with debug_info, not stripped

file 命令的输出,其中的 MSB 标识编译出来的为大端程序

QEMU 会判定程序的大小端,然后将 CPU 设置为程序要求的大小端模式。用 QEMU 模拟大端程序的运行:

$ qemu-system-arm -M vexpress-a9 -m 256M -kernel endian_test_system_assign.elf -nographic -d in_asm,cpu,int,exec
pulseaudio: set_sink_input_volume() failed
pulseaudio: Reason: Invalid argument
pulseaudio: set_sink_input_mute() failed
pulseaudio: Reason: Invalid argument
----------------
IN: 
0x00008024:  e3a0d206      mov sp, #1610612736 ; 0x60000000
0x00008028:  e28dd00c      add sp, sp, #12 ; 0xc
0x0000802c:  e3010234      movw r0, #4660 ; 0x1234
0x00008030:  e14d00b6      strh r0, [sp, #-6]
0x00008034:  e15d10b6      ldrh r1, [sp, #-6]
0x00008038:  e54d1007      strb r1, [sp, #-7]
0x0000803c:  e3a03000      mov r3, #0 ; 0x0
0x00008040:  e55d3007      ldrb r3, [sp, #-7]
0x00008044:  e3a04000      mov r4, #0 ; 0x0
0x00008048:  e15d40b6      ldrh r4, [sp, #-6]
0x0000804c:  e3a05000      mov r5, #0 ; 0x0
0x00008050:  e51d5008      ldr r5, [sp, #-8]
0x00008054:  eafffffe      b 0x8054

Trace 0x7fd88b3160c0 [0: 00008024] 
R00=00000000 R01=00000000 R02=00000000 R03=00000000
R04=00000000 R05=00000000 R06=00000000 R07=00000000
R08=00000000 R09=00000000 R10=00000000 R11=00000000
R12=00000000 R13=00000000 R14=00000000 R15=00008024
PSR=400003d3 -Z-- A S svc32
----------------
IN: 
0x00008054:  eafffffe      b 0x8054

Linking TBs 0x7fd88b3160c0 [00008024] index 0 -> 0x7fd88b316400 [00008054]
Trace 0x7fd88b316400 [0: 00008054] 
R00=00001234 R01=00001234 R02=00000000 R03=00000034
R04=00001234 R05=00341234 R06=00000000 R07=00000000
R08=00000000 R09=00000000 R10=00000000 R11=00000000
R12=00000000 R13=6000000c R14=00000000 R15=00008054
PSR=400003d3 -Z-- A S svc32
Linking TBs 0x7fd88b316400 [00008054] index 0 -> 0x7fd88b316400 [00008054]
Trace 0x7fd88b316400 [0: 00008054] 
R00=00001234 R01=00001234 R02=00000000 R03=00000034
R04=00001234 R05=00341234 R06=00000000 R07=00000000
R08=00000000 R09=00000000 R10=00000000 R11=00000000
R12=00000000 R13=6000000c R14=00000000 R15=00008054
PSR=400003d3 -Z-- A S svc32

通过 QEMU 模拟器的 -d in_asm,cpu,int,exec 选项,输出指令执行后的寄存器值。我们这里主要观察 PSR,R0,R1,R3,R4,R5 这几个寄存器的输出值:

. PSR=400003d3,bit 91,表示 CPU 处于大端模式;
. R00=00001234 R01=00001234:表示成功对堆栈一个 u16 空间写入、读取;
. R03=00000034:表示对堆栈一个 u8 空间成功写入。

同时:

R03=00000034 R04=00001234 R05=00341234

反映出在大端机器上,程序代码的 u16u8 变量读写后内存空间布局如下:

在这里插入图片描述
从以上的测试可以了解到,在 ARM 大端模式的机器上,代码片段:

// ? 将代码编译为大端程序后,在 ARM 大端模式机器上运行,u8_var 的值是 0x34,还是 0x12 ?
u16 u16_var = 0x1234;
u8 u8_var = u16_var;

最后 u8_var 的值为 0x34(即上面测试验证中寄存器 R3 的值),这表示:不同类型间的直接赋值操作,其结果是由语言语义定义的,和机器的大小端无关,即不管是在大端机器上运行,还是在小端机器上运行,总是会得到相同的、由语言语义定义的结果

前面讨论的情形是类型间的直接赋值,那如果使用指针方式,结果将会怎样?假设有如下代码片段:

// ? 将代码编译为大端程序后,在 ARM 大端模式机器上运行,u8_var 的值是 0x34,还是 0x12 ?
unsigned short u16_var = 0x1234;
unsigned char u8_var = *((unsigned char *)&u16_var);

u8_var 的值最后会是多少?我们将上面的代码片段转换为如下 ARM 汇编代码 endian_test_system_pointer.S ,并进行裸机测试,看看结果如何。

	.text

	.global _start

_start:
	@ setup SP pointer
	mov	sp, #0x60000000
	add	sp, sp, #12

	@ unsigned short u16_var = 0x1234;
	ldr	r1, .Lword_var
	strh	r1, [sp, #-8]
	@ unsigned char u8_var = *((unsigned char *)&u16_var);
	sub	r2, sp, #8
	ldrb	r3, [r2]
	strb	r3, [sp, #-5]
	nop

	@ read u8_var
	mov	r4, #0
	ldrb	r4, [sp, #-5]
	@ read u32
	mov	r5, #0
	ldrh	r5, [sp, #-8]

1:
	b 1b

.Lword_var:
	.word	0x1234

安装 ARM 交叉编译小端编译器,并使用小端编译器进行编译

$ sudo apt-get install gcc-arm-linux-gnueabihf
$ arm-linux-gnueabihf-gcc -nostdlib -g -march=armv7-a -mbig-endian -o endian_test_system_pointer.elf endian_test_system_pointer.S

运行测试:

$ qemu-system-arm -M vexpress-a9 -m 256M -kernel endian_test_system_pointer.elf -nographic -d in_asm,cpu,int,exec
pulseaudio: set_sink_input_volume() failed
pulseaudio: Reason: Invalid argument
pulseaudio: set_sink_input_mute() failed
pulseaudio: Reason: Invalid argument
----------------
IN: 
0x00008024:  e3a0d206      mov	sp, #1610612736	; 0x60000000
0x00008028:  e28dd00c      add	sp, sp, #12	; 0xc
0x0000802c:  e59f1024      ldr	r1, [pc, #36]	; 0x8058
0x00008030:  e14d10b8      strh	r1, [sp, #-8]
0x00008034:  e24d2008      sub	r2, sp, #8	; 0x8
0x00008038:  e5d23000      ldrb	r3, [r2]
0x0000803c:  e54d3005      strb	r3, [sp, #-5]
0x00008040:  e320f000      nop	{0}
0x00008044:  e3a04000      mov	r4, #0	; 0x0
0x00008048:  e55d4005      ldrb	r4, [sp, #-5]
0x0000804c:  e3a05000      mov	r5, #0	; 0x0
0x00008050:  e15d50b8      ldrh	r5, [sp, #-8]
0x00008054:  eafffffe      b	0x8054

Trace 0x7fbf5adc80c0 [0: 00008024] 
R00=00000000 R01=00000000 R02=00000000 R03=00000000
R04=00000000 R05=00000000 R06=00000000 R07=00000000
R08=00000000 R09=00000000 R10=00000000 R11=00000000
R12=00000000 R13=00000000 R14=00000000 R15=00008024
PSR=400003d3 -Z-- A S svc32
----------------
IN: 
0x00008054:  eafffffe      b	0x8054

Linking TBs 0x7fbf5adc80c0 [00008024] index 0 -> 0x7fbf5adc8400 [00008054]
Trace 0x7fbf5adc8400 [0: 00008054] 
R00=00000000 R01=00001234 R02=60000004 R03=00000012
R04=00000012 R05=00001234 R06=00000000 R07=00000000
R08=00000000 R09=00000000 R10=00000000 R11=00000000
R12=00000000 R13=6000000c R14=00000000 R15=00008054
PSR=400003d3 -Z-- A S svc32
Linking TBs 0x7fbf5adc8400 [00008054] index 0 -> 0x7fbf5adc8400 [00008054]
Trace 0x7fbf5adc8400 [0: 00008054] 
R00=00000000 R01=00001234 R02=60000004 R03=00000012
R04=00000012 R05=00001234 R06=00000000 R07=00000000
R08=00000000 R09=00000000 R10=00000000 R11=00000000
R12=00000000 R13=6000000c R14=00000000 R15=00008054
PSR=400003d3 -Z-- A S svc32

汇编代码最后将 u8_var 的值加载到了寄存器 R3

R03=00000012

可以看到 R3 寄存器的值为 0x12,即 u8_var 的值为 0x12,这个测试结果不同于前面直接赋值的情形。对于使用指针进行赋值的情形,是将长类型 u16 变量的低地址字节存储的值,赋给了 u8 变量,由于大端字节序低地址存储的是高位数据,所以结果为 0x12。因此,在使用指针赋值时,要想在大小端机器上得到相同的结果,需要做不同的处理,处理方式类似如下伪代码

unsigned short u16_var = 0x1234;
unsigned char u8_var, *u8_var_ptr;

u8_var_ptr = (unsigned char *)&u16_var;
if (是大端机器)
	u8_var = *(u8_var_ptr + 1);
else // 小端机器
	u8_var = *u8_var_ptr;

2.1.2 小端模拟测试

小端裸机测试的结果:不管是直接赋值,还是指针访问,u8 变量得到的结果都是 0x12。本文不对小端裸机测试做展开,感兴趣的读者可自行研究。

2.2 用户空间模拟测试

2.2.1 大端模拟测试

本小节进行用户空间程序的大端模拟测试,编写代码文件 endian_test_user.c

#include <stdio.h>

int main(void)
{
	unsigned short u16_var = 0x1234;
	unsigned char u8_var, *u8_var_p;

	u8_var = u16_var;
	printf("u8_var = 0x%02x\n", u8_var);

	u8_var_p = (unsigned char *)&u16_var;
	printf("*u8_var_p = 0x%02x\n", *u8_var_p);

	return 0;
}

使用 2.1.1 小节下载的 ARM 大端交叉编译器进行编译:

$ armeb-eabi-gcc -static -mbig-endian -o endian_test_user endian_test_user.c
$ file endian_test_user
endian_test_user: ELF 32-bit MSB executable, ARM, EABI5 version 1 (SYSV), statically linked, BuildID[sha1]=d30ccd867632029b8b42592da509d43a7ce35041, with debug_info, not stripped

file 命令的输出,其中的 MSB 标识编译出来的为大端程序

安装 ARM 用户空间程序运行环境模拟器程序 qemu-user-static,并运行测试程序,进行用户空间程序大端模拟测试

$ sudo apt-get install qemu-user-static
$ qemu-armeb-static endian_test_user
u8_var = 0x34
*u8_var_p = 0x12

可见,在大端模式下,使用直接赋值方式u8_var 得到的值为 0x34;使用指针方式u8_var 得到的值是 0x12,这和裸机大端模拟测试的结果一致。

2.2.2 小端模拟测试

本小节进行用户空间程序的小端模拟测试,编写代码文件同 2.2.1 小节的 endian_test_user.c,使用前面安装的 ARM 交叉编译小端编译器 gcc-arm-linux-gnueabihf 进行编译:

$ arm-linux-gnueabihf-gcc -static -mlittle-endian -o endian_test_user endian_test_user.c
$ file endian_test_user
endian_test_user: ELF 32-bit LSB executable, ARM, EABI5 version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=de9c3a8a02cff662db8fea1a660945e82932a446, not stripped

file 命令的输出,其中的 LSB 标识编译出来的为小端程序

运行测试程序:

$ qemu-arm-static endian_test_user
u8_var = 0x34
*u8_var_p = 0x34

从结果看到,在小端模式下,不管是用直接赋值方式,还是指针方式u8_var 得到的值都是 0x34,这和裸机大端模拟测试的结果一致。

2.3 结论

通过前面的大小端模拟测试,我们得出结论:

  • 使用直接赋值方式,将长类型赋值给短类型,总是取长类型低位值给短类型其结果由语言语义定义的,和机器的大小端无关
  • 使用指针赋值方式,将长类型赋值给短类型,总是取长类型低地址字节的值给短类型。这是由机器的存储和 CPU 访存方式决定的

另外,在多字节的赋值中,还应该注意大端字节序的 BE8BE32 不同,更多关于这方面的细节,可参考链接:

https://developer.arm.com/documentation/ddi0290/g/unaligned-and-mixed-endian-data-access-support/mixed-endian-access-support/differences-between-be-32-and-be-8-buses

3. 参考链接

[1] https://developer.arm.com/documentation/ddi0290/g/unaligned-and-mixed-endian-data-access-support/mixed-endian-access-support/differences-between-be-32-and-be-8-buses
[2] https://github.com/pcrost/arm-be-test
[3] https://community.arm.com/support-forums/f/compilers-and-libraries-forum/49616/latest-arm-gcc-compiler-for-big-endian-processors

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2152051.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

leetcode刷题3

文章目录 前言回文数1️⃣ 转成字符串2️⃣ 求出倒序数再比对 正则表达式匹配[hard]1️⃣ 动态规划 盛最多水的容器1️⃣ 遍历分类2️⃣ 双指针贪心 最长公共前缀1️⃣ 遍历&#xff08;zip解包&#xff09; 三数之和1️⃣ 双指针递归 最接近的三数之和1️⃣ 迭代一次双指针 电…

携手阿里云CEN:共创SD-WAN融合广域网

在9月19日举行的阿里云云栖大会上&#xff0c;犀思云作为SD-WAN领域的杰出代表及阿里云的SD-WAN重要合作伙伴&#xff0c;携手阿里云共同推出了创新的企业上云方案——Fusion WAN智连阿里云解决方案。这一创新方案不仅彰显了犀思云在SD-WAN技术领域的深厚积累&#xff0c;更体现…

前端web端项目运行的时候没有ip访问地址

我们发现 没有netWork 的地址 导致 团队内其他同学无法打开我们的地址 进行访问 在page.json 中的运行 指令中 添加 --host 记得加上空格 这样我们就可以看到这个地址了 团队其他同学 就可以访问我们这个地址了

Resnet50网络——口腔癌病变识别

一 数据准备 1.导入数据 import matplotlib.pyplot as plt import tensorflow as tf import warnings as w w.filterwarnings(ignore) # 支持中文 plt.rcParams[font.sans-serif] [SimHei] # 用来正常显示中文标签 plt.rcParams[axes.unicode_minus] False # 用来正常显示负…

2024华为杯研究生数学建模竞赛(研赛)选题建议+初步分析

难度&#xff1a;DE<C<F&#xff0c;开放度&#xff1a;CDE>F。 华为专项的题目&#xff08;A、B题&#xff09;暂不进行选题分析&#xff0c;不太建议大多数同学选择&#xff0c;对自己专业技能有很大自信的可以选择华为专项的题目。后续会直接更新A、B题思路&#…

计算机网络传输层---课后综合题

线路&#xff1a;TCP报文下放到物理层传输。 TCP报文段中&#xff0c;“序号”长度为32bit&#xff0c;为了让序列号不会循环&#xff0c;则最多能传输2^32B的数据&#xff0c;则最多能传输&#xff1a;2^32/1500B个报文 结果&#xff1a; 吞吐率一个周期内传输的数据/周期时间…

2024/9/19、20 数学20题

极大线性无关组&#xff1a;

基于C#+SQL Server2005(WinForm)图书管理系统

图书管理系统 一、 首先把数据库脚本贴出来(数据库名为library) USE [library] GO /****** Object: Table [dbo].[books] Script Date: 06/12/2016 11:27:12 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[books]([bNum] [nvarchar](10…

Arthas sysprop(查看和修改JVM的系统属性)

文章目录 二、命令列表2.1 jvm相关命令2.1.4 sysprop&#xff08;查看和修改JVM的系统属性&#xff09;举例1&#xff1a;sysprop 查看所有系统属性举例2&#xff1a;sysprop java.version 查看单个属性&#xff0c;支持通过tab补全 二、命令列表 2.1 jvm相关命令 2.1.4 sysp…

STL-常用算法 遍历/查找/排序/拷贝和替换/算数生成/集合算法

STL常用算法 常用的遍历算法 for_each #define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; #include<vector> #include<algorithm>void myPrint(int v) {cout << v << " "; }class MyPrint { public:void op…

React学习笔记(三)——React 组件通讯

1. 组件通讯-概念 了解组件通讯的意义 大致步骤&#xff1a; 知道组件的特点知道组件通讯意义 具体内容&#xff1a; 组件的特点 组件是独立且封闭的单元&#xff0c;默认情况下&#xff0c;只能使用组件自己的数据在组件化过程中&#xff0c;通常会将一个完整的功能拆分成多…

cesium.js 入门到精通(5-2)

在cesium 的配置中 有一些参数 可以配置地图的显示 显示出 水的动态显示 山的效果 相当于一些动画显示的效果 var viewer new Cesium.Viewer("cesiumContainer", {infoBox: false,terrainProvider: await Cesium.createWorldTerrainAsync({requestWaterMask: tru…

【计算机网络】计算机网络基础二

&#x1f351;个人主页&#xff1a;Jupiter. &#x1f680; 所属专栏&#xff1a;Linux从入门到进阶 欢迎大家点赞收藏评论&#x1f60a; 目录 以太网的通信原理令牌环网的通信原理网络传输基本流程 数据包封装和分用 网络传输流程图 局域网通信&#xff08;同一个网段内的两台…

PY+MySQL(等先完成mysql的学习)

第一章&#xff1a;准备工作&#xff08;重点关于mysql&#xff09; win安装 下载&#xff1a; 网址&#xff1a;MySQL :: Download MySQL Community Server版本&#xff1a;我的是8.0&#xff0c;但是建议5.7 下载&#xff1a;安装&#xff0c;因为是zip文件所以直接解压就好了…

股价预测,非线性注意力更佳?

作者:老余捞鱼 原创不易,转载请标明出处及原作者。 写在前面的话: 本文探讨了在 transformer 模型中使用非线性注意力来预测股票价格的概念。我们讨论了黎曼空间和希尔伯特空间等非线性空间的数学基础,解释了为什么非线性建模可能是有利的,并提供了在代码中实现这种…

MySQL 主从复制部署与优化

文章目录 前言 在现代数据库管理中&#xff0c;MySQL 主从复制是一种关键技术&#xff0c;用于提高数据的可用性和性能。随着 Docker 容器技术的普及&#xff0c;利用 Docker 搭建 MySQL 主从复制环境已成为一种趋势&#xff0c;它提供了一种简便、高效且可扩展的解决方案。本…

828华为云征文|Flexus X实例Docker+Jenkins+gitee实现CI/CD自动化部署-解放你的双手~

目录 前言 实验步骤 环境准备 安装Portainer 拉取镜像 更换镜像源 启动容器 安装jenkins 拉取镜像 获取管理员密码 新建流水线项目 Portainer配置 gitee配置WebHooks 构建 修改代码&#xff0c;自动部署 前言 &#x1f680; 828 B2B企业节特惠来袭&#xff0c;…

Hadoop 常用生态组件

Hadoop核心组件 安装 Hadoop 时&#xff0c;通常会自动包含以下几个关键核心组件&#xff0c;特别是如果使用了完整的 Hadoop 发行版&#xff08;如 Apache Hadoop、Cloudera 或 Hortonworks 等&#xff09;。这些组件构成了 Hadoop 的核心&#xff1a; 1. HDFS&#xff08;H…

数据篇| 关于Selenium反爬杂谈

友情提示:本章节只做相关技术讨论, 爬虫触犯法律责任与作者无关。 LLM虽然如火如荼进行着, 但是没有数据支撑, 都是纸上谈兵, 人工智能的三辆马车:算法-数据-算力,缺一不可。之前写过关于LLM微调文章《微调入门篇:大模型微调的理论学习》、《微调实操一: 增量预训练(Pretrai…

选择五金车床精密加工厂的五大要点

在五金制造行业&#xff0c;五金车床精密加工是生产高品质零部件的关键环节。随着市场需求的日益多样化和对产品质量要求的不断提高&#xff0c;选择一家合适的五金车床精密加工厂变得至关重要。然而&#xff0c;面对众多的加工厂&#xff0c;如何做出正确的选择却是一个难题。…