C语言 通过gdb在微控制器目标上执行任意代码?

zaq34kh6  于 2023-04-19  发布在  其他
关注(0)|答案(1)|浏览(117)

让我试着解释一下我在寻找什么,因为我找不到更好的标题措辞。
假设我正在对一个RP 2040微控制器进行编程,我可以使用gdbopenocd与它建立一个调试会话。(注意,即使我在这里讨论的是一个具体的MCU平台,我感兴趣的是这种方法是否可以实现 * 一般 * -使用任何类型的“外部微控制器”,gdb可能能够针对)
现在,假设我想用外部硬件执行一些(相对简单的)过程:为了举例,假设我想打开某个GPIO引脚,等待2000个CPU周期,然后关闭同一个GPIO。即使是这样一个简单的例子,这也需要硬件初始化,因此,在固件代码中,我必须做类似于(C使用pico-sdk)的事情:

#define MY_PIN_NR 12
static inline void my_hardware_init(void) {
  gpio_init(MY_PIN_NR);
  gpio_set_dir(MY_PIN_NR, GPIO_OUT);
}

static inline void my_hardware_do_process(void) {
  // raise pin high:
  gpio_put(MY_PIN_NR, 1);
  // wait for 2000 CPU cycles
  uint16_t cycles_to_wait = 2000;
  while(cycles_to_wait--) {
    asm volatile("nop");
  }
  // set pin low:
  gpio_put(MY_PIN_NR, 0);
}

void my_hardware_full_process(void) {
  // ensure hardware is initialized
  my_hardware_init();
  // do process:
  my_hardware_do_process();
}

如果这是在固件中编译并在Flash中刻录的,我可以在GDB会话中直接在目标微控制器上调用它,say

(gdb) call my_hardware_full_process()

(or甚至只是p my_hardware_full_process());则即使调试器使微控制器在断点处暂停,函数仍执行,且接着返回到调试器。
现在,这意味着闪存上刻录了实际代码(从gdb解析为符号my_hardware_full_process的地址开始)。
所以,我的问题是-我可以以某种方式做类似的事情,也就是说,执行相同的代码在my_hardware_full_process,但如果微控制器闪存完全擦除/未初始化?(这意味着微控制器没有要运行的代码,因此不运行任何代码-注意gdb通过openocd仍然可以挂钩到这种状态)。在这种情况下,即使gdb从.elf文件中获得地址my_hardware_full_process,它仍然是一个不包含可运行代码的地址,因此使用(gdb) call function-symbol()的方法失败。
考虑到这一点,我推测,也许有可能编译一个“二进制blob”,它将包含my_hardware_full_process()函数的程序集-例如,arm-none-eabi-objdump -S --disassemble=my_hardware_full_process firmware.elf在这里将给予:

Disassembly of section .text:

10000310 <my_hardware_full_process>:
  }
  // set pin low:
  gpio_put(MY_PIN_NR, 0);
}

