【问题标题】:Going through AVR assembler "hello world" code浏览 AVR 汇编程序“hello world”代码
【发布时间】:2013-06-26 15:04:12
【问题描述】:

我正在尝试为 Arduino Duemilanove (AVR ATmega328P) 编写一些汇编语言。与编译和反汇编C代码并行学习汇编语言,我得到了这个:

(使用AVR_GCC编译)

int main() {
  volatile int a = 0;
  while (1) {
    ++a;
  }
  return 0;
}

变成了

00000000 <__vectors>:
   0: 0c 94 34 00   jmp 0x68  ; 0x68 <__ctors_end>
   4: 0c 94 51 00   jmp 0xa2  ; 0xa2 <__bad_interrupt>
  ...
  64: 0c 94 51 00   jmp 0xa2  ; 0xa2 <__bad_interrupt>

00000068 <__ctors_end>:
  68: 11 24         eor r1, r1
  6a: 1f be         out 0x3f, r1  ; 63
  6c: cf ef         ldi r28, 0xFF ; 255
  6e: d8 e0         ldi r29, 0x08 ; 8
  70: de bf         out 0x3e, r29 ; 62
  72: cd bf         out 0x3d, r28 ; 61

00000074 <__do_copy_data>:
  74: 11 e0         ldi r17, 0x01 ; 1
  76: a0 e0         ldi r26, 0x00 ; 0
  78: b1 e0         ldi r27, 0x01 ; 1
  7a: e4 ec         ldi r30, 0xC4 ; 196
  7c: f0 e0         ldi r31, 0x00 ; 0
  7e: 02 c0         rjmp  .+4       ; 0x84 <__do_copy_data+0x10>
  80: 05 90         lpm r0, Z+
  82: 0d 92         st  X+, r0
  84: a0 30         cpi r26, 0x00 ; 0
  86: b1 07         cpc r27, r17
  88: d9 f7         brne  .-10      ; 0x80 <__do_copy_data+0xc>

0000008a <__do_clear_bss>:
  8a: 11 e0         ldi r17, 0x01 ; 1
  8c: a0 e0         ldi r26, 0x00 ; 0
  8e: b1 e0         ldi r27, 0x01 ; 1
  90: 01 c0         rjmp  .+2       ; 0x94 <.do_clear_bss_start>

00000092 <.do_clear_bss_loop>:
  92: 1d 92         st  X+, r1

00000094 <.do_clear_bss_start>:
  94: a0 30         cpi r26, 0x00 ; 0
  96: b1 07         cpc r27, r17
  98: e1 f7         brne  .-8       ; 0x92 <.do_clear_bss_loop>
  9a: 0e 94 53 00   call  0xa6  ; 0xa6 <main>
  9e: 0c 94 60 00   jmp 0xc0  ; 0xc0 <_exit>

000000a2 <__bad_interrupt>:
  a2: 0c 94 00 00   jmp 0 ; 0x0 <__vectors>

000000a6 <main>:
  a6: cf 93         push  r28
  a8: df 93         push  r29
  aa: 00 d0         rcall .+0       ; 0xac <main+0x6>
  ac: cd b7         in  r28, 0x3d ; 61
  ae: de b7         in  r29, 0x3e ; 62
  b0: 1a 82         std Y+2, r1 ; 0x02
  b2: 19 82         std Y+1, r1 ; 0x01
  b4: 89 81         ldd r24, Y+1  ; 0x01
  b6: 9a 81         ldd r25, Y+2  ; 0x02
  b8: 01 96         adiw  r24, 0x01 ; 1
  ba: 9a 83         std Y+2, r25  ; 0x02
  bc: 89 83         std Y+1, r24  ; 0x01
  be: fa cf         rjmp  .-12      ; 0xb4 <main+0xe>

000000c0 <_exit>:
  c0: f8 94         cli

000000c2 <__stop_program>:
  c2: ff cf         rjmp  .-2       ; 0xc2 <__stop_program>

