LPC810 - 为什么机器代码不能从闪存中的 uint8_t 数组执行?
LPC810 - why won't machine code execute from an array of uint8_t in flash?
我正在为 NXP LPC810 微控制器编写嵌入式 C/assembler 代码(只是一个业余项目)。
我有一个函数fn
。我在 uint8_t
数组中还有该函数机器代码的 精确 副本。 (我检查了十六进制文件。)
我创建了一个函数指针 fnptr
,其类型与 fn
相同,并使用强制转换将其指向数组。
所有交叉编译都没有警告。
当 MCU 执行时 fn
它工作正常。
当MCU执行fnptr
时崩溃(我看不到任何调试,因为只有8个引脚,都在使用)。
代码与位置无关。
该数组具有正确的 4 字节对齐方式。
fn
在 elf 文件的 .text
部分。
数组被强制放入 elf 文件的 .text
部分(仍在闪存中,而不是 RAM)。
我假设在这样一个基本的 Coretex M0+ MCU 上没有类似 NX 的功能。 (Cortex M3 和 M4 确实有某种形式的代码只读内存保护。)
是否还有其他原因导致数组中的机器码不起作用?
更新:
代码如下:
#include "stdio.h"
#include "serial.h"
extern "C" void SysTick_Handler() {
// generate an interrupt for delay
}
void delay(int millis) {
while (--millis >= 0) {
__WFI(); // wait for SysTick interrupt
}
}
extern "C" int fn(int a, int b) {
return a + b;
}
/* arm-none-eabi-objdump -d firmware.elf
00000162 <fn>:
162: 1840 adds r0, r0, r1
164: 4770 bx lr
166: 46c0 nop ; (mov r8, r8)
*/
extern "C" const uint8_t machine_code[6] __attribute__((aligned (4))) __attribute__((section (".text"))) = {
0x40,0x18,
0x70,0x47,
0xc0,0x46
};
int main() {
LPC_SWM->PINASSIGN0 = 0xFFFFFF04UL;
serial.init(LPC_USART0, 115200);
SysTick_Config(12000000/1000); // 1ms ticks
int(*fnptr)(int a, int b) = (int(*)(int, int))machine_code;
for (int a = 0; ; a++) {
int c = fnptr(a, 1000000);
printf("Hello world2 %d.\n", c);
delay(1000);
}
}
这里是 arm-none-eabi-objdump -D -Mforce-thumb firmware.elf
的反汇编输出:
00000162 <fn>:
162: 1840 adds r0, r0, r1
164: 4770 bx lr
166: 46c0 nop ; (mov r8, r8)
00000168 <machine_code>:
168: 1840 adds r0, r0, r1
16a: 4770 bx lr
16c: 46c0 nop ; (mov r8, r8)
16e: 46c0 nop ; (mov r8, r8)
00000170 <main>:
...
我修改了代码以通过函数指针调用原始 fn
,以便能够生成希望接近相同的工作和非工作汇编代码。
machine_code
变得更长了,因为我现在没有使用优化 (-O0
)。
#include "stdio.h"
#include "serial.h"
extern "C" void SysTick_Handler() {
// generate an interrupt for delay
}
void delay(int millis) {
while (--millis >= 0) {
__WFI(); // wait for SysTick interrupt
}
}
extern "C" int fn(int a, int b) {
return a + b;
}
/*
000002bc <fn>:
2bc: b580 push {r7, lr}
2be: b082 sub sp, #8
2c0: af00 add r7, sp, #0
2c2: 6078 str r0, [r7, #4]
2c4: 6039 str r1, [r7, #0]
2c6: 687a ldr r2, [r7, #4]
2c8: 683b ldr r3, [r7, #0]
2ca: 18d3 adds r3, r2, r3
2cc: 1c18 adds r0, r3, #0
2ce: 46bd mov sp, r7
2d0: b002 add sp, #8
2d2: bd80 pop {r7, pc}
*/
extern "C" const uint8_t machine_code[24] __attribute__((aligned (4))) __attribute__((section (".text"))) = {
0x80,0xb5,
0x82,0xb0,
0x00,0xaf,
0x78,0x60,
0x39,0x60,
0x7a,0x68,
0x3b,0x68,
0xd3,0x18,
0x18,0x1c,
0xbd,0x46,
0x02,0xb0,
0x80,0xbd
};
int main() {
LPC_SWM->PINASSIGN0 = 0xFFFFFF04UL;
serial.init(LPC_USART0, 115200);
SysTick_Config(12000000/1000); // 1ms ticks
int(*fnptr)(int a, int b) = (int(*)(int, int))fn;
//int(*fnptr)(int a, int b) = (int(*)(int, int))machine_code;
for (int a = 0; ; a++) {
int c = fnptr(a, 1000000);
printf("Hello world2 %d.\n", c);
delay(1000);
}
}
我编译了上面的代码,通过取消注释 //int(*fnptr)(int a, int b) = (int(*)(int, int))machine_code;
(并注释掉上面的行)生成 firmware.fn.elf
和 firmware.machinecode.elf
。
第一个代码 (fn
) 有效,第二个代码 (machine_code
) 崩溃了。
fn
的文本和 machine_code
处的代码相同:
000002bc <fn>:
2bc: b580 push {r7, lr}
2be: b082 sub sp, #8
2c0: af00 add r7, sp, #0
2c2: 6078 str r0, [r7, #4]
2c4: 6039 str r1, [r7, #0]
2c6: 687a ldr r2, [r7, #4]
2c8: 683b ldr r3, [r7, #0]
2ca: 18d3 adds r3, r2, r3
2cc: 1c18 adds r0, r3, #0
2ce: 46bd mov sp, r7
2d0: b002 add sp, #8
2d2: bd80 pop {r7, pc}
000002d4 <machine_code>:
2d4: b580 push {r7, lr}
2d6: b082 sub sp, #8
2d8: af00 add r7, sp, #0
2da: 6078 str r0, [r7, #4]
2dc: 6039 str r1, [r7, #0]
2de: 687a ldr r2, [r7, #4]
2e0: 683b ldr r3, [r7, #0]
2e2: 18d3 adds r3, r2, r3
2e4: 1c18 adds r0, r3, #0
2e6: 46bd mov sp, r7
2e8: b002 add sp, #8
2ea: bd80 pop {r7, pc}
000002ec <main>:
...
调用代码的唯一区别是调用代码的位置:
$ diff firmware.fn.bin.xxd firmware.machine_code.bin.xxd
54c54
< 0000350: 0040 0640 e02e 0000 bd02 0000 4042 0f00 .@.@........@B..
---
> 0000350: 0040 0640 e02e 0000 d402 0000 4042 0f00 .@.@........@B..
第二个地址d402
是machine_code
数组的地址。
奇怪的是,第一个地址 bd02
是一个小端奇数(d
在十六进制中是奇数)。
fn
的地址是02bc
(大端bc02
),所以指向fn
的指针是不是是fn
的地址,而是fn
的地址加一(或者低位置位)。
将代码更改为:
...
int main() {
LPC_SWM->PINASSIGN0 = 0xFFFFFF04UL;
serial.init(LPC_USART0, 115200);
SysTick_Config(12000000/1000); // 1ms ticks
//int(*fnptr)(int a, int b) = (int(*)(int, int))fn;
int machine_code_addr_low_bit_set = (int)machine_code | 1;
int(*fnptr)(int a, int b) = (int(*)(int, int))machine_code_addr_low_bit_set;
for (int a = 0; ; a++) {
int c = fnptr(a, 1000000);
printf("Hello world2 %d.\n", c);
delay(1000);
}
}
让它发挥作用。
谷歌搜索,我发现:
切换机制利用了所有指令必须(至少)半字对齐的事实,这意味着分支目标地址的位[0]是冗余的。因此,该位可以重新用于指示该地址处的目标指令集。 Bit[0] 设置为 0 表示 ARM,bit[0] 设置为 1 表示 Thumb。
于 http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka12545.html
tl;博士
在 ARM Thumb 上将数据作为代码执行时,需要设置函数指针的低位。
我正在为 NXP LPC810 微控制器编写嵌入式 C/assembler 代码(只是一个业余项目)。
我有一个函数fn
。我在 uint8_t
数组中还有该函数机器代码的 精确 副本。 (我检查了十六进制文件。)
我创建了一个函数指针 fnptr
,其类型与 fn
相同,并使用强制转换将其指向数组。
所有交叉编译都没有警告。
当 MCU 执行时 fn
它工作正常。
当MCU执行fnptr
时崩溃(我看不到任何调试,因为只有8个引脚,都在使用)。
代码与位置无关。
该数组具有正确的 4 字节对齐方式。
fn
在 elf 文件的 .text
部分。
数组被强制放入 elf 文件的 .text
部分(仍在闪存中,而不是 RAM)。
我假设在这样一个基本的 Coretex M0+ MCU 上没有类似 NX 的功能。 (Cortex M3 和 M4 确实有某种形式的代码只读内存保护。)
是否还有其他原因导致数组中的机器码不起作用?
更新:
代码如下:
#include "stdio.h"
#include "serial.h"
extern "C" void SysTick_Handler() {
// generate an interrupt for delay
}
void delay(int millis) {
while (--millis >= 0) {
__WFI(); // wait for SysTick interrupt
}
}
extern "C" int fn(int a, int b) {
return a + b;
}
/* arm-none-eabi-objdump -d firmware.elf
00000162 <fn>:
162: 1840 adds r0, r0, r1
164: 4770 bx lr
166: 46c0 nop ; (mov r8, r8)
*/
extern "C" const uint8_t machine_code[6] __attribute__((aligned (4))) __attribute__((section (".text"))) = {
0x40,0x18,
0x70,0x47,
0xc0,0x46
};
int main() {
LPC_SWM->PINASSIGN0 = 0xFFFFFF04UL;
serial.init(LPC_USART0, 115200);
SysTick_Config(12000000/1000); // 1ms ticks
int(*fnptr)(int a, int b) = (int(*)(int, int))machine_code;
for (int a = 0; ; a++) {
int c = fnptr(a, 1000000);
printf("Hello world2 %d.\n", c);
delay(1000);
}
}
这里是 arm-none-eabi-objdump -D -Mforce-thumb firmware.elf
的反汇编输出:
00000162 <fn>:
162: 1840 adds r0, r0, r1
164: 4770 bx lr
166: 46c0 nop ; (mov r8, r8)
00000168 <machine_code>:
168: 1840 adds r0, r0, r1
16a: 4770 bx lr
16c: 46c0 nop ; (mov r8, r8)
16e: 46c0 nop ; (mov r8, r8)
00000170 <main>:
...
我修改了代码以通过函数指针调用原始 fn
,以便能够生成希望接近相同的工作和非工作汇编代码。
machine_code
变得更长了,因为我现在没有使用优化 (-O0
)。
#include "stdio.h"
#include "serial.h"
extern "C" void SysTick_Handler() {
// generate an interrupt for delay
}
void delay(int millis) {
while (--millis >= 0) {
__WFI(); // wait for SysTick interrupt
}
}
extern "C" int fn(int a, int b) {
return a + b;
}
/*
000002bc <fn>:
2bc: b580 push {r7, lr}
2be: b082 sub sp, #8
2c0: af00 add r7, sp, #0
2c2: 6078 str r0, [r7, #4]
2c4: 6039 str r1, [r7, #0]
2c6: 687a ldr r2, [r7, #4]
2c8: 683b ldr r3, [r7, #0]
2ca: 18d3 adds r3, r2, r3
2cc: 1c18 adds r0, r3, #0
2ce: 46bd mov sp, r7
2d0: b002 add sp, #8
2d2: bd80 pop {r7, pc}
*/
extern "C" const uint8_t machine_code[24] __attribute__((aligned (4))) __attribute__((section (".text"))) = {
0x80,0xb5,
0x82,0xb0,
0x00,0xaf,
0x78,0x60,
0x39,0x60,
0x7a,0x68,
0x3b,0x68,
0xd3,0x18,
0x18,0x1c,
0xbd,0x46,
0x02,0xb0,
0x80,0xbd
};
int main() {
LPC_SWM->PINASSIGN0 = 0xFFFFFF04UL;
serial.init(LPC_USART0, 115200);
SysTick_Config(12000000/1000); // 1ms ticks
int(*fnptr)(int a, int b) = (int(*)(int, int))fn;
//int(*fnptr)(int a, int b) = (int(*)(int, int))machine_code;
for (int a = 0; ; a++) {
int c = fnptr(a, 1000000);
printf("Hello world2 %d.\n", c);
delay(1000);
}
}
我编译了上面的代码,通过取消注释 //int(*fnptr)(int a, int b) = (int(*)(int, int))machine_code;
(并注释掉上面的行)生成 firmware.fn.elf
和 firmware.machinecode.elf
。
第一个代码 (fn
) 有效,第二个代码 (machine_code
) 崩溃了。
fn
的文本和 machine_code
处的代码相同:
000002bc <fn>:
2bc: b580 push {r7, lr}
2be: b082 sub sp, #8
2c0: af00 add r7, sp, #0
2c2: 6078 str r0, [r7, #4]
2c4: 6039 str r1, [r7, #0]
2c6: 687a ldr r2, [r7, #4]
2c8: 683b ldr r3, [r7, #0]
2ca: 18d3 adds r3, r2, r3
2cc: 1c18 adds r0, r3, #0
2ce: 46bd mov sp, r7
2d0: b002 add sp, #8
2d2: bd80 pop {r7, pc}
000002d4 <machine_code>:
2d4: b580 push {r7, lr}
2d6: b082 sub sp, #8
2d8: af00 add r7, sp, #0
2da: 6078 str r0, [r7, #4]
2dc: 6039 str r1, [r7, #0]
2de: 687a ldr r2, [r7, #4]
2e0: 683b ldr r3, [r7, #0]
2e2: 18d3 adds r3, r2, r3
2e4: 1c18 adds r0, r3, #0
2e6: 46bd mov sp, r7
2e8: b002 add sp, #8
2ea: bd80 pop {r7, pc}
000002ec <main>:
...
调用代码的唯一区别是调用代码的位置:
$ diff firmware.fn.bin.xxd firmware.machine_code.bin.xxd
54c54
< 0000350: 0040 0640 e02e 0000 bd02 0000 4042 0f00 .@.@........@B..
---
> 0000350: 0040 0640 e02e 0000 d402 0000 4042 0f00 .@.@........@B..
第二个地址d402
是machine_code
数组的地址。
奇怪的是,第一个地址 bd02
是一个小端奇数(d
在十六进制中是奇数)。
fn
的地址是02bc
(大端bc02
),所以指向fn
的指针是不是是fn
的地址,而是fn
的地址加一(或者低位置位)。
将代码更改为:
...
int main() {
LPC_SWM->PINASSIGN0 = 0xFFFFFF04UL;
serial.init(LPC_USART0, 115200);
SysTick_Config(12000000/1000); // 1ms ticks
//int(*fnptr)(int a, int b) = (int(*)(int, int))fn;
int machine_code_addr_low_bit_set = (int)machine_code | 1;
int(*fnptr)(int a, int b) = (int(*)(int, int))machine_code_addr_low_bit_set;
for (int a = 0; ; a++) {
int c = fnptr(a, 1000000);
printf("Hello world2 %d.\n", c);
delay(1000);
}
}
让它发挥作用。
谷歌搜索,我发现:
切换机制利用了所有指令必须(至少)半字对齐的事实,这意味着分支目标地址的位[0]是冗余的。因此,该位可以重新用于指示该地址处的目标指令集。 Bit[0] 设置为 0 表示 ARM,bit[0] 设置为 1 表示 Thumb。
于 http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka12545.html
tl;博士
在 ARM Thumb 上将数据作为代码执行时,需要设置函数指针的低位。