嵌入式系统:使用汇编语言时的内存布局 [英] Embedded System: Memory Layout when using Assembly Language

查看:72
本文介绍了嵌入式系统:使用汇编语言时的内存布局的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

据我了解,嵌入式系统运行机器代码.有多种方法可以生成此代码.一种是用C之类的高级语言编写一个程序,然后使用编译器来获得这样的代码.另一种方法是使用该嵌入式系统的复杂语言编写指令,然后使用汇编程序将其转换为机器代码.现在,我们获得了机器代码,该机器代码已加载到系统并执行.程序代码存储在非易失性存储器中.

From my understanding, an embedded system runs machine code. There are multiple ways to generate this code. One is to write a programm in a higher level language like C and use a compiler to get such code. An other way is writing instructions in the assambly language for that embedded system and using an assembler to translate that to machine code. Now we got machine code which is loaded to the system and executed. The programm code is stored in non-volatile memory.

现在,如果编程代码是从C编译器获得的,那么我知道以下几点:该代码包含多个部分:

Now, if the programm code was obtained from a C compiler I know the following: The code contains multiple sections:

  • .text:实际说明
  • .bss:已声明但未定义的变量
  • .data:声明和定义的变量
  • .rodata:声明和定义的只读变量("const")

然后,在启动时,.bss和.data(在大多数情况下)被加载到ram中.然后,将堆栈指针放置在数据段之后,将堆指针放置在ram的末尾,以便在执行期间,它们彼此再次增长.

Then, on startup .bss and .data is (in most cases) loaded into ram. Then, a stack pointer is placed after the data section and a heap pointer is placed at the end of the ram, so that during execution, they grow agains each other.

现在的问题是,如果我用汇编语言编写代码,事情会如何表现?根据我的理解,应该没有上面的节(在程序代码或ram中),只有代码(等价于.text).我可以手动访问内存地址,并从那里进行读写,但是没有堆栈和堆之类的东西.这是正确的写照吗?

The question is now, how do things behave if I write code in the assembly language? From my understanding, there should be no sections like above (in the programm code nor the ram), only the code (equivialent to .text). I can manually access memory addresses and write and read from there, but there are no such things as stack and heap. Is this portrayal correct?

推荐答案

您的图是一本关于事物的教科书视图,不一定正确,但是对于微控制器来说,事物的外观并不完全正确.

Your diagram is a textbook view of things and is not necessarily incorrect, but for a microcontroller that is not exactly how things look.

C和汇编语言会产生相同的结果,通常,一个对象包含机器代码和数据以及一些使链接程序知道什么的结构.包括某种信息以指示什么字节块是什么字节,通常称为段.特定的名称.text,.data等并非一成不变,工具开发人员可以自由选择所需的名称.如果他们不使用这些名称,那么会使习惯于这些术语的一般人群感到困惑.因此,即使您可能正在编写一个新的编译器,也要在某种程度上保持一致是明智的,因为您不喜欢任何现有的编译器.

C and assembly language result in the same thing, in general, an object containing machine code and data and some structure for the linker to know what is what. Including some sort of information to indicate what chunks of bytes are what, often called sections. The specific names .text, .data, etc are not cast in stone, tools developers are free to choose whatever names they want. If they do not use those names then that adds confusion to the general population who are used to those terms. So it is wise to somewhat conform even though you might be writing a new compiler because you do not like any of the existing ones.

与语言中的语言无关,堆栈指针与处理器中的任何其他寄存器/概念一样有用.大多数处理器受到通用寄存器数量的限制,因此有时会需要暂时节省一些空间,以便有更多空间来做更多的工作.子例程/函数的概念要求某种跳跃,并带有返回的概念.独立于编程语言(这意味着包括汇编语言,这是一种编程语言).

A stack pointer is as useful as any other register/concept in a processor, independent of language. Most processors are limited by the number of general purpose registers so there will come a time when you need to save some off temporarily to have room to do some more work. And concepts of subroutines/functions require some sort of jump with a notion of a return. Independent of programming language (which means assembly language, which is a programming language, is included).

