我正在查看使用 avr-gcc
编译后编写的一些代码的生成程序集。具体来说,我使用 -Os
选项进行了编译。总的来说,输出是我所期望的,但我无法理解的是发出的指令 push r1
。更奇怪的是函数末尾的补充指令是 pop r0
。所以 r1
的值正在保存,但它似乎已恢复为 r0
根据此处的文档:
https://gcc.gnu.org/wiki/avr-gcc#Register_Layout
寄存器 r1
始终包含零,但函数可以在恢复寄存器时使用该寄存器。恢复它将是 ldi r1, 0x0
,根据我的理解,不需要 push,pop。
这是编译版本的 C 代码和反汇编示例。它是用 -Os
编译的。它有点长,但我必须编写一个大函数才能让编译器发出它。
C 代码:
void mqtt_create_connect(mqtt_parser *p, mqtt_connect_config *cfg){
uint8_t idx = 0;
p->buffer[idx++] = MQTT_CTRL_CONNECT << 4;
idx++; // skip remaining length for now
p->buffer[idx++] = 0x0;
p->buffer[idx++] = 0x4;
p->buffer[idx++] = 'M';
p->buffer[idx++] = 'Q';
p->buffer[idx++] = 'T';
p->buffer[idx++] = 'T';
p->buffer[idx++] = 0x04; // protocol level 3.1.1
p->buffer[idx++] =
MQTT_CONNECT_FLAG_CLEAN_SESSION;
push_uint16_t(p, cfg->keepAliveInterval, &idx);
push_charptr(p, cfg->clientIdentifier, &idx);
p->bufferIdx = idx;
// fill in remaining length
p->buffer[1] = p->bufferIdx - 2;
}
反汇编:
0000006a <mqtt_create_connect>:
6a: 0f 93 push r16
6c: 1f 93 push r17
6e: cf 93 push r28
70: df 93 push r29
72: 1f 92 push r1
74: cd b7 in r28, 0x3d ; 61
76: de b7 in r29, 0x3e ; 62
78: 8c 01 movw r16, r24
7a: fb 01 movw r30, r22
7c: 80 e1 ldi r24, 0x10 ; 16
7e: d8 01 movw r26, r16
80: 11 96 adiw r26, 0x01 ; 1
82: 8c 93 st X, r24
84: 11 97 sbiw r26, 0x01 ; 1
86: 13 96 adiw r26, 0x03 ; 3
88: 1c 92 st X, r1
8a: 13 97 sbiw r26, 0x03 ; 3
8c: 84 e0 ldi r24, 0x04 ; 4
8e: 14 96 adiw r26, 0x04 ; 4
90: 8c 93 st X, r24
92: 14 97 sbiw r26, 0x04 ; 4
94: 9d e4 ldi r25, 0x4D ; 77
96: 15 96 adiw r26, 0x05 ; 5
98: 9c 93 st X, r25
9a: 15 97 sbiw r26, 0x05 ; 5
9c: 91 e5 ldi r25, 0x51 ; 81
9e: 16 96 adiw r26, 0x06 ; 6
a0: 9c 93 st X, r25
a2: 16 97 sbiw r26, 0x06 ; 6
a4: 94 e5 ldi r25, 0x54 ; 84
a6: 17 96 adiw r26, 0x07 ; 7
a8: 9c 93 st X, r25
aa: 17 97 sbiw r26, 0x07 ; 7
ac: 18 96 adiw r26, 0x08 ; 8
ae: 9c 93 st X, r25
b0: 18 97 sbiw r26, 0x08 ; 8
b2: 19 96 adiw r26, 0x09 ; 9
b4: 8c 93 st X, r24
b6: 19 97 sbiw r26, 0x09 ; 9
b8: 82 e0 ldi r24, 0x02 ; 2
ba: 1a 96 adiw r26, 0x0a ; 10
bc: 8c 93 st X, r24
be: 1a 97 sbiw r26, 0x0a ; 10
c0: 80 81 ld r24, Z
c2: 91 81 ldd r25, Z+1 ; 0x01
c4: 1b 96 adiw r26, 0x0b ; 11
c6: 9c 93 st X, r25
c8: 1b 97 sbiw r26, 0x0b ; 11
ca: 9c e0 ldi r25, 0x0C ; 12
cc: 99 83 std Y+1, r25 ; 0x01
ce: 1c 96 adiw r26, 0x0c ; 12
d0: 8c 93 st X, r24
d2: 62 81 ldd r22, Z+2 ; 0x02
d4: 73 81 ldd r23, Z+3 ; 0x03
d6: ae 01 movw r20, r28
d8: 4f 5f subi r20, 0xFF ; 255
da: 5f 4f sbci r21, 0xFF ; 255
dc: c8 01 movw r24, r16
de: 0e 94 00 00 call 0 ; 0x0 <push_charptr>
e2: 89 81 ldd r24, Y+1 ; 0x01
e4: f8 01 movw r30, r16
e6: ef 5b subi r30, 0xBF ; 191
e8: ff 4f sbci r31, 0xFF ; 255
ea: 80 83 st Z, r24
ec: 82 50 subi r24, 0x02 ; 2
ee: f8 01 movw r30, r16
f0: 82 83 std Z+2, r24 ; 0x02
f2: 0f 90 pop r0
f4: df 91 pop r29
f6: cf 91 pop r28
f8: 1f 91 pop r17
fa: 0f 91 pop r16
fc: 08 95 ret
push r1
和 pop r0
的目的是什么?
最佳答案
这里有两个技巧。
首先,gcc 需要在堆栈上保留一个字节的位置——用于 uint8_t idx;
堆栈指针需要递减并保存回 SPH:SPL
。但是这种双out
操作可能会因灾难性的结果而中断。所以它必须由 cli
/sei
对包裹——额外的代码和时间。
推送任何寄存器以原子方式给出相同的结果并使用短代码。
其次:正如您所注意到的,根据 avr-gcc/avr-libc 约定,r1
是 __zero_reg__
,假定在任何 C 代码中始终为零。
所以,push r1
不仅为idx
预留了空间,还用0
对其进行了初始化。
pop r0
恢复堆栈指针。按照上述约定,r0
是 __temp_reg__
,可以被任何 C 代码破坏的临时寄存器。因此编译器可以随时销毁其内容。
附注该函数不改变r1
,因此不需要r1
恢复。
关于assembly - 为什么 avr-gcc 将 "push r1"指令添加到函数的开头?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51958795/