1

我正在为 NXP LPC810 微控制器编写嵌入式 C/汇编代码(只是一个爱好项目)。

我有一个功能fn。我还有一个该函数机器代码的精确副本,位于uint8_t. (我已经检查了十六进制文件。)

我创建了一个函数指针fnptr,其类型与fn并使用强制转换将其指向数组。

这一切都在没有警告的情况下交叉编译。

当 MCU 执行fn时,它工作正常。

当 MCU 执行fnptr时它崩溃(我看不到任何调试,因为只有 8 个引脚,全部在使用中)。

代码与位置无关。

该数组具有正确的 4 字节对齐方式。

fn.textelf文件的部分。

该数组被强制进入.textelf 文件的部分(仍在闪存中,而不是 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>:
...
4

1 回答 1

1

我修改了代码以通过函数指针调用原始代码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);
    }
}

我编译了上面的代码,生成firmware.fn.elffirmware.machinecode.elf取消注释//int(*fnptr)(int a, int b) = (int(*)(int, int))machine_code;(并注释掉上面的行)。

第一个代码 ( 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..

第二个地址d402machine_code数组的地址。

奇怪的是,第一个地址bd02是一个小端奇数(d十六进制奇数)。

的地址fn02bcbc02大端),所以指向的指针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 上将数据作为代码执行时,您需要设置函数指针的低位。

于 2015-04-13T11:31:34.320 回答