我试图理解一些事情:

  1. .-8 或类似的语法是什么? (例如地址 0x98 或 0xAA。)
  2. 地址为 80 到 88 的行(__do_copy_data 结束)周围有一些有趣的事情。在我看来,这会将所有程序代码从地址 0xC4 加载到RAM。为什么?
  3. 在 __do_clear_bss_start/loop 中,我们通过将 RAM 中的字节设置为 0(r1 的值)来清除刚刚完成的所有工作。为什么?所有这一切最终都打电话给main。有什么一般性的解释吗?
  4. 为什么反汇编不显示 .bss、.rodata 或其他部分?
  5. 第 6a 行,为什么 SREG 被清除?不是在每条指令之后都设置为应有的状态吗?
  6. 第 6c 和 6e 行:0xFF 和 0x08 对应什么? r28 和 r29 是堆栈指针的低位和高位。
  7. 我玩了一下,添加了一个静态全局变量。为什么我们存储在 RAM 中是从 0x0100 开始而不是 0x0000?
  8. 在第 8a 行,为什么是 ldi r17, 1?我们以前做过(只是一句愚蠢的话)。或者其他东西可以改变 r17 吗?
  9. 我们开始将闪存中的程序复制到 RAM,从 0xC4(我猜是 .bss 和其他部分)开始,但是 X 的 cpi/cpc 相对于 1 将使所有闪存复制到所有 RAM 中。当 .bss 部分完成复制时,是否只是因为编译器的懒惰才停止复制?

【问题讨论】:

  • 您的链接描述文件是什么样的?或者如果您没有指定一个,您在链接时是否使用了-Tbss-Tdata 选项?
  • 我用过avr-gcc -Wall -Os -DF_CPU=$(CLOCK) -mmcu=$(DEVICE) -o main.elf main.oavr-objcopy -j .text -j .data -O ihex main.elf main.hex
  • 编辑,它显示的内容(.text、.bss、.rodata)可能与您的反汇编方式有关,我使用whatever-objdump -D,它通常会这样做。
  • 从你那里的情况来看,0x0000 处似乎有一个向量表,所以你不能只把数据放在那里,我必须重新熟悉 avr 细节才能说更多。基本上我会认为代码必须在那里,并且 .bss 和 .data 或任何放置部分取决于它们在链接器脚本或命令行中的描述位置
  • ldi r17,1 出现两次很可能是因为这两个循环是用 asm 编写的,或者该人根本不关心进行优化,或者可能是由于宏或其他原因。如果我写了我会把 ldi 留在那里,因为这两个循环是独立的代码,你可以删除一个而不破坏另一个。

标签: assembly arduino avr-gcc


【解决方案1】:

点/句点用作指示该指令的地址或位置或与之相关的东西的快捷方式。 .+8 表示从这里开始加 8。您必须考虑指令集和/或汇编程序相对于指令集的细微差别。正如来自汇编器的附加信息所表明的那样,.-8 将返回到 do_clear_bss_loop,它向后八个字节,包括指令本身的两个字节。原始代码可能只是在那里有标签,brne do_clear_bss_loop

很可能是在复制数据段; .text 基本上是只读的。这是您的代码,它希望在这个平台上以闪存形式存在。不过,.data 是读/写的,通常初始化为非零值。因此,在关闭电源的情况下,您的初始值需要保存在某个地方,例如在闪存中,但在您启动实际程序之前,引导程序需要将初始 .data 段值从闪存复制到它们在RAM 中的实际位置。然后在程序运行时,它可以根据需要读取和/或修改这些值。

例如:

int x = 5;

main ()
{
    x = x + 1;
}

该值 5 必须在闪存中才能从通电开始,仅使用闪存来保存非易失性信息。但是在你可以读/写 x 的内存位置之前,你需要它在 RAM 中,所以一些启动代码将所有 .data sgement 的东西从闪存复制到 RAM。

抱歉,对您的问题只是猜测的内容进行了冗长的解释。

.bss 是程序中初始化为零的变量。对于 .data 段,如果我们有 100 个项目,我们将需要 100 个闪存。但是对于.bss,如果我们有 100 个项目,我们只需要告诉某人有 100 个项目。我们不需要闪存中的 100 个零,只需将其编译/组装到代码中即可。

所以

int x = 5;
int y;

int main ()
{
    while(1)
    {
        y = y + x + 1;
    }
}

x.data 中,5 需要在非易失性存储中。 y 在.bss 中,只需在调用 main 之前清零以符合 C 标准。

当然,您自己可能没有使用全局变量,但可能还有其他数据以某种方式使用.data 和/或.bss 段,因此引导代码准备了.data.bss 在调用 main() 之前进行分段,以便您的 C 编程体验符合预期。