void my_hardware_full_process(void) {
10000310:       b510            push    {r4, lr}
  gpio_init(MY_PIN_NR);
10000312:       200c            movs    r0, #12
10000314:       f003 fcf2       bl      10003cfc <gpio_init>
 * Switch all GPIOs in "mask" to output
 *
 * \param mask Bitmask of GPIO to set to output, as bits 0-29
 */
static inline void gpio_set_dir_out_masked(uint32_t mask) {
    sio_hw->gpio_oe_set = mask;
10000318:       23d0            movs    r3, #208        ; 0xd0
1000031a:       061b            lsls    r3, r3, #24
1000031c:       2280            movs    r2, #128        ; 0x80
1000031e:       0152            lsls    r2, r2, #5
10000320:       625a            str     r2, [r3, #36]   ; 0x24
    sio_hw->gpio_set = mask;
10000322:       615a            str     r2, [r3, #20]
  uint16_t cycles_to_wait = 2000;
10000324:       22fa            movs    r2, #250        ; 0xfa
10000326:       00d2            lsls    r2, r2, #3
  while(cycles_to_wait--) {
10000328:       e001            b.n     1000032e <my_hardware_full_process+0x1e>
    asm volatile("nop");
1000032a:       46c0            nop                     ; (mov r8, r8)
  while(cycles_to_wait--) {
1000032c:       001a            movs    r2, r3
1000032e:       1e53            subs    r3, r2, #1
10000330:       b29b            uxth    r3, r3
10000332:       2a00            cmp     r2, #0
10000334:       d1f9            bne.n   1000032a <my_hardware_full_process+0x1a>
    sio_hw->gpio_clr = mask;
10000336:       23d0            movs    r3, #208        ; 0xd0
10000338:       061b            lsls    r3, r3, #24
1000033a:       2280            movs    r2, #128        ; 0x80
1000033c:       0152            lsls    r2, r2, #5
1000033e:       619a            str     r2, [r3, #24]
  // ensure hardware is initialized
  my_hardware_init();
  // do process:
  my_hardware_do_process();
}
10000340:       bd10            pop     {r4, pc}

Disassembly of section .data:

因此,基本上,我需要这段代码,以及<gpio_init>和依赖项跳转到的任何地方-本质上,一个“静态构建”,就像在PC上所知道的那样。原则上,我可以想象一个“静态构建”blob,它“包括”运行(在本例中)my_hardware_full_process函数所需的所有需求/依赖项。
那么问题就变成了:我是否可以使用gdb在PC上读取这种“静态构建二进制blob”文件,然后以某种方式将指令及其数据“推”到微控制器,并在那里执行blob的指令(即“即时”),因此硬件执行预期的功能(之后,控制返回到gdb提示)-即使闪存被完全擦除?
如果是这样的话,我如何创建这样一个“静态构建二进制blob”?我如何指示gdb在目标微控制器上运行它?

31moq8wy

31moq8wy1#

一般来说,当然。这就是你用调试器做的事情。一般来说......没有必要从flash运行它。MCU有ram,特别是皮科(芯片)没有flash,只有ram。(flash在芯片外和板上)。如果你愿意,你可以像其他任何人一样使用gdb下载它并运行和停止它。
现在C函数不能独立运行,即使你认为它们是静态构建的,库调用也不应该作为一个函数孤立地运行。C必须被引导. stack,.data,.bss等。这些供应商的库往往有额外的东西被加载到链接器脚本中,并在后台引导库调用。就像试图在不启动汽车的情况下驾驶汽车一样,靠在方向盘上踩油门而不发动汽车是不会让你到达任何地方的(如果你松开驻车制动器可能会撞车)。
您需要为此用例设计独立的函数并解决先决条件。
你应该做的只是做一个普通的二进制文件,它可以做你想做的最小的事情,下载并在RAM中运行它。(就像在Link中一样,它是为RAM而不是Flash构建的)
现在有了物联网的问题,MCU开始得到保护,我认为皮科已经得到了,什么不一定是保护,但它如何引导是一个rom引导加载程序为基础的东西,面向试图找到一个闪存,然后解析,如果你将一个文件系统关闭它,其中包含二进制加载到sram和运行。我将不得不查阅我的笔记/示例和文档,看看你是否可以只释放重置和加载代码到sram中。当然,虽然你可以在flash上有一个最小的程序,如果没有其他东西通过这个 Boot 过程,并离开程序在一个无限循环,然后从调试器你可以停止,将代码加载到SRAM中并在一个地址处恢复。
你选择了一个比较难的mcu来尝试这个练习,有很多种方法。Nucleo板有一个内置的调试器,和较大的(仍然为10美元左右),调试器可以用于其他cortex-m板,甚至其他品牌的芯片。stm32 g部分与g往往有一些保护,可能使它更难运行只在ram上。stm32 f和stm32 l和旧的部分根本不是问题。你可以得到一个蓝色的药丸,然后花5美元左右得到一个调试器板(jlink clone)。那个品牌和其他领先的cortex-m品牌有比rp 2040更好的文档。Broadcom芯片有一些非常酷的功能,但如果你想编写一些不依赖于库的干净代码,那么就需要经验和挖掘。
闪存最初只是英特尔的一部分。本质上它被Map到处理器的地址空间。如果你有某种调试接口,运行时,调试器(openocd等)只需要知道基地址和协议是一样的它都是英特尔闪存部件。但我们有spi和i2c部件用于隔离部件,在mcu内部它是芯片供应商自己的接口,所以我们再也不能有任何形式的处理器,给予我地址,我可以编程闪存,现在我们有,产品线中的每个子系列或单个部件的编程都与任何其他部件不同(有些重叠,但比你希望的要少)现在调试器必须知道无数组合中的每一个。而那些人并不关心。因此,如果某个特定公司的某个人选择为openocd这样的开源项目做贡献,为他们的特定产品集添加对特定闪存控制器的支持发生了这种情况。例如,蓝色药丸中的stm32f103是支持的,如果我没记错的话。DFU在某种程度上有所帮助,但是需要在部件上运行引导加载程序,以将通用DFU命令转换为芯片特定例程。
然后你有很多的mcu有一个闪存银行的问题,所以你不能从它执行,而擦除/编程它,即使它是一个页面,你不使用.你通常必须复制和跳转到ram和接口与调试器或任何这样的闪存可以编程.(即或者只是停止处理器并从调试器控制它,这可能是您的使用情况)。一些现在具有多个存储体并且在运行时通告 Flink 。RP 2040根本不具有 Flink ,电路板供应商选择一个并填充它,您可以在片上引导加载程序源代码中看到旋转,以试图找出那里的部分。
所以我不确定你问闪存问题是因为你想看看你是否可以从RAM加载和运行,或者你想知道你是否可以在闪存上编程一个小blob,或者你是否必须向闪存写入一个小blob。你当然可以,因为你已经停止了处理器,但为什么?对于这个用例,如果我理解它,如果可以的话,使用RAM。对于大多数产品,尤其是那些基于Cortex-M的系统,你可以

正如上面所暗示的,我不认为这是真实的的问题,真正的问题是假设一个函数可以独立执行,即使main()也不能独立执行,一般来说,这是你需要关注的。
至于确认运行从ram只是采取一个简单的程序

here:
add r0,r0,#1
b here

构建它,将它加载到RAM中的某个地址(它是位置无关的,所以它只需要在偶数地址上),启动它,等待,停止它,然后使用调试器读取寄存器0,恢复,停止,读取寄存器0。
制作一个可用的blob是另一套SO问题和/或只是理解你应该只做一个小的,完整的,程序,做你想做的最小的事情。并加载,运行它,然后停止。当然,不幸的是,如果你使用供应商库,每个二进制文件将重置/擦除前一个程序的设置(一个程序启用GPIO输出,另一个程序使其 Flink 而不启用它不太可能在供应商的沙箱中工作)。所以你可能需要自己卷。如上所述,你需要设计“功能”或者实际上整个blob是独立的(读:不要使用别人的图书馆)
所以我强烈推荐picoprobe路径,两个Picos.你不需要的UART最初,所以从图中的顶部两个引脚在左边,探针,到目标pico上的较低引脚. SWD信号.现在我有问题,直到我实际上从探针供电的目标,所以两个电源引脚在探针的右边的两个电源引脚在目标的右边.
我下载了flash_nuke.uf2并在目标MCU上使用它来擦除闪存。
我只是下载了picoprobe.uf2文件,我按照说明为pico克隆了openocd并构建了它。
然后cd到tcl目录并

sudo ../src/openocd -f interface/picoprobe.cfg -f target/rp2040.cfg

Open On-Chip Debugger 0.11.0-g8e3c38f-dirty (2023-04-16-22:44)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
Info : only one transport option; autoselect 'swd'
adapter speed: 5000 kHz

Info : Hardware thread awareness created
Info : Hardware thread awareness created
Info : RP2040 Flash Bank Command
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : clock speed 5000 kHz
Info : SWD DPIDR 0x0bc12477
Info : SWD DLPIDR 0x00000001
Info : SWD DPIDR 0x0bc12477
Info : SWD DLPIDR 0x10000001
Info : rp2040.core0: hardware has 4 breakpoints, 2 watchpoints
Info : rp2040.core1: hardware has 4 breakpoints, 2 watchpoints
Info : starting gdb server for rp2040.core0 on 3333
Info : Listening on port 3333 for gdb connections

一切都好
然后在另一个窗口

telnet localhost 4444
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Open On-Chip Debugger
> halt
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x00000178 msp: 0x20041f00
target halted due to debug-request, current mode: Thread 
xPSR: 0x61000000 pc: 0x00001bd0 msp: 0x50100f4c
>

telnet到openocd服务器并停止目标。
开始.s

.cpu cortex-m0
.thumb

    mov r0,#0
here:
    add r0,#1
    b here

memmap.ld

MEMORY
{
    here : ORIGIN = 0x20000000, LENGTH = 0xFC
}
SECTIONS
{
    .text   : { *(.text*)   } > here
}

建造

arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m0 start.s -o start.o
arm-none-eabi-ld -nostdlib -nostartfiles -T memmap.ld start.o -o notmain.elf
arm-none-eabi-objdump -D notmain.elf > notmain.list

从telnet,停止后/停止时

load_image /path/to/notmain.elf

6 bytes written at address 0x20000000
downloaded 6 bytes in 0.001275s (4.596 KiB/s)

现在从telnet会话恢复并停止

> resume 0x20000000
> halt
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x00000178 msp: 0x20041f00
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x20000002 msp: 0x50100f4c
> reg r0
r0 (/32): 0x00bef3f5

> resume
> halt
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x00000178 msp: 0x20041f00
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x20000002 msp: 0x50100f4c
> reg r0
r0 (/32): 0x01b31ecd

>

我们可以看到r 0在增加,pc在我们预期的地方,所以它正在运行这个程序,这个程序被下载到一个有擦除闪存的Pi中。
开始.s

.cpu cortex-m0
.thumb

    ldr r0,=0x20001000
    mov sp,r0
    bl notmain
    b .

.thumb_func
.globl PUT32
PUT32:
    str r1,[r0]
    bx lr

.thumb_func
.globl GET32
GET32:
    ldr r0,[r0]
    bx lr

.globl DELAY
.thumb_func
DELAY:
    sub r0,#1
    bne DELAY
    bx lr

notmain.c

void PUT32 ( unsigned int, unsigned int );
unsigned int GET32 ( unsigned int );
void DELAY ( unsigned int );

#define RESETS_BASE                 0x4000C000

#define RESETS_RESET_RW             (RESETS_BASE+0x0+0x0000)
#define RESETS_RESET_XOR            (RESETS_BASE+0x0+0x1000)
#define RESETS_RESET_SET            (RESETS_BASE+0x0+0x2000)
#define RESETS_RESET_CLR            (RESETS_BASE+0x0+0x3000)

#define RESETS_RESET_DONE_RW        (RESETS_BASE+0x8+0x0000)
#define RESETS_RESET_DONE_XOR       (RESETS_BASE+0x8+0x1000)
#define RESETS_RESET_DONE_SET       (RESETS_BASE+0x8+0x2000)
#define RESETS_RESET_DONE_CLR       (RESETS_BASE+0x8+0x3000)

#define SIO_BASE                    0xD0000000

#define SIO_GPIO_OUT_RW             (SIO_BASE+0x10)
#define SIO_GPIO_OUT_SET            (SIO_BASE+0x14)
#define SIO_GPIO_OUT_CLR            (SIO_BASE+0x18)
#define SIO_GPIO_OUT_XOR            (SIO_BASE+0x1C)

#define SIO_GPIO_OE_RW              (SIO_BASE+0x20)
#define SIO_GPIO_OE_SET             (SIO_BASE+0x24)
#define SIO_GPIO_OE_CLR             (SIO_BASE+0x28)
#define SIO_GPIO_OE_XOR             (SIO_BASE+0x2C)

#define IO_BANK0_BASE               0x40014000

#define IO_BANK0_GPIO25_STATUS_RW   (IO_BANK0_BASE+0x0C8+0x0000)
#define IO_BANK0_GPIO25_STATUS_XOR  (IO_BANK0_BASE+0x0C8+0x1000)
#define IO_BANK0_GPIO25_STATUS_SET  (IO_BANK0_BASE+0x0C8+0x2000)
#define IO_BANK0_GPIO25_STATUS_CLR  (IO_BANK0_BASE+0x0C8+0x3000)

#define IO_BANK0_GPIO25_CTRL_RW     (IO_BANK0_BASE+0x0CC+0x0000)
#define IO_BANK0_GPIO25_CTRL_XOR    (IO_BANK0_BASE+0x0CC+0x1000)
#define IO_BANK0_GPIO25_CTRL_SET    (IO_BANK0_BASE+0x0CC+0x2000)
#define IO_BANK0_GPIO25_CTRL_CLR    (IO_BANK0_BASE+0x0CC+0x3000)

int notmain ( void )
{

    PUT32(RESETS_RESET_CLR,1<<5); //IO_BANK0
    while(1)
    {
        if((GET32(RESETS_RESET_DONE_RW)&(1<<5))!=0) break;
    }
    PUT32(SIO_GPIO_OE_CLR,1<<25);
    PUT32(SIO_GPIO_OUT_CLR,1<<25);
    PUT32(IO_BANK0_GPIO25_CTRL_RW,5); //SIO
    PUT32(SIO_GPIO_OE_SET,1<<25);
    while(1)
    {
        PUT32(SIO_GPIO_OUT_XOR,1<<25);
        DELAY(0x1000000);
    }
    return(0);
}

memmap.ld

MEMORY
{
    stuff : ORIGIN = 0x20000000, LENGTH = 0xFC
}
SECTIONS
{
    .text   : { *(.text*)   } > stuff
}

建造它

arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m0 start.s -o start.o
arm-none-eabi-gcc -Wall -O2 -ffreestanding -mcpu=cortex-m0 -mthumb -c notmain.c -o notmain.o
arm-none-eabi-ld -nostdlib -nostartfiles -T memmap.ld start.o notmain.o -o notmain.elf
arm-none-eabi-objdump -D notmain.elf > notmain.list

现在在telnet提示符下你可以

> reset halt
target halted due to debug-request, current mode: Thread 
xPSR: 0xf1000000 pc: 0x000000ee msp: 0x20041f00
target halted due to debug-request, current mode: Thread 
xPSR: 0xf1000000 pc: 0x000000ee msp: 0x20041f00
>

然后load_image这个新的notmain.elf
简历0x 20000000
并且在目标皮科上LED将 Flink 。
从技术上讲,你应该能够使用任何swd调试器,但我忘记了如果失败。与picoprobe我有两个板由同一个usb集线器供电,它没有工作是得到一些dap错误或什么。只有探针板插入故障使它看起来像它找到了探针,但无法找到目标。所以看目标侧,不确定是否有一些论坛或文档或什么决定尝试从探头供电,这工作。
这个例子是一个可以为flash或sram构建的。对于flash,第一阶段的引导加载程序在器件上,第二阶段来自闪存上第一个uf 2分区上的252字节。2所以我的第一次闪存尝试我做了这个小闪光灯。3当你移动到更大的程序时,我会忘记血淋淋的细节。有一个更高的地址sram是从flash拷贝的一部分,然后0x 20000000是sram的一个非常典型的地址。(Cortex-M具有用于芯片供应商的地址空间规则0x 40000000是外围设备开始的地方,有些将执行0x 10000000,但大多数为0x 20000000,有些将镜像低于0x 10000000以满足其他规则,但您可以从0x 20000000空间执行)
我没有使用过gdb,所以经验几乎为零,已经有十年或二十年了。我只是telnet到openocd,我经常这样做,有时也经常使用load_image

flash write_image erase /path/file.elf

对于支持的部分,然后在命令行上重置或重置板。我经常会在像这个皮科这样的板上焊接一个重置按钮,这样就不需要拔出usb并重新插入,但是使用openocd,如果你想让它重置部分但不释放处理器执行(允许你下载代码到sram中,然后继续),你可以做一个重置或重置停止
无论如何,如果你已经了解了这么多,那么你就可以解决gdb加载和运行的方式了。这是非常可行的,毫无疑问,我只是没有理由知道,也不能帮助你。
从你的一个评论和修改,我得到了上述。
当闪存被擦除时,它确实显示出第一阶段引导加载程序正在运行并达到一个点,它加快了处理器上的时钟。基于延迟计数,以获得一个既不太快也不太慢的视觉 Flink 。但是,如果你或让我说,当我用程序构建闪存映像时:

b .

然后把那个放在闪光灯上。然后使用探头,加载上面的闪光灯程序,速度要慢得多。
我强烈推荐这条路,因为谁知道 Bootstrap 还搞砸了什么,你想开发一个干净的,重置后的系统,而不是一个停止了 Bootstrap 的系统。

相关问题