堆是在不受完全控制的操作系统或环境上运行的概念.关于微控制器,您所说的就是裸机编程.这通常意味着没有操作系统.这意味着/意味着您完全可以控制.您不必简单地占用存储空间即可.

Heap is a notion of running on an operating system or an environment where you are not completely in control. What you are talking about with respect to microcontrollers is called baremetal programming. Which generally means without operating system. Which implies/means you are in complete control. You do not have to ask for memory you simply take it.

通常,对于微控制器(几乎所有这些语句都有例外),存在某种形式的非易失性存储器(闪存,eeprom等,某种形式的rom)和ram(随机存取存储器).芯片供应商为特定芯片或芯片系列选择这些逻辑组件的地址空间.处理器核心本身很少在乎,它们只是地址.程序员负责连接所有点.因此,MCU存储器模型将具有闪存地址空间,是的,基本上,它具有代码和理想的只读项(您需要程序员告知工具才能执行此操作).并且sram将具有读/写项.但是,还有另一个问题.所谓的.data项需要在代码主体之前设置为一个值,或者在C的情况下希望在C语言编译的代码开始执行之前将其设置为一个值.同样,如果.bss被假定为零,那也必须发生.这有时被称为引导程序.一些(理想的)汇编语言代码弥合了应用程序的入口点和高级语言(C)的入口点之间的鸿沟.首先使用操作系统,支持有限数量的二进制格式的文件类型.然后,操作系统作者将决定是否要为您准备内存,而不只是为应用程序分配空间,通常是您没有我要描述的MCU问题.操作系统可以简单地将数据放置在链接的位置,并将零.bss放置在链接的位置.

With microcontrollers in general (there are exceptions to almost all of these statements) there is some form of non-volatile memory (flash, eeprom, etc, a rom of some sort), and ram (sram). The chip vendor chooses the address space for these logic components for a particular chip or family of chips. The processor core itself rarely cares, they are just addresses. The programmer is responsible for connecting all of the dots. So a MCU memory model will have a flash address space which, yes, basically has the code and ideally read-only items (you the programmer need to tell the tools to do this). And the sram will have the read/write items. But there exists another problem. The so called .data items desire to be set to a value before the body of the code or in the case of C before the C language compiled code starts to execute. Likewise if .bss is assumed to be zeroed, that has to happen as well. This is done in what is sometimes called a bootstrap. Some (ideally) assembly language code that bridges the gap between the entry point of the application and the entry point of the high level language (C). With an operating system first off a limited number of binary format files types are supported. Then within those the operating system authors decide if they want to prepare the memory for you other than simply allocating room for your application, normally be all ram you do not have the MCU problem I am about to describe. The OS can simply place data where linked and zero .bss where linked.

通常使用MCU引导处理器,您的代码是第一个代码,没有操作系统可以为您准备和管理事物,这对IMO来说是件好事,但同时也意味着更多工作.具体来说,启动时只有非易失性存储,为了将.data项放入ram,您需要在rom中复制它们,并且在执行任何假定最终状态的已编译代码之前,需要复制它们地方.这是引导程序的工作之一,另一个是设置堆栈指针,因为编译器在生成编译代码时会假定存在堆栈.

With an MCU you are generally booting the processor, your code is the first code, there is no operating system to prepare and manage things for you, this is IMO good, but also means more work. Specifically all you have on boot is the non-volatile storage, in order to get .data items into ram you need to have a copy of them in rom and you need to copy them before executing any compiled code that assumes they are in their final place. That is one of the jobs of the bootstrap, another is to set the stack pointer as compilers assume there is a stack when they generate compiled code.

unsigned int a;
unsigned int b = 5;
const unsigned int c = 7;
void fun ( void  )
{
    a = b + c;
}
Disassembly of section .text:

