当前位置: 首页 > news >正文

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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 性能测试工具——JMeter
  • expressjs 和 Router 配置 POST 请求
  • Mac使用Nginx设置代理,并禁用自带Apache
  • mac安装JetBtains全家桶新版本时报错:Cannot start the IDE
  • 关于 ModuleNotFoundError: No module named ‘Crypto‘
  • AngularJS 模块
  • 数字IC设计\FPGA 职位经典笔试面试整理--基础篇1
  • C#基础(14)冒泡排序
  • 【架构设计】多级缓存:应用案例与问题解决策略
  • Unity DOTS系列之IJobChunk来迭代处理数据
  • python教程修订版
  • 当电子设计竞赛照进生活!
  • 4G 网络下资源加载失败?一次运营商封禁 IP 的案例分享
  • 【RabbitMQ】死信队列、延迟队列
  • 什么是电商云手机?可以用来干什么?
  • [PHP内核探索]PHP中的哈希表
  • Apache的基本使用
  • - C#编程大幅提高OUTLOOK的邮件搜索能力!
  • Java 内存分配及垃圾回收机制初探
  • javascript面向对象之创建对象
  • JavaScript实现分页效果
  • JSDuck 与 AngularJS 融合技巧
  • nginx 配置多 域名 + 多 https
  • node-sass 安装卡在 node scripts/install.js 解决办法
  • node入门
  • Python学习笔记 字符串拼接
  • react-native 安卓真机环境搭建
  • REST架构的思考
  • Vue.js 移动端适配之 vw 解决方案
  • 订阅Forge Viewer所有的事件
  • 记一次和乔布斯合作最难忘的经历
  • 漫谈开发设计中的一些“原则”及“设计哲学”
  • 前端相关框架总和
  • 前端性能优化——回流与重绘
  • 数据库写操作弃用“SELECT ... FOR UPDATE”解决方案
  • 翻译 | The Principles of OOD 面向对象设计原则
  • #Linux(Source Insight安装及工程建立)
  • #我与Java虚拟机的故事#连载06:收获颇多的经典之作
  • (07)Hive——窗口函数详解
  • (14)目标检测_SSD训练代码基于pytorch搭建代码
  • (C语言)编写程序将一个4×4的数组进行顺时针旋转90度后输出。
  • (STM32笔记)九、RCC时钟树与时钟 第一部分
  • (二)【Jmeter】专栏实战项目靶场drupal部署
  • (二)原生js案例之数码时钟计时
  • (附表设计)不是我吹!超级全面的权限系统设计方案面世了
  • (附源码)apringboot计算机专业大学生就业指南 毕业设计061355
  • (附源码)springboot课程在线考试系统 毕业设计 655127
  • (附源码)ssm高校志愿者服务系统 毕业设计 011648
  • (附源码)计算机毕业设计SSM教师教学质量评价系统
  • (每日持续更新)jdk api之FileFilter基础、应用、实战
  • (篇九)MySQL常用内置函数
  • (贪心 + 双指针) LeetCode 455. 分发饼干
  • (已解决)什么是vue导航守卫
  • (游戏设计草稿) 《外卖员模拟器》 (3D 科幻 角色扮演 开放世界 AI VR)
  • (轉貼) VS2005 快捷键 (初級) (.NET) (Visual Studio)