使用裸金属覆盆子pi调用printf到uart时应用程序挂起

时间:2014-07-05 18:12:51

标签: c gcc arm raspberry-pi bare-metal

我正在尝试在raspberry pi上实现一个裸机应用程序,并希望将stdout连接到mini uart以进行调试。

我已遵循概述herehere

的流程

我创建了一个uart_putc函数,它似乎工作得很好,允许我将消息打印到我的PC的COM端口。然后我实现了_write系统调用,使其调用我的uart_putc函数进行输出。如果我将单个字符串文字传递给printf其他文字参数或任何非文字参数没有打印到串行端口,并且在几次调用之后,应用程序挂起,这可以正常工作。

有没有人有任何想法可能会出错?如果需要,很高兴提供更多信息......

void uart_putc(char c)
{
    while(1)
    {
        if(aux[AUX_MU_LSR]&0x20) break;

        led_blink(); // Blink the LED off then on again to 
                     // make sure we aren't stuck in here
    }
    aux[AUX_MU_IO] = c;
}

...

int _write(int file, char* ptr, int len)
{
    int todo;

    for (todo = 0; todo < len; todo++) 
    {
        uart_putc(*ptr++);
    }
    return len;
}

...

while(1)
{
    printf("Hello World\r\n"); // Works
}

while(1)
{
    printf("Hello %s\r\n", "World"); // This doesn't print anything
                                     // and will hang after about five calls
}

char* s = (char*)malloc(sizeof(char) * 100); // Heap or stack it doesn't matter
strcpy(s, "Hello World\r\n");
while(1)
{
    printf(s); // This doesn't print anything
               // and will hang after about five calls
}

while(1)
{
    for (i = 0; i < 13; i++) 
    {
        uart_putc(s[i]);  // Works
    }
}

更新

我正在使用newlib,_write在直接调用时正常工作。 snprintf似乎表现出同样的问题,即

snprintf(s, 100, "hello world\r\n"); // Works
snprintf(s, 100, "hello %s\r\n", "world"); // Doesn't work

_sbrk的实现是从我的OP

中引用的页面中删除的
char *heap_end = 0;
caddr_t _sbrk(int incr) {
    extern char heap_low; /* Defined by the linker */
    extern char heap_top; /* Defined by the linker */
    char *prev_heap_end;

    if (heap_end == 0)
    {
        heap_end = &heap_low;
    }
    prev_heap_end = heap_end;

    if (heap_end + incr > &heap_top)
    {
        /* Heap and stack collision */
        return (caddr_t)0;
    }

    heap_end += incr;
    return (caddr_t) prev_heap_end;
 }

链接描述文件

OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm",
          "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib");
SECTIONS
{
  /* Read-only sections, merged into text segment: */
  PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x8000)); . = SEGMENT_START("text-segment", 0x8000);
. = 0x8000;
 .ro : {
  *(.text.startup)
  *(.text)
  *(.rodata)
 }
 .rw : {
  *(.data)
  __bss_start__ = .;
  *(.bss)
  __bss_end__ = .;
  *(COMMON)
 }
 . = ALIGN(8);
 heap_low = .; /* for _sbrk */
 . = . + 0x10000; /* 64kB of heap memory */
 heap_top = .; /* for _sbrk */
 . = . + 0x10000; /* 64kB of stack memory */
 stack_top = .; /* for startup.s */
}

的start.s

.section ".text.startup"

.global _start

_start:
    ldr sp, =stack_top

    // The c-startup
    b       _cstartup

_inf_loop:
    b       _inf_loop

更新2

涉及snprintf的进一步实验:

snprintf(s, 100, "hello world\r\n"); // Works

snprintf(s, 100, "hello %s\r\n", "world"); // Doesn't work
snprintf(s, 100, "hello %d\r\n", 1); // Doesn't work

char s[100];
char t[100];

strcpy(s, "hello world\r\n");
snprintf(t, 100, s); // Doesn't work

2 个答案:

答案 0 :(得分:2)

这看起来不像UART问题,而是库问题。

如果您想确保我的假设是正确的,请直接致电_write()并查看是否有效。最有可能的是。另外,我假设您使用的是newlib

如果_write()按预期工作,则问题仅限于printf的上层。不幸的是printf就像一个洋葱,你必须逐层剥离它,它会让你哭泣。

只是为了好玩,来自newlib源代码的片段:

/*
 * Actual printf innards.
 *
 * This code is large and complicated...
 */

幸运的是,仍有一些方法可以调试问题而不会迷失vfprintf.c。也许最简单的出发点是尝试snprintf(),因为它缺乏内存管理问题。内存分配代码包括诸如sbrk之类的内容,这可能是一个问题。人们很容易认为内存管理是可以的,因为malloc()似乎有效,但情况并非总是如此。 (malloc()即使它提供了错误的地址也可能看起来不错,但会发生内存损坏。)

告诉我们您在这些调试步骤中的位置! (我的有根据的猜测是,sbrk由于某种原因不起作用,这会破坏内存管理。)


更新因为看起来问题不在于内存分配 - 至少不仅仅是内存分配 - 我们需要解决洋葱。我希望你不要穿太重的化妆品......(这让我哭了,我不能100%肯定下面的分析是正确的。所以带上一点盐。)

调用newlibprintf会发生什么?故事位于文件夹newlib中的newlib/libc/stidio来源。

第1层:printf()

首先,printf.c