00000000 <fun>:
   0:   e59f3010    ldr r3, [pc, #16]   ; 18 <fun+0x18>
   4:   e5933000    ldr r3, [r3]
   8:   e59f200c    ldr r2, [pc, #12]   ; 1c <fun+0x1c>
   c:   e2833007    add r3, r3, #7
  10:   e5823000    str r3, [r2]
  14:   e12fff1e    bx  lr
    ...

Disassembly of section .data:

00000000 <b>:
   0:   00000005    andeq   r0, r0, r5

Disassembly of section .bss:

00000000 <a>:
   0:   00000000    andeq   r0, r0, r0

Disassembly of section .rodata:

00000000 <c>:
   0:   00000007    andeq   r0, r0, r7

您可以在此示例中看到所有这些元素.

You can see all of these elements in this example.

arm-none-eabi-ld -Ttext=0x1000 -Tdata=0x2000 -Tbss=0x3000 -Trodata=0x4000 so.o -o so.elf

Disassembly of section .text:

00001000 <fun>:
    1000:   e59f3010    ldr r3, [pc, #16]   ; 1018 <fun+0x18>
    1004:   e5933000    ldr r3, [r3]
    1008:   e59f200c    ldr r2, [pc, #12]   ; 101c <fun+0x1c>
    100c:   e2833007    add r3, r3, #7
    1010:   e5823000    str r3, [r2]
    1014:   e12fff1e    bx  lr
    1018:   00002000
    101c:   00003000

Disassembly of section .data:

00002000 <b>:
    2000:   00000005

Disassembly of section .bss:

00003000 <a>:
    3000:   00000000

Disassembly of section .rodata:

00001020 <c>:
    1020:   00000007

(自然这不是有效/可执行的二进制文件,工具不知道/不在意)

(naturally this is not a valid/executable binary, the tools do not know/care)

该工具忽略了我的-Trodata,但是您可以看到否则我们可以控制事情的进行,并且通常通过链接来实现.我们最终负责确保构建与目标相匹配,并进行链接以匹配芯片地址空间布局.

The tool ignored my -Trodata, but you can see otherwise we control where things go, and we normally do that through linking. We ultimately are responsible for making sure the build matches the target, that we link things to match the chip address space layout.

使用许多编译器,尤其是gnu GCC,您可以创建汇编语言输出.对于GCC,它会编译为汇编语言,然后调用汇编器(明智的设计选择,但不是必需的).

With many compilers, and particularly gnu GCC, you can create an assembly language output. In the case of GCC it compiles to assembly language then calls the assembler (a wise design choice, but not required).

arm-none-eabi-gcc -O2 -save-temps -c so.c -o so.o
cat so.s
    .cpu arm7tdmi
    .eabi_attribute 20, 1
    .eabi_attribute 21, 1
    .eabi_attribute 23, 3
    .eabi_attribute 24, 1
    .eabi_attribute 25, 1
    .eabi_attribute 26, 1
    .eabi_attribute 30, 2
    .eabi_attribute 34, 0
    .eabi_attribute 18, 4
    .file   "so.c"
    .text
    .align  2
    .global fun
    .arch armv4t
    .syntax unified
    .arm
    .fpu softvfp
    .type   fun, %function
fun:
    @ Function supports interworking.
    @ args = 0, pretend = 0, frame = 0
    @ frame_needed = 0, uses_anonymous_args = 0
    @ link register save eliminated.
    ldr r3, .L3
    ldr r3, [r3]
    ldr r2, .L3+4
    add r3, r3, #7
    str r3, [r2]
    bx  lr
.L4:
    .align  2
.L3:
    .word   .LANCHOR1
    .word   .LANCHOR0
    .size   fun, .-fun
    .global c
    .global b
    .global a
    .section    .rodata
    .align  2
    .type   c, %object
    .size   c, 4
c:
    .word   7
    .data
    .align  2
    .set    .LANCHOR1,. + 0
    .type   b, %object
    .size   b, 4
b:
    .word   5
    .bss
    .align  2
    .set    .LANCHOR0,. + 0
    .type   a, %object
    .size   a, 4
a:
    .space  4
    .ident  "GCC: (GNU) 10.2.0"

其中有钥匙.了解汇编语言是特定于汇编程序(程序)而不是目标程序(CPU/芯片)的,这意味着对于同一处理器芯片,您可以有许多不兼容的汇编语言,只要它们生成正确的机器代码,它们都是有用的.这是gnu汇编程序(gas)汇编语言.

And in there lies the keys. Understanding that assembly language is specific to the assembler (the program) not the target (the cpu/chip), meaning you can have many incompatible assembly languages for the same processor chip, so long as they generate the right machine code they are all useful. This is gnu assembler (gas) assembly language.

.text
nop
add r0,r0,r1
eor r1,r2
b .
.align
.bss
.word 0
.data
.word 0x12345678
.section .rodata
.word 0xAABBCCDD

Disassembly of section .text:

00000000 <.text>:
   0:   e1a00000    nop         ; (mov r0, r0)
   4:   e0800001    add r0, r0, r1
   8:   e0211002    eor r1, r1, r2
   c:   eafffffe    b   c <.text+0xc>

Disassembly of section .data:

00000000 <.data>:
   0:   12345678

Disassembly of section .bss:

00000000 <.bss>:
   0:   00000000

Disassembly of section .rodata:

00000000 <.rodata>:
   0:   aabbccdd

以相同的方式链接:

Disassembly of section .text:

00001000 <.text>:
    1000:   e1a00000    nop         ; (mov r0, r0)
    1004:   e0800001    add r0, r0, r1
    1008:   e0211002    eor r1, r1, r2
    100c:   eafffffe    b   100c <__data_start-0xff4>

Disassembly of section .data:

00002000 <__data_start>:
    2000:   12345678

Disassembly of section .bss:

00003000 <__bss_start+0xffc>:
    3000:   00000000

Disassembly of section .rodata:

00001010 <_stack-0x7eff0>:
    1010:   aabbccdd

对于具有gnu链接器(ld)的MCU,请注意链接器脚本或如何告诉链接器所需的链接器特定内容,不要假定它可以以任何方式移植到其他工具链中的其他链接器.

For an MCU with gnu linker (ld), note linker scripts or how you tell the linker what you want is specific to the linker do not assume that it is portable in any way to other linkers from other toolchains.

MEMORY
{
    rom : ORIGIN = 0x10000000, LENGTH = 0x1000
    ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
    .text   : { *(.text*)   } > rom
    .rodata : { *(.rodata*) } > rom
    .data   : { *(.data*)   } > ram AT > rom
    .bss    : { *(.bss*)    } > ram AT > rom
}

我首先要告诉链接器,我想要一个地方只有只读的东西,而另一个地方却要读/写东西.请注意,rom和ram这两个单词仅用于连接点(对于gnu链接器):

I am telling the linker first off that I want the read only things in one place and read/write things in another. Note that the words rom and ram are only there to connect the dots (for gnu linker):

MEMORY
{
    ted : ORIGIN = 0x10000000, LENGTH = 0x1000
    bob : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
    .text   : { *(.text*)   } > ted
    .rodata : { *(.rodata*) } > ted
    .data   : { *(.data*)   } > bob AT > ted
    .bss    : { *(.bss*)    } > bob AT > ted
}

现在我们得到:

Disassembly of section .text:

10000000 <.text>:
10000000:   e1a00000    nop         ; (mov r0, r0)
10000004:   e0800001    add r0, r0, r1
10000008:   e0211002    eor r1, r1, r2
1000000c:   eafffffe    b   1000000c <.text+0xc>

Disassembly of section .rodata:

10000010 <.rodata>:
10000010:   aabbccdd

Disassembly of section .data:

20000000 <.data>:
20000000:   12345678

Disassembly of section .bss:

20000004 <.bss>:
20000004:   00000000

但是!我们有机会成功使用MCU:

BUT! We have a chance at success with a MCU:

arm-none-eabi-objcopy -O binary so.elf so.bin
hexdump -C so.bin
00000000  00 00 a0 e1 01 00 80 e0  02 10 21 e0 fe ff ff ea  |..........!.....|
00000010  dd cc bb aa 78 56 34 12                           |....xV4.|
00000018

arm-none-eabi-objcopy -O srec --srec-forceS3 so.elf so.srec
cat so.srec
S00A0000736F2E7372656338
S315100000000000A0E1010080E0021021E0FEFFFFEAFF
S30910000010DDCCBBAAC8
S3091000001478563412BE
S70510000000EA

您可以看到AABBCCDD和12345678

You can see the AABBCCDD and 12345678

S30910000010DDCCBBAAC8 AABBCCDD at address 0x10000010
S3091000001478563412BE 12345678 at address 0x10000014

在闪光灯中.如果您的链接器可以帮助您,那么下一步将无法解决,

In flash. The next step if your linker can help you which would be no good if it cannot:

MEMORY
{
    ted : ORIGIN = 0x10000000, LENGTH = 0x1000
    bob : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
    .text   : { *(.text*)   } > ted
    .rodata : { *(.rodata*) } > ted
    __data_rom_start__ = .;
    .data   : 
        {
            __data_start__ = .;
            *(.data*)   
        } > bob AT > ted
    .bss    : 
        { 
            __bss_start__ = .;
            *(.bss*)    
        } > bob AT > ted
}

本质上创建您可以用其他语言查看的变量/标签:

Essentially creating variables/labels that you can see in other languages:

.text
nop
add r0,r0,r1
eor r1,r2
b .
.align
.word __data_rom_start__
.word __data_start__
.word __bss_start__
.bss
.word 0
.data
.word 0x12345678
.section .rodata
.word 0xAABBCCDD

Disassembly of section .text:

10000000 <.text>:
10000000:   e1a00000    nop         ; (mov r0, r0)
10000004:   e0800001    add r0, r0, r1
10000008:   e0211002    eor r1, r1, r2
1000000c:   eafffffe    b   1000000c <__data_rom_start__-0x14>
10000010:   10000020
10000014:   20000000
10000018:   20000004

Disassembly of section .rodata:

1000001c <__data_rom_start__-0x4>:
1000001c:   aabbccdd

Disassembly of section .data:

20000000 <__data_start__>:
20000000:   12345678

Disassembly of section .bss:

20000004 <__bss_start__>:
20000004:   00000000

S00A0000736F2E7372656338
S315100000000000A0E1010080E0021021E0FEFFFFEAFF
S311100000102000001000000020040000205A
S3091000001CDDCCBBAABC
S3091000002078563412B2
S70510000000EA

工具将.data放置在0x10000020

The tools placed .data at 0x10000020

S3091000002078563412B2

我们一眼就能看到

10000010: 10000020 __data_rom_start__
10000014: 20000000 __data_start__
10000018: 20000004 __bss_start__

arm-none-eabi-nm so.elf 
20000004 B __bss_start__
10000020 R __data_rom_start__
20000000 D __data_start__

添加更多此类内容(请注意gnu ld链接程序脚本是使这些内容正确的PITA),然后您可以编写一些汇编语言代码以将.data项目复制到ram,就像现在知道的那样.二进制文件以及链接程序在ram中放置的位置.而.bss所在的位置,现在需要清除/归零的内存量很大.

Add some more of these types of things (note that gnu ld linker script is a PITA to get these things right) and you can then write some assembly language code to copy the .data items to ram as you now know where in the binary and where in ram the linker placed things. And where .bss is and now much memory to clear/zero.

在裸机中的内存分配是不可取的,通常是因为这些天裸机是微控制器类型的工作.不限于此,操作系统本身是裸机程序,由另一个裸机程序(引导加载程序)引导.但是使用MCU时,您的资源(特别是ram)非常有限,如果您使用诸如globals而不是locals,并且您没有动态分配而是静态地声明东西,那么大多数sram的使用情况可以通过这些工具看到,并且也可以受链接描述文件的限制.

Memory allocation in baremetal is not desireable, often because baremetal these days is microcontroller type work. It is not limited to that, an operating system itself is a baremetal program, booted by another baremetal program, a bootloader. But with an MCU, your resources, in particular ram are quite limited and if you use say globals instead of locals, and you do not allocate dynamically but instead statically declare things, then most of your sram usage can be seen using the tools, and can also be limited by the linker script.

arm-none-eabi-readelf -l so.elf

Elf file type is EXEC (Executable file)
Entry point 0x10000000
There are 2 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x010000 0x10000000 0x10000000 0x00020 0x00020 R E 0x10000
  LOAD           0x020000 0x20000000 0x10000020 0x00004 0x00008 RW  0x10000

 Section to Segment mapping:
  Segment Sections...
   00     .text .rodata 
   01     .data .bss 

通常将链接脚本的大小设置为与目标硬件相匹配,此处出于演示目的将其夸大.

Normally setting the linker script sizes to match the target hardware, exaggerated here for demonstration purposes.

bob : ORIGIN = 0x20000000, LENGTH = 0x4

arm-none-eabi-ld -T flash.ld so.o -o so.elf
arm-none-eabi-ld: so.elf section `.bss' will not fit in region `bob'
arm-none-eabi-ld: region `bob' overflowed by 4 bytes

如果您使用过多的动态分配(无论是局部变量还是malloc()调用系列),那么您必须对使用情况进行分析,以查看堆栈是否溢出到数据中.或将您的数据放入堆栈.充其量很难做到.

If you use too much dynamic allocation be it local variables or the family of malloc() calls, then you have to do an analysis of consumption to see if your stack overflows into data. Or your data into stack. Which can be quite difficult at best.

还要理解,裸机意味着没有操作系统极大地限制了您可以使用的C库,因为它们中的很大一部分依赖于操作系统来完成某些工作.特别是一般的alloc函数.因此,为了在运行时甚至具有动态内存分配,您需要为实现分配的C库实现后端.(提示使用链接程序脚本找出未使用的ram的大小/位置).因此,建议不要在运行时分配动态内存.但是有时候您会想要做并且需要实现它.

Also understanding that baremetal meaning no operating system greatly limits the C libraries you can use as a larger percentage of them rely on an operating system for something. Specifically the alloc functions in general. So in order to even have dynamic memory allocation at runtime you need to implement the back end for the C library that implements the allocation. (hint use your linker script to find out the size/location of unused ram). So dynamic memory allocation at runtime is discouraged. But there are times you will want to do it and will need to implement it.

汇编语言显然是可以自由使用堆栈的,因为它只是体系结构的另一部分,而且通常有特定于堆栈的指令也受到汇编语言的支持.堆和任何其他C库语言调用都可以从汇编语言进行,因为按定义的汇编语言可以像C一样对标签/地址进行调用.

Assembly language is obviously free to use a stack as it is just another part of the architecture and there are often instructions specific to the stack that are also supported by the assembly language. Heap and any other C library language call can be made from assembly language as assembly language by definition can make calls to labels/addresses just like C can.

unsigned char * fun ( unsigned int x )
{
    return malloc(x);
}

fun:
    push    {r4, lr}
    bl  malloc
    pop {r4, lr}
    bx  lr

.text,.rodata,.data,.bss,堆栈和堆都可用于汇编语言,至少对于面向目标文件和链接的汇编器而言.有些汇编器是事物的单一文件类型,或者不与对象和链接器一起使用,因此不需要节,而是具有诸如

.text, .rodata, .data, .bss, stack, and heap are all available to assembly language at least for assemblers that are geared toward object files and linking. There are assemblers that are meant to be a single file type of thing or not used with objects and linkers so have no need for sections, but will instead have things like

.org 0x1000
nop
add r0,r1,r2
.org 0x2000
.word 0x12345678

您要在其中声明特定地址的地方是汇编语言本身.某些工具可能会让您混淆这些概念,但可能会使您和这些工具感到困惑.

Where you are declaring the specific address where things are in the assembly language itself. And some tools may let you mix these concepts but it can get quite confusing for you and the tools.

借助诸如gnu/binutils和clang/llvm之类的大量使用的现代工具,节的使用/概念可用于所有受支持的语言,以及从一个对象到另一个对象的函数/库调用(可以使用C库,与调用它的语言无关).

With the heavily used modern tools like gnu/binutils and clang/llvm the use/notion of sections is available for all of the supported languages, as well as function/library calls from one object to another (can have and use a C library independent of the language used to call it).

这篇关于嵌入式系统:使用汇编语言时的内存布局的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