【讨论】:

  • jmp . 跳转到同一行/指令。无限循环。 rjmp .-2(因为. 似乎是下一条指令的地址)。
  • 可能是真的,我在没有检查 arm 的情况下将 arm 翻译为 avr,它是 b .,avr 我不记得了...
  • 我明白了,谢谢你的解释。所以不知何故,这一切都是为了将​​数据复制到 ram 中,并将未初始化的东西归零。我会在虚拟程序上尝试。顺便说一句,我认为在 C 中,未初始化的值不应等于零。
  • 它可能或取决于标准和语言(C vs C++),但未初始化的全局变量应该为零,所以我被告知但没有确认我自己..我个人的裸机代码没有在这些假设中,我在使用它之前在代码中初始化了一个变量,我的引导程序只是将堆栈指针和分支设置为 main 避免这些复制循环和链接器脚本等以使其全部工作。 bssdata 目录中的 github.com/dwelch67/raspberrypi 我通过一些示例从 gnu 的角度了解所有这些是如何工作的(不幸的是,它是特定于工具链的)。
  • 我已经阅读了您在 github 上的链接,非常有用且解释清楚。谢谢
【解决方案2】:

我意识到这是一个迟到的答案。但是,我仍然认为对所有问题进行详细的逐点回答可能会很有趣。

  1. .-8 或类似的语法是什么? (例如地址 0x98 或 0xAA。)

意思是:“从这里跳回 8 个字节”。请注意,程序计数器已​​经增加了指令的长度(2 个字节),因此brne .-8 将在 brne 指令本身之前移动 6 个字节(而不是 8 个)。同样,rcall .+0 会将程序计数器推入堆栈,而不会改变程序流程。这是一个仅用于在单个指令中保留两个字节堆栈空间的技巧。

  1. 地址为 80 到 88 的行(__do_copy_data 结束)周围有一些有趣的事情。在我看来,这会将所有程序代码从地址 0xC4 加载到 RAM 中。为什么?

不,没有复制任何内容,这是一个空循环。在第 84 到 88 行,有一个测试在指针 X (r27:r26) 等于 0x0100 时退出循环。由于 X 初始化为 0x0100,因此根本不会循环。

此循环旨在将数据部分从闪存复制到 RAM。它基本上是这样的:

X = DATA_START;  // RAM address
Z = 0x00C4;      // Flash address
while (X != DATA_START + DATA_SIZE)
    ram[X++] = flash[Z++];

但是您的程序恰好有一个空数据部分(上述伪代码中的DATA_SIZE == 0)。

另外,您应该注意您的程序在地址 0x00c3 处结束,因此 Z 指针被初始化为指向 就在程序代码之后。这是初始化变量的初始值应该在的位置。

  1. 在 __do_clear_bss_start/loop 中,我们通过将 RAM 中的字节设置为 0(r1 的值)来清除刚刚完成的所有工作。为什么?所有这一切最终都打电话给main。有什么一般性的解释吗?

不,不会覆盖任何内容。这个循环清除 BSS,它通常紧跟在数据部分之后,没有重叠。伪代码:

X = BSS_START;
while (X != BSS_START + BSS_SIZE)
    ram[X++] = 0;

在哪里BSS_START == DATA_START + DATA_SIZE。这也是你程序中的一个空循环,因为你有一个空的 bss。

  1. 为什么反汇编不显示 .bss、.rodata 或其他部分?

因为objdump -d 只反汇编预期保存代码的部分。

  1. 第 6a 行,为什么 SREG 被清除?不是在每条指令之后都设置为应有的状态吗?

大多数指令只改变 SREG 的 一些 位。此外,这会清除全局中断使能位。

  1. 第 6c 和 6e 行:0xFF 和 0x08 对应什么? r28 和 r29 是堆栈指针的低位和高位。

堆栈指针加载 0x08ff,这是 ATmega328P 中的最后一个 RAM 位置。堆栈将从那里向下增长。

  1. 我玩了一下,添加了一个静态全局变量。为什么我们存储在 RAM 中是从 0x0100 开始而不是 0x0000?

RAM 在 328P 上位于 0x0100–0x08ff。在这个地址下面有一些内存映射寄存器(CPU 寄存器和 I/O 寄存器)。详情请查看the datasheet,“8.3 SRAM 数据存储器”部分。

  1. 在第 8a 行,为什么要ldi r17, 1?我们以前做过(只是一句愚蠢的话)。或者其他东西可以改变 r17 吗?

第 8a 行没用。这是因为链接器通过将不同的部分粘合在一起来构建程序的方式:__do_copy_data__do_clear_bss 是独立的例程,它们不依赖于寄存器中剩下的任何其他部分。

  1. 我们开始将闪存中的程序复制到 RAM,从 0xC4 开始(我猜是 .bss 和其他部分),但是 X 的 cpi/cpc 相对于 1 将使所有闪存复制到所有 RAM 中。当 .bss 部分完成复制时,是否只是因为编译器的懒惰才停止复制?

您误解了这部分代码。仅当 X 不同于 r17:0x00(即 0x0100,因为 r17 = 1)时,cpi、cpc 和 brne 指令才会循环。参考文献上面的伪代码。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多