int
_DEFUN(printf, (fmt),
       const char *__restrict fmt _DOTS) 
{
  int ret;
  va_list ap;
  struct _reent *ptr = _REENT;

  _REENT_SMALL_CHECK_INIT (ptr);
  va_start (ap, fmt);
  ret = _vfprintf_r (ptr, _stdout_r (ptr), fmt, ap);
  va_end (ap);
  return ret;
}

非常简单。如果出现问题,可以是:

  • _REENT_SMALL_CHECK_INIT(ptr);
  • 处理varargs

我不认为重新入侵是一个问题,所以我会专注于varargs。也许最好制作一个最小的varargs测试代码,然后显示它们是否已损坏。 (我不明白为什么它们会被破坏,但在嵌入式系统中,不假设任何东西都更安全。)

第2层:_vfprintf_r()

这是标准vfprintf(varargs-version of file - printf)的内部版本,带有可重入代码。它在vfprintf.c中定义。它有多种风格,具体取决于库编译期间使用的开关:STRING_ONLY(无内存分配)和/或NO_FLOATING_POINT。我假设您有完整版本,在这种情况下,可以在名称_VFPRINTF_R下找到正确的函数(某些#define正在进行中。)

代码不容易阅读,函数的前几百行包括声明许多变量(取决于编译选项)和十几个宏。但是,该函数真正做的第一件事是无限循环来扫描%的格式字符串。当它找到\0时,它确实goto done;(是的,它还有goto s - 我想把它扔给代码审查...)

然而,这给了我们一个线索:如果我们不添加任何额外的参数,我们只需跳转到done我们有一些清理。我们可以生存,但不能处理任何格式参数。那么,让我们看一下%s最终会在哪里。这是按照人们的预期完成的,有一个很大的switch(ch) ...。在s,它说:

    case 's':
        cp = GET_ARG (N, ap, char_ptr_t);
        sign = '\0';
        if (prec >= 0) {
            /*
             * can't use strlen; can only look for the
             * NUL in the first `prec' characters, and
             * strlen () will go further.
             */
            char *p = memchr (cp, 0, prec);

            if (p != NULL) {
                size = p - cp;
                if (size > prec)
                    size = prec;
            } else
                size = prec;
        } else
            size = strlen (cp);

        break;

(现在,我假设您没有在MB_CAPABLE中启用多字节字符串支持newlib。如果有,那么事情就变得更复杂了。)其余部分看起来很容易调试(strlenmemchr),但GET_ARG宏可能很复杂 - 再次取决于您的编译设置(如果您有_NO_POS_ARGS,则更简单)。< / p>

切换后,简单的情况(格式字符串中没有填充)是:

PRINT (cp, size);

这是打印宏。至少如果指针cp是错误的,那么奇怪的事情就会发生。

宏本身并不是非常疯狂,因为我们可以打印简单的案例;只有参数引起问题。

我担心调试有点棘手,但症状指向内存中被破坏的东西。要检查的一件事是printf的返回值。它应该返回打印的字符数。返回值是否合理有助于调试其余部分。

答案 1 :(得分:2)

对不起,我为你解决这个问题已经很晚了。我是valvers.com裸机教程的作者。崩溃的原因是我所知道的,但没有时间解决。实际上,我不知道它会解决你的问题。

简而言之,问题是我们告诉工具链处理器是ARM​​1176,更重要的是浮点单元是VFP,我们应该使用硬浮ABI。

使用VFP是一个重要选项 - 这意味着我们选择了使用此选项编译的C库。通常不使用VFP指令,因此不会使我们失望。显然,printf的某些部分确实使用了VFP指令。

这让我们失望的原因是因为负责生成良好C运行时环境的启动汇编程序不启用VFP,因此当您到达VFP指令时,处理器会跳转到未定义的指令异常向量。

这就是我发现这就是问题的方法。我简单地在任何异常向量中启用了LED,并在使用printf格式时点亮。然后是在异常向量中移除LED调用直到它不再亮的情况。这发生在“未定义指令”例外中。在ARM站点上快速搜索显示,如果遇到VFP指令并且未启用VFP,处理器将进入此处。因此,它提醒我解决这个问题!

解决方案

您需要做一些事情。您需要将CMAKE_C_FLAGS复制到CMakeLists.txt文件中的CMAKE_ASM_FLAGS,以便将正确的选项传递给汇编程序,目前它们不是!我会尽快更新教程来解决这个问题!

在CMakeLists.txt文件中的最后set( CMAKE_C_FLAGS ... )命令的正下方添加set( CMAKE_ASM_FLAGS ${CMAKE_C_FLAGS} ),因为CMake使用gcc作为汇编程序,因此可以正常工作。

接下来,我们需要修改启动汇编程序文件(在我的教程armc-nnn-start.S中)以启用VFP。在bl _cstartup

上方插入以下代码

(这直接取决于TI Website

// Enable VFP/NEON
// r1 = Access Control Register
MRC p15, #0, r1, c1, c0, #2
// enable full access for p10,11
ORR r1, r1, #(0xf << 20)
// ccess Control Register = r1
MCR p15, #0, r1, c1, c0, #2
MOV r1, #0
// flush prefetch buffer because of FMXR below
MCR p15, #0, r1, c7, c5, #4
// and CP 10 & 11 were only just enabled
// Enable VFP itself
MOV r0,#0x40000000
// FPEXC = r0
FMXR FPEXC, r0

您可以从ARM找到有关此here的一些信息。

这些更改足以使printf格式化工作正常(我已在UART上测试过它)。如果您有任何其他问题,请不要犹豫。

最后,抱歉,你因为启动代码不正确而感到悲伤!我想做的最后一件事是花费一些时间!!