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

文章目录

  • 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 pointermov sp, #0x60000000add sp, sp, #12@ u16 = 0x1234movw r0, #0x1234strh r0, [sp, #-6]@ u8 = u16ldrh r1, [sp, #-6]strb r1, [sp, #-7]@ read u8mov r3, #0ldrb r3, [sp, #-7]@ read u16mov r4, #0ldrh r4, [sp, #-6]@ read u32mov r5, #0ldr 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 0x8054Trace 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 0x8054Linking 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 pointermov	sp, #0x60000000add	sp, sp, #12@ unsigned short u16_var = 0x1234;ldr	r1, .Lword_varstrh	r1, [sp, #-8]@ unsigned char u8_var = *((unsigned char *)&u16_var);sub	r2, sp, #8ldrb	r3, [r2]strb	r3, [sp, #-5]nop@ read u8_varmov	r4, #0ldrb	r4, [sp, #-5]@ read u32mov	r5, #0ldrh	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	0x8054Trace 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	0x8054Linking 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.xdnf.cn/news/146260.html

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

相关文章

PLC通信协议的转化

在自动化程序设计中&#xff0c;常常需要对通信协议进行相互转化。例如&#xff0c;某个控制器需要通过PLC控制设备的某个部件的运动&#xff0c;但PLC只支持ModbusTCP协议&#xff0c;而控制器只支持CanOpen通讯协议。这时&#xff0c;就需要一个网关进行通信协议的转化。网关…

双击就可以打开vue项目,而不用npm run dev

右键点击桌面或其他位置&#xff0c;选择“新建” -> “快捷方式”&#xff0c;在“对象的位置”处直接输入“npm run dev”&#xff0c;然后下一步 自定义一个快捷方式名称 完成后&#xff0c;桌面会创建一个快捷方式&#xff0c;右键快捷方式选择属性&#xff0c;可以看…

MiniCPM3-4B | 笔记本电脑运行端侧大模型OpenBMB/MiniCPM3-4B-GPTQ-Int4量化版 | PyCharm环境

MiniCPM3-4B&#xff0c;轻松在笔记本电脑上运行大模型&#xff1f; 背景一、选择模型二、模型下载三、模型运行四、总结 背景 2024年9月5日&#xff0c;面壁智能发布了MiniCPM3-4B&#xff0c;面壁的测试结果声称MiniCPM3-4B表现超越 Phi-3.5-mini-instruct 和 GPT-3.5-Turbo-…

proxy认识一下

免责声明:本文仅做分享。 遵守规则&#xff0c;自行跳过。 Proxy 代理技术介绍 1. 代理简介 代理&#xff08;Proxy&#xff09; 是指在客户端和目标服务器之间充当中介的设备或应用程序。代理服务器的主要功能是接收客户端的请求&#xff0c;并将这些请求转发给目标服务器&a…

解决Mac下Vscode编译运行C语言程序会自动生成DSYM文件夹的问题

&#x1f389; 前言 好久没写C语言了&#xff0c;今天打开Vscode打算写点程序练练手&#xff0c;结果发现一个让我非常苦恼的事情&#xff0c;那就是每次我运行程序的时候&#xff0c;左侧的资源管理器就会生成一大堆的文件&#xff0c;如图&#xff1a; 强迫症犯了&#xff…

模方单体化建模,建模的时候画线突然无法显示垂直线,如何解决?

垂直线对应线都可以在联动软件中设定。 模方是一款针对实景三维模型的冗余碎片、水面残缺、道路不平、标牌破损、纹理拉伸模糊等共性问题研发的实景三维模型修复编辑软件。模方4.1新增自动单体化建模功能&#xff0c;支持一键自动提取房屋结构&#xff0c;平均1栋复杂建筑物只…

机器翻译之数据处理

目录 1.导包 2.读取本地数据 3.定义函数&#xff1a;数据预处理 4.定义函数&#xff1a;词元化 5.统计每句话的长度的分布情况 6. 获取词汇表 7. 截断或者填充文本序列 8.将机器翻译的文本序列转换成小批量tensor 9.加载数据 10.知识点个人理解 1.导包 #导包 import o…

2016年国赛高教杯数学建模A题系泊系统的设计解题全过程文档及程序

2016年国赛高教杯数学建模 A题 系泊系统的设计 近浅海观测网的传输节点由浮标系统、系泊系统和水声通讯系统组成&#xff08;如图1所示&#xff09;。某型传输节点的浮标系统可简化为底面直径2m、高2m的圆柱体&#xff0c;浮标的质量为1000kg。系泊系统由钢管、钢桶、重物球、…

生信初学者教程(四):软件

文章目录 RRstudioLinux系统其他软件本书是使用R语言编写的教程,用户需要下载R和RStudio软件用于进行分析。 版权归生信学习者所有,禁止商业和盗版使用,侵权必究 R R语言是一种免费的统计计算和图形化编程语言,是一种用于数据分析和统计建模的强大工具。它具有丰富的统计…

solidwork找不到曲面

如果找不到曲面 则右键找到选项卡&#xff0c;选择曲面

Pybullet 安装过程

Pybullet 安装过程 1. 安装C编译工具2. 安装Pybullet 1. 安装C编译工具 pybullet 需要C编译套件&#xff0c;直接装之前检查下&#xff0c;要不会报缺少某版本MVSC的error&#xff0c;最好的方式是直接下载visual studio&#xff0c;直接按默认的来装。 2. 安装Pybullet 这里…

Mycat中间件

一、案例目标 &#xff08;1&#xff09;了解Mycat提供的读写分离功能。 &#xff08;2&#xff09;了解MySQL数据库的主从架构。 &#xff08;3&#xff09;构建以Mycat为中间件的读写分离数据库集群。 二、案例分析 1.规划节点 使用Mycat作为数据库中间件服务构建读写分…

聚观早报 | 小米三折叠手机专利曝光;李斌谈合肥投资蔚来

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 9月20日消息 小米三折叠手机专利曝光 李斌谈合肥投资蔚来 索尼PS5 Pro包装亮相 新一代Spectacles AR眼镜发布 通…

媒体专访 | CertiK首席安全官李康教授:变化中的加密资产监管环境带来了新机遇

在2024韩国区块链周期间&#xff0c;CertiK首席安全官李康教授接受了韩国媒体E-Today的独家专访。采访中&#xff0c;李康教授探讨了加密资产监管环境的最新动态及其为行业带来的新机遇。同时&#xff0c;他也表达了对加密资产生态系统所面临的安全挑战的担忧&#xff0c;并强调…

无人机视角应急救援(人)数据集

无人机视角应急救援&#xff08;人&#xff09;&#xff0c;两个数据集 part1&#xff0c;使用DJI Phantom 4A拍摄&#xff0c;分辨率为19201080像素。山区场景&#xff0c;图像中人员姿势分为站立、坐着、躺着、行走、奔跑。共1981张图像6500个不同姿势的标记&#xff0c; par…

低代码平台后端搭建-阶段完结

前言 最近又要开始为跳槽做准备了&#xff0c;发现还是写博客学的效率高点&#xff0c;在总结其他技术栈之前准备先把这个专题小完结一波。在这一篇中我又试着添加了一些实际项目中可能会用到的功能点&#xff0c;用来验证这个平台的扩展性&#xff0c;以及总结一些学过的知识。…

Vue使用代理方式解决跨域问题

1、解决跨域问题 如果 Vue 前端应用请求后端 API 服务器&#xff0c;出现跨域问题&#xff08;CORS&#xff09;&#xff0c;如下图&#xff1a; 解决方法&#xff1a;在 Vue 项目中&#xff0c;打开 vue.config.js 配置文件&#xff0c;在配置文件中使用代理解决跨域问题。 …

腾讯云ssl证书到期,续期免费证书并部署

首先咱用的免费证书&#xff0c;现在最长90天有效期&#xff0c;今天又到期了&#xff0c;得及时续期避免关联资源访问受限。 我这个证书是关联了一个负载均衡和两个容器服务的&#xff08;如果您的应用是ngnix或其他的应用那和本文操作可能有点不一样&#xff09; 所以需要做…

SpringCloud (1) 服务拆解

1 服务拆解和治理 1.1 服务拆解 微服务的核心就是服务拆分,将传统的大项目拆分为多个微型服务(服务或微服务),实现服务之间"高内聚(微服务职责单一),低耦合(微服务功能相对独立)"的目的 (1) 水平(横向)拆分:先搭出拆分框架,比如【公共服务】(比如:common服务,client…

Redis作为单线程模型,为什么效率高、速度快呢?

前言&#xff1a; 效率高、速度快是相较于数据库来说的&#xff08;MySQL、Orcale、SQL server&#xff09; 文章目录 一、单线程模式的工作流程二、为什么快&#xff1f; 一、单线程模式的工作流程 这里我们所说的单线程是指&#xff1a;Redis只使用一个线程&#xff0c;来处…