我正在做一个二进制分析项目,我正在构建一个将汇编转换为llvm的提升器。我构建了一个内存模型,但是对str和ldr arm汇编指令如何在内存上工作有点困惑。所以我的问题是。例如,给定内存地址0000b8f0,我想在其中存储64位十进制值20000000。str指令是将整个20000000存储在地址0000b8f0中,还是将其划分为多个字节,并将第一个字节存储在0000b8f0中,将第二个字节存储在0000b8f1中,将第三个字节存储在0000b8f2中,依此类推......从地址加载时也是如此(0000b8f0)LDR指令是否仅取存储在0000b8f0处的字节或从0000b8f0到0000b8f4的字节的完整集合。
很抱歉,我的问题是非常基本的,但我需要确保我正确地实现了str和ldr对我的内存模型的影响。
2条答案
按热度按时间6yjfywim1#
在逻辑1上,内存是一个8位字节数组。
字加载/存储一次访问多个字节,就像C中的SIMD intrinsic一样,或者像
((char*)my_int)[2]
的相反操作一样,加载int
的第3个字节。C的内存模型是围绕支持更广泛访问的字节可寻址机器(如PDP-11或ARM)设计的,因此,如果您了解
char*
在C中如何访问其他对象的对象表示,例如为什么memcpy
2工作,那么您就会习惯于此。(我没有使用将
int*
指向char数组的C示例,因为C中的严格别名规则使这种行为未定义。在ISO C中,只允许char*
为其他类型提供别名。Asm具有良好定义的行为,可以访问任何宽度的内存字节,与早期存储有任何部分或完全重叠,就像用-fno-strict-aliasing
编译GNU C时禁用基于类型的别名分析/优化一样。)str
是32位字存储器;如果从0000b8f1
、...2
或...3
加载,则会得到第2、第3或第4个字节,因此str
等效于4条单独的strb
指令(通过移位来提取正确的字节),只是明显缺乏原子性和性能。str
总是存储32位寄存器中的4个字节。如果寄存器保存的值为2,则意味着高位字节全为零。ARM可以是big-endian,也可以是little-endian。我认为现代ARM系统通常是little-endian,比如x86,所以值的最低有效字节存储在最低地址。
在
0000b8f0
的字节不能自己保存20000000;一个字节并没有那么大,如果你问的是这个的话注意,0000 b8 f4是下一个字的低字节;它是一个4字节对齐地址。
此外,将
int64_t
与20000000
存储将需要 * 两个 * 32位存储。例如,两个str
指令,或ARMv 8stp
以进行一对寄存器的64位存储,或stm
多存储指令与两个寄存器。或八个strb
字节存储指令。脚注1:这是来自软件PoV,而不是存储器控制器、数据总线或DRAM芯片的物理组织方式,甚至是缓存,因此比ARM上的整个字要多1 e0f1x,甚至除了像
str
或stp
那样只移动1/4或1/8的数据量脚注2:
memcpy(pointer, &tmp, sizeof(uint32_t))
是C语言中描述4字节存储的可移植方式;sizeof(uint32_t) == 4
。memcpy
在C抽象机器中的两个内存位置之间进行复制,但实际上编译器可以将4字节变量优化为寄存器,并将memcpy
优化为str
指令,使用寻址模式生成pointer
地址。另请参阅 * Why does unaligned access to mmap'ed memory sometimes segfault on AMD64? * re:对齐和严格别名的注意事项,以使C编译器满意。严格别名在asm中不是一个东西,因为没有进一步的优化,只是翻译成机器代码(由汇编程序)。57hvy0tb2#
如果你想知道软件是如何工作的,从高层次的Angular 来看,如果你眯着眼睛,是的。地址0x 0000 b8 f0是基址,值0x 20000000存储为
但是硬件是完全不同的故事。首先你有很多总线,在像ARM这样的内核中你可能有一个内部的L1缓存(您可能启用了也可能没有启用)。以及芯片供应商连接到的ahb/axi/etc总线。这些总线通常为32或64位宽(在核心总线外部,在芯片内部)。因此假设没有MMU,这将是地址0xb 8 f0处的具有数据0x 20000000或0xXXXXXXXX 20000000的单个总线事务,其中XX可以是垃圾,通常是陈旧的,不被假设为零,为什么要浪费门呢?内部或外部的高速缓存在物理上不会从字节宽的组件创建,它们可能是32位宽或64加奇偶校验或ecc所以33或65或40或72或任何。内部sram是8位宽的倍数是不被假设的,单元库具有数百种尺寸和形状(宽度和深度)的SRAM。
假设总线上的读取被假定为总线宽度,这是非常常见的,因此如果您读取单个字节或认为您是从软件读取,则可能导致完整的32或64或更宽的读取,因为它遍历总线,所有这些字节/位将返回,处理器(核心)本身将隔离它感兴趣的字节,并执行指令想要的任何操作(例如LDRB)。存储另一方面如果你想存储一个字节那么硬件需要这样做,所以使用了一些方案,对于AXI/AHB等使用了字节掩码,因此,如果是32位总线,则有4位字节屏蔽/使能。每个字节通道一位。64位有8位。所以将字节存储到地址0x 0123实质上是用字节掩码0 b1000写入0x 0120以指示字节通道正在获取数据,其中该字节在适当的字节通道上(其中其他字节被假定为垃圾/陈旧的,在那里没有期望)。
假设你有一个缓存,哪一层无关紧要。它们是总线的理想倍数,所以如果是32位总线,那么32位加上奇偶校验或ecc宽(33、40、作为结果的单个字节的存储导致读-修改-写,因为该高速缓存SRAM本身只能在32或64位宽的事务中被读/写(地址本身就是这样你不使用/需要较低的地址位,它们被剥离来进行基于字或基于双字的寻址),所以逻辑将读取整个字或双字,修改你想写的一个字节,然后将其写回SRAM。这是一个性能打击,这取决于整体架构,例如您是否可以轻松地检测到它,以及您损失了什么整体性能(您可能有太多其他开销,如x86没有看到它)。
硬件中的一个字大小的存储区在任何方面、形状或形式上都不等同于在四个不同地址上的四个字节大小的存储区。根据另一个回答这个问题的人的请求,我已经在这个站点上演示了这一点。从软件的Angular 来看,是的,如上所示(假设小尾或be-8大尾),这是一个功能上的等价物。如果你做一个字写你可以做字节读来访问那些基于字节地址的字节,如果执行字节写入,则可以执行字读取,并在该读取中查看来自这些单独事务的字节。
还应理解,存储倍数stm并不假定为单独的32位事务。无论是32位还是64位总线,每个事务都有开销,即使不超过几个时钟,总线的处理器端也会对总线的芯片/总线控制器端说,我想执行写入操作,好的,我已准备好执行写入操作。然后您声明一个长度,要发送的总线宽度项的数量。因此,32位总线上的stmia sp!{r 0,r1,r2,r3}将是具有4个数据周期、握手以及数据总线上的4个时钟的单个事务。对于64位宽的总线,不作此假设,这就是为什么ARM现在需要在堆栈指针上进行64位对齐的原因。如果地址是64位对齐的则它是一个长度为2的事务,但是如果不是64位对齐而是32位对齐,则是三个事务,一个具有32位值(屏蔽字节以将总线切成两半)64位事务,然后是32位事务。性能下降。如果处理器支持未对齐(甚至不是32位也不是16位)那么我自己也没有见过这样的内核,但我会假设它也是不止一个事务。
所以纯粹从软件编译器的Angular 来看。字长存储是一个字长存储,4字节,基地址上的32位数。功能上等效于4字节大小的写,基地址为+0,+1,+2,+3。但性能不等效,指令也不等效。加载也一样。
许多编译器作者在设计中会更进一步,尽可能避免字节大小的加载/存储指令。
或
当后者知道要进行加法运算时,r 0的高位已经按照变量类型(有符号或无符号)进行了填充,尽管是32位寄存器和8位变量类型,但仍保持32位表示。编译器确定何时必须修改它以表示实际宽度。即使
有了8位变量和8位指针,您仍然可以使用ARM中的32位寄存器来完成此操作。
由于存储将忽略/屏蔽寄存器的高24位,因此不需要准备。
更短。使用str指令将0x 20000000写入0xb 8 f0是单个指令,以及处理器外的单个事务(或在L1缓存内部)。它不是四个单独的字节写入。如果选择使用strb四次,则四个字节写入在功能上是等效的(并且假设ISR试图读取这个字大小的值时没有发生中断),但是您必须自己显式地执行四字节